【Linux從入門到放棄】探究進程如何退出以進程等待的前因后果

🧑?💻作者: @情話0.0
📝專欄:《Linux從入門到放棄》
👦個人簡介:一名雙非編程菜鳥,在這里分享自己的編程學習筆記,歡迎大家的指正與點贊,謝謝!

在這里插入圖片描述

進程退出和等待

  • 前言
  • 一、進程創建
    • 1.1 fork函數
    • 1.2 寫時拷貝
    • 1.3 fork常規用法
    • 1.4 fork調用失敗的原因
  • 二、進程退出
    • 2.1 進程退出場景
      • 2.1.1 查看退出碼
      • 2.1.2 退出碼的含義
    • 2.2 如何理解進程退出?
    • 2.3 進程退出的方式
  • 三、進程等待
    • 3.1 進程等待的原因
    • 3.2 什么是進程等待?
    • 3.3 進程等待的方式
      • 3.3.1 wait方法
      • 3.3.2 waitpid方法
    • 3.4 子進程退出狀態
    • 3.5 非阻塞式等待
  • 總結


前言

之前的幾篇博客已經是對進程的相關概念做了詳細了解,現階段對進程的定義為內核數據結構加上該進程對應的代碼和數據,操作系統對進程通過先描述再組織的方式做管理。有了這些預備知識,接下來就是要學習如何控制進程,也就是在操作上該怎么做。


一、進程創建

1.1 fork函數

??關于fork函數的知識,此篇博客有詳細介紹:進程創建

進程調用fork,當控制轉移到內核中的fork代碼后,內核做:

  1. 分配新的內存塊和內核數據結構給子進程
  2. 將父進程部分數據結構內容拷貝至子進程
  3. 添加子進程到系統進程列表當中
  4. fork返回,開始調度器調度

在這里插入圖片描述

在調用fork函數之后,系統會將父進程的代碼拷貝一份給子進程,同時會有兩個執行流分別執行父進程和子進程,要注意的是子進程不會去執行fork之前的代碼。

1.2 寫時拷貝

??父子進程代碼共享,父子再不寫入時,數據也是共享的,當任意一方試圖寫入,便以寫時拷貝的方式各自一份副本。具體見下圖:
在這里插入圖片描述

在修改內容之前,父子進程的在物理內存頁的數據、代碼指向同一塊位置,如子進程對數據進行修改,,那么此時就會發生寫時拷貝,在物理內存頁重新開辟一塊空間將修改后的數據存入其中。
因為在操作系統是不允許空間的浪費,所以不會將父進程的所有代碼數據都在物理內存中重新拷貝一份,而是通過寫時拷貝的方式在子進程需要使用(修改)數據的時候才會重新開辟空間,它是一種按需申請資源的策略。

1.3 fork常規用法

  1. 一個父進程希望復制自己,使父子進程同時執行不同的代碼段。例如,父進程等待客戶端請求,生成子進程來處理請求。
  2. 一個進程要執行一個不同的程序。例如子進程從fork返回后,調用exec函數。

1.4 fork調用失敗的原因

  1. 系統中有太多的進程
  2. 實際用戶的進程數超過了限制

二、進程退出

2.1 進程退出場景

a. 正常運行完畢(1. 結果正確 ?2. 結果不正確)
b. 崩潰了(進程異常) ?崩潰的本質:進程因為某些原因,導致進程收到了來自操作系統的信號(kill -9)

2.1.1 查看退出碼

我們一般在寫C語言程序都會在main函數結束時返回 0,這個0代表著該進程的退出碼,在linux中,可通過這樣的指令查看進程的退出碼:echo $?。看下面代碼:

int add_to_top(int num)
{int sum=0;for(int i=1;i<=num;i++){sum+=i;}return sum;
}int main()
{int ret=add_to_top(100);if(ret==5050)return 1;else return 0;
}

