Linux环境编程之进程管理
一、进程的基本概念
-
进程与程序
程序是存储在磁盘上的可执行文件,程序被加载到内存中开始运行时叫做进程
一个程序可以被多次加载生成多个进程,进程就是出于活动状态的计算机程序
-
进程的分类
进程一般分为三个种类:交互进程、批处理进程、守护进程
-
查看进程
-
简单模式: ps
显示当前用户有终端控制进程简单信息
-
列表模式: ps -auxw
显示所有进程的详细信息
a | 所有用户的有终端控制的进程 |
---|
x | 无终端控制的进程 |
u | 显示进程的详细信息 |
w | 以更大的列宽显示 |
-
ps -aux显示的信息
USER | 进程的属主用户名 |
---|
PID | 进程号 |
%CPU | CPU的使用率 |
%MEM | 内存的使用率 |
VSZ | 虚拟内存使用的字节数 |
RSS | 物理内存使用的字节数 |
TTY | 终端设备号,? 表示无终端控制 |
STAT | 进程的状态 |
START | 进程的启动时间 |
TIME | 进程运行时间 |
COMMAND | 启动进程的命令 |
其中,STAT显示的状态又分为以下几种
O | 就绪态 等待被调用 |
---|
R | 运行态,Linux系统没有O,就绪也用R表示 |
S | 可被唤醒的睡眠态,如系统中断、获取资源、收到信号等都可以唤醒进入运行态 |
D | 不可被唤醒的睡眠态,只能被系统唤醒 |
T | 暂停态 收到SIGTSTP信号进入暂停态,收到SIGCONT信号转回运行态 |
X | 死亡态 |
Z | 僵尸态 |
N | 低优先级 |
< | 高优先级 |
l | 多线程进程 |
s | 进程的领导者 |
-
父进程、子进程、孤儿进程、僵尸进程
- 一个进程可以被另一个进程创建,创建者叫做父进程,被创建者叫子进程,子进程被父进程创建后会在操作系统的调度下同时运行。
- 当子进程先于父进程结束,死前子进程会向父进程发送信号SIGCHLD,此时父进程应该去回收子进程的相关资源。
孤儿进程:父进程先于子进程结束,子进程就变成了孤儿进程,孤儿进程会被孤儿院 (init守护进程)领养,init就是孤儿进程的父进程),在Linux目前的图形界面下,一般不会被init收养,会被Linux图形化界面的upstart收养,本质上是一样的。
僵尸进程:该进程已死亡,但是它的父进程没有立即回收它的相关资源,该进程就进入僵尸态
-
进程标识符
每个进程都有一个用非负整数表示唯一标识,即进程ID\PID,进程ID在任意时刻都是唯一的,但是可以重用,进程一旦结束它的进程ID就会被系统回收,过一段时间后再重新分配给其他新创建的进程使用(延时重用)。
pid_t getpid(void);
功能:返回调用者的进程ID
pid_t getppid(void);
功能:返回父进程的ID
二、创建进程
pid_t fork(void);
功能:创建子进程
返回值:一次调用两次返回,子进程返回0,父进程返回子进程的ID,当进程数量超过系统的限制时会创建失败,返回-1
- 通过fork创建的子进程会拷贝父进程(数据段、bss段、堆、栈、I/O缓冲区),与父进程共享代码段、子进程会继承父进程的信号处理方式。
- fork函数调用后父子进程各自独立运行,谁先返回不确定,但是可以通过睡眠确定让哪个进程先执行。
- 通过fork创建的子进程可以共享父进程的文件描述符。
- 可以根据返回值的不同让父子进程进入不同的分支,执行不同的代码。
练习1:为进程创建4个子进程,再为这4个子进程,分别创出2个子进程
解题思路:子进程创建后,执行完任务后就休眠,这样不会被其他的fork影响
pid_t vfork(void);
功能:以加载可执行文件的方式来创建子进程
返回值:子进程返回0,父进程返回子进程的ID
注意:vfork创建的子进程一定先返回,此时子进程并没有创建成功,需要加载一个可执行文件替换当前子进程当前的所有资源,当替换完成后子进程才算创建成功,此刻父进程才返回
使用 exec 系列函数让子进程加载可执行文件
extern char **environ;
int execl(const char *path, const char *arg, ...
);
path:可执行文件的路径
arg:命令行参数,个数不定,由实际的可执行文件所需命令行参数决定。
一般第一个是可执行文件的名字,至少有一个,一定要以NULL结尾。
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
file:可执行文件名字
arg:命令行参数,同上
注意:该命令会去系统默认路径 PATH 指定的路径下加载file
int execle(const char *path, const char *arg, ... /*, (char *) NULL, char * const envp[]*/);
path:可执行文件的路径
arg:命令行参数,同上
envp:环境变量表,父进程可以在加载子进程时把环境变量表传递给子进程
int execv(const char *path, char *const argv[]);
path:可执行文件的路径
argv:命令行参数数组,最后以NULL结尾
int execvp(const char *file, char *const argv[]);
file:可执行文件名字
argv:命令行参数数组,同上
注意:也是根据PATH的路径加载file
int execvpe(const char *file, char *const argv[], char *const envp[]);
file:可执行文件名字
argv:命令行参数数组,同上
envp:环境变量表
注意:也是根据PATH的路径加载file
exec系列函数正常情况下是不会返回的,当子进程加载失败时才会返回-1
虽然通过vfork、exec系列函数创建加载的子进程不会继承父进程的信号处理函数,但是能继承父进程的信号屏蔽集。
练习2:实现出孤儿进程和僵尸进程,可以使用ps查看stat状态来查看是否创建成功
三、进程的正常退出
-
在main函数中执行 return n,该返回值可以被父进程获取,几乎与exit(n)等价
-
进程调用了exit函数,该函数是C标准库中的函数
void exit(int status);
功能:在任何时间、地点调用该函数都可以立即结束进程。
status:结束状态码 (EXIT_SUCCESS\EXIT_FAILURE),与main函数中return的返回值效果是一样的。
返回值:该函数不会返回,直接转到另一个进程执行代码去了。
进程退出前要完成:
- 先调用事先通过atexit\on_exit函数注册的函数,如果都注册了,则执行顺序与注册顺序相反。
int atexit(void (*function)(void));
功能:向内核注册一个进程结束前必须调用的函数。
int on_exit(void (*function)(int,void *), void *arg);
功能:向内核注册一个进程结束前必须调用的函数。
arg:会在调用function时传给它。 - 冲刷并关闭所有打开状态的标准IO流。
- 底层继续调用_Exit/_exit函数。
-
调用_Exit/_exit函数
void _exit(int status);
功能:结束进程,由系统提供的
void _Exit(int status);
功能:结束进程,由标准库提供的
- 它们的参数会被父进程获取到
- 进程结束前会关闭所有处于打开状态的文件描述符
- 向父进程发送信号SIGCHLD
- 该函数也不会返回
-
进程的最后一个线程执行了return返回语句
-
进程的最后一个线程执行了pthread_exit函数
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)