fork
1. 进程标志
#include <unistd.h> #include <sys/types.h> uid_t getpid(void); uid_t getppid(void); uid_t getuid(void); uid_t geteuid(void); gid_t getgid(void); git_t getegid(void);
struct passed *getpwuid(uid_t uid); struct passwd { char *pw_name; /* 登录名称 */ char *pw_passwd; /* 登录口令 */ uid_t pw_uid; /* 用户 ID */ gid_t pw_gid; /* 用户组 ID */ char *pw_gecos; /* 用户的真名 */ char *pw_dir; /* 用户的目录 */ char *pw_shell; /* 用户的 SHELL */ }; #include <pwd.h>; #include <sys/types.h>;
2. fork
pid_t fork();
当 fork 掉用失败的时候(内存不足或者是用户的最大进程数已到)fork 返回-1.父进程中返回子进程id,子进程返回0.
3. wait和waitpid
#include <sys/types.h>;
#include <sys/wait.h>;
pid_t wait(int *stat_loc);
pid_t waitpid(pid_t pid,int *stat_loc,int options);
两个系统调用用于等待子进程状态改变,并获取状态信息。如果该进程没有子进程或子进程已经结束,则wait/waitpid就会立即返回。
wait()阻塞执行直到子进程终止。
waitpid()默认情况下仅仅等待终止的子进程。但可通过options指定另外两种子进程状况改变。状态改变包含: the child terminated(默认,options=0); the child was stopped by a signal(options=WUNTRACED); or the child was resumed by a signal (options=WCONTINUED). 在子进程终止情况下,wait/waitpid准许系统释放子进程相关资源;若没有调用wait或waitpid子进程变为僵尸进程。
wait <==> waitpid(-1, &status, 0);
假如chilid已经改变状态,wait/waitpid会立即返回,否则两函数阻塞直到子进程状态改变或两函数被中断。若子进程状态改变,但还没有被wait/waitpid,则该进程被称为waitable。
waitpid()中pid取值范围如下:
< -1 等子进程中进程组ID等于pid绝对值的任一进程。
-1 等任一子进程。
0 等进程组ID等于调用进程组ID的任一进程。
> 0 等指定pid的状态改变。
waitpid()的options为0或(|)下列值:
WNOHANG,父进程不阻塞直接返回。
WUNTRACED,假如一个子进程停止(不能通过ptrace跟踪)就返回。若此选项不指定,则可追踪(traced)的已停止子进程的状态被返回。
WCONTINUED,一个已停止的进程由于收到信号SIGCONT而恢复则返回。
stat_loc保存子进程退出状态(一般exit,_exit,return),是一个整型指针,存储退出状态。若stat_loc不为NULL,则wait/waitpid储存退出状态(int型)在stat_loc中。可通过下列宏判断,参数为整型数,非指针。
WIFEXITED(status) return true假如子进程正常终止(exit,_exit, return).
》》WEXITSTATUS(status) 返回子进程的退出状态,其由status的最低8bits组成。仅当WIFEXITED返回真时有效。
WIFSIGNALED(status) 返回真,假如子进程被信号终止。
》》WTERMSIG(status) 返回信号数字值(造成子进程终止的信号),仅当WIFSIGNALED为真时有效。
》》WCOREDUMP(status) 返回真,假如子进程生成core dump。仅当WIFSIGNALED为真时有效。
WIFSTOPPED(status) 返回真,假如子进程被信号stopped。仅当options包含WUNTRACED或子进程正在被traced时有效。
》》WSTOPSIG(status) 返回信号数字值(造成子进程stop的信号)。仅当WIFSTOPPED为真时有效。
WIFCONTINUED(status) 返回真,假如子进程被SIGCONT恢复。
RETURN VALUE
wait(): on success, returns the process ID of the terminated child; on error, -1 is returned
waitpid(): on success, returns the process ID of the child whose state has changed; if WNOHANG was specified and one or more child(ren) specified by pid exist, but have not yet changed state, then 0 is returned.On error, -1 is returned.
4. SIGCHLD
SIGCHLD信号产生的条件
1.子进程终止时会向父进程发送SIGCHLD信号,告知父进程回收自己,但该信号的默认处理动作为忽略,因此父进程仍然不会去回收子进程,需要捕捉处理实现子进程的回收;
(注意:需要注意的是,虽然进程对于 `SIGCHLD`的默认动作是忽略,但是还是显示写出来,才能有效;signal(SIGCHLD, SIG_IGN)
,这样子进程直接会退出。)
2.子进程接收到SIGSTOP(19)信号停止时;
3.子进程处在停止态,接受到SIGCONT后唤醒时。
综上:子进程结束、接收到SIGSTOP停止(挂起)和接收到SIGCONT唤醒时都会向父进程发送SIGCHLD信号。父进程可以捕捉该信号,来实现对子进程的回收,或者了解子进程所处的状态。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> #include <sys/wait.h> #include <signal.h> void sys_err(char *str) { perror(str); exit(1); } void do_sig_child(int signo) { int status; pid_t pid;
// 不可以将捕捉函数内部的while替换为if。因为在执行捕捉函数期间,发送了多次SIGCHLD信号,未决信号集只是记录了一次
// 因此下一次再调用捕捉函数时,if只能完成对一个子进程的回收(即使有多个子进程都发了信号,但是只是调用一次捕捉函数)。
// 而while循环则可以对所有结束了的子进程都完成回收。因此对于多个子进程的回收,最好采用循环的方式,不采用if。 // if ((pid = waitpid(0, &status, WNOHANG)) > 0) { while ((pid = waitpid(0, &status, WNOHANG)) > 0) { if (WIFEXITED(status)) printf("------------child %d exit with %d ", pid, WEXITSTATUS(status)); else if (WIFSIGNALED(status)) printf("child %d killed by the %dth signal ", pid, WTERMSIG(status)); } } int main(void) { pid_t pid; int i; //阻塞SIGCHLD for (i = 0; i < 10; i++) { if ((pid = fork()) == 0) break; else if (pid < 0) sys_err("fork"); } if (pid == 0) { //10个子进程 int n = 1; while (n--) { printf("child ID %d ", getpid()); sleep(1); } return i+1; //子进程结束状态依次为1、2、••••••、10 } else if (pid > 0) { struct sigaction act; act.sa_handler = do_sig_child; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGCHLD, &act, NULL); //解除对SIGCHLD的阻塞 while (1) { printf("Parent ID %d ", getpid()); sleep(1); } } return 0; }
父子进程信号处理:
1.子进程继承了父进程的信号屏蔽字和信号处理动作,但子进程没有继承未决信号集spending。
2.注意注册信号捕捉函数的位置。
3.应该在fork之前,阻塞SIGCHLD信号。注册完捕捉函数后解除阻塞。
参考:SIGCHLD信号 Linux: 关于 SIGCHLD 的更多细节
5. 僵尸进程危害
1)占用资源(少量);2)难以清除(杀死僵尸进程父进程)。
如果一个父进程终止,而子进程还存在(仍运行或僵尸进程),子进程的父进程改为init进程,子进程终止时,init调用wait函数清理它。