所谓进程控制,就是系统使用一些具有特定功能的程序段来创建进程、撤消进程以及完成进程在各种状态之间的转换,
从而达到多进程高效率并发执行和协调资源共享的目的。进程控制是进程管理和处理机管理的一个重要任务。
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); }
运行截图: