Linux的fork使用

fork函数可以算是Linux里有点不好明白的函数了,调用一次,返回两次,虽然在平时的写法中,有基本固定的写法,但是有时候看起来还是有些让人头疼的。这里就把关于fork函数好好整理一下

函数介绍

功能:fork函数是从一个已经存在的进程中创建一个新的进程,新的进程称为子进程,原来的进程称为父进程。
参数:无
返回值
成功:子进程中返回 0,父进程中返回子进程 ID。pid_t,为无符号整型。
失败:返回 -1。

失败的两个主要原因是:
1)当前的进程数已经达到了系统规定的上限,这时 errno 的值被设置为 EAGAIN。
2)系统内存不足,这时 errno 的值被设置为 ENOMEM。

测试的例子

下面是一个简单的创建子进程的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <unistd.h>
#include <sys/types.h>

int main(){
int pid=1;
pid=fork();
if(0==pid){ //pid为0,表示为子进程
cout<<"我是子进程,我的pid是:"<<getpid()<<endl;
}
else if(pid>0){ //pid>0表示父进程,此时返回值为子进程的pid
cout<<"我是父进程,我的pid是"<<getpid()<<endl;
}
else { //fork 失败
cout<<"fork失败"<<endl;
}
}

从上面的例子可以看出,fork函数会有两个返回值,一个是在子进程中返回0,一个是在父进程中返回子进程的pid。因此在编程过程中,需要判断这个值的返回值来判断当前是父进程还是子进程。

fork进程的原理

使用 fork() 函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间:包括进程上下文(进程执行活动全过程的静态描述)、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号等。子进程所独有的只有它的进程号,计时器等(只有小量信息)。因此,使用 fork()函数的代价是很大的。

日常使用fork

简单来说, 一个进程调用 fork() 函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

实际上,更准确来说,Linux 的 fork() 使用是通过写时拷贝 (copy- on-write) 实现。写时拷贝是一种可以推迟甚至避免拷贝数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只用在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享

子进程是父进程的一个复制品,可以简单认为父子进程的代码一样的。一般来说,在 fork() 之后是父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。

因此,在实际的使用过程中,一般都需要对fork()的返回值进行判断,看我们操作的是父进程还是子进程。

一般的操作:同时创建多个子进程

不对的例子:父进程会生成 n(n+1)/2+1个子进程,N 为循环次数,本例中共有 7 个子进程, 但实际上只有 3 个是父进程产生的,其余都为子进程 fork()出来的。父进程fork了3个进程,第一个子进程执行完之后又fork了2个进程,第2个子进程fork了1个进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(){
pid_t p1,p2;
int i;
int N=100;
for(int i=0;i<=N;++i){
if((p1=fork())==0){
cout<<"子进程1:"<<getpid()<<endl;
//return 0; //很关键的地方,为什么需要返回呢
}
wait(p1,NULL,0); //父进程等待p1子进程执行后才能继续fork其他子进程
cout<<"这是父进程: "<<getpid()<<endl;
}
}

正确的使用Linux中的用fork()由一个父进程创建同时多个子进程 的格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main(){
pid_t p1,p2;
int i;
int N=100;
for(int i=0;i<=N;++i){
status=fork();
if(status==0||status==1) break;//每次循环时,如果发现是子进程就直接从创建子进程的循环中跳出来,不让你进入循环,这样就保证了每次只有父进程来做循环创建子进程的工作
if (status == -1)
{
//error
}
else if (status == 0) //每个子进程都会执行的代码

{
//sub process
}
else
{
//parent process
}
}
}

无聊的问题

1.下面的程序,不算 main这个进程自身,到底创建了多少个进程啊?

1
2
3
4
5
6
int main(int argc, char* argv[])
{
fork();
fork() && fork() || fork();
fork();
}

每fork一次就翻倍

1
2
3
4
5
6
7
  fork(); //2个
fork() && fork() || fork();
//A&&B||C
//A为假,跳过B,判断C-----------------------2
//A为真,判断B,若B为真,跳过C-----------1
//若B为假,判断C ------------2
fork(); //2

总共有:
2(2+1+2)2=20
不算自己的话有20-1=19个