上面的代碼要實現的功能:從1加到100,若和為5050,則返回1,否則返回0。通過下圖可以看到該進程的退出碼為1,表示結果正確。但是奇怪的是,后兩次的查看退出碼都為了0,這是因為該指令只會保留最近一次執行的進程的退出碼!后兩次代表著該條指令執行后的退出碼。
在這里插入圖片描述

2.1.2 退出碼的含義

我們看到的退出碼都是數字,對于程序員來說,我們可能知道一些退出碼所代表的含義,但是對于一般人來說看到這些數字并不了解所蘊含的意義。所以對于一般人來說,如果你只給他退出碼是沒有價值,因為他并不知道這些退出碼代表的含義。關于退出碼的含義我們可以自定義,下面看一下C語言所提供的退出碼的含義。

int main()
{for(int i=0;i<200;i++){printf("%d:%s\n",i,strerror(i));}return 0;
}

這只是前二十個,后面還有更多。當然這是在linux操作系統下,在windows下所提供的退出碼含義是不同的。

在這里插入勝多負少描述

2.2 如何理解進程退出?

關于進程的退出,可以理解的是操作系統內少了一個進程,操作系統要釋放進程對應的內核數據結構+代碼和數據。

2.3 進程退出的方式

  1. main函數return。而其他函數的return僅僅代表該函數的返回。對于這種方式來說,進程執行本質是main執行流執行,當main函數執行完時代表著進程也就結束了。
  2. exit函數退出。exit函數所包含的數字為該進程的退出碼,在函數任意位置調用直接使進程退出。
  3. _exit函數退出。直觀感覺上和exit的功能是一樣的,但是在一些細節是不一樣的。exit函數在退出的時候會自動刷新緩沖區,而_exit函數不會刷新緩沖區。它們兩個的關系是一種包含和被包含的關系。從下面這個圖可以得到一個暗藏的點:緩沖區不在操作系統內。

在這里插入圖片描述

三、進程等待

3.1 進程等待的原因

  1. 之前講過若子進程先退出,而父進程并沒有讀取子進程狀態,就可能造成‘僵尸進程’的問題,進而造成內存泄漏。
  2. 進程一旦變成僵尸狀態,那就刀槍不入,“殺人不眨眼”的kill -9 也無能為力,因為誰也沒有辦法殺死一個已經死去的進程。
  3. 我們為什么要創建子進程,目的就是為了讓子進程幫助我們去完成某些事情,關于父進程派給子進程的任務完成的情況,可能我們不會關心完成的對不對,也可能會關心子進程運行完成的結果對還是不對,亦或是否正常退出。
  1. 避免內存泄漏(必)
  2. 獲取子進程的執行結果。(可能)

關于子進程的退出結果,有三種可能性:
a. 代碼跑完,結果對;
b. 代碼跑完,結果不對;
c. 代碼運行異常;
關于結果對或不對,可以通過退出碼的方式判別,代碼運行異常則是收到某種信號。因此衡量一個進程運行的怎樣是通過退出碼+信號的方式來執行的。

3.2 什么是進程等待?

通過系統調用,獲取子進程退出碼或者退出信號的方式,同時釋放內存問題。

3.3 進程等待的方式

3.3.1 wait方法

pid_t wait(int *status);

返回值:成功返回被等待進程pid,失敗返回-1。
參數:輸出型參數,獲取子進程退出狀態,不關心則可以設為NULL

//代碼功能:父進程在休眠5秒的過程中子進程先運行2秒,然后子進程退出,2秒之后,父進程對子進程做進程等待操作。
int main()
{pid_t ret=fork();if(ret==0){//子進程int cnt=2;while(cnt--){printf("我是子進程,我現在活著呢,我離死亡還有%d秒,pid:%d,ppid:%d\n",cnt,getpid(),getppid());sleep(1);}_exit(0);}sleep(5);//父進程pid_t ret_id=wait(NULL);printf("我是父進程,等待子進程成功,pid:%d,ppid:%d\n",getpid(),getppid());return 0;
}

在運行代碼之后我們應該觀察到的現象:父子進程的狀態最開始都為運行狀態,子進程經2秒輸出2條語句,然后退出變為僵尸狀態,父進程依然為運行狀態,再過3秒之后,父進程對子進程等待回收,然后全部退出。

在這里插入圖片描述

3.3.2 waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options);

