fork系统调用

fork函数介绍:《Linux高性能服务器编程》P239

#include<sys/types.h>

#include<unistd.h>

pid_t fork(void);

      该函数的每次调用都返回两次,在父进程中返回的是子进程的PID,在子进程中则返回0,该返回值是后续代码判断当前进程是父/子进程的依据。fork调用失败时返回-1,并设置errno

      fork函数复制当前进程,在内核进程表中创建一个新的进程表项。新的进程表项有很多属性和原进程相同,比如堆指针、栈指针和标志寄存器的值。但也有许多属性被赋予了新的值,比如该进程的PPID被设置为原进程的PID,信号位图被清除(原进程设置的信号处理函数不在对新进程起作用)。

      子进程的代码与父进程完全相同,同时它还会复制父进程的数据(堆数据、栈数据和静态数据)。数据的复制采用的是所谓的写时复制,即只有在任一进程(父进程或子进程)对数据执行了写操作时,复制才会发生(先是缺页中断,然后操作系统给子进程分配内存并复制父进程的数据)。即便如此,如果我们在程序中分配了大量内存,那么使用fork时也应当十分谨慎,尽量避免没必要的内存分配和数据复制。

        此外,创建子进程后,父进程中打开的文件描述符默认在子进程中也是打开的,且文件描述符的引用计数加1.不仅如此,父进程的用户根目录,当前工作目录等变量的引用计数均会加1.

fork出错可能有两种原因:
    1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
    2)系统内存不足,这时errno的值被设置为ENOMEM。

 

 

接下来看看一个简单的fork:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>

int main()
{
	char *s = NULL;
	int n = 0;

	pid_t pid = fork();
	assert(pid != -1);

	if(pid == 0)
	{
		s = "child";
		n = 3;
	}
	else 
	{
		s = "parent";
		n = 6;
	}

	int i = 0;
	for( ;i < n;i++)
	{
		printf("ppid = %d,pid = %d ,s = %s,&s = %x\n",getppid(),getpid(),s,&s);
		sleep(2);
	}
	exit(3);
}

程序结果如下:

 

fork系统调用

fork中常见面试题:

1、

int main()
{
    fork() || fork();
    printf("A\n");
    exit(0);
} 

你可以先想一想最后结果会出现几个A呢?

两个?四个?----都不对!看下图:

fork系统调用

结果是三个!(至于为什么终端提示符出现时因为这个等待的是父进程,父进程一结束就会出现提示符,而此时子进程还没完,所以就会盖住提示符,结束后执行命令是正常的,如果看着不舒服可以回车就ok(#^.^#))

为什么是三个?看图:

fork系统调用

 

如果是 || 运算符,>0表示不再看后面一个,为0,则继续看下一个,就是三个;&& 运算符呢,举一反三!

2、接下来看一个稍微复杂的,也是可以先自己算一遍鸭:

int main()
{
	int i = 0;
	for( ; i < 2;i++)
	{
		fork();
		printf("A\n");
	}

	exit(0);
}

结果:

fork系统调用

看一下过程,图比较清晰:

fork系统调用

可以自己画图理解一下!

3、

改一改上述代码:

​int main()
{
	int i = 0;
	for( ; i < 2;i++)
	{
		fork();
		printf("A");
	}
	exit(0);
}
​

好~现在想想应该打印几个呢?

对的-----8个

fork系统调用

这是因为去掉\n后缓冲区的数据连带被打出来,因此fork的时候会将缓冲区的A也复制过来,多的就是最下面的A和最右边那个里面的A.还是看图吧~~~

fork系统调用

printf打印数据条件:

1、缓冲区放满

2、虽然没有满,但是强制刷新,  fflush(stdout) 或者“\n”

 3、程序要退出,会先刷新缓冲区

看到一篇比较详细的fork可以参考:

https://www.cnblogs.com/dongguolei/p/8086346.html