当一个进程正常或异常终止时会向父进程发送SIGCHLD信号。对于这种信号系统默认会忽略。调用wait/waidpid的进程可能会:shell
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *statloc); pid_t waitpid(pid_t pid, int *statloc, int options); 返回值: 成功返回进程ID, 出错-1.
这两个函数区别:数组
若是调用者阻塞并且它有多个子进程,则在其一个子进程终止时,wait就当即返回。由于wait返回子进程ID,因此调用者知道是哪一个子进程终止了。
参数statloc是一个整型指针。若是statloc不是一个空指针,则终止状态就存放到它所指向的单元内。若是不关心终止状态则将statloc设为空指针。
这两个函数返回的整型状态由实现定义。其中某些位表示退出状态(正常退出),其余位则指示信号编号(异常返回),有一位指示是否产生了一个core文件等等。POSIX.1规定终止状态用定义在<sys/wait.h>中的各个宏来查看。有三个互斥的宏可用来取得进程终止的缘由,它们的名字都已WIF开始。基于这三个宏中哪个值是真,就可选用其余宏(这三个宏以外的其余宏)来取得终止状态、信号编号等。
函数
下面的程序中pr_exit函数使用上表中的宏以打印进程的终止状态。测试
#include <sys/types.h> #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> void pr_exit(int status) { if (WIFEXITED(status)) { printf("normal termination, exit status=%d\n", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("abnormal termination, signal number = %d\n", WTERMSIG(status), #ifdef WCOREDUMP WCOREDUMP(status) ? "(core file generated)" : ""); #else ""); #endif } else if (WIFSTOPPED(status)) { printf("child stopped, signal number = %d\n", WSTOPSIG(status)); } } int main(void) { pid_t pid; int status; if ((pid = fork()) < 0) { fprintf(stderr, "fork error"); } else if (pid == 0) { exit(7); } if (wait(&status) != pid) { fprintf(stderr, "wait error"); } pr_exit(status); if ((pid = fork()) < 0) { fprintf(stderr, "fork error"); } else if (pid == 0) { abort(); } if (wait(&status) != pid) { fprintf(stderr, "wait error"); } pr_exit(status); if ((pid = fork()) < 0) { fprintf(stderr, "fork error"); } else if (pid == 0) { status /= 0; } if (wait(&status) != pid) { fprintf(stderr, "wait error"); } pr_exit(status); return 0; }
编译运行结果:ui
wait是只要有一个子进程终止就返回,waitpid能够指定子进程等待。对于waitpid的pid参数:spa
对于wait,其惟一的出错是没有子进程(函数调用被一个信号中断,也可能返回另外一种出错)。对于waitpid, 若是指定的进程或进程组不存在,或者调用进程没有子进程都能出错。 options参数使咱们能进一步控制waitpid的操做。此参数或者是0,或者是下表中常数的逐位或运算。
命令行
当多个进程都企图对某共享数据进行某种处理,而最后的结果又取决于进程运行的顺序,则咱们认为这发生了竞态条件(race condition)。若是在fork以后的某种逻辑显式或隐式地依赖于在fork以后是父进程先运行仍是子进程先运行,那么fork函数就会是竞态条件活跃的孽生地。
若是一个进程但愿等待一个子进程终止,则它必须调用wait函数。若是一个进程要等待其父进程终止,则可以使用下列形式的循环:指针
while(getppid() != 1) sleep(1);
这种形式的循环(称为按期询问(polling))的问题是它浪费了CPU时间,由于调用者每隔1秒都被唤醒,而后进行条件测试。
为了不竞态条件和按期询问,在多个进程之间须要有某种形式的信号机制。在UNIX中可使用信号机制,各类形式的进程间通讯(IPC)也可以使用。
在父、子进程的关系中,经常有如下状况:在fork以后,父、子进程都有一些事情要作。例如:父进程可能以子进程ID更新日志文件中的一个记录,而子进程则可能要为父进程建立一个文件。在本例中,要求每一个进程在执行完它的一套初始化操做后要通知对方,而且在继续运行以前,要等待另外一方完成其初始化操做。这种状况能够描述为以下:日志
TELL_WAIT();
if ((pid = fork()) < 0) { err_sys("fork error"); } else if (pid == 0) { TELL_PARENT(getppid()); WAIT_PARENT(); exit(0); } TELL_CHILD(pid); WAIT_CHILD(); exit(0);
当进程调用exec函数时,该进程彻底由新进程代换,而新程序则从其main函数开始执行。由于调用exec并不建立新进程,因此先后的进程ID不会改变。exec只是用另外一个程序替换了当前进程的正文、数据、堆和栈段。excel
#include <unistd.h> int execl(const char *pathname, const char *arg0, ... /* (char *) 0 */); int execv(const char *pathname, char *const argv[]); int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */); int execve(const char *pathname, char *const argv[], char *const envp[]); int execlp(const char *filename, const char *arg0, ... /* (char *) 0 */); int execvp(const char *filename, char *const argv[]); 返回值:出错-1,若成功不返回
这些函数之间的第一个区别是前四个取路径名做为参数,后两个取文件名做为参数。当制定filename做为参数时:
若是excelp和execvp中的任意一个使用路径前缀中的一个找到了一个可执行文件,可是该文件不是机器可执行代码文件,则就认为该文件是一个shell脚本,因而试着调用/bin/sh,并以该filename做为shell的输入。
第二个区别与参数表的传递有关(l 表示表(list),v 表示矢量(vector))。函数execl、execlp和execle要求将新程序的每一个命令行参数都说明为一个单独的参数。这种参数表以空指针结尾。另外三个函数execv,execvp,execve则应先构造一个指向个参数的指针数组,而后将该数组地址做为这三个函数的参数。
最后一个区别与向新程序传递环境表相关。以 e 结尾的两个函数excele和exceve能够传递一个指向环境字符串指针数组的指针。其余四个函数则使用调用进程中的environ变量为新程序复制现存的环境。
六个函数之间的区别:
每一个系统对参数表和环境表的总长度都有一个限制。当使用shell的文件名扩充功能产生一个文件名表时,可能会收到此值的限制。例如,命令:
grep _POSIX_SOURCE /usr/include/*/*.h
在某些系统上可能产生下列形式的shell错误。
arg list too long
执行exec后进程ID没改变。除此以外,执行新程序的进程还保持了原进程的下列特征:
对打开文件的处理与每一个描述符的exec关闭标志值有关。进程中每一个打开描述符都有一个exec关闭标志。若此标志设置,则在执行exec时关闭该文件描述符,不然该描述符仍打开。除非特意用fcntl设置了该标志,不然系统的默认操做是在exec后仍保持这种描述符打开。
POSIX.1明确要求在exec时关闭打开目录流。这一般是由opendir函数实现的,它调用fcntl函数为对应于打开目录流的描述符设置exec关闭标志。
在exec先后实际用户ID和实际组ID保持不变,而有效ID是否改变则取决于所执行程序的文件的设置-用户-ID位和设置-组-ID位是否设置。若是新程序的设置-用户-ID位已设置,则有效用户ID变成程序文件的全部者的ID,不然有效用户ID不变。对组ID的处理方式与此相同。
在不少UNIX实现中,这六个函数只有一个execve是系统调用。另外5个是库函数