第8章 进程控制

来源:互联网 发布:淘宝客服修改显示名称 编辑:程序博客网 时间:2024/06/02 08:39

1) fork函数

fork函数可以在主进程中启动子进程,该函数调用一次返回两次,两次返回值是不同的,fork函数所在的头文件和调用形式如下

#include <unistd.h>pid_t fork(void);
  • 在fork产生的子进程中fork的返回值为0,注意这个返回值不是子进程的进程id,如果在子进程中要得到该子进程的进程id,需要在该子进程中调用getpid函数获取。在子进程中调用getppid可以得到父进程的进程id。
  • 在产生fork的父进程中fork的返回值为非零,这个非零值是子进程的进程id。
  • 不同的子进程有同一个父进程,而父进程却无法方便的获取其下子进程的进程id,所以只能在调用fork函数时,将非零的返回值保存下来,达到在父进程中记录子进程的目的

下面的代码演示了子,父进程中如果获取相关的进程id

#include <sys/types.h>#include <sys/wait.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>int main(){  int stat;  pid_t pid;  pid_t pc, pr;   pc = fork();  if(pc<0){    printf("fork error\n");  }else if(pc == 0){ //***pc==0说明是子进程,然后用getpid获得子进程的pid,看看此pid和父进程中fork的返回值是否一致***    printf("get first child pid[getpid()] from child process = %d\n", getpid());  }else{    //***此pc的返回值大于0所以是子进程返回给父进程的子进程的pid,其和子进程中调用getpid得到的id是一致的***    printf("get child pid[fork()] from parent process = %d\n", pc);      pr = fork();    if(pr == 0)       printf("get second child pid[getpid()] from second process = %d\n", getpid());    else{       printf("get second child pid[fork()] from parent process = %d\n", pr);       printf("parent pid = %d\n", getpid());       sleep(20);       //while((pr = wait(NULL)) != -1)       //***防止有僵尸进程存在,在父进程中调用waitpid无阻塞的处理所有子进程***       while((pid = waitpid(-1, &stat, WNOHANG)) > 0)       {              printf("child %d terminated\n", pid);       }          printf("this is parent process.\n");       sleep(20);        }     }  exit(0);}

2)进程退出/终止

进程终止方式

进程有8中终止方式,其中五种为正常终止,剩下的三种为异常终止。

  • 进程终止的五种正常方式

    • (1)从main返回
    • (2)调用exit返回
    • (3)调用_exit或_Exit返回
    • (4)最后一个线程从其启动例程返回
    • (5)从最后一个线程调用pthread_exit
  • 进程终止的三种异常方式

    • (1)调用abort
    • (2)接受到一个信号
    • (3)最后一个线程对取消请求作出响应

进程的退出流程图

系统内核通过exec函数启动一个进程后,执行到退出的流程图如下


进程退出流程图

与退出状态相关的函数

  • exit()先执行一些清理工作,如清理IO现场关闭打开的文件指针,然后退回内核。
  • _exit(),_Exit()不做任何清理工作,直接退回内核。
  • atexit函数用于绑定上图中的终止处理程序,当进程退出后由exit自动调用这些绑定的处理程序,对进程中的相关现场进行处理。
#include <stdlib.h> int atexit(void (*function)(void));

atexit使用实例(Unix高级环境编程p161)

#include "apue.h"static void my_exit1(void);static void my_exit2(void);intmain(void){    if (atexit(my_exit2) != 0)        err_sys("can’t register my_exit2");    if (atexit(my_exit1) != 0)        err_sys("can’t register my_exit1");    if (atexit(my_exit1) != 0)        err_sys("can’t register my_exit1");    printf("main is done\n");    return(0);}static voidmy_exit1(void){    printf("first exit handler\n");}static voidmy_exit2(void){    printf("second exit handler\n");}

处理退出方式

对于任何一种退出方式,父进程都希望能够获得子进程的退出方式

  • 子进程正常退出
    当子进程正常退出时,只要将子进程的退出状态传递给三个exit函数即可。
  • 异常退出
    内核产生一个异常中止原因的终止状态,父进程调用wait或waitpid获取子进程异常退出的状态,并处理异常中止的进程。
  • 无论是正常退出还是异常终止都可以调用wait或waitpid对退出状态进行处理。
  • 子进程正常退出时的状态称为“退出状态”,子进程异常终止时的状态称为“终止状态”,但是在最终调用_exit()内核将“退出状态”转化成“终止状态”。

3)处理僵尸进程

转自http://blog.chinaunix.net/uid-1911213-id-3387225.html

