2024年4月8日发(作者:)

1 . 父进程可以利用wait()/ waitpid()等待子进程的结束,避免僵死子进程的

产生,当然也可以循环的wait()/ watipid()来等待所有的子进程的结束;最好可

以用法是,在子进程结束时,会向父进程发送的SIGCHLD信号,父进程通过

signal()/sigaction()来响应子进程的结束.具体实例可参考:TestFork4.c,关

键代码如下:

signal(SIGCHLD, sigchld_handler);

void sigchld_handler(int sig)

{

pid_t pid;

int status;

for(; (pid =waitpid(-1, &status, WNOHANG)) >0;)

{

printf("child %d died:%dn", pid, WEXITSTATUS(status));// pid,

status);

}

//while((pid =waitpid(-1, &status, WNOHANG))>0){}

return;

}

2 . 当父进程结束时,还未结束的子进程就会成为孤儿进程,系统会把init进

程作为其父进程;从而使得子进程在父进程结束后,继续运行.

3 . 对于父子进程共存时,产生相关信号如SIGINT时,父子进程都能接受到此

信号,只是先由父进程响应,再由父进程把此信号传递给子进程;但要注意的地

方是:(1)如父进程没有对该信号进程自定义处理,则父子进程都接受信号的默

认处理,eg:在没有对SIGINT进程自定义处理时,产生此信号,由父子进程都马

上中止;(2)如父进程自定义了信号处理方法,则子进程一样接受此信号和其信

号处理方法;此时,若子进程成为孤儿进程,则此时的子进程不会再接受此信号

和其信号处理方法.具体实例可参考 TestFork6.c,关键代码如下:

signal(SIGINT, sig_handler);

for(i =0; i<5; i++)

{

if(fork() ==0)

{

printf("child %dn", getpid());

sleep(5);//此刻,响应SIGINT信号,且信号处理完后唤醒进程

printf("after sleep1:child %dn", getpid());

sleep(5);//到此刻,子进程成为孤儿进程,因此不再响应SIGINT

信号

printf("after sleep2:child %dn", getpid());

exit(0);

}

}

4 . 在创建子进程时,内核将父进程的很多内容拷贝给子进程,同时在调用fork()

后,父子进程共享正文部分(其实也相当于拷贝);注意拷贝两字,所以子进程

中修改的东东对父进程没有作用,除非利用父子进程通信方法;因此,要特别注

意子进程的代码编写,如动态空间的释放问题,内存信号修改问题;具体实例可

参考:TestFork1.c,关键代码如下:

gets(buf);

pid =fork();

if(pid ==0)

{

printf("child:");

strcpy(buf, "xiaofeng");

//此处的buf(为主进程中buf的一个副本)与else(主进程)中的buf

无关,因为对其修改不会影响主进程中buf的内容

}

else

{

printf("parent:");

sleep(5);

}

printf("%sn", buf);//此处输出结果处决于该语句位于哪个进程(父/子

进程)

结果:(hello 为gets(buf)语句)

hello

child:xiaofeng

parent:hello

5 . 补充下有关信号的几个结论:

(1)信号处理有三种模式,一为默认处理方式;二为信号忽略方式;三为自定

义信号处理方法;

(2) 当一个进程睡眠在可中断优先级时,进程捕获的信号将中断进程的睡眠

//补充,添加多线程

Linux下的多线程模型的特点:

Linux下的线程实质上是轻量级进程(light weighted process),线程生成时会

生成对应的进程控制结构,只是该结构与父线程的进程控制结构共享了同一个进

程内存空间。 同时新线程的进程控制结构将从父线程(进程)处复制得到同样

的进程信息,如打开文件列表和信号阻塞掩码等。由于我们是在子线程生成之后

修改了信号阻塞掩码,此刻子线程使用的是主线程原有的进程信息,因此子线程

仍然会对SIGINT和SIGTERM信号进行反应,因此当我们用Ctrl+C发出了SIGINT

信号的时候,主进程不处理该信号,而子进程(线程)会进行默认处理,即退出。

子进程退出的同时会向父进程(线程)发送SIGCHLD信号,表示子进程退出,由

于该信号没有被阻塞,因此会导致主进程(线程)也立刻退出,出现了前述的运

行情况。因而该问题的一个解决方法是在子线程生成前进行信号设置, 或在子

线程内部进行信号设置。 由于子线程是往往是一个事务处理函数,因此我建议

在简单的情况下采用前者,如果需要处理的信号比较复杂,那就必须使用后一种

方法来处理。

------------------------------------------补充: 僵尸进程

Unix: 僵死(Zombie)进程

进程在它的生命周期有几种状态:睡眠,可运行,停止,正在运行和僵

死状态。所谓僵死进程,指的是一个进程已经退出,它的内存和相关的资源已经

被内核释放掉,但是在进程表中这个进程项(entry)还保留着,以便它的父进

程得到它的退出状态。一个进程退出时,它的父进程会收到一个SIGCHLD信号。

一般情况下,这个信号的句柄通常执行wait系统调用,这样处于僵死状态的进

程会被删除。如果父进程没有这么做,结果是什么呢?毫无疑问,进程会处于僵

死状态。实际上,僵死进程不会对系统有太大的伤害,最多就是它的进程号(PID)

和进程表中的进程项系统不能使用。

一个父进程fork了一个子进程出来,然后它们两个就各自执行自己的

代码,在某一时刻,子进程退出了。但是此时子进程还有一定的空间并没有被操

作系统回收。需要父进程使用wait系列的系统调用对该子进程进行回收,这样

子进程就能够完全地从系统中消失。

所以僵尸进程就是说子进程在退出之后到被父进程回收之前的这段时间。

子进程死后,会发送SIGCHLD信号给父进程。

一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正

的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用exit,

它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并

不能将其完全销毁)。在Linux进程的状态中,僵尸进程 是非常特殊的一种,

它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在

进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此

之外,僵尸进程不再占有任何内存空间。它需要它的父进程来为它收尸,如果他

的父进程没安装SIGCHLD信号处理函数调用wait或waitpid()等待子进程结束,

由于该信号的默认处理是忽略, 所以它就一直保持僵尸状态,如果这时父进程结

束了,那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。

但是如果如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,

这就是为什么系统中有时会有很多的僵尸进程。