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中常见面试题:
1、
int main()
{
fork() || fork();
printf("A\n");
exit(0);
}
你可以先想一想最后结果会出现几个A呢?
两个?四个?----都不对!看下图:
结果是三个!(至于为什么终端提示符出现时因为这个等待的是父进程,父进程一结束就会出现提示符,而此时子进程还没完,所以就会盖住提示符,结束后执行命令是正常的,如果看着不舒服可以回车就ok(#^.^#))
为什么是三个?看图:
如果是 || 运算符,>0表示不再看后面一个,为0,则继续看下一个,就是三个;&& 运算符呢,举一反三!
2、接下来看一个稍微复杂的,也是可以先自己算一遍鸭:
int main()
{
int i = 0;
for( ; i < 2;i++)
{
fork();
printf("A\n");
}
exit(0);
}
结果:
看一下过程,图比较清晰:
可以自己画图理解一下!
3、
改一改上述代码:
int main()
{
int i = 0;
for( ; i < 2;i++)
{
fork();
printf("A");
}
exit(0);
}
好~现在想想应该打印几个呢?
对的-----8个
这是因为去掉\n后缓冲区的数据连带被打出来,因此fork的时候会将缓冲区的A也复制过来,多的就是最下面的A和最右边那个里面的A.还是看图吧~~~
printf打印数据条件:
1、缓冲区放满
2、虽然没有满,但是强制刷新, fflush(stdout) 或者“\n”
3、程序要退出,会先刷新缓冲区
看到一篇比较详细的fork可以参考:
https://www.cnblogs.com/dongguolei/p/8086346.html