linux-进程(2)fork. vfork .进程的退出

本文详细介绍了Unix/Linux系统中进程创建的关键函数fork和vfork,以及它们的工作原理和区别。通过示例代码展示了fork在创建子进程后的执行情况,解释了写时复制技术。此外,还讨论了进程退出、退出状态的收集以及wait和waitpid函数在处理子进程退出状态中的作用。最后,提到了孤儿进程的概念及其在Linux系统中的处理方式。
摘要由CSDN通过智能技术生成

fork()函数

函数原型

 pid_t fork(void);

返回值
返回两次pid
成功:在父进程中返回子进程的pid(非负数),在子进程中返回0。
失败:父进程中返回-1,不创建子进程。
ps:在使用fork的时候,fork()之前的语句只会执行一次,fork()之后的语句会执行两次,分别是父子进程的执行。父子进程的执行顺序是不一定的。
简要概述引用连接
早期的Unix内核:
把父进程的所有内容拷贝给子进程
Unix系统,用于实现一种傻瓜式的进程创建:当发出fork()系统调用时,内核原样复制父进程的整个地址空间并把复制的那一份分配给子进程。
现在的Unix内核(包括Linux)共享数据段,只有当子进程更改了数据段,才会拷贝一份数据段。
采用一种更为有效的方法称之为写时复制:内核只为新生成的子进程创建虚拟空间结构,它们来复制于父进程的虚拟空间结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。这时就是实现了对不同进程的操作而不会产生影响其他的进程,同时也节省了很多的物理存储器。

p1表示父进程,p2表示子进程,内核只为新生成的子进程创建虚拟空间结构,它们复制于父进程的虚拟空间结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,当父子进程中有更改相应的段的行为发生时,再为子进程相应的段分配物理空间。

在这里插入图片描述

int main()
{

    pid_t pid;
    int a=10;

    printf("before fork %d\n",getpid());
    pid= fork();
//进程

    if(pid>0){//父进程
        printf("this is father pid %d,father is %d\n",getpid(),getppid());
        a=a+10;

    }
    if(pid==0){//子进程
         printf("this is child pid%d,father is %d\n",getpid(),getppid());
         a=a-10;
    }
    printf("%d\n",a);
    return 0;
}
//如果只是单单重复执行两次那么输出结果应该会出现10,
//这也说明了父子之间的储存空间不是公用的,是拷贝的

before fork 44902
this is father pid 44902,father is 44109
20
this is child pid44903,father is 44902//(有时候出现孤儿进程此时子进程的father为1)
0

fork创建一个子进程的目的
1、一个父进程希望复制自己,使父、子进程同时执行不同的代码段。
比如在网络服务中,父进程等待客户端的服务请求。当收到请求时,父进程调用fork,使子进程处理此请求(可一直循环,用来处理多个用户请求)。父进程则继续等待下一个服务请求到达。
2、一个进程要执行一个不同的程序。
这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec。

vfork

vfork()和fokr()的区别
1.vfork直接使用父进程存储空间,不拷贝。
2.vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。

int main()
{
    pid_t pid;
    int n=0;
    pid= vfork();
    if(pid>0){
        while(1){
                printf("this is father pid %d\n",getpid());
                sleep(3);
                printf("%d\n",n);
        }
    }
    if(pid==0){
         while(1){
                printf("this is child pid%d\n",getpid());
                sleep(1);
                n=n+10;
                if(n==30){
                     exit(0);
                }
         }
    }
    return 0;
}

this is child pid45191
this is child pid45191
this is child pid45191
this is father pid 45190
30
this is father pid 45190
30
this is father pid 45190
30
this is father pid 45190
30
…//子进程先运行,直到调用exit推出后才执行父进程,而且子进程
“n=n+10”的执行,对父进程的“n”的输出结果是有影响的。

进程退出

退出方式:
正常退出:
1.Main函数调用return
2.进程调用exit(),标准c库
3.进程调用_exit()或者_Exit(),属于系统调用
4.进程最后一个线程返回
5.最后一个线程调用pthread_exit
异常退出:
1.调用abort
2.当进程收到某些信号时,如ctrl+C
3.最后一个线程对取消(cancellation)请求做出响应

