函數wait、waitpid、孤兒進程、僵尸進程

一、函數wait、waitpid

一個進程在終止時會關閉所有文件描述符,釋放在用戶空間釋放的內存,但它的PCB還保留著,內核在其中保存一些信息:如果是正常終止時則保存著退出狀態,如果是異常終止則保存著導致該進程終止的信號是哪個,這個進程的父進程可以調用wait或waitpid獲取這些信息,然后徹底清除這個進程,我們知道一個進程的退出狀態可以在shell用特殊變量$?查看,因為shell是它的父進程,當它終止時shell調用wait或waitpid得到它的退出狀態同時徹底清除這個進程。

?

1. wait函數原型:一次只能回收一個子進程

pid_t wait(int *status); 
  • ?當進程終止時,操作系統隱式回收機制會:1. 關閉所有的文件描述符 2. 釋放用戶空間分配的內存。內核PCB仍存在,其中保存該進程的退出狀態。(正常終止--------退出值;異常終止-------終止信號

?

2. 函數waitpid原型:
作用:同wait,但可指定pid進程清理,可以不阻塞(?一次只能回收一個子進程)

pid_t waitpid(pid_t pid, int *staloc, int options);

參數pid:

  • pid == -1:回收任一子進程
  • pid??>??0 :回收指定pid的進程
  • pid == 0 :回收與父進程同一個進程組的任一個子進程
  • pid < -1??:回收指定進程組內的任意子進程

參數 options:

  • 設置為WNOHANG:函數不阻塞;
  • 設置為0:函數阻塞。

3. 測試代碼

#include <stdio.h>
#include <unistd.h>
#include<sys/wait.h>int main(int argc, const char* argv[])
{pid_t pid = fork();if (pid > 0) // 父進程{   printf("parent process, pid = %d, ppid = %d\n", getpid(), getppid());int status;pid_t wpid = wait(&status);if (WIFEXITED(status)) printf("exit value: %d", WEXITSTATUS(status));if (WIFSIGNALED(status)) printf("exit by signal: %d\n", WTERMSIG(status)); //是否被信號殺死printf(" die child pid = %d\n", wpid);}else if(pid == 0) {sleep(1);printf("child process, pid = %d, ppid = %d\n", getpid(), getppid());    }return 9;
}

輸出結果:

?

二、孤兒進程、僵尸進程

  • 孤兒進程:一個父進程退出,而它的一個或多個子進程還在運行,那么那些子進程將成為孤兒進程。孤兒進程將被init進程(進程號為1)所收養,并由init進程對它們完成狀態收集工作。
  • 僵尸進程:一個進程使用fork創建子進程,如果子進程退出,而父進程并沒有調用wait或waitpid獲取子進程的狀態信息,那么子進程的進程描述符仍然保存在系統中。這種進程稱之為僵死進程。

1. unix提供了一種機制可以保證只要父進程想知道子進程結束時的狀態信息, 就可以得到。這種機制就是: 在每個進程退出的時候,內核釋放該進程所有的資源,包括打開的文件,占用的內存等。 但是仍然為其保留一定的信息(包括進程號the process ID,退出狀態the termination status of the process,運行時間the amount of CPU time taken by the process等)。直到父進程通過wait / waitpid來取時才釋放。 但這樣就導致了問題,如果進程不調用wait / waitpid的話,?那么保留的那段信息就不會釋放,其進程號就會一直被占用,但是系統所能使用的進程號是有限的,如果大量的產生僵死進程,將因為沒有可用的進程號而導致系統不能產生新的進程. 此即為僵尸進程的危害,應當避免。

2.?孤兒進程是沒有父進程的進程,孤兒進程這個重任就落到了init進程身上,init進程就好像是一個民政局,專門負責處理孤兒進程的善后工作。每當出現一個孤兒進程的時候,內核就把孤 兒進程的父進程設置為init,而init進程會循環地wait()它的已經退出的子進程。這樣,當一個孤兒進程凄涼地結束了其生命周期的時候,init進程就會代表黨和政府出面處理它的一切善后工作。因此孤兒進程并不會有什么危害。

1.?僵尸進程測試

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>int main()
{pid_t pid;while (1){pid = fork();if (pid < 0){perror("fork error:");exit(1);}else if (pid == 0){printf("I am a child process.\nI am exiting.\n");exit(0); //子進程退出,成為僵尸進程}else{sleep(20); //父進程休眠20s繼續創建子進程continue;}}return 0;
}

輸出結果:

?

僵尸進程解決辦法

1) . 通過信號機制

子進程退出時向父進程發送SIGCHILD信號,父進程處理SIGCHILD信號。在信號處理函數中調用wait進行處理僵尸進程。測試程序如下所示:

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>static void sig_child(int signo)
{pid_t  pid;int    stat;while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)  //處理僵尸進程printf("child %d terminated.\n", pid);
}int main()
{pid_t pid;signal(SIGCHLD, sig_child); //創建捕捉子進程退出信號pid = fork();if (pid < 0){perror("fork error:");exit(1);}else if (pid == 0){printf("I am child process,pid id %d.I am exiting.\n", getpid());exit(0);}printf("I am father process.I will sleep two seconds\n"); //等待子進程先退出sleep(2);system("ps -o pid,ppid,state,tty,command"); //輸出進程信息printf("father process is exiting.\n");return 0;
}

輸出結果:?

?

2)fork兩次

