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