前几篇博文中我已经介绍过进程的创建以及一些基本知识,这里就不再赘述,本篇博文我主要会涉及进程的终止细节以及父进程对子进程的监控,还有函数组exec()
1.进程的终止
(1)_exec()和exit()
进程终止分俩中情况,一种是收到某种信号异常终止,另一种是调用_exit()正常退出
#include<unistd.h>
void _exit(int status);
参数status保存了进程的终止状态,父进程可调用wait()获取该状态
#include<stdio.h>
void _exit(int status);
一般情况下进程并不会直接调用_exit()而是调用exit(),它会在调用_exit()之前执行如下操作
.调用退出处理程序,其执行顺序与注册顺序相反
.刷新stdio流缓冲区
.使用由status提供的值执行_exit()系统调用
程序的另一种终止方法是从main函数返回(return),其实其也是调用了exit()唯一的区别就是如果在退出的处理过程中所执行的任何函数想要访问main函数的本地变量,那么从main函数返回会导致未定1义的行为
(2)进程终止的细节
无论进程是否正常终止都会发生以下动作
.关闭所有打开的文件描述符,目录流。。。
.因为文件描述符被关闭,所以释放该进程所持有的一切文件锁
.若进程是管理终端的管理进程,则它会向该终端进程组中的所有进程发送SIGHUP信号
.将关闭进程打开的任何信号量
(3)退出处理程序
#include<stdlib.h>
int atexit(void(*func)(void));
//返回0成功,否则失败
函数atexit()将func()加到一个函数列表中,进程终止时会调用该函数列表中的所有函数,应将函数func定义成无返回值也不接受任何参数
2.监控子进程
(1).系统调用wait()
#include<sys/wait.h>
pid_t wait(int status);
//返回退出子进程的ID
该调用阻塞父进程等待子进程的终止,并在status指向的缓冲区中返回子进程的终止状态,wait()执行的具体操作如下:
.如果调用进程并无之前未被等待的子进程终止,调用将一直阻塞,直至某个子进程终止,如果调用时已有子进程终止,则立即返回
.如果status非空,那么关于子进程如何终止的信息则会通过status指向的整个变量返回
.内核将会为父进程下所有子进程的运行总量追加进程CPU时间以及资源使用数据
.将终止子进程的PID作为wait的返回结果
(2)系统调用waitpid()
#include<sys/wait.h>
pid_t waitpid(pid_t pid,int *status,int options);
//返回子进程ID
waitpid()比起wait()所突破的地方
.如果pid大于0,表示等待进程ID为pid的子进程
.如果pid等于0,则等待与调用进程同一个进程组的所有子进程
.如果pid小于-1,则会等待进程组标识符与pid绝对值相等的子进程
.如果pid等于-1,则等待任意子进程
参数options是一个位掩码,可以包含0个或多个如下标志
options | 具体描述 |
---|---|
WUNTRACED | 返回终止子进程的信息,还返回因信号而停止的子进程信息 |
WCONTINUED | 返回那些因收到SIGCONT(暂停重新开始)信号而恢复执行的已停止子进程的状态信息 |
WNOHANG | 如果参数pid所指定的子进程并未发生状态改变,则立即返回,而不会阻塞 |
(3)wait3()和wait4()
#include<sys/resource.h>
#include<sys/wait.h>
pid_t wait3(int *status,int option,struct rusage *rusage);
pid_t wait4(pid_t pid,int *status,int options,struct rusage *rusage);
wait3与wait4与上述的根本区别是,他们通过rusage结构体返回子进程的资源使用情况
(4)孤儿进程与僵尸进程
孤儿进程:
当父进程先于子进程退出时,该子进程就变成了孤儿进程,该孤儿进程会被init收养
在父进程执行wait()之前,其子进程就已终止,那么系统会将子进程转化为僵尸进程,来使父进程获得其终止状态,僵尸进程把自己把持的大部分资源都释放掉,只保留了内核进程表中的一条记录,其中包含该进程ID,终止状态,资源使用数据等信息,当父进程执行wait()后,内核将删除该僵尸进程,如果父进程未执行wait()随即退出,则init会接管该进程并调用wait()
(5).信号SIGCHLD
当子进程终止时,内核都会向父进程发送SIGCHLD信号,对该信号的默认处理是将其忽略,不过我们可以安装信号处理函数,来处理它,以收拾僵尸进程
(6)程序的执行
#include<unistd.h>
int execve(const char *pathname,char *const argv[],char *const envp);
//不返回则成功,-1则失败
参数pathname包含准备载入当前进程空间的新程序的路径名,参数argv则指定了传递给新程序的命令行参数,参数envp指定了新程序的环境列表