小何讲进程: Linux进程控制编程 (fork、vfork)

来源:互联网 发布:js城市选择器插件 编辑:程序博客网 时间:2024/05/17 18:03

所谓进程控制,就是系统使用一些具有特定功能的程序段来创建进程、撤消进程以及完成进程在各种状态之间的转换,

从而达到多进程高效率并发执行和协调资源共享的目的。进程控制是进程管理和处理机管理的一个重要任务。


1. fork()创建进程
在Linux系统中,除了系统启动之后的第一个进程(根进程)由系统来创建外,

其余所有进程都必须由已存在的进程来创建新创建的进程叫子进程,而创建子进程的进程叫父进程,

具有相同父进程的进程叫兄弟进程。

在Linux中创建一个新进程的方法是使用fork()函数。 
fork()函数用于根据已存在的进程以“复制”的方式创建一个子进程。


调用fork()时,fork()将父进程所有的资源通过数据结构的"复制"传给子进程:
让子进程与父进程使用同一代码段
将父进程的数据段和堆栈段复制一份给子进程,这样父进程的所有数据都可以留给子进程,

但父子进程的地址空间已经分开,不再共享任何数据,因此,相互之间不再有影响。

子进程的执行独立于父进程,父子进程的数据共享需要通过专门的通信机制。
采用“复制”的方法创建子进程控制块
把复制这个词加上引号,是因为这个复制并不是完全复制。因为父进程控制块中某些项的内容必须按照子进程的特点来修改,

如:进程的标识、状态等。另外,子进程控制块还必须有表示自己父进程的域及私有空间,如数据空间、用户堆栈等。


使用fork()函数得到的子进程是父进程的一个“复制品”,它从父进程处继承了整个进程的地址空间:
包括进程上下文、代码段、进程堆栈、内存信息、打开的文件描述符、信号控制设定、进程优先级、进程组号、

当前工作目录、根目录、资源限制和控制终端等,而子进程所独有的只有它的进程号、资源使用和计时器等少量信息。





父、子两个进程运行同一个程序:

因为子进程几乎是父进程的完全复制,所以父子两个进程会运行同一个程序。

因此需要用一种方式来区分它们,否则,这两个进程不可能做不同的事。


子进程中,fork()返回0
父进程中,fork()返回子进程的ID(>0)

实际上是在父进程中执行fork()函数时,父进程会复制出一个子进程,而且父子进程的代码从fork()函数的返回开始分别在两个地址空间中同时运行。

从而两个进程分别获得其所属fork()的返回值,其中在父进程中fork()的返回值是子进程的进程号,而在子进程中fork()返回0。

因此,可通过返回值来判定该进程是父进程还是子进程。
若fork()创建进程失败,则返回-1。



fork()系统调用的工作流程示意图:



fork()函数的不足:
使用fork()函数时,它“复制”了父进程中的代码段、数据段和堆栈段里的大部分内容,使得fork()函数的系统开销比较大。
当创建一个子进程后,子进程接下来调用exec()执行另一个程序,那么前面的复制工作将是多余的
如果调用fork()函数的程序的数据段和堆栈段都很大,那么复制工作的开销会严重影响系统性能。
解决办法:采用copy-on-write(写时复制)技术。
复制时只是“逻辑”上的,并非“物理”上的:实际执行fork()时,物理空间上两个进程的数据段和堆栈段都还是共享着的,

当有一个进程写了某个数据时,这时两个进程之间的数据才有了区别,此时系统就将有区别的“页”从物理上分离,

这样系统在空间上的开销就可以达到最小。

fork()函数举例:

#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<sys/types.h>int main(){pid_t result;result=fork();if(result==-1){printf("FORK ERROR!\n");}else if(result==0){printf("In child process PID is %d \n The returned value is %d \n\n",getpid(),result);}else{printf("In father process PID is %d \n The return value is %d \n\n",getpid(),result);}return result;}


运行截图如下:



2.  vfork():
vfork是linux提供的另一个用来生成一个子进程的系统调用。
与fork的区别:vfork并不把父进程全部复制到子进程中,而只是用复制指针的方法使子进程和父进程的资源实现共享。

因为通常创建新进程的目的是 exec一个新程序,所以vfork不产生父进程的副本,可提高效率。
vfork与fork一样都创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用 exec (或exit ),

不过在子进程调用 exec或exit之前,它在父进程的空间中运行。
vfork保证子进程先于父进程运行,只有当子进程调用exec或exit后,父进程才能被调度运行。
用vfork()创建的子进程不能用return返回,只能用exit()或_exit()退出。而用fork()创建的子进程可以用return返回。



3. 对比fork()和vfork()函数,观察之间的区别

(1)调用fork()函数:

#include <sys/types.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>int main(void){    pid_t result;   int  data=5;  result = fork(); /*调用fork()函数*/    if(result ==  -1)/*首先进行出错处理*/          printf("Fork error\n");  else if (result == 0) /*返回值为0代表子进程*/{data++;           printf("data=%d,child\'s PID=%d\n",data,getpid());}   else /*返回值大于0代表父进程*/       { data++;                 printf("data=%d,father\'s PID=%d\n",data,getpid());}   exit(1);  }

运行截图:



(2)调用vfork()函数:

#include <sys/types.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>int main(void){    pid_t result;   int  data=5;  result = vfork(); /*调用fork()函数*/    if(result ==  -1)/*首先进行出错处理*/          printf("Fork error\n");  else if (result == 0) /*返回值为0代表子进程*/{  data++;             printf("data=%d,child\'s PID=%d\n",data,getpid());}   else /*返回值大于0代表父进程*/       {   data++;                   printf("data=%d,father\'s PID=%d\n",data,getpid());}   exit(1);  }
运行截图:


4 0