0%

fork

fork

前言

​ 一个进程,包括代码、数据和分配给进程的资源。

​ fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
​ 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main(){
pid_t child_pid;
/*fork函数
*作用:系统调用,产生一个子进程,调用一次返回两次
*返回值:pid_t类型,在头文件中定义,通常为整形
* 0 子进程返回
* ID 父进程返回的子进程ID
*/
child_pid=fork();

if(child_pid < 0){
printf("the failure\n");
return 1;
}

if(child_pid==0){
int i=0;
for(;i<5;i++){
printf("the child\n");
sleep(1);
}
}
else{
printf("child's pid is %d",child_pid);
int i=0;
for(;i<5;i++){
printf("the father\n");
sleep(1);
}
}
printf("I'm over\n");
return 0;
}

那么调用这个fork函数时发生了什么呢?

​ fork函数启动一个新的进程,前面我们说过,这个进程几乎是当前进程的一个拷贝:子进程和父进程使用相同的代码段;子进程复制父进程的堆栈段和数据段。

​ 这样,父进程的所有数据都可以留给子进程,但是,子进程一旦开始运行,虽然它继承了父进程的一切数据,但实际上数据却已经分开,相互之间不再有影响了,也就是说,它们之间不再共享任何数据了。它们再要交互信息时,只有通过进程间通信来实现。

既然它们如此相象,系统如何来区分它们呢?

​ 这是由函数的返回值来决定的。对于父进程,fork函数返回了子程序的进程号,而对于子程序,fork函数则返回零。在操作系统中,我们用ps就可以看到不同的进程号,对父进程而言,它的进程号是由比它更低层的系统调用赋予的,而对于子进程而言,它的进程号即是fork函数对父进程的返回值。在程序设计中,父进程和子进程都要调用函数fork()下面的代码,而我们就是利用fork()函数对父子进程的不同返回值用if…else…语句来实现让父子进程完成不同的功能。

正如我们上面举的例子一样。我们看到,上面例子执行时两条信息是交互无规则的打印出来的,这是父子进程独立执行的结果,虽然我们的代码似乎和串行的代码没有什么区别。

细节

   如果一个大程序在运行中,它的数据段和堆栈都很大,一次fork就要复制一次,那么fork的系统开销不是很大吗?其实UNIX自有其解决的办法,大家知道,一般CPU都是以”页”为单位来分配内存空间的,每一个页都是实际物理内存的一个映像,象INTEL的CPU,其一页在通常情况下是4086字节大小,而无论是数据段还是堆栈段都是由许多”页”构成的,fork函数复制这两个段,只是”逻辑”上的,并非”物理”上的,也就是说,实际执行fork时,物理空间上两个进程的数据段和堆栈段都还是共享着的,当有一个进程写了某个数据时,这时两个进程之间的数据才有了区别,系统就将有区别的”页”从物理上也分开。系统在空间上的开销就可以达到最小。

wait and exit

定义函数wait()

pid_t wait (int * status);

  • wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。
  • 如果在调用 wait()时子进程已经结束,则 wait()会立即返回子进程结束状态值
  • 子进程的结束状态值会由参数 status 返回,而子进程的进程识别码也会一起返回。
  • 如果不在意结束状态值,则参数status 可以设成 NULL

UTOOLS1576735868543.png

1
2
3
pid_t pid_1;
int status=0;
pid_1=wait(&status);

status的使用

定义函数exit()

exit() 结束当前进程/程序,在整个进程/程序中,只要调用 exit ,就结束。

void exit(int status)

传入的参数是程序退出时的状态码,0表示正常退出,其他表示非正常退出,一般都用-1或者1,标准C里有EXIT_SUCCESS和EXIT_FAILURE两个宏,用exit(EXIT_SUCCESS)可读性比较好一点。

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<sys/types.h>
#include<sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(){
pid_t pid_1,pid_2;

pid_1 = fork();
if (pid_1 < 0){
printf("the failure\n");
return 1;
}
if (pid_1 == 0){
printf("I am a child process i am going to sleep\n");
sleep(5);
printf("I am a child process and I exit normally\n");
exit(EXIT_SUCCESS);
}else{
int status;
pid_2 = wait(&status);
if (pid_2 < 0){
printf("Something went wrong\n");
return 1;
}
if (status = 0){
printf("I'm the father process and I know my child process %d exits\n",pid_2);
}else{
printf("I'm the father process and I know my child process %d exited abnormally\n",pid_2);
}
}
return 0;
}