返回值: 當正常返回的時候waitpid返回收集到的子進程的進程ID; 如果設置了選項WNOHANG,而調用中waitpid發現沒有已退出的子進程可收集,則返回0; 如果調用中出錯,則返回-1,這時errno會被設置成相應的值以指示錯誤所在;

參數 pid

  1. pid=-1,等待任意一個子進程,與wait等效。
  2. pid>0,等待其進程ID與pid相等的子進程。

參數 status:

  1. WIFEXITED(status):若為正常終止子進程返回的狀態,則為真。(查看進程是否是正常退出)
  2. WEXITSTATUS(status): 若WIFEXITED非零,提取子進程退出碼。(查看進程的退出碼)

參數 options:

  1. WNOHANG: 若pid指定的子進程沒有結束,則waitpid()函數返回0,不予以等待。若正常結束,則返回該子進程的ID。

3.4 子進程退出狀態

  1. 在 wait 和 waitpid 中,都有一個status參數,該參數是一個輸出型參數,由操作系統填充。它的功能是為了獲取子進程的退出狀態。如果傳遞NULL,表示不關心子進程的退出狀態信息。否則,操作系統會根據該參數,將子進程的退出信息反饋給父進程。
  2. status不能簡單的當作整形來看待,可以當作位圖來看待,具體細節如下圖(只研究status低16比特位):
    在這里插入圖片描述
  3. 通過對上圖理解,我們應該明白關于子進程的退出狀態。如果進程是正常退出,那么status位圖的低八位為0,次低八位為進程的退出狀態,也就是通過這次低八位獲取進程的退出碼。如果進程是被某種信號所殺而導致的異常退出,則只需要關心低七位,讀到的結果為導致該進程退出的終止信號所對應的數字,coredump標志位目前不需要了解。
