进程管理
进程的基本概念:
1、进程与程序
程序是存储在磁盘上的可执行文件,程序被加载到内存中开始运行叫做进程,一个程序可以被多次加载生成多个进程,进程就是处于活跃状态的计算机程序
2、进程的分类
进程根据功能的不同一般分为三种类型:
交互进程:由shell终端启动的进程,在执行过程中需要与用户进行交互操作,可以在前台运行,也可以在后台运行
批处理进程:该进程是一个进程的集合,负责按照顺序启动执行其他进程
守护进程:一般一直处于活跃状态,运行在后台,由系统在开机时通过启动脚本来开启
3、查看进程
简单模式: ps 显示当前用户有终端控制进程的简单信息
列表模式: ps -auxw 显示所有进程的详细信息
a 所有用户有终端控制的进程
x 无终端控制的进程
u 显示进程的详细信息
w 以更大的列宽显示
USER 属主用户名
PID 进程的ID(唯一)
%CPU CPU的使用率
%MEM 内存的使用率
VSZ 虚拟内存使用的字节数
RSS 物理内存使用的字节数
TTY 终端设备号 ? 表示是无终端控制
STAT 进程的状态
O 就绪态 等待被调度
R 运行态 Linux中没有O,就绪也用R表示
S 可被唤醒的休眠态,例如系统调用、系统中断、等待资源时
D 不可被唤醒的休眠态,只能被系统唤醒
T 暂停态, 当进程收到停止类信号,会转入暂停态,当收到唤醒类信号,会转入运行态
X 死亡态
Z 僵尸态,进程变为僵尸进程(僵死进程)
s 进程领导者
< 高优先级
N 低优先级
l 多线程的进程
L 有内存页被锁
+ 处于后台的进程
START 启动时间点
TIME 运行时间
COMMAND 启动进程的命令
4、父进程、子进程、孤儿进程、僵尸进程
一个进程可以创建另一个进程,创建者叫做父进程,被创建这叫做子进程,子进程被父进程创建启动后会在操作系统的调度下可以同时运行
孤儿进程:父进程先于子进程结束,子进程变成孤儿进程,被"孤儿院"领养,大部分情况下init(1)是孤儿进程的父进程,由它来回收死亡后的孤儿进程的资源
僵尸进程:子进程先于父进程结束,如果结束时父进程没有回收资源,子进程就变成僵尸进程
5、进程标识符pid
每个进程都有一个非负整数表示的唯一的标识,即进程ID\pid
进程ID在任意时刻都是唯一的,但是可以重用,进程一旦结束后它的ID会被操作系统回收,过一段时间后可以重新分配给新创建的其它进程使用(延时重用)
pid_t getpid(void);
功能:获取进程自己的ID
pid_t getppid(void);
功能:获取父进程的ID
注意:init进程的pid永远是1
创建进程:
system()函数
底层调用了fork、exec系列函数来创建子进程去执行系统命令程序
pid_t fork(void);
功能:创建一个子进程
返回值:一次调用,两次返回,子进程返回0,父进程返回子进程的ID,当进程的数量超过系统的最大进程数时,则创建子进程失败,返回-1
1、可以根据返回值的不同让父子进程分别进入不同的分支语句,执行不同的代码功能
2、通过fork创建的子进程会拷贝父进程的数据段、bss段、堆、栈、I/O流缓冲区等数据,与父进程共享代码段,子进程会继承父进程的信号处理方式
3、通过fork创建的子进程可以共享父进程的文件描述符
[不同进程之间不能共享值相同的文件描述符]
4、通过fork创建的父子进程是独立运行的,因此谁先返回不确定,可以通过睡眠函数,让另一个进程先执行
练习1:制造出僵尸进程、孤儿进程 ps -aux验证
练习2:父进程创建出四个子进程,这四个子进程再各自创建两个子进程
pid_t vfork(void);
功能:创建子进程,子进程以加载可执行文件的方式运行
返回值:子进程返回0,父进程返回子进程的PID
区别:
1、子进程一定先返回,但是此时子进程并没有创建成功,需要加载一个可执行文件来替换当前的所有资源,替换完成后子进程才算创建完成,此时父进程才会返回
2、需要使用exec系列函数让子进程加载可执行文件
3、exec系列函数如果正常执行是不会返回的,当加载可执行文件失败时会返回-1
4、以exec系列函数执行的子进程不会继承父进程的信号处理方式,但是能够继承父进程的信号屏蔽集
int execl(const char *path, const char *arg, ...);
path:要加载的可执行文件路径
arg:命令行参数,一般第一个是可执行文件名,至少一个,以NULL结尾
例如:execl("./hehe","./hehe","-l","-m",NULL,"-c")
int execlp(const char *file, const char *arg,...);
file:可执行文件文件名,会根据PATH环境变量中的路径来加载该文件
arg:同上
int execle(const char *path, const char *arg, ...);
path:要加载的可执行文件路径
arg:同上
envp:环境变量表,把父进程的环境变量表拷贝了一份传给子进程要加载的可执行文件
int execv(const char *path, char *const argv[]);
path:要加载的可执行文件路径
argv:指针数组,命令参数
int execvp(const char *file, char *const argv[]);
file:可执行文件文件名,会根据PATH环境变量中的路径来加载该文件
argv:指针数组,命令参数
int execvpe(const char *file, char *const argv[],char *const envp[]);
file:可执行文件文件名,会根据PATH环境变量中的路径来加载该文件
argv:指针数组,命令参数
envp:环境变量表指针数组
进程的正常退出:
1、在main中执行了return n,该返回值会被父进程接收到
等价于在main中执行了exit(n)
2、进程调用了exit函数,都会正常结束
void exit(int status);//C标准库函数
功能:在任意时间调用此函数可以立即结束进程
status:结束状态码EXIT_SUCCESS\EXIT_FAILURE
效果与return中的返回值一样
返回值:不会返回
进程在正常退出之前要完成的事情:
1、先调用事先通过atexit\on_exit函数注册过的函数
int atexit(void (*function)(void));
功能:注册一个函数在进程结束前执行
int on_exit(void (*function)(int,void *),void *arg);
功能:注册一个函数在进程结束前执行
注意:执行注册函数的顺序与注册顺序相反
2、冲刷并关闭所有的标准IO流
3、调用_exit/_Exit函数
void _exit(int status);
功能:结束进程,由系统提供
void _Exit(int status);
功能:结束进程,由标准库提供
a、结束状态码会被父进程接收到
b、结束前会关闭所有打开状态下的文件描述符
c、向父进程发送SIGCHLD(17)信号
d、该函数不会返回
3、调用_exit/_Exit函数
4、进程的最后一个线程执行了返回语句
5、进程的最后一个线程调用了pthread_exit函数
进程的异常终止:
1、进程调用了abort函数,产生SIGABRT信号
2、进程接收到某些信号,可能是别的进程发出的,也可能是自己的错误导致发出的
3、进程的最后一个线程收到了"取消"请求操作,并响应该请求
以上三种结束方式父进程都无法获取到异常终止的子进程的结束状态码,因此叫做异常终止
注意:无论进程是如何结束的,它们都会执行同一段代码:关闭所有打开的文件描述符、释放所有的内存
父进程如何回收子进程:
对于任何结束方式,都希望父进程能够知道,通过wait、waitpid函数可以知道子进程是如何结束的以及结束状态码
pid_t wait(int *status);
功能:等待子进程的结束,并获取结束状态码
status:输出型参数
返回值:结束的子进程的PID
1、如果所有的子进程都还在运行,则阻塞
2、有一个子进程结束,立即返回该进程的结束状态码和PID
3、如果没有子进程,则立即返回-1
WIFEXITED(status) 判断子进程是否正常结束,如果是则返回真
WEXITSTATUS(status) 如果进程是正常结束的,那么可以获取到正确的结束状态码,只能获取低8位(-128~127)
WIFSIGNALED(status) 判断子进程是否异常结束,如果是则返回真
WTERMSIG(status) 如果进程是异常结束的,获取导致异常的信号id
注意:由于wait函数可能会阻塞,因此不太适合放在父进程的主业务逻辑中调用,因为子进程结束时会向父进程发送SIGCHLD信号,可以把wait在该信号处理函数中调用,这样就不影响父进程的主业务逻辑
pid_t waitpid(pid_t pid, int *status, int options);
功能:可以指定回收某个、某些子进程
pid:
< -1 等待进程组组id为abs(pid)的进程组中的任意进程结束
-1 等待任意进程结束,与wait一样
0 等待同组的任意进程结束
>0 等待该进程结束
status:结束状态,与wait一样
options:
WNOHANG 非阻塞模式,如果没有子进程返回,立即结束不再阻塞等待
WUNTRACED 如果有子进程转入了暂停态,返回该进程的状态码
WCONTINUED 如果有子进程从暂停态转入运行态,返回该进程状态码
WIFSTOPPED(status) 如果接受到的子进程是因为转入暂停,返回真
WSTOPSIG(status) 如果子进程是转入了暂停态,获取导致暂停的信号id
WIFCONTINUED(status) 如果接受到的子进程是因为从暂停态转入运行态,返回真
返回值:成功返回子进程pid,失败返回-1