目錄
1.進程等待
為什么需要進程等待?
相關系統調用:wait()和waitpid()
wait():
waitpid():
解析子進程狀態(status)
2.進程替換
為什么需要進程替換?
相關系統調用:exec函數家族
3.環境變量
?編輯
?編輯
? ? ? ? 本文將對Linux環境下的進程:包括進程創建、終止與進程等待、替換進行講解,作者使用XShell連接配置為CentOs 7.6的主機進行演示,希望能幫助你更好理解操作系統的運行原理!
1.進程等待
????????在操作系統中,進程等待是指父進程主動等待子進程結束執行,并回收子進程資源的過程。這是進程管理中的重要機制,主要用于解決子進程結束后資源釋放和狀態獲取的問題。
為什么需要進程等待?
-
回收子進程資源
????????子進程結束后,其內核數據結構PCB不會立即釋放,會變成僵尸進程。(ps:僵尸進程不能被殺死,只能通過進程等待解決!)父進程通過等待機制回收子進程的殘留資源,避免內存泄漏和系統資源浪費。 -
獲取子進程退出狀態
? ? ? ? 父進程可以通過等待機制獲取子進程的執行結果(退出的狀態值、被終止收到的信號),進行后續處理。
相關系統調用:wait()和waitpid()
????????先來看看man-pages的wait介紹:
wait():
- 作用:阻塞當前父進程,直到任意一個子進程結束或被信號中斷。
- 參數:
status是一個指向整數的指針,用于存儲子進程的退出狀態(可通過宏解析)。 - 返回值:
- 成功時返回結束的子進程的PID;
- 失敗時返回 -1。
//以下是對進程等待的測試
int testwait()
{pid_t id=fork();if(id<0){perror("fork");return 1;}else if(id==0){//childint cnt=5;while(cnt){printf("I am child,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}exit(0);}else {//parentint cnt=10;while(cnt){printf("I am father,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}//這里先不介紹status
-----------------------------------------------------------------------------------------pid_t ret=wait(NULL);//這里wait返回的就是子進程創建成功后返回的pidif(ret==id){printf("success!\n");}
-----------------------------------------------------------------------------------------}return 0;
}//補充:如果創建了多個子進程,又如何通過wait獲得子進程的運行狀態呢?
//在---畫出的區域可以這樣修改:
//對于多個子進程,只需要將wait遍歷即可for(i=0;i<n;i++){pid_t ret=wait(NULL);if(ret>0){printf("wait %d success\n",ret);}}
waitpid():
- 作用:等待指定的子進程(更靈活,可設置非阻塞等待)。
- 參數:
pid
:- pid =? -1:等待任意子進程;
- pid > 0:等待 PID 為 pid?的子進程(一般設定為指定等待子進程的PID);
status
:同上,用于獲取子進程狀態。options
:- 0(默認選擇):阻塞模式,若子進程未結束則一直等待(阻塞狀態);
- WNOHANG:?非阻塞模式,若子進程未結束則立即返回0。
- 返回值:
- 子進程未結束:根據options返回 0(非阻塞)或阻塞等待;
- 子進程結束:返回該子進程的 PID;
- 出錯:返回 -1。
int testwaitplus()
{pid_t id=fork();if(id<0){perror("fork");return 1;}else if(id==0){//childint cnt=20;while(cnt){printf("I am child,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}exit(0);}else {//parentint cnt=10;while(cnt){printf("I am father,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);cnt--;sleep(1);}//pid_t ret=wait(NULL);//這里wait返回的就是子進程創建成功后返回的pidwhile(1){int status=0;// pid_t ret=waitpid(-1,&status,0);pid_t ret=waitpid(-1,&status,WNOHANG);if(ret>0){printf("success!\n");printf("exit successfully,pid:%d\n,exit_code:%d,exit_signal:%d\n",ret,(status>>8)&0xFF,status&0x7F);//還可以用宏if(WIFEXITED(status))//判斷是否異常退出{printf("進程正常退出,無異常!exit_code:%d\n",WEXITSTATUS(status));}exit(0);}else if(ret<0){printf("wait failed\n");}else {printf("子進程還未退出,waiting...\n");}sleep(1);}}return 0;
}
解析子進程狀態(status)
? ? ? ? 這里的status到底是什么?這里詳細介紹一下這個玩意:
status本質上就是一個整形:4個字節對應32個bit位:
0000 0000 0000 0000 0000 0000 0000 0000
前16位不考慮,后十六位進行寫入標記;
0000 0000 0000 0000后七位表示異常信號,倒數第八位(標紅)標識core dump,前八位表示退出碼,自己想要查看兩碼可以進行位操作,也可以用宏:
WIFEXITED(status): 若為正常終止子進程返回的狀態,則為真
WEXITSTATUS(status): 若WIFEXITED非零,提取子進程退出碼其實本質上就是進行的位操作:
(status>>8)&0xFF(0xFF十六進制)
status&0x7F(0x7F十六進制)
2.進程替換
????????在操作系統中,進程替換是指用一個新的程序(可執行文件或腳本)替換當前進程的內存空間、代碼和數據,使進程轉而執行新程序的過程。這一過程不會創建新進程,而是直接覆蓋當前進程的上下文,因此進程的PID保持不變,但執行的內容被完全替換。
為什么需要進程替換?
- 執行外部程序
例如,在Shell中輸入ls命令時,Shell會通過進程替換讓當前子進程執行ls程序。 - 程序升級或切換功能
一個進程在運行中需要切換到另一個功能模塊時,可通過替換自身來實現。
相關系統調用:exec函數家族
? ? ? ?進程替換通過exec系列函數實現,共有 7個函數,統稱為exec函數族。它們的作用是加載并執行一個新程序,替換當前進程的內存空間。
函數原型與區別:
上述六個都是庫函數,都是基于系統調用execve實現的:
給出總結:?
函數名 | 參數形式 | 是否搜索?PATH ?環境變量 | 能否自定義環境變量 |
---|---|---|---|
execl | 可變參數列表(以?NULL ?結尾) | 否 | 否 |
execlp | 可變參數列表 | 是(根據?PATH ?查找程序) | 否 |
execle | 可變參數列表 | 否 | 是(傳入環境變量數組) |
execv | 字符指針數組(argv ) | 否 | 否 |
execvp | 字符指針數組 | 是 | 否 |
execvpe | 字符指針數組 | 是 | 是 |
execve | 字符指針數組 | 否 | 是(系統調用) |
核心區別:
參數傳遞方式:帶?I?的函數(類似鏈表)通過逗號分隔的可變參數列表傳遞參數,最后以NULL結尾;帶?v?的函數(類似數組)通過字符指針數組傳遞參數,類似main函數的參數形式。
是否搜索?PATH
:帶?p?的函數會根據系統環境變量PATH查找程序路徑,無需指定絕對路徑。
環境變量控制:帶?e?的函數可以自定義環境變量,否則繼承當前進程的環境變量。
底層系統調用:execve?是唯一的系統調用,其他函數均是對它的封裝。
exec函數的特點:
執行成功后不返回
? ? ? ? 若?exec
?調用成功,當前進程的代碼、數據、堆、棧等會被新程序完全替換,進程從新程序的入口點開始執行,不會返回原程序。(也沒有辦法返回)僅當?exec
?調用失敗時,才會返回?-1
,并繼續執行原程序后續代碼。
進程 ID 不變
? ? ? ? 替換前后進程的PID保持不變,因為替換的是進程的 “內容”,而非進程本身。
文件描述符繼承
? ? ? ? 原進程打開的文件描述符在EXEC后默認保持打開狀態。
環境變量繼承
? ? ? ? 原進程所擁有的環境變量在EXEC后默認保持相同。
不是說進程替換是將新的程序所帶有的代碼數據,替換掉原先的代碼數據嗎?PID保持不變,其他全部都被替換掉了,但是!實際上只有內存管理部分大換血,而環境變量,文件描述符包括IO部分完全相同,或者說原封不動地繼承了下去,兩個部分解耦合。
void test1()
{//單個進程進行進程替換printf("this is a begin:pid:%d,ppid:%d\n",getpid(),getppid());execl("/usr/bin/ls","ls","-a","-l",NULL); printf("this is a end:pid:%d,ppid:%d\n",getpid(),getppid());//可以看到進程替換后代碼和數據進行了替換,pid不變,execl后面的代碼不再執行
}extern char** environ;void test2()
{//對exec系統調用函數進行使用char* const arr[]={"ls","-a","-l",NULL };//execlp("testcpp","testcpp",NULL);//execv("/usr/bin/ls",arr);execle("./testcpp","testcpp",NULL,environ);
}
3.環境變量
? ? ? ? 抱歉抱歉,環境變量姍姍來遲,前面已經提到了它,現在來補充認識一下吧:
?????????環境變量是操作系統中存儲的一系列鍵值對,用于控制系統和應用程序的行為。它們在進程的上下文中生效,影響進程的運行環境...
環境變量本質:
存儲形式:以?NAME=VALUE
?的格式存儲,例如PATH=/home/usr/testfile。
進程關聯:每個進程啟動時會繼承其父進程的環境變量,并可修改自身的環境變量(但通常不影響父進程)。
存儲位置:進程的環境變量存儲在內存中的環境變量表(由指針environ指向)(使用時記得extern char** environ),可通過main函數的第三個參數envp訪問。
常見環境變量:
變量名 | 作用描述 | 典型值 |
---|---|---|
PATH | 命令搜索路徑,多個路徑用冒號?: ?分隔 | /usr/local/sbin:/usr/local/bin:... |
HOME | 當前用戶主目錄 | /home/username |
USER | 當前用戶名 | username |
SHELL | 用戶默認 Shell 路徑 | /bin/bash |
LANG | 系統語言和區域設置 | en_US.UTF-8 |
PWD | 當前工作目錄(由 Shell 動態更新) | /home/username/projects |
TERM | 終端類型(影響命令行顯示效果,如顏色、光標控制) | xterm-256color |
EDITOR | 默認文本編輯器 | vim ?或?nano |
JAVA_HOME | Java 運行環境路徑(供 Java 程序查找 JRE/JDK) | /usr/lib/jvm/java-17-openjdk |
PATH (特殊) | 注意:部分程序(如?systemd )使用?PATH ?以外的變量(如?PATH ?需顯式設置) | - |
?查看環境變量:
set//可以查看所有環境變量,包括本地變量
env//查看環境變量
查看單個環境變量:
echo $PATH
echo $USER
....
設置環境變量:
臨時設置(僅當前 Shell 會話有效):
export NAME=VALUE
export MY_VAR="hello world" # 聲明為環境變量(可被子進程繼承)
MY_VAR="hello" # 僅為當前 Shell 的局部變量(不被子進程繼承)可以用set查到,echo打印
#因為echo是內建命令,不會將環境變量繼承給子進程#
?刪除環境變量:
unset TEST
#將TEST環境變量永久刪除#
環境變量與進程的關系
-
繼承性:子進程會自動繼承父進程的環境變量,但父進程無法感知子進程對環境變量的修改;進程替換(exec函數族)時,默認繼承當前進程的環境變量,除非經過?execle/execve?顯式傳遞新的環境變量數組。
-
作用域:全局變量:通過 export 聲明或寫入系統配置文件,可被所有子進程繼承。局部變量:未用 export?聲明的變量,僅在當前 Shell 進程內有效,不被子進程繼承。