《Unix 環境高級編程》8.6節說的非常詳細。原理是將子進程成為孤兒進程,從而其的父進程變為init進程,通過init進程可以處理僵尸進程。測試程序如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>int main()
{pid_t pid;pid = fork(); if (pid < 0){perror("fork error:");exit(1);}else if (pid == 0) //第一個子進程{printf("I am the first child process.  pid:%d\tppid:%d\n", getpid(), getppid());pid = fork();if (pid < 0){perror("fork error:");exit(1);}else if (pid > 0) //第一個子進程退出{printf("first procee is exited.\n");exit(0);}//第二個子進程//睡眠3s保證第一個子進程退出,這樣第二個子進程的父親就是init進程里sleep(3);printf("I am the second child process.  pid: %d\tppid:%d\n", getpid(), getppid());exit(0);}if (waitpid(pid, NULL, 0) != pid) //父進程處理第一個子進程退出{perror("waitepid error:");exit(1);}exit(0);return 0;
}

輸出結果:

?

1.?孤兒進程與僵尸進程[總結]

二、exec函數族

1. 簡介

  • 進程程序替換原理

fork創建子進程執行的是和父進程相同的程序(也有可能是某個分支),通常fork出的子進程是為了完成父進程所分配的任務,所以子進程通常會調用一種exec函數(六種中的任何一種)來執行另一個任務。當進程調用exec函數時,當前用戶空間的代碼和數據會被新程序所替換,該進程就會從新程序的啟動歷程開始執行。在這個過程中沒有創建新進程,所以調用exec并沒有改變進程的id。

  • 替換圖解(圖解)

?

(1). execl函數原型:

int execl(const char *path, const char *arg, ...);

?分析:

  • path: 要執行的程序的絕對路徑
  • 變參arg: 要執行的程序的需要的參數
  • 第一arg:占位
  • 后邊的arg: 命令的參數
  • 參數寫完之后: NULL
  • 一般執行自己寫的程序
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{printf("entering main process---\n");if(execl("ls","ls","-l",NULL)<0)perror("excl error");return 0;
}

輸出結果:

(2). execv函數原型:

int execv(const char *path, char *const argv[]);

分析:

  • path = /bin/ps
  • char* args[] = {"ps", "aux", NULL};
  • execv("/bin/ps", args);

(3). execlp函數原型

int execlp(const char *file, const char *arg, ...);

分析:

  • file: 執行的命令的名字
  • 第一arg:占位
  • 后邊的arg: 命令的參數
  • 參數寫完之后: NULL
  • 執行系統自帶的程序
  • execlp執行自定義的程序: file參數絕對路徑

(4). execvp函原型:

int execvp(const char *file, char *const argv[]);

(5). execle函數原型:

