Linux進程控制方式
1.進程創(chuàng)建
fork函數(shù)
#include <unistd.h> pid_t fork(void);
返回值:自進程中返回 0 ,父進程返回子進程 id ,出錯返回 -1
進程調(diào)用 fork ,當控制轉移到內(nèi)核中的 fork 代碼后,內(nèi)核做:
- 1.分配新的內(nèi)存塊和內(nèi)核數(shù)據(jù)結構給子進程
- 2.將父進程部分數(shù)據(jù)結構內(nèi)容拷貝至子進程
- 3.添加子進程到系統(tǒng)進程列表當中
- 4.fork返回,開始調(diào)度器調(diào)度
fork之后,誰先執(zhí)行完全由調(diào)度器決定?。?!
fork的常規(guī)用法
1.一個父進程希望復制自己,使父子進程同時執(zhí)行不同的代碼段。例如,父進程等待客戶端請求,生成子進程來處理請求。
2.一個進程要執(zhí)行一個不同的程序。例如子進程從fork返回后,調(diào)用exec函數(shù)。
2.寫時拷貝
通常,父子代碼共享,父子再不寫入時,數(shù)據(jù)也是共享的,當任意一方試圖寫入,便以寫時拷貝的方式各自一份副本。
具體見下圖:
為什么要有寫時拷貝?
1.因為有寫時拷貝技術的存在,父子進程得以徹底分離,保證了進程獨立性。
2.寫時拷貝是一種延遲申請技術,可以提高整機內(nèi)存的使用率。
3.進程終止
進程退出的場景
- 1.代碼運行完畢,結果正確
- 2.代碼運行完畢,結果不正確
- 3.代碼異常終止
進程常見退出方法
1.正常終止(可以通過 echo $? 查看進程退出碼)
- 從main返回
- 調(diào)用exit
- 調(diào)用_exit
2.異常退出
ctrl + c,信號終止
_exit 函數(shù)
#include <unistd.h> void _exit(int status);
參數(shù): status 定義了進程的終止狀態(tài),父進程通過 wait 來獲取該值
說明:雖然 status 是 int ,但是僅有低 8 位可以被父進程所用。所以 _exit(-1) 時,在終端執(zhí)行 $? 發(fā)現(xiàn)返回值是255 。
exit 函數(shù)
exit 最后也會調(diào)用 _exit , 但在調(diào)用_ exit 之前,還做了其他工作:
- 1. 執(zhí)行用戶通過 atexit 或 on_exit 定義的清理函數(shù)。
- 2. 關閉所有打開的流,所有的緩存數(shù)據(jù)均被寫入
- 3. 調(diào)用 _exit
實例:
int main() { printf("hello"); exit(0); } 運行結果 : [root@localhost linux]# ./a.out hello[root@localhost linux]# ------------------------------------------------------------------------ int main() { printf("hello"); _exit(0); } 運行結果 : [root@localhost linux]# ./a.out [root@localhost linux]#
通過上邊的例子,我們看到當printf中的字符串后邊沒有加 \n 時,調(diào)用exit函數(shù)會打印出字符串內(nèi)容,但是調(diào)用 _exit 函數(shù)并不會打印任何內(nèi)容,原因就是因為 exit 底層調(diào)用的是 _exit,exit在調(diào)用_exit之前,會刷新緩沖區(qū),表現(xiàn)為原來緩沖區(qū)中的字符串被打印出來,但是 _exit并不會刷新緩沖區(qū)。
那么這個所謂的“緩沖區(qū)”在哪里呢?誰來維護的?
一定不在操作系統(tǒng)內(nèi)部?。∪绻遣僮飨到y(tǒng)維護的,緩沖區(qū)內(nèi)的內(nèi)容也能被 _exit 刷新來。
C標準庫給我們維護的?。?!
退出碼
int main() { int i; for(i=0;i<150;i++) { printf("%d: %s\n",i,strerror(i)); } return 0; }
通過上圖,可以看出,一共有134個退出碼。
4.進程等待
進程等待的必要性
1.之前講過,子進程退出,父進程如果不管不顧,就可能造成‘僵尸進程’的問題,進而造成內(nèi)存泄漏。
2.另外,進程一旦變成僵尸狀態(tài),那就刀槍不入,“殺.人不眨眼”的kill -9 也無能為力,因為誰也沒有辦法殺死一個已經(jīng)死去的進程。
3.最后,父進程派給子進程的任務完成的如何,我們需要知道。如,子進程運行完成,結果對還是不對,或者是否正常退出。
4.父進程通過進程等待的方式,回收子進程資源,獲取子進程退出信息
進程等待的方法
wait方法
#include<sys/types.h> #include<sys/wait.h> pid_t wait(int*status);
返回值:
- 成功返回被等待進程pid,失敗返回-1。
參數(shù):
- 輸出型參數(shù),獲取子進程退出狀態(tài),不關心則可以設置成為NULL
waitpid方法
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
- 當正常返回的時候waitpid返回收集到的子進程的進程ID;
- 如果設置了選項WNOHANG,而調(diào)用中waitpid發(fā)現(xiàn)沒有已退出的子進程可收集,則返回0;
- 如果調(diào)用中出錯,則返回-1,這時errno會被設置成相應的值以指示錯誤所在;
參數(shù):
pid
:
- Pid=-1,等待任一個子進程。與wait等效。
- Pid>0.等待其進程ID與pid相等的子進程。
status
:
- WIFEXITED(status): 若為正常終止子進程返回的狀態(tài),則為真。(查看進程是否是正常退出)
- WEXITSTATUS(status): 若WIFEXITED非零,提取子進程退出碼。(查看進程的退出碼)
options
:
- WNOHANG: 若pid指定的子進程沒有結束,則waitpid()函數(shù)返回0,不予以等待。若正常結束,則返回該子進程的ID。
注意:
- 1.如果子進程已經(jīng)退出,調(diào)用wait/waitpid時,wait/waitpid會立即返回,并且釋放資源,獲得子進程退出信息。
- 2.如果在任意時刻調(diào)用wait/waitpid,子進程存在且正常運行,則進程可能阻塞。
- 3.如果不存在該子進程,則立即出錯返回。
說明:
- 1.wait和waitpid是系統(tǒng)調(diào)用?。?!
- 2. 父進程等待子進程,當子進程執(zhí)行 return / exit / _exit / 因為某種原因異常崩潰退出 后,子進程會將自己的退出碼信息寫入自己的進程控制塊(task_struct)中,此時,退出后的子進程處于Z狀態(tài),此時代碼可以釋放,但是task_struct必須維護,直到父進程讀取信息完畢后,子進程的task_struct才能釋放。
- 3.父進程通過 wait / waitpid 傳遞的參數(shù),就可以拿到子進程的退出結果(退出碼等信息)。
驗證上邊說到的 “子進程會將自己的退出碼信息寫入到自己的進程控制塊中” :
獲取子進程status
1.wait和waitpid,都有一個status參數(shù),該參數(shù)是一個輸出型參數(shù),由操作系統(tǒng)填充。
2.如果傳遞NULL,表示不關心子進程的退出狀態(tài)信息。
3.否則,操作系統(tǒng)會根據(jù)該參數(shù),將子進程的退出信息反饋給父進程。
4.status不能簡單的當作整形來看待,可以當作位圖來看待,具體細節(jié)如下圖(只研究status低16比特位):
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> #include <sys/types.h> int main() { pid_t id=fork(); if(id==0) { int cnt=5; while(cnt--) { printf("我是子進程,pid: %d,ppid(): %d\n",getpid(),getppid()); } exit(100); } else if(id>0) { printf("我是父進程,pid: %d,ppid: %d\n",getpid(),getppid()); int status=0; pid_t result = waitpid(-1,&status,0); if(result>0) { //printf("等待子進程成功,status: %d\n",status>>8); printf("等待子進程成功,status: %d\n",status>>8 & 0xFFFF); } } else { //fork error } return 0; }
注意:獲取status時,一定要先右移8位再按位與0xFFFF,如果不這么做的話,結果為:
顯然與100不符,結果錯誤。(這就是為什么要將status右移8位再按位與0xFFFF)
將status右移8位再按位與0xFFFF后,結果為:
與100符合,結果正確。
當然,還有別的方法,將 status>>8 & 0xFFFF 改為 WEXITSTATUS(status)。
5.進程程序替換
替換原理
用 fork 創(chuàng)建子進程后執(zhí)行的是和父進程相同的程序 ( 但有可能執(zhí)行不同的代碼分支 ), 子進程往往要調(diào)用一種 exec 函數(shù)以執(zhí)行另一個程序。當進程調(diào)用一種exec 函數(shù)時 , 該進程的用戶空間代碼和數(shù)據(jù)完全被新程序替換 , 從新程序的啟動例程開始執(zhí)行。調(diào)用exec 并不創(chuàng)建新進程 , 所以調(diào)用 exec 前后該進程的 id 并未改變。
程序替換,上邊紫色方框這一部分沒有發(fā)生改變,改變的只是,把要替換的進程加載到物理內(nèi)存,頁表重新建立了映射關系。
替換函數(shù)
其實有六種以 exec 開頭的函數(shù) , 統(tǒng)稱 exec 函數(shù):
execl,execlp,execle,execv,execvp,execvpe這六個函數(shù)是系統(tǒng)提供的基本封裝,底層調(diào)用的都是execve這個系統(tǒng)調(diào)用接口。
函數(shù)返回值問題
- 這些函數(shù)如果調(diào)用成功則加載新的程序從啟動代碼開始執(zhí)行 , 不再返回。
- 如果調(diào)用出錯則返回 -1。
- 假設調(diào)用成功有返回值,但是這個返回值也是返回給原來調(diào)用這個exec*系列函數(shù)的進程,但是原來的這個進程已經(jīng)被程序替換掉了,所以也沒辦法返回,方法行不通。
- 所以 exec 函數(shù)只有出錯的返回值而沒有成功的返回值。
命名理解
這些函數(shù)原型看起來很容易混 , 但只要掌握了規(guī)律就很好記。
- l(list) : 表示參數(shù)采用列表
- v(vector) : 參數(shù)用數(shù)組
- p(path) : 有p自動搜索環(huán)境變量PATH
- e(env) : 表示自己維護環(huán)境變量
總結
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Linux報錯cannot?open?shared?object?file問題及解決
這篇文章主要介紹了Linux報錯cannot?open?shared?object?file問題及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08