第一部分緩存
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. 編譯運行記錄spa
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# code
小結:blog
當fork遇到管道,若是此前標準I/O庫函數也在場,那麼fork以前緩存中的內容會被拷貝給子進程一份。 若是體會不深,本身跑一遍上面的實驗代碼,感覺感覺就是了。進程
知識點補充:
atexit註冊多個進程終止處理函數,先註冊的後執行(先進後出,和棧同樣)
atexit()用於註冊進程結束後所執行的函數
return、exit和_exit的區別:
return和exit效果同樣,都是會執行進程終止處理函數,
可是用_exit終止進程時並不執行atexit註冊的進程終止處理函數。內存
貼個實驗代碼的圖(看圖更方便)ci
第二部分get
在第一部分的代碼基礎上只增長一句代碼: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前,使用標準輸入和標準錯誤的情形,本博客未作實驗,讀者能夠自行嘗試一下。
根據本實驗的運行結果來看,推測:雖然printf是行緩衝,可是執行代碼 printf("before fork!!, pid = %d\n", getpid() ) 時,並無當即刷新用戶空間的緩衝區到內核,而當咱們使用fflush時,才刷新到了內核。
.