int main()
{pid_t id=fork();if(id==0){//子進程int cnt=2;while(cnt--){printf("我是子進程,我現在活著呢,我離死亡還有%d秒,pid:%d,ppid:%d\n",cnt,getpid(),getppid());sleep(1);}// int a=10;// a/=0;_exit(123);}sleep(5);int status=0;pid_t ret_id=waitpid(id,&status,0);printf("我是父進程,等待子進程成功,pid:%d,ppid:%d,status signal:%d,status code:%d\n",getpid(),getppid(),(status&0x7F),((status>>8)&0xff));return 0;
}

看上面這段代碼,如果按照這樣的邏輯,那么最終的運行結果為(只看退出狀態):父進程獲取到子進程的退出信號肯定為0,因為是正常退出,退出狀態則為數字123;若將那兩條注釋的代碼取消,那么子進程就會因為除0操作導致異常退出,那么此時父進程就會讀到對應的退出信號,輸出結果為該信號對應的數字。
在這里插入圖片描述

  1. 父進程是如何獲取子進程的退出狀態信息的呢?子進程有自己的PCB、地址空間、頁表和內存,而在PCB的內部會有兩個屬性:exit_code、exit_signal。當子進程執行完畢時將main函數的返回值寫到 exit_code 中,如果出現異常操作系統則將遇到信號所對應的數字編號寫到 exit_signal 中。當子進程退出后,操作系統會將這份PCB維護起來,所以就需要通過wait/waitpid這樣的系統調用接口將從這份PCB讀到的這兩個屬性以上面那種位圖的方式設置到status參數中。
  2. 父進程在wait的時候,如果子進程沒退出,那父進程在干什么?在子進程沒有退出的時候,父進程只能一直在調用waitpid進行等待——阻塞等待

3.5 非阻塞式等待

waitpid(id,&status,WNOHANG)

??上一小節的 waitpid 方法為阻塞等待,而非阻塞等待與阻塞等待的區別在于第三個參數的不同阻塞等待是在子進程還沒有退出的時候父進程只能一直等待直到子進程退出,非阻塞等待是子進程還沒有退出時,父進程可以干一些其他事情而不是什么事情不干就在等待子進程退出。
??下面這段代碼將通過非阻塞的形式讓父進程在還未等待到子進程的退出信息的時候去執行其他事情。

#define TASK_NUM 10
void sync_disk()
{printf("這是一個刷新數據的任務!\n");
}
void sync_log()
{printf("這是一個同步日志的任務!\n");
}
void net_send()
{printf("這是一個進行網絡發送的任務!\n");
}
typedef void (*func_t)();
func_t other_task[TASK_NUM] = {NULL};  //函數指針數組int LoadTask(func_t func)
{int i = 0;for(; i < TASK_NUM; i++){if(other_task[i] == NULL) break;}if(i == TASK_NUM) return -1;else other_task[i] = func;return 0;
}
void InitTask()
{for(int i = 0; i < TASK_NUM; i++) other_task[i] = NULL;LoadTask(sync_disk);LoadTask(sync_log);LoadTask(net_send);
}
void RunTask()
{for(int i = 0; i < TASK_NUM; i++){if(other_task[i] == NULL) continue;other_task[i]();}
}
int main()
{pid_t id=fork();if(id==0){//子進程int cnt=5;while(cnt--){printf("我是子進程,我現在活著呢,我離死亡還有%d秒,pid:%d,ppid:%d\n",cnt,getpid(),getppid());sleep(1);}_exit(123);}InitTask();while(1){int status=0;pid_t ret_id=waitpid(id,&status,WNOHANG);if(ret_id==-1){printf("等待錯誤!\n");break;}else if(ret_id==0){//子進程還未退出,父進程執行RunTask函數RunTask();sleep(1);}else{if(WIFEXITED(status))//正常退出{printf("我是父進程,等待子進程成功,pid:%d,ppid:%d,status signal:%d,status code:%d\n",getpid(),getppid(),(status&0x7F),WEXITSTATUS(status));}else//非正常退出printf("我是父進程,等待子進程成功,pid:%d,ppid:%d,status signal:%d,status code:%d\n",getpid(),getppid(),(status&0x7F),((status>>8)&0xff));break;}}return 0;
}

在子進程正常退出并且父進程等待成功的時候可以通過宏的方式來獲取子進程的退出碼,之前的方法優雅度或者可擴展性都不太好,當WIFEXITED(status)為真的時候,通過WEXITSTATUS(status)獲取退出碼,若不為真也就是異常退出時只能使用以前的方法。


總結

總結:

??本文深入探討了操作系統中進程管理的三個核心方面:進程的創建、退出和等待。首先,我們了解了進程創建的過程,它涉及到操作系統如何為新進程分配必要的資源,包括內存空間和處理器時間,并初始化進程表以跟蹤和管理進程狀態。接著,我們討論了進程退出的不同方式,如正常退出、異常退出以及由于接收到信號導致的退出,每種方式都對系統穩定性和資源管理產生不同的影響。
??最后,我們詳細分析了進程等待的概念,即一個進程可能需要暫停執行,直到滿足特定條件。這可能包括等待I/O操作完成、等待獲取資源或等待其他進程的結束。文章強調了實現有效等待機制的重要性,并指出了同步和通信在確保系統資源合理利用和進程間順暢協作中的關鍵作用。
??通過這篇博客,我們不僅學習了關于進程操作的基本知識,還加深了對于操作系統內部機制如何協同工作的理解。這些內容為我們進一步研究計算機科學的其他領域打下了堅實的基礎。

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

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

相關文章

常見反爬及應對

一&#xff0c;特殊混淆的還原 1.1 還原 AAEncode 與 JJEncode AAEncode是一種JavaScript代碼混淆算法&#xff0c;利用它&#xff0c;可以將代碼轉換成 顏文字 表示的JavaScript代碼。 去掉代碼最后的 (‘‘)&#xff0c;這是函數的自調用&#xff0c;去除后就是函數的聲明…

【CSharp】定義結構體并指定字段對齊

【CSharp】定義結構體并指定字段對齊 1.背景2.代碼3.分析1.背景 在 C# 中可以通過 StructLayout 屬性來定義結構體并指定字段對齊方式。 在 C# 中,內存對齊是指數據在內存中的排列方式,使用StructLayout 特性用于控制結構體的內存布局。其特性可以指定字段的內存排列順序(例…

【揭秘】國內十大頂尖AI大模型,引領智能科技新紀元

大模型大模型通常指的是參數量非常大、數據量也非常大的深度學習模型。這些模型由數百萬到數十億甚至更多的參數組成&#xff0c;需要海量的數據和強大的計算資源進行訓練和推理學習的模型。大模型設計的目的在于提高模型的表示能力和性能、應對復雜數據集和任務、提升泛化能力…

6、限界上下文:定義領域邊界的利器

在DDD限界上下文&#xff1a;定義領域邊界的利器領域建模和微服務建設過程中&#xff0c;會有很多項目參與者&#xff0c;包括領域專家、產品經理、項目經理、架構師、開發經理和測試經理等。對于同樣的領域知識&#xff0c;不同的參與者可能會有不同的理解。而且有的時候同一個…

嵌入式學習——硬件(Linux系統在2440上的啟動)——day57

1. Linux2.6系統在s3c2440上的啟動過程分三個階段 1.1 啟動u-boot 1.2 啟動Linux內核 1.3 掛載根文件系統 2. bootloader 2.1 定義 bootloader的本質是一個裸機程序&#xff0c;bootlood專門是為了能夠正確地啟動linux操作系 統&#xff0c;在系統初上電時需要對系統做一些…

BK145FRC10HSK、BK165FRC10HSK電液比例開環控制變量泵放大器

BK15FRC10HAK、BK35FRC10HAK、BK45FRC10HAK、BK55FRC10HAK、BK70FRC10HSK、BK80FRC10HSK、BK90FRC10HSK、BK100FRC10HSK、BK120FRC10HSK、BK145FRC10HSK、BK165FRC10HSK、BK180FRC10HSK電液比例開環控制柱塞泵主要是在傳統的液壓泵基礎上&#xff0c;增加了電液比例控制先導閥。…

從零開始實現大語言模型(二):文本數據處理

1. 前言 神經網絡不能直接處理自然語言文本&#xff0c;文本數據處理的核心是做tokenization&#xff0c;將自然語言文本分割成一系列tokens。 本文介紹tokenization的基本原理&#xff0c;OpenAI的GPT系列大語言模型使用的tokenization方法——字節對編碼(BPE, byte pair en…

重采樣(上采樣或下采樣)是什么?

重采樣&#xff08;Resampling&#xff09;是在數據處理中常用的一種技術&#xff0c;主要用于處理數據集中的不平衡問題。具體來說&#xff0c;重采樣可以分為上采樣&#xff08;Oversampling&#xff09;和下采樣&#xff08;Undersampling&#xff09;&#xff0c;它們分別是…

【bug報錯已解決】ERROR: Could not find a version that satisfies the requirement

&#x1f3ac; 鴿芷咕&#xff1a;個人主頁 &#x1f525; 個人專欄: 《C干貨基地》《粉絲福利》 ??生活的理想&#xff0c;就是為了理想的生活! 文章目錄 引言一、問題描述1.1 報錯示例1.2 報錯分析 二、解決方法2.1 方法一2.2 方法二 三、總結 引言 有沒有遇到過那種讓人…

軟件開發中常用環境你都知道哪些?

目錄 本地環境&#xff08;Local Environment&#xff0c;簡稱 LOCAL&#xff09; 開發環境&#xff08;Development Environment&#xff0c;簡稱 DEV&#xff09; 測試環境&#xff08;Testing Environment&#xff0c;簡稱 TEST&#xff09; 集成測試環境&#xff08;Sy…

墨烯的C語言技術棧-C語言基礎-003

三.數據類型 1.char // 字符數據型 2.short // 短整型 3.int // 整型 4.long // 長整型 5.long long // 更長的整型 6.float // 單精度浮點數 7.double // 雙精度浮點數 為什么寫代碼? 為了解決生活中的問題 購物,點餐,看電影 為什么有這么多類型呢? 因為說的話都是字符型…

CM-UNet: Hybrid CNN-Mamba UNet for Remote Sensing Image Semantic Segmentation

論文&#xff1a;CM-UNet: Hybrid &#xff1a;CNN-Mamba UNet for Remote Sensing Image Semantic Segmentation 代碼&#xff1a;https://github.com/XiaoBuL/CM-UNet Abstrcat: 由于大規模圖像尺寸和對象變化&#xff0c;當前基于 CNN 和 Transformer 的遙感圖像語義分割方…

mysql 中 單獨獲取已知日期的年月日其中之一

限定條件&#xff1a;2021年8月&#xff0c;寫法有很多種&#xff0c;比如用year/month函數的year(date)2021 and month(date)8&#xff0c;比如用date_format函數的date_format(date, "%Y-%m")"202108"每天&#xff1a;按天分組group by date題目數量&…

java之靜態屬性方法

在java中有一個static的關鍵字&#xff0c;它用來修飾類的成員。如果用static修飾屬性&#xff0c;該屬性被稱為靜態屬性 靜態屬性的訪問格式如下 類名.屬性名 如果沒有修飾靜態屬性示例代碼如下 class Xuesheng1{String name;int age;String school"A大學";publ…

openGauss真的比PostgreSQL差了10年?

前不久寫了MogDB針對PostgreSQL的兼容性文章&#xff0c;我在文中提到針對PostgreSQL而言&#xff0c;MogDB兼容性還是不錯的&#xff0c;其中也給出了其中一個能源客戶之前POC的遷移報告數據。 But很快我發現總有人回留言噴我&#xff0c;而且我發現每次噴的這幫人是根本不看文…

2024廣州智能音箱展|廣州藍牙耳機展

2024廣州智能音箱展|廣州藍牙耳機展 時間&#xff1a;2024年11月29日-12月1日 地點&#xff1a;廣州琶洲保利世貿博覽館 【展會簡介】 中國是全球最大的音頻產品制造基地和消費市場&#xff0c;隨著國內外互聯網巨頭紛紛瞄準音頻行業并投入巨資布局AI產品矩陣&#xff0c;音…

pom.xml文件加載后沒有變成maven圖標

原因&#xff1a; 開啟了IDEA的節電模式 現象為&#xff1a; xml會變橙色&#xff0c;yml變粉色&#xff0c;自動提示關閉等 把這個節能模式的勾選給取消掉就可以正常顯示了

python提取圖片中的文字寫入excel文件,并打包為exe可執行文件

python提取圖片數據寫入excel&#xff0c;并打包為exe可執行文件 1. 以下面的圖片為例2. python環境需要的依賴包3. 創建交互式窗口4. 讀取文件夾下的所有文件并提取數據5. 提取圖片中字段的代碼6. 打包代碼為exe可執行文件安裝打包依賴文件運行打包代碼 1. 以下面的圖片為例 2…

入門Salesforce:必須掌握的20+基礎專業術語!

Salesforce的發展令人印象深刻。在過去的20年中&#xff0c;Salesforce創建了一個由管理員、開發人員、顧問和用戶組成的生態系統&#xff0c;不斷顛覆創新CRM&#xff0c;促進平等和多樣性。 作為初學者&#xff0c;探索Salesforce領域就像學習一門新語言。Salesforce中有著大…

Postman環境變量秘籍:pm.environment的高級使用指南

&#x1f4d3; Postman環境變量秘籍&#xff1a;pm.environment的高級使用指南 Postman是API開發和測試的強大工具&#xff0c;它提供了豐富的功能來簡化和加速開發過程。pm.environment 是Postman中用于管理環境變量的內置對象&#xff0c;它允許你在集合運行時存儲和訪問環境…