int execle(const char *path, const char *arg, ..., char *const envp[]);

分析:

  • path: 執行的程序的絕對路徑??/home/itcast/a.out
  • arg: 執行的的程序的參數
  • envp: 用戶自己指定的搜索目錄, 替代PATH
  • char* env[] = {"/home/itcast", "/bin", NULL};
int execve(const char *path, char *const argv[], char *const envp[]);
函數名參數格式是否帶路徑是否使用當前環境變量
execl參數列表
execlp參數列表
execle參數列表
execv參數數組
execvp參數數組
execve參數數組

?

7. 測試代碼?

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main()
{for (int i = 0; i < 8; ++i)printf(" parent i = %d\n", i);pid_t pid = fork();if (pid == 0){execlp("ps", "ps", "aux", NULL);perror("execlp");exit(1);}for (int i = 0; i < 3; ++i)printf("----------- i = %d\n", i);return 0;
}


?

參考資料?

1.?操作系統重點知識匯總

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/385483.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/385483.shtml
英文地址,請注明出處:http://en.pswp.cn/news/385483.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MySQL中的字符集與字符序

這篇文章詳細介紹一下MySQL中的字符集和字符序相關的問題&#xff0c;里里外外地了解一下字符集和字符序的方方面面&#xff0c;同時重點說明一下開發中需要注意的問題。 文章基于MySQL 8.0&#xff0c;也會涉及到5.7版本。主要參考MySQL手冊&#xff1a;https://dev.mysql.com…

MySQL中的JSON

從5.7.8開始&#xff0c;MySQL開始支持JSON類型&#xff0c;用于存儲JSON數據。 JSON類型的加入模糊了關系型數據庫與NoSQL之間的界限&#xff0c;給日常開發也帶來了很大的便利。 這篇文章主要介紹一下MySQL中JSON類型的使用&#xff0c;主要參考MySQL手冊&#xff1a;https…

【C++ Primer | 15】虛函數表剖析(一)

一、虛函數 1. 概念 多態指當不同的對象收到相同的消息時&#xff0c;產生不同的動作 編譯時多態&#xff08;靜態綁定&#xff09;&#xff0c;函數重載&#xff0c;運算符重載&#xff0c;模板。運行時多態&#xff08;動態綁定&#xff09;&#xff0c;虛函數機制。為了實現…

【Leetcode | 02】二叉樹、線性表目錄

二叉樹序號題號1 94. 二叉樹的中序遍歷 295. 不同的二叉搜索樹 II396. 不同的二叉搜索樹4 98. 驗證二叉搜索樹 5100. 相同的樹6101. 對稱二叉樹7102. 二叉樹的層次遍歷8103. 二叉樹的鋸齒形層次遍歷9104. 二叉樹的最大深度10105. 從前序與中序遍歷序列構造二叉樹11106. 從中序與…

Leetcode 118. 楊輝三角

給定一個非負整數 numRows&#xff0c;生成楊輝三角的前 numRows 行。 在楊輝三角中&#xff0c;每個數是它左上方和右上方的數的和。 示例: 輸入: 5 輸出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1] ] class Solution { public:vector<vector<int>> generate(…

管道符、重定向與環境變量

輸入輸出重定向 輸入重定向&#xff1a;將文件內容導入到命令中&#xff1b;輸出重定向&#xff1a;將命令執行后顯示到屏幕上的內容導入到文件中&#xff0c;不在屏幕中顯示。共分為&#xff1a;標準輸入重定向&#xff08;文件描述符為0&#xff09;、標準覆蓋輸出&#xff0…

【C++ Primer | 0 】字符串函數實現

1. memcpy函數原型&#xff1a; void* memcpy(void* dst, const void* src, size_t size); void* memmove(void* dst, const void* src, size_t size); 分析&#xff1a; source和destin所指的內存區域可能重疊&#xff0c;但是如果source和destin所指的內存區域重疊,那么這個…

編寫Shell腳本(批處理,一次執行多條命令)