不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。对上述任意一种终止情形、我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit._exit和_Exit),实现这一点的方法是,将其退出状态((exit status)作为参数传送给函数。在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态(termination status)。在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数取得其终止状态。

exit _exit _Exit

 void _exit(int status);
 void _Exit(int status);
 void exit(int status);

子进程退出状态的收集
为什么要等待子进程退出?

创建子进程的目的
子进程干活
干完活
5个正常退出
没干完活
3个异常退出

如果没有收集子进程的退出状态

子进程退出状态不被收集,变成僵死进程(僵尸进程)

int main()
{

    pid_t pid;

    int n=0;

    pid= vfork();


    if(pid>0){//父进程
        while(1){
                printf("this is father pid %d\n",getpid());
                sleep(3);
                printf("%d\n",n);
        }

    }
    if(pid==0){//子进程
         while(1){
                printf("this is child pid%d\n",getpid());
                sleep(1);
                n=n+10;
                if(n==30){
                     exit(0);//正常退出
                }
         }
    }
    return 0;
}

this is child pid45571
this is child pid45571
this is child pid45571
this is father pid 45570
30
this is father pid 45570
30
this is father pid 45570
30
this is father pid 45570
30
this is father pid 45570
^C

在这里插入图片描述
Z为Zombie的缩写,意思为僵尸

收集退出状态 wait() waitpid()

wait
函数原型

pid_t wait(int *status);

status参数:
是一个整型数指针:
子进程退出状态放在它所指向的地址中。
如果为空(NULL):则不关心退出状态(有exit就行不关心里面的参数)
返回值:
wait():如果成功,返回终止子进程的进程ID
失败,返回-1
wait(NULL)

if(pid>0){
        while(1){
                wait(NULL);//父进程堵塞,等待子进程结束
                printf("this is father pid %d\n",getpid());
                sleep(1);
                printf("%d\n",n);
        }
    }

在这里插入图片描述
不会出现僵尸进程(子进程结束后退出,不会出现僵尸进程)

wait(&status)
在这里插入图片描述

WIFEXITED(status)	如果子进程正常结束,它就返回真;否则返回假。
WEXITSTATUS(status)	如果WIFEXITED(status)为真,则可以用该宏取得子进程exit()返回的结束代码。
WIFSIGNALED(status)	如果子进程因为一个未捕获的信号而终止,它就返回真;否则返回假。
WTERMSIG(status)	如果WIFSIGNALED(status)为真,则可以用该宏获得导致子进程终止的信号代码。
WIFSTOPPED(status)	如果当前子进程被暂停了,则返回真;否则返回假。
WSTOPSIG(status)	如果WIFSTOPPED(status)为真,则可以使用该宏获得导致子进程暂停的信号代码。

int main()
{

    pid_t pid;
    pid_t pid2;
    int cnt=0;
    int status=10;
    pid=fork();
    if(pid>0){
          pid2=wait(&status);//父进程堵塞,等待子进程结束
          printf("*********pid2= %d \n",pid2);//wait的返回值为子进程的pid
          while(1){

              printf("father: pid=%d***%d \n ",getpid(),WEXITSTATUS(status));
              //获取父进程的pid和子进程的退出状态
              sleep(1);
          }

    }
    if(pid==0){
          while(cnt!=3){

               cnt++;
               printf("child:%d\n",getpid());
               sleep(1);
          }
          exit(7);


    }
    return 0;
}

在这里插入图片描述

子进程结束后,退出状态被收集,被关闭

waitpid
函数原型

pid_t waitpid(pid_t pid, int *status, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

参数
pid参数:
pid<-1 等待进程组识别码为 pid 绝对值的任何子进程。
pid=-1 等待任何子进程,相当于 wait()。
pid=0 等待进程组识别码与目前进程相同的任何子进程。
pid>0 等待任何子进程识别码为 pid 的子进程。
status参数:
是一个整型数指针
非空:子进程退出状态放在它所指向的地址中。
空:不关心退出状态
options参数:
参数options提供了一些额外的选项来控制waitpid,参数 option 可以为 0 或可以用"|"运算符把它们连接起来使用,比如:
ret=waitpid(-1,NULL,WNOHANG | WUNTRACED);
如果我们不想使用它们,也可以把options设为0,如:
ret=waitpid(-1,NULL,0);
WNOHANG 如果pid指定的子进程没有结束,则waitpid()函数立即返回0,而不是阻塞在这个函数上等待;如果结束了,则返回该子进程的进程号。
WUNTRACED 如果子进程进入暂停状态,则马上返回,但子进程的结束状态不予以理会。

返回值:

成功:返回其状态子进程的进程ID;如果指定了WNOHANG,并且子进程没有结束,则waitpid()函数立即返回0
失败:返回-1

int main()
{

    pid_t pid;
    int cnt=0;
    int status=10;
    pid=fork();
    if(pid>0){
         waitpid(pid,&status,WNOHANG);
          while(1){

              printf("father: pid=%d***%d \n ",getpid(),WEXITSTATUS(status));
              sleep(1);
          }

    }
    if(pid==0){
          while(cnt!=3){

               cnt++;
               printf("child:%d\n",getpid());
               sleep(1);
          }
          exit(7);
    }
    return 0;
}

在这里插入图片描述

不堵塞的时候,父进程不会等待子进程,而且出现僵尸进程,说明该子进程的状态未被收集。 所以父进程status的值不是7.

孤儿进程

父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程。
Linux避免系统存在过多孤儿进程,init进程(系统初始化进程,进程ID为1)收留孤儿进程,变成孤儿进程的父进程

int main()
{

    pid_t pid;
    int cnt=0;
    int status=10;
    pid=fork();
    if(pid>0){
             printf("father: pid=%d*** \n ",getpid());
    }
    if(pid==0){
          while(cnt!=5){
              cnt++;
               printf("child:%d my father is %d\n",getpid(),getppid());
               sleep(1);
          }
          exit(7);
    }
    return 0;
}

在这里插入图片描述

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值