第一部分
1. 直接上代码
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int globvar = 6; char buf[] = "a write to stdout!\n"; void son_process_end_func(void) { printf("son process end!\n"); } int main(void) { int var; pid_t pid; var = 88; if ( write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1 ){ printf("write error!! \n"); return -1; } printf("before fork!!, pid = %d\n", getpid() ); /* 父进程中,fork返回新创建子进程的进程ID. 子进程中,fork返回0. 出现错误,fork返回负值. */ if ((pid = fork()) < 0){ printf("fork error!! \n"); } else if (pid == 0){ atexit(son_process_end_func); globvar++; var++; printf("son: pid = %d \n", getpid() ); } else{ sleep(2); printf("father: pid = %d \n", getpid() ); } printf("pid = %d, glob = %d, var = %d \n", getpid(), globvar, var); exit(0); }
2. 编译运行记录
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1#
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# gcc fork.c -o ab
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# ./ab > out.txt 注:这里使用了管道进行重定向
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# cat out.txt
a write to stdout!
before fork!!, pid = 10425
son: pid = 10426
pid = 10426, glob = 7, var = 89
son process end! // 调用了进程终止函数,表明子进程正在终止
before fork!!, pid = 10425 // 难点解释:看第22行的printf使用,由于标准I/O库带缓冲,
father: pid = 10425 // 重定向则会将缓存中的内容也拷贝到子进程中一份,当子进程结束,该缓存就会被再次输出。
pid = 10425, glob = 6, var = 88
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1#
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1#
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# ./ab
a write to stdout!
before fork!!, pid = 10430
son: pid = 10431
pid = 10431, glob = 7, var = 89
son process end! // 调用了进程终止函数,表明子进程正在终止
father: pid = 10430
pid = 10430, glob = 6, var = 88
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1#
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1#
小结:
当fork遇到管道,如果此前标准I/O库函数也在场,那么fork之前缓存中的内容会被拷贝给子进程一份。 如果体会不深,自己跑一遍上面的实验代码,感受感受就是了。
知识点补充:
atexit注册多个进程终止处理函数,先注册的后执行(先进后出,和栈一样)
atexit()用于注册进程结束后所执行的函数
return、exit和_exit的区别:
return和exit效果一样,都是会执行进程终止处理函数,
但是用_exit终止进程时并不执行atexit注册的进程终止处理函数。
贴个实验代码的图(看图更方便)
第二部分
在第一部分的代码基础上只增加一句代码:fflush(stdout),
完整的代码如下
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int globvar = 6; char buf[] = "a write to stdout!\n"; void son_process_end_func(void) { printf("son process end!\n"); } int main(void) { int var; pid_t pid; var = 88; if ( write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1 ){ printf("write error!! \n"); return -1; } printf("before fork!!, pid = %d\n", getpid() );
fflush(stdout); // 注意该行代码产生的效果
/* 父进程中,fork返回新创建子进程的进程ID. 子进程中,fork返回0. 出现错误,fork返回负值. */ if ((pid = fork()) < 0){ printf("fork error!! \n"); } else if (pid == 0){ atexit(son_process_end_func); globvar++; var++; printf("son: pid = %d \n", getpid() ); } else{ sleep(2); printf("father: pid = %d \n", getpid() ); } printf("pid = %d, glob = %d, var = %d \n", getpid(), globvar, var); exit(0); }
我们再次编译运行:
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# gcc fork.c -o ab
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# ./ab > out.txt
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# cat out.txt
a write to stdout!
before fork!!, pid = 14030
son: pid = 14031
pid = 14031, glob = 7, var = 89
son process end!
father: pid = 14030
pid = 14030, glob = 6, var = 88
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1#
现在我们通过管道得到的打印结果,和本博客第一部分中直接./ab 运行的结果,是一样的。
分析:
增加了一行代码,使用fflush(stdout)刷新了缓冲区,所以fork前缓冲区内是空的,我们使用管道重定位的时候,子进程就不会从缓冲区复制数据了。
此时的out.txt内的内容将和直接运行 ./ab 一样。
小结: fflush是从内存缓冲区将数据写到内核缓冲,针对用户空间。
fsync再将内核缓冲写到磁盘,针对内核空间。
由此可见,当fork遇到管道的时候,子进程内会复制fork前的用户空间的缓冲区的数据,例如使用了C标准库的IO函数scanf、printf时,因为标准输入和标准输出对应终端设备时通常是行缓冲的。
PS: 而标准错误输出通常是无缓冲的,这样用户程序产生的错误信息可以尽快输出到设备。
当fork遇到管道的时候,在fork前,使用标准输入和标准错误的情形,本博客未做实验,读者可以自行尝试一下。
.