fork和缓冲区
fork在面试中经常被问到,在这里复习一下。
frok创建子进程,父子进程共享.text段,子进程获得父进程数据段、堆和栈的副本,由于在fork之后经常跟随者exec,所以很多实现并不执行父进程数据段、堆和栈的完全复制,而是使用写时复制(Copy-On-Write,COW)技术。这些区域由父子进程共享,并被内核设为只读,如果父子进程试图修改这些区域,则内核只为修改区域的那块内存制作一个副本。
接下来的程序演示一下fork函数的功能
#include<unistd.h> #include<stdio.h> #include<stdlib.h> #include<sys/types.h> void err_sys(const char *s) { printf("error:%s",s); exit(EXIT_FAILURE); } int glob = 6; char buf[] = "a write to stdout\n"; int main() { int var; pid_t pid; var = 88; if(write(STDOUT_FILENO,buf,sizeof(buf)-1) != sizeof(buf)-1) err_sys("write error"); printf("before fork\n"); if((pid = fork())<0) err_sys("fork error"); else if(pid == 0) { glob++; var++; } else sleep(2); printf("pid = %d,glob = %d,var = %d\n",getpid(),glob,var); exit(0); }
编译得到fork_sample,运行结果如下:
当直接执行fork_sample时,结果不出乎意料。在fork前先打印"before fork\n",fork之后父子进程分别打印。
但是当程序输出重定向到temp文件时(即./fork_sample > temp的作用),读取temp文件(即cat temp),我们发现"before fork\n"被输出了两遍,这是为什么呢?
这得从标准I/O库的缓冲说起。
标准I/O库提供了三种类型的缓冲:
(1) 全缓冲:填满标准I/O缓冲区才实际进行I/O操作。
(2)行缓冲:在输入和输出中遇到换行符时,标准I/O库执行I/O操作,当流涉及终端时,通常使用行缓冲。
(3)不带缓冲:标准出错流stderr通常是不带缓冲的。
上面说的缓冲指的是应用层的缓冲,在进行实际的I/O操作时,相关的系统调用(read和write)其实在内核也有缓冲区的。
当直接执行./fork_sample时,由于标准输出时行缓冲的,所以遇到换行符'\n'后缓冲区被冲洗。
当将程序输出重定向到别的文件时,是标准输出是全缓冲的,fork之前printf的数据仍在缓冲区中,在fork时该缓冲区也被复制到子进程中,因此我们就会看到"before fork\n"输出了两次。