Bash終端的優勢&#xff1a;1.上下鍵重復執行命令&#xff1b;2.tab鍵自動補齊&#xff1b;3.提供有用的環境變量&#xff1b;4.批處理。 shell腳本文件建議以.sh為后綴。 其實vim創建文本文件時&#xff0c;對名字無要求&#xff0c;但最好規定格式。 echo $SHELL&#xff08…

判斷用戶的參數(條件測試語句)

說明$?: $&#xff1f;為上一次命令的執行返回值&#xff0c;若上一次命令正常執行&#xff0c;則返回0&#xff1b;若執行出錯&#xff0c;則返回一個非0的隨機數。比如創建一個已經存在的目錄&#xff0c;則返回一個非0數。 另外&#xff0c;測試語句成立返回0&#xff0c…

流程控制語句(bash)

1.if控制語句 if then fi if then else fi if then elif then elif then else fi if 條件表達式 then 命令序列&#xff08;滿足條件才執行&#xff09; #注意&#xff0c;如果if與then&#xff08;elif與then&#xff09;寫在同一行&#xff0c;要用;隔開&#xff…

用戶身份與文件的權限(普通權限、特殊權限、隱藏權限和文件控制列表ACL)

用戶身份 root用戶是存在于所有類UNIX操作系統中的超級用戶&#xff0c;它擁有最高的系統所有權。root用戶的用戶身份號碼UID為0&#xff0c;UID相當于用戶的身份證號碼一樣&#xff0c;具有唯一性。管理員用戶&#xff08;超級用戶&#xff09;UID為0&#xff1b;系統用戶UID為…

存儲結構與磁盤劃分

文件系統層次化標準&#xff08;FHS&#xff0c;file system hierarchy standard&#xff09; 在windows操作系統中&#xff0c;要找到一個文件需要先進入該文件所在的磁盤分區&#xff08;如C:\等 C:\ZSX\zsx.txt&#xff09;&#xff0c;然后在進入該分區下的一個具…

Linux中常用文件的含義

在Linux中配置了服務文件后&#xff0c;需要重啟該服務&#xff0c;配置信息才會生效。 /etc/passwd 保存了系統中所有用戶的信息&#xff0c;一旦用戶的登陸終端設置為/sbin/nologin&#xff0c;則不再允許登錄到系統 /etc/shadow與/etc/passwd均為用戶信息文件 /…

64. 最小路徑和

給定一個包含非負整數的 m x n 網格&#xff0c;請找出一條從左上角到右下角的路徑&#xff0c;使得路徑上的數字總和為最小。 說明&#xff1a;每次只能向下或者向右移動一步。 示例: 輸入: [[1,3,1],[1,5,1],[4,2,1] ] 輸出: 7 解釋: 因為路徑 1→3→1→1→1 的總和最小。…

Linux本地yum源配置以及使用yum源安裝各種應用程序

將軟件包傳送到Linux中后&#xff0c;掛載&#xff0c;然后配置yum軟件倉庫&#xff0c;最后就可以使用yum來安裝相應的應用程序了。假設掛載目錄為/tmp/ruanjianbao&#xff0c;則下面說明配置本地yum倉庫的過程&#xff1a; &#xff08;1&#xff09;cd /etc/yum.repos.d/…

gcc與g++編譯器

首先在Linux(RHEL7.0)上安裝gcc&#xff1a;yum install gcc gcc-c -y 其中gcc-c是為了能夠編譯c源代碼&#xff0c;即g。 gcc為Linux C/C下重要的編譯環境&#xff0c;是GUN項目中符合ANSIC標準的編譯系統&#xff0c; gcc可以編譯C、C、Objective-C、Java、Fortran、Pascal…

【Leetcode | 49】230. 二叉搜索樹中第K小的元素

給定一個二叉搜索樹&#xff0c;編寫一個函數 kthSmallest 來查找其中第 k 個最小的元素。 說明&#xff1a; 你可以假設 k 總是有效的&#xff0c;1 ≤ k ≤ 二叉搜索樹元素個數。 示例 1: 輸入: root [3,1,4,null,2], k 1 3 / \ 1 4 \ 2 輸出: 1 示例 2: 輸入…