进程的创建、等待与终止

时间:2022-10-30 14:53:35


一、进程的创建
1、fork函数

在Linux中,fork函数是非常重要的函数,它从已存在的进程中创建一个新进程。新进程为子进程,原进程为父进程。
进程的创建、等待与终止
返回值:

进程的创建、等待与终止
子进程返回0,父进程返回子进程id,出错返回-1。

什么时候使用fork呢?当一个父进程希望复制自己,使父子进程同时执行不同的代码段。或者一个进程要执行一个不同的程序。
那么,进程调用fork函数后,控制权转移到内核中的fork代码后,内核做了什么呢?
如下图:

进程的创建、等待与终止
如上图,内核分配新的内存块和内核数据结构给子进程,将父进程部分数据结构内容拷贝至子进程,添加子进程到系统进程列表当中,fork返回,开始调度器调度。
那么调用完之后呢?就有了两个二进制代码相同的进程,而且运行到相同的地方。但每个进程之后就可以开始自己的旅程。
用代码解释一下:
  1 #include<unistd.h>
  2 int main(void){
  3     pid_t pid;
  4 
  5     printf("Before:pid is %d\n",getpid());
  6     if((pid=fork())==-1)
  7         perror("fork()"),exit(1);
  8     printf("After:pid is %d,fork return %d\n",getpid(),pid);
  9     sleep(1);
 10     return 0;
 11 }

编译后执行结果:
进程的创建、等待与终止
一共有三行输出,注意观察。进程4966先打印before消息,然后打印After消息。而4967进程只打印了After消息。这是因为上文中提到的,fork之前父进程独立执行,fork后产生子进程,分别执行。而谁先执行完全由调度器决定。

2、vfork函数
同样可以创建子进程但是与fork有区别:

vfork用于创建一个子进程,而子进程和父进程共享地址空间,fork的子进程具有独立地址空间
vfork保证子进程先运行,在它调用exec或(exit)之后父进程才可能被调度运行。

看一个例子:
 1 #include<unistd.h>
  2 
  3 int glob=100;
  4 int main(){
  5     pid_t pid;
  6 
  7     if((pid=vfork())==-1)
  8         perror("fork"),exit(1);
  9     if(pid==0){//child
 10         sleep(3);
 11         glob=200;
 12         printf("child glob %d\n",glob);
 13         exit(0);
 14     }
 15     else{//parent
 16         printf("parent glob %d\n",glob);
 17     }
 18     return 0;
 19 }

结果:
进程的创建、等待与终止

子进程直接改变了glob的值,证明在同一块空间中运行。

二、进程等待

1、进程等待的必要性
子进程若已退出,父进程若不等待,可能造成僵尸进程,造成内存泄漏。父进程也应知道子进程完成的如何,是否正常退出。且可通过进程等待的方式,回收子进程资源,获取子进程退出信息。

2、进程等待的方法
进程的创建、等待与终止

返回值:
进程的创建、等待与终止
如上图可知:
1、wait

返回值:成功返回被等待进程pid,失败返回-1
参数:输出型参数,获取子进程退出状态,不关心则可以设置为NULL

2、waitpid
返回值:当正常返回的时候waitpid返回收集到的子进程id

如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在

参数:
pid:

          pid=-1,等待任一个子进程,与wait等效。
          pid>0,等待其进程id与pid相等的子进程

status:
          WIFEXITED:若为正常终止子进程返回的状态,则为真(查看进程是否正常退出)

          WEXITSTATUS:若WIFEXITED非零,提取子进程退出码(查看进程的退出码)
注:不能以简单的整型来看待,可以看作位图,只研究status的低16比特位,如下图:
进程的创建、等待与终止
options:

        WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的id。
举例:进程的阻塞等待方式,即option取0:
 1 #include<sys/wait.h>
  2 #include<stdio.h>
  3 #include<stdlib.h>
  4 #include<string.h>
  5 #include<errno.h>
  6 
  7 int main(){
  8     pid_t pid;
  9 
 10     if((pid=fork())<0){
 11         printf("%s fork error\n",__FUNCTION__);
 12         return 1;
 13     }
 14     else if(pid==0){//child
 15         printf("child is run,pid is %d\n",getpid());
 16         sleep(5);
 17         exit(257);
 18     }else{
 19         int st;
 20         pid_t ret=waitpid(-1,&st,0);
 21         printf("this is test for wait\n");
 22 
 23         if(WIFEXITED(st)&&ret==pid){
 24             printf("wait child 5s success,child return code is:%d.\n",WEXITS    TATUS(st));
 25         }else{
 26             printf("wait child failed,return \n");
 27             return 1;
 28         }
 29     }
 30     return 0;
 31 }


结果:
进程的创建、等待与终止

三、进程终止
进程退出有几种场景,代码运行完毕,结果正确或不正确;代码异常终止

1、进程正常终止
从main返回       调用exit     _exit

2、进程异常退出
ctrl+c,信号终止

现在来认识一下_exit与exit函数
3、_exit函数

进程的创建、等待与终止
参数:status定义了进程的终止状态,父进程通过wait来获取该值

4、exit函数
exit函数最后也会调用_exit,但在调用之前,还做了其他工作

进程的创建、等待与终止
举例子如下:
  1 
  2 #include<sys/wait.h>
  3 #include<stdio.h>
  4 #include<stdlib.h>
  5 
  6 int main(){
  7     printf("hello\n");
  8     exit(0);
  9 }

结果:
进程的创建、等待与终止

5、return

return是一种更常见的退出进程方法,执行return n的等同于执行exit(n)