Linux | c&cpp | Email | github | QQ群:425043908 关注本站

itarticle.cc

您现在的位置是:网站首页 -> Linux 文章内容

进程复制fork,孤儿进程,僵尸进程-itarticl.cc-IT技术类文章记录&分享

发布时间: 9年前Linux 121人已围观返回

一,进程复制(或产生)

使用fork函数得到的子进程从父进程的继承了整个进程的地址空间,包括:进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。

子进程与父进程的区别在于:

1、父进程设置的锁,子进程不继承(因为如果是排它锁,被继承的话,矛盾了)

2、各自的进程ID和父进程ID不同

3、子进程的未决告警被清除;

4、子进程的未决信号集设置为空集。


二,fork系统调用

包含头文件 <sys/types.h> 和 <unistd.h>

函数功能:创建一个子进程

函数原型

pid_t fork(void); //一次调用两次返回值,是在各自的地址空间返回,意味着现在有两个基本一样的进程在执行

参数:无参数。

返回值:

如果成功创建一个子进程,对于父进程来说返回子进程ID

如果成功创建一个子进程,对于子进程来说返回值为0

如果为-1表示创建失败

流程图:

父进程调用fork()系统调用,然后陷入内核,进行进程复制,如果成功:

1,则对调用进程即父进程来说返回值为刚产生的子进程pid,因为进程PCB没有子进程信息,父进程只能通过这样获得。

2,对子进程(刚产生的新进程),则返回0,

这时就有两个进程在接着向下执行

如果失败,则返回0,调用进程继续向下执行

注:fork英文意思:分支,fork系统调用复制产生的子进程与父进程(调用进程)基本一样:代码段+数据段+堆栈段+PCB,当前的运行环境基本一样,所以子进程在fork之后开始向下执行,而不会从头开始执行。

示例程序:

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#define ERR_EXIT(m) \

do\

{\

perror(m);\

exit(EXIT_FAILURE);\

}\

while (0)\

int main(void)

{

pid_t pid;

printf("before calling fork,calling process pid = %d\n",getpid());

pid = fork();

if(pid == -1)

ERR_EXIT("fork error");

if(pid == 0){

printf("this is child process and child's pid = %d,parent's pid = %d\n",getpid(),getppid());

}

if(pid > 0){

//sleep(1);

printf("this is parent process and pid =%d ,child's pid = %d\n",getpid(),pid);

}

return 0;

}

运行结果:

当没给父进程没加sleep时,由于父进程先执行完,子进程成了孤儿进程,系统将其托孤给了1(init)进程,

所以ppid =1。

当加上sleep后,子进程先执行完:

这次可以正确看到想要的结果。


三,孤儿进程、僵尸进程

fork系统调用之后,父子进程将交替执行,执行顺序不定。

如果父进程先退出,子进程还没退出那么子进程的父进程将变为init进程(托孤给了init进程)。(注:任何一个进程都必须有父进程)

如果子进程先退出,父进程还没退出,那么子进程必须等到父进程捕获到了子进程的退出状态才真正结束,否则这个时候子进程就成为僵进程(僵尸进程:只保留一些退出信息供父进程查询)

示例:

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#define ERR_EXIT(m) \

do\

{\

perror(m);\

exit(EXIT_FAILURE);\

}\

while (0)\

int main(void)

{

pid_t pid;

printf("before calling fork,calling process pid = %d\n",getpid());

pid = fork();

if(pid == -1)

ERR_EXIT("fork error");

if(pid == 0){

printf("this is child process and child's pid = %d,parent's pid = %d\n",getpid(),getppid());

}

if(pid > 0){

sleep(100);

printf("this is parent process and pid =%d ,child's pid = %d\n",getpid(),pid);

}

return 0;

}

以上程序跟前面那个基本一致,就是让父进程睡眠100秒,好让子进程先退出

运行结果:

从上可以看到,子进程先退出,但进程列表中还可以查看到子进程,[a.out] <defunct>,死的意思,即僵尸进程,如果系统中存在过多的僵尸进程,将会使得新的进程不能产生。


