一、进程的创建
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)