先看下面一段代码:
1 #include <unistd.h> 2 #include <stdio.h> 3 4 int globvar = 6; 5 char buf[] = "a write to stdout "; 6 7 int main() 8 { 9 int var; 10 pid_t pid; 11 12 var = 88; 13 if(write(STDOUT_FILENO, buf, sizeof(buf) - 1) != sizeof(buf) - 1 ) 14 { 15 printf("write error "); 16 } 17 printf("before fork "); //没有调用flush冲刷缓冲区 18 19 if((pid = fork()) < 0) 20 { 21 printf("fork error "); 22 } 23 else if(pid ==0 ) 24 { 25 globvar++; 26 var++; 27 } 28 else 29 { 30 sleep(2); 31 } 32 33 printf("pid = %ld, glob = %d, var = %d ", (long)getpid(), globvar, var); 34 return 0; 35 }
编译并执行上述程序,结果如下:
pid=2500的输出是子进程,其它三条输出都是父进程的输出,第13行的write函数是不带缓冲区的,这里的缓冲区说的是用户空间的缓冲区,但是在内核中还是有page cache缓冲区的,这两个缓冲区是不一样的,也就是不管写多少数据,write是不会将数据缓冲到用户空间的,而是直接将数据写到内核中的page cache中。而printf是带用户空间缓冲区的,printf输出的数据会先写到由C库维护的用户空间缓冲区中,当然,也跟一些参数有关,会涉及到行缓冲、全缓冲、无缓冲,后面会讲到。
父进程执行fork的时候,会将其持有的资源复制给子进程,内核中的page cache是不会复制的,因为page cache是跟文件绑定的,而不是与单个进程相关的。也就是说一个文件会对应一些page cache,但是可以有多个进程往这些page cache中写数据或者读数据,内核不会为每个进程都维护一份page cache。
父进程复制给子进程包括一些文件描述符,它们共享文件表项,也就是指向同一个file数据结构,而且共享同一个文件偏移量。也包括用户空间的其它一些资源。
printf会维护进程的用户空间缓冲区,我们可以认为它就是每个进程用户态地址空间中堆上的一些存储区域,这些区域在fork时是会复制给子进程的。在上面的输出中,我们看到a write to stdout直接写进了内核的page cache,但是before fork是由printf输出的,会先写到用户空间的缓冲区,执行fork后,用户空间缓冲区也会复制给子进程(当然缓冲区中的数据也会复制),子进程应该也会输出brfore fork才对,但是上图并没有相应的输出。原因是这样的,printf输出到标准输出时是行缓冲的,往用户空间写数据时,遇到换行符就会一次性将所有数据全部刷到内核的page cache中,这样用户空间缓冲区就没有数据了,所以复制给子进程时缓冲区中是空的。这样子进程没有输出before fork也就可以解释了。
下面我们换一种执行方式:
可以看到,这次子进程输出了before fork,因为这次将标准输出重定向到了一个文件,printf输出到一个文件时是全缓冲的,也就是只有写满用户空间的缓冲区之后才会一次性冲刷到内核page cache中,而before fork不足以写满用户空间缓冲区,所以在执行fork的时候,父进程的用户空间缓冲区中是有数据的,这些数据一并复制给了子进程。
任何一个进程退出时,都会冲刷用户空间的缓冲区,因此两个进程都输出了before fork。main函数的最后调用了return,编译器会自动加上exit(0),exit是C库中对系统调用_exit的封装,也就是在exit中会冲刷用户空间的缓冲区,如果直接调用_exit,则可能不会有冲刷缓冲区的动作,而是直接进入内核进行操作。
将main函数最后的return 0改为_exit(0),再次执行,结果如下:
输出到标准输出的情况下和上一个程序没有区别,因为printf执行完之后,数据会立刻冲刷到内核page cache,而page cache中的数据在进程退出时是一定会写到最终的设备的。
再次重定向标准输出到temp.out,执行结果如下:
这次只输出了一行,printf输出到文件时是全缓冲的,也就是必须写满用户空间缓冲区才会冲刷,而本程序中并不会写满这个缓冲区,而_exit退出时也不会冲刷用户空间缓冲区,所以printf输出的数据都无效了。
小知识:行缓冲、全缓冲、无缓冲是对用户空间的缓冲区说的,也叫标准IO缓冲,不是对内核中的page cache说的,page cache是由内核来管理的,对用户是透明的。