一、进程的创建


1、fork函数



在Linux中,fork函数是非常重要的函数,它从已存在的进程中创建一个新进程。新进程为子进程,原进程为父进程。



进程的创建、等待与终止_进程


返回值:




进程的创建、等待与终止_父进程_02


子进程返回0,父进程返回子进程id,出错返回-1。



什么时候使用fork呢?当一个父进程希望复制自己,使父子进程同时执行不同的代码段。或者一个进程要执行一个不同的程序。


那么,进程调用fork函数后,控制权转移到内核中的fork代码后,内核做了什么呢?


如下图:




进程的创建、等待与终止_进程_03


如上图,内核分配新的内存块和内核数据结构给子进程,将父进程部分数据结构内容拷贝至子进程,添加子进程到系统进程列表当中,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 }


编译后执行结果:



进程的创建、等待与终止_#include_04


一共有三行输出,注意观察。进程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 }


结果:


进程的创建、等待与终止_进程_05

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


二、进程等待



1、进程等待的必要性


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



2、进程等待的方法


进程的创建、等待与终止_#include_06


返回值:



进程的创建、等待与终止_子进程_07


如上图可知:


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比特位,如下图:



进程的创建、等待与终止_进程_08


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 }




结果:



进程的创建、等待与终止_子进程_09




三、进程终止


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



1、进程正常终止


从main返回       调用exit     _exit



2、进程异常退出


ctrl+c,信号终止



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


3、_exit函数




进程的创建、等待与终止_父进程_10


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



4、exit函数


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




进程的创建、等待与终止_子进程_11


举例子如下:


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 }


结果:



进程的创建、等待与终止_子进程_12



5、return


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