【Linux學習筆記】進程的fork創建 exit終止 wait等待
🔥個人主頁:大白的編程日記
🔥專欄:Linux學習筆記
文章目錄
- 【Linux學習筆記】進程的fork創建 exit終止 wait等待
- 前言
- 1.進程創建
- 1.1 fork函數初識
- 1.2fork函數返回值
- 1.3寫時拷貝
- 1.4 fork常規用法
- 1.5fork調用失敗的原因
- 2. 進程終止
- 2.1進程退出場景
- 2.2進程常見退出方法
- 2.2.1退出碼
- 2.3.2 _exit函數
- 2.3.3 exit函數
- 2.3.4 return退出
- 3. 進程等待
- 3.1進程等待必要性
- 3.2進程等待的方法
- 3.2.1wait方法
- 3.2.2 waitpid方法
- 3.2.3 獲取子進程status
- 3.2.4阻塞與非阻塞等待
- 后言
前言
哈嘍,各位小伙伴大家好!上期我們講了環境變量今天我們講的是【Linux學習筆記】進程的fork創建 exit終止 wait等待。話不多說,我們進入正題!向大廠沖鋒!
1.進程創建
1.1 fork函數初識
在linux中fork函數是非常重要的函數,它從已存在進程中創建一個新進程。新進程為子進程,而原進程為父進程。
#include <unistd.h>pid_t fork(void);
//返回值:?進程中返回0,?進程返回?進程id,出錯返回-1
進程調用fork,當控制轉移到內核中的fork代碼后,內核做:
- 分配新的內存塊和內核數據結構給子進程
- 將父進程部分數據結構內容拷貝至子進程
- 添加子進程到系統進程列表當中
- fork返回, 開始調度器調度
當一個進程調用fork之后,就有兩個二進制代碼相同的進程。而且它們都運行到相同的地方。但每個進程都將可以開始它們自己的旅程,看如下程序。
int main( void ){pid_t pid;printf("Before: pid is %d\n", getpid());if ( (pid=fork()) == -1 )perror("fork()"),exit(1);printf("After:pid is %d, fork return %d\n", getpid(), pid);sleep(1);return 0;}
運?結果:
[root@localhost linux]# ./a.outBefore: pid is 43676After:pid is 43676, fork return 43677After:pid is 43677, fork return 0
這里看到了三行輸出,一行before,兩行after。進程43676先打印before消息,然后它有打印after。另一個after消息有43677打印的。注意到進程43677沒有打印before,為什么呢?如下圖所示
所以,fork之前父進程獨立執行,fork之后,父子兩個執行流分別執行。注意,fork之后,誰先執行完全由調度器決定。
1.2fork函數返回值
- 子進程返回0,
- 父進程返回的是子進程的pid。
1.3寫時拷貝
通常,父子代碼共享,父子再不寫入時,數據也是共享的,當任意一方試圖寫入,便以寫時拷貝的方式各自一份副本。具體見下圖:
因為有寫時拷貝技術的存在,所以父子進程得以徹底分離離!完成了進程獨立性的技術保證!寫時拷貝,是一種延時申請技術,可以提高整機內存的使用率
1.4 fork常規用法
- 一個父進程希望復制自己,使父子進程同時執行不同的代碼段。例如,父進程等待客戶端請求,生成子進程來處理請求。
- 一個進程要執行一個不同的程序。例如子進程從fork返回后,調用exec函數。
1.5fork調用失敗的原因
- 系統中有太多的進程
- 實際用戶的進程數超過了限制
2. 進程終止
進程終止的本質是釋放系統資源,就是釋放進程申請的相關內核數據結構和對應的數據和代碼。
2.1進程退出場景
- 代碼運行完畢,結果正確
- 代碼運行完畢,結果不正確
- 代碼異常終止
2.2進程常見退出方法
正常終止(可以通過echo$?查看進程退出碼):
- 從main返回
- 調用exit
- _exit
異常退出:
- ctrl+c,信號終止
2.2.1退出碼
退出碼(退出狀態)可以告訴我們最后一次執行的命令的狀態。在命令結束以后,我們可以知道命令是成功完成的還是以錯誤結束的。其基本思想是,程序返回退出代碼時表示執行成功,沒有問題。代碼1或以外的任何代碼都被視為不成功。
Linux Shell中的主要退出碼:
退出碼 | 解釋 |
---|---|
0 | 命令成功執行 |
1 | 通用錯誤代碼 |
2 | 命令(或參數)使用不當 |
126 | 權限被拒絕(或)無法執行 |
127 | 未找到命令,或 PATH 錯誤 |
128+n | 命令被信號從外部終止,或遇到致命錯誤 |
130 | 通過 Ctrl+C 或 SIGINT 終止(終止代碼 2 或鍵盤中斷) |
143 | 通過 SIGTERM 終止(默認終止) |
255/* | 退出碼超過了 0-255 的范圍,因此重新計算(LCTT 譯注:超過 255 后,用退出取模) |
-
退出碼表示命令執行無誤,這是完成命令的理想狀態。
-
退出碼1我們也可以將其解釋為“不被允許的操作”。例如在沒有sudo權限的情況下
-
使用yum;再例如除以等操作也會返回錯誤碼,對應的命令為
let a=1/0
-
130
(SIGINT
或^C
)和143
(SIGTERM
)等終止信號是非常典型的,它們屬于128+n
信號,其中n代表終止碼。 -
可以使用
strerror
函數來獲取退出碼對應的描述。
2.3.2 _exit函數
#include <unistd.h>void _exit(int status);
//參數:status 定義了進程的終?狀態,?進程通過wait來獲取該值
- 說明:雖然status是int,但是僅有低8位可以被父進程所用。所以_exit(-1)時,在終端執行$?發現返回值是255。
2.3.3 exit函數
#include <unistd.h>void exit(int status);
exit最后也會調用_exit,但在調用_exit之前,還做了其他工作:
- 執行用戶通過atexit或on_exit定義的清理函數。
- 關閉所有打開的流,所有的緩存數據均被寫入
- 調用_exit
int main(){printf("hello");exit(0);}
運?結果
:[root@localhost linux]# ./a.outhello[root@localhost linux]#int main(){printf("hello");_exit(0);}
運?結果
:[root@localhost linux]# ./a.out[root@localhost linux]#
2.3.4 return退出
return是一種更常見的退出進程方法。執行returnn等同于執行exit(n),因為調用main
的運行時函數會將main的返回值當做exit的參數。
3. 進程等待
3.1進程等待必要性
- 之前講過,子進程退出,父進程如果不管不顧,就可能造成‘僵尸進程’的問題,進而造成內存泄漏。
- 另外,進程一旦變成僵尸狀態,那就刀槍不入,“殺人不眨眼”的kill-9也無能為力,因為誰也沒有辦法殺死一個已經死去的進程。
- 最后,父進程派給子進程的任務完成的如何,我們需要知道。如,子進程運行完成,結果對還是不對,或者是否正常退出。
- 父進程通過進程等待的方式,回收子進程資源,獲取子進程退出信息
3.2進程等待的方法
3.2.1wait方法
#include<sys/types.h>#include<sys/wait.h>pid_t wait(int* status);
返回值:成功返回被等待進程pid,失敗返回-1
參數:輸出型參數,獲取?進程退出狀態,不關?則可以設置成為NULL
3.2.2 waitpid方法
pid_t waitpid(pid_t pid, int *status, int options);返回值:
當正常返回的時候waitpid返回收集到的子進程的進程ID;
如果設置了選項WNOHANG,而調用中waitpid發現沒有已退出的子進程可收集,則返回0;
如果調用中出錯,則返回-1,這時errno會被設置成相應的值以指示錯誤所在;參數:
pid:Pid=-1,等待任一個子進程。與wait等效。Pid>0.等待其進程ID與pid相等的子進程。
status:輸出型參數WIFEXITED(status):若為正常終止子進程返回的狀態,則為真。(查看進程是否是正常退出)WEXITSTATUS(status):若WIFEXITED非零,提取子進程退出碼。(查看進程的退出碼)
options:默認為0,表示阻塞等待WNOHANG:若pid指定的子進程沒有結束,則waitpid()函數返回0,不予以等待。若正常結束,則返回該子進程的ID。
- 如果子進程已經退出,調用wait/waitpid時,wait/waitpid會立即返回,并且釋放資源,獲得子進程退出信息。
- 如果在任意時刻調用wait/waitpid,子進程存在且正常運行,則進程可能阻塞。
- 如果不存在該子進程,則立即出錯返回。
3.2.3 獲取子進程status
- wait和waitpid,都有一個status參數,該參數是一個輸出型參數,由操作系統填充。
- 如果傳遞NULL,表示不關心子進程的退出狀態信息。
- 否則,操作系統會根據該參數,將子進程的退出信息反饋給父進程。
- status不能簡單的當作整形來看待,可以當作位圖來看待,具體細節如下圖(只研究status低16比特位)
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>int main( void )
{pid_t pid;if ( (pid=fork()) == -1 )perror("fork"),exit(1);if ( pid == 0 ){sleep(20);exit(10);} else {int st;int ret = wait(&st);if ( ret > 0 && ( st & 0X7F ) == 0 ){ // 正常退出printf("child exit code:%d\n", (st>>8)&0XFF);} else if( ret > 0 ){ // 異常退出printf("sig code : %d\n", st&0X7F );}}
}
測試結果:
# ./a.out #
等20秒退出
child exit code:10
# ./a.out #
在其他終端
kill掉
sig code : 9
3.2.4阻塞與非阻塞等待
- 進程的阻塞等待方式:
int main()
{pid_t pid;pid = fork();if(pid < 0){printf("%s fork error\n",__FUNCTION__);return 1;} else if( pid == 0 ){ //childprintf("child is run, pid is : %d\n",getpid());sleep(5);exit(257);} else{int status = 0;pid_t ret = waitpid(-1, &status, 0); //阻塞式等待,等待5Sprintf("this is test for wait\n");if( WIFEXITED(status) && ret == pid ){printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));}else{printf("wait child failed, return.\n");return 1;}}return 0;
}
運?結果:[root@localhost linux]# ./a.outchild is run, pid is : 45110this is test for waitwait child 5s success, child return code is :1.
- 進程的非阻塞等待方式:
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>typedef void (*handler_t)(); // 函數指針類型
std::vector<handler_t> handlers; // 函數指針數組void fun_one() {printf("這是一個臨時任務1\n");
}void fun_two() {printf("這是一個臨時任務2\n");
}void Load() {handlers.push_back(fun_one);handlers.push_back(fun_two);
}void handler() {if (handlers.empty())Load();for (auto iter : handlers)iter();
}int main() {pid_t pid;pid = fork();if (pid < 0) {printf("%s fork error\n", __FUNCTION__);return 1;} else if (pid == 0) { // childprintf("child is run, pid is : %d\n", getpid());sleep(5);exit(1);} else {int status = 0;pid_t ret = 0;do {ret = waitpid(-1, &status, WNOHANG); // 非阻塞式等待if (ret == 0) {printf("child is running\n");}handler();} while (ret == 0);if ((WIFEXITED(status)) && ret == pid) {printf("wait child 5s success, child return code is :%d.\n", WEXITSTATUS(status));} else {printf("wait child failed, return.\n");return 1;}}return 0;
}
后言
這就是進程的fork創建 exit終止 wait等待。大家自己好好消化!今天就分享到這! 感謝各位的耐心垂閱!咱們下期見!拜拜~