当有多个子进程的SIGCHLD信号到达父进程的时候,如果父进程用wait等待,那么父进程在处理第一个达到的SIGCHLD信号的时候,其他的SIGCHLD信号被堵塞,而且信号不被缓存,这样就会导致信号丢失,这样会产生很多的僵尸进程。。解决办法是父进程用waitpid来等待子进程信号。。。

正好看到有人问这样一个问题

看unix网络编程第一卷的时候,碰到书上这样一个例子:
一个并发服务器, 每一个客户端连接服务器就fork一个子进程.书上讲到当同时有n多个客户端断开连接时,
服务器端同时有n多个子进程终止, 这时候内核同时向父进程发送n多个sigchld信号.它的sigchld信号处理
函数如下:

void sig_chld(int signo){       pid_t   pid;       int     stat;       while((pid = waitpid(-1, &stat, WNOHANG)) > 0){               printf("child %d terminated\n", pid);       }        return;}

我的问题是:既然sigchld是不可靠的信号,进程就不可能对sigchld进行排队, 直接丢弃了sigchld信号(当进程注册信号的时候,发现已有sigchld注册进未决信号, 因为内核同时发送多个sigchld).请问大家上面的代码是如何保证不产生僵尸进程的.谢谢!

超清晰的解答,来自与chinaunix论坛的flw大版主。。。:

根本就不需要找回来!
好比有五个进程,
不妨分别称为 p1 p2 p3 p4 p5,
一开始 p1 结束了,发了一个 SIGCHLD(s1),
这时父进程可能空闲了,于是开始处理这个信号,假设处理的过程中 p2 又结束了,又发了一个 SIGCHLD(s2),
这时候已经有两个信号了(一个正在处理,一个待处理),这时如果 p3 又结束了,那么它发的那个 SIGCHLD(s3) 势必会丢失,
丢失了怎么办?
没关系,因为那个信号处理函数是个循环嘛,
所以 while(waitpid()) 的时候,会把 p1 p2 p3 都处理的。
即使是很不幸,因为十分凑巧的原因,p3 没有被回收,导致变成僵尸进程了,也没关系,
因为还有 p4 p5 嘛,等到 p4 或者 p5 结束的时候,
又会再一次调用 while(waitpid()),到时候虽说这个 while(waitpid()) 是由 p4/p5 引起的,但是它也会一并把 p3 也处理的,因为它是个循环嘛!

如果还搞不懂,你就再看看 waitpid 的 man。

记住一点:
waitpid 和 SIGCHLD 没关系,即使是某个子进程对应的 SIGCHLD 丢失了,只要父进程在任何一个时刻调用了 waitpid,那么这个进程还是可以被回收的。

哎呀呀,简直费劲死了,其实说白了,就是一个“生产者-消费者”问题。
子进程结束的时候,系统“生产”出一个僵尸进程,
同时用 SIGCHLD 通知父进程来“消费”这个僵尸进程,
即使是 SIGCHLD 丢失了,没有来得及消费,
但是只要有一次消费,就会把所有的僵尸进程都处理光光!
(我再说一遍:因为,while(waitpid()) 是个循环嘛!)
我试了一下,wait好像也可以哦

#include <sys/types.h>#include <sys/wait.h>#include <unistd.h>#include <stdio.h>int main(){  pid_t pc, pr;  pc = fork();  if(pc<0){    printf("fork error\n");  }else if(pc == 0){        printf("child pid = %d\n", getpid());  }else{        pr = fork();        if(pr == 0)                printf("child pid = %d\n", getpid());        else{                printf("parent pid = %d\n", getpid());                sleep(20);                while((pr = wait(NULL)) != -1)                        ;                printf("this is parent process.\n");                sleep(20);        }  }  exit(0);}

运行后,
程序先输出
child pid = 14478
parent pid = 14477
child pid = 14479
然后进入sleep(20); 这时候 开另一个terminal, 运行 ps auxw | grep 1447, 得到
1000 14477 0.0 0.0 4124 316 pts/3 S+ 05:36 0:00 ./wait
1000 14478 0.0 0.0 0 0 pts/3 Z+ 05:36 0:00 [wait]
1000 14479 0.0 0.0 0 0 pts/3 Z+ 05:36 0:00 [wait]
1000 14499 0.0 0.0 109244 868 pts/5 S+ 05:36 0:00 grep –color=auto 1447

可以看到,两个子进程已经编程僵尸进程

然后20秒后,父进程执行了wait() ,输出
child pid = 14478
parent pid = 14477
child pid = 14479
this is parent process.

这时候,到另一个终端里,再次运行ps
1000 14477 0.0 0.0 4124 316 pts/3 S+ 05:36 0:00 ./wait
1000 14619 0.0 0.0 109244 872 pts/5 S+ 05:37 0:00 grep –color=auto 1447

可以看到两个僵尸子进程已经被回收。

0 0
原创粉丝点击