四,写时复制

linux系统为了提高系统性能和资源利用率,在fork出一个新进程时,系统并没有真正复制一个副本。

如果多个进程要读取它们自己的那部分资源的副本,那么复制是不必要的。

每个进程只要保存一个指向这个资源的指针就可以了。

如果一个进程要修改自己的那份资源的“副本”,那么就会复制那份资源。这就是写时复制的含义

fork 和vfork:

在fork还没实现copy on write之前。Unix设计者很关心fork之后立刻执行exec所造成的地址空间浪费,所以引入了vfork系统调用。

vfork有个限制,子进程必须立刻执行_exit或者exec函数。

即使fork实现了copy on write,效率也没有vfork高,但是我们不推荐使用vfork,因为几乎每一个vfork的实现,都或多或少存在一定的问题


五,fork之后父子进程共享文件:

fork产生的子进程与父进程相同的文件文件描述符指向相同的文件表,引用计数增加,共享文件文件偏移指针

示例程序:

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <fcntl.h>

#define ERR_EXIT(m) \

do\

{\

perror(m);\

exit(EXIT_FAILURE);\

}\

while (0)\

int main(void)

{

pid_t pid;

int fd;

fd = open("test.txt",O_WRONLY);

if(fd == -1)

ERR_EXIT("OPEN ERROR");

pid = fork();

if(pid == -1)

ERR_EXIT("fork error");

if(pid == 0){

write(fd,"child",5);

}

if(pid > 0){

//sleep(1);

write(fd,"parent",6);

}

return 0;

}

运行结果:

可知父子进程共享文件偏移指针,父进程写完后文件偏移到parent后子进程开始接着写。


五,僵尸进程

当一个子进程先于父进程结束运行时,它与其父进程之间的关联还会保持到父进程也正常地结束运行,或者父进程调用了wait才告终止。

子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程,它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态。

进程表中代表子进程的数据项是不会立刻释放的,虽然不再活跃了,可子进程还停留在系统里,因为它的退出码还需要保存起来以备父进程中后续的wait调用使用。它将称为一个“僵进程”。


六,如何避免僵尸进程

调用wait或者waitpid函数查询子进程退出状态,此方法父进程会被挂起。

如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。


七,SIGCHLD信号

当子进程退出的时候,内核会向父进程发送SIGCHLD信号,子进程的退出是个异步事件(子进程可以在父进程运行的任何时刻终止)

如果不想让子进程编程僵尸进程可在父进程中加入:signal(SIGCHLD,SIG_IGN);

如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。

示例:

#include <stdio.h>

#include <unistd.h>

#include <signal.h>

#include <stdlib.h>

int main(void)

{

pid_t pid;

if(signal(SIGCHLD,SIG_IGN) == SIG_ERR)

{

perror("signal error");

exit(EXIT_FAILURE);

}

pid = fork();

if(pid == -1)

{

perror("fork error");

exit(EXIT_FAILURE);

}

if(pid == 0)

{

printf("this is child process\n");

exit(0);

}

if(pid > 0)

{

sleep(100);

printf("this is parent process\n");

}

return 0;

}

查看运行结果可知,虽然子进程先退出了,但进程表中已经不存在子进程的僵尸状态


八,wait()函数

#include <sys/types.h>

#include <sys/wait.h>

pid_t wait(int *status);

进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就象下面这样:

pid = wait(NULL);


如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。


wait系统调用会使父进程暂停执行,直到它的一个子进程结束为止。

返回的是子进程的PID,它通常是结束的子进程

状态信息允许父进程判定子进程的退出状态,即从子进程的main函数返回的值或子进程中exit语句的退出码。

如果status不是一个空指针,状态信息将被写入它指向的位置


发布时间: 9年前Linux121人已围观返回回到顶端

很赞哦! (1)

文章评论

  • 请先说点什么
    热门评论
    120人参与,0条评论

站点信息

  • 建站时间:2016-04-01
  • 文章统计:728条
  • 文章评论:82条
  • QQ群二维码:扫描二维码,互相交流