Linux:進程地址空間、進程控制(一.進程創建、進程終止、進程等待)

上次介紹了環境變量:Linux:進程概念(四.main函數的參數、環境變量及其相關操作)


文章目錄

  • 1.程序地址空間
    • 知識點總結
    • 上述空間排布結構是在內存嗎?(進程地址空間引入)
  • 2.進程地址空間
      • 明確幾個點
      • 進程地址空間實質
      • 圖示過程
    • 2.1進程地址空間意義
  • 3.創建進程
    • 3.1fork()函數創建子進程補充
      • 寫時拷貝
  • 4.進程終止
    • 4.1進程退出場景
      • 退出碼
      • 進程出現異常
    • 4.2進程常見退出方法
      • 4.2.1正常退出
      • 4.2.2異常退出
      • 進程最終執行情況
    • 4.3 OS會做什么
      • 進程創建時:
      • 進程終止時:
  • 5.進程等待
    • 5.1必要性
    • 5.2進程等待的方法
      • 5.2.1 wait()方法
      • 5.2.2waitpid()方法
      • 獲取子進程status
    • 5.3阻塞等待與非阻塞等待


1.程序地址空間

在這里插入圖片描述

牽扯到內存,肯定有事這張圖啦。這次我們寫段代碼驗證一下

#include <stdio.h>
#include <unistd.h>
int a;
int init_a = 0;
int main()
{printf("code:%p\n", main);//代碼printf("uninit data:%p\n", &a);//未初始化變量printf("init data:%p\n", &init_a);//初始化變量char* arr = (char*)malloc(10);printf("heap:%p\n", arr);//堆printf("stack:%p\n", &arr);//棧return 0;
}

在這里插入圖片描述

知識點總結

  1. 字符串常量

    • 字符串常量通常存儲在高地址,其地址是固定的,不可更改。
    • 字符串的下標從0到n一直在增加,即字符串中的每個字符在內存中是連續存儲的。
  2. main函數地址

    • main 函數地址通常是程序中最底部的地址,因為它是程序的入口點。
  3. 初始化數據和未初始化數據

    • 初始化數據(如全局變量、靜態變量)存儲在比 main 函數地址高的位置,因為它們在程序啟動時需要被初始化。
    • 未初始化數據(如全局未初始化變量、靜態變量)存儲在比初始化數據更高的位置,因為它們在程序啟動時不需要被初始化。
  4. 堆區

    • 堆區是用于動態內存分配的區域,在堆區中存儲動態分配的內存。
    • 堆區是向上增長的,即分配的內存地址逐漸增加,地址比未初始化數據高。
  5. 棧區

    • 棧區用于存儲函數的參數值、局部變量和函數調用返回地址等信息。
    • 棧區是向下增長的,即棧頂地址逐漸減小,整體比堆區的地址要高。

上述空間排布結構是在內存嗎?(進程地址空間引入)

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>int main()
{pid_t id = fork();if (id == 0){int cnt = 0;while (1){printf("child, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);sleep(1);cnt++;if (cnt == 3){g_val = 200;printf("child change g_val: 100->200\n");}}}else{while (1){printf("father, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);sleep(1);}}return 0;
}

在這里插入圖片描述

我們都知道父子進程共享代碼段,那么一開始二者訪問的g_vla值和地址均相同

后面子進程中改變g_val的值后,二者值不同,但是地址還是一樣

  • 這就說明,我們看到的這個地址絕對不是物理地址。我們程這些地址為虛擬地址/線性地址
  • 上面那些空間排布結構也不是在物理內存里。我們稱之為:進程地址空間

2.進程地址空間

在這里插入圖片描述

進程地址空間是操作系統中一個重要的概念,它描述了一個進程在運行時所能訪問的虛擬地址范圍。每個進程都有自己獨立的地址空間,使得多個進程可以同時運行而互相不干擾

地址空間是指一個進程可以使用的內存范圍,通常由連續的地址組成。對于32位系統,進程地址空間通常是從0到4GB,這個范圍內包含了代碼段、數據段、堆、棧等部分,用于存放程序的指令、數據以及動態分配的內存(就是我們上面那個圖)

每個進程都有自己獨立的地址空間,使得進程間可以互相隔離,不會相互干擾。在這個地址空間內,操作系統會進行地址映射,將進程的虛擬地址映射到物理內存上,以實現對內存的訪問和管理。

當一個進程被創建時,操作系統會為該進程分配一塊內存空間,用來存放進程的地址空間。這個地址空間是虛擬的,因為它并不直接對應物理內存中的連續空間,而是通過頁表和頁表項來映射到物理內存中的不同位置。

頁表(Page Table)是操作系統中用于管理虛擬內存的重要數據結構之一。它將進程的虛擬地址映射到物理內存中的實際地址,實現了虛擬內存的地址轉換和管理

明確幾個點

  1. 程序與進程的區別

    在操作系統中,程序和進程是兩個不同的概念。

    程序(Program)是一組指令的集合,是靜態的代碼文件,通常以可執行文件的形式存在。程序本身并不具有執行能力,只有當程序加載到內存中,并由操作系統創建一個進程來執行時,程序的指令才會被解釋和執行。程序可以被多個進程同時執行,因為每個進程都有自己獨立的地址空間,程序的指令在不同進程中是相互隔離的。

    進程(Process)是操作系統中的一個執行實體,是程序在運行過程中的一個實例。進程包含了程序的代碼、數據、堆棧等信息,以及操作系統為其分配的資源。每個進程都有自己獨立的地址空間和執行流,可以獨立運行、調度和管理。進程是操作系統中的基本執行單位,是程序在執行過程中的動態體現。

    可以說進程是運行和執行的主體,程序只是進程執行的指令集合。程序需要被加載到內存中,由進程來執行,進程才是真正執行代碼、管理資源、與其他進程交互的實體。

  2. 進程地址空間不直接保存代碼和數據本身,而是提供了一種邏輯上的組織和管理方式,用于標識和訪問這些代碼和數據在物理內存中的位置。

    當程序執行時,CPU會根據指令從進程地址空間中讀取代碼和數據,并進行相應的處理。這些代碼和數據實際上是存儲在物理內存中的,但通過地址映射機制,它們被映射到了進程地址空間中的對應位置,使得程序可以方便地訪問和操作這些內容。

    當我們說進程地址空間用于存儲“不同類型的數據”時,實際上是指它組織和標識了這些數據和代碼在物理內存中的位置。進程地址空間中的每個部分都通過虛擬地址來標識,這些虛擬地址在運行時會被操作系統和硬件轉換為實際的物理地址,以便訪問對應的內存位置

    因此,可以說進程地址空間是用于組織和管理代碼和數據的虛擬內存區域,而代碼和數據本身實際存儲在物理內存中。進程地址空間提供了一個抽象的視圖,使得程序可以像訪問內存一樣訪問代碼和數據,而無需關心它們的實際存儲位置。

  3. 虛擬地址并不是真實存在的物理內存地址,而是邏輯上的地址空間。虛擬地址空間是操作系統為每個進程提供的一個假象,使得進程仿佛擁有整個內存空間

  • 進程地址空間可以理解成是一套規范,或者是一套邊界,可以方便我們系統進行編輯性檢查的一個東西

  • 進程地址空間并不會把每個虛擬地址都顯式地存儲起來,而是存儲了地址空間的區間范圍以及相關的管理信息。這些區間范圍定義了虛擬地址的邊界,以及每個區間對應的內存屬性(如可讀、可寫、可執行等)

    進程地址空間不會存儲每個使用的虛擬地址,而是會維護每個內存范圍的開始與結束地址

    當進程需要訪問某個虛擬地址時,操作系統會根據該地址所屬的內存范圍,查找相應的頁表或其他內存管理數據結構,以確定該地址對應的物理地址

  • 進程地址空間中的虛擬地址是通過程序計數器、指令集和其他相關機制來使用的。當CPU執行進程中的指令時,它會根據程序計數器的值來獲取下一條要執行的指令的虛擬地址

進程地址空間實質

代碼和數據實際上是存儲在物理內存中的,而進程空間(或稱為虛擬地址空間)里存儲的是代碼和數據的虛擬地址。這些虛擬地址通過頁表等機制映射到物理內存中的實際地址。

每個進程都有自己的虛擬地址空間,這個空間是邏輯上連續的,但并不一定在物理內存中連續。操作系統負責維護頁表,將虛擬地址轉換為物理地址,從而實現進程對內存的訪問。

操作系統肯定也要對進程地址空間進行管理,那就說明也需要:先描述再組織

進程地址空間是數據結構,具體到進程中就是特定數據結構的對象。需要注意的是:這個結構體里不保存代碼和數據

在這里插入圖片描述

圖示過程

在這里插入圖片描述

2.1進程地址空間意義

地址空間和頁表的結合是操作系統中實現虛擬內存管理的關鍵機制,它們的存在有助于解耦進程管理和內存管理,并提供了保護內存安全的重要手段。

  1. 統一的內存視圖: 地址空間和頁表的結合使得每個進程都擁有一個統一的內存視圖,無論實際物理內存的情況如何,進程都可以將內存看作是一段連續的地址空間。這種統一的內存視圖簡化了程序的編寫和調試過程,程序員可以使用相對地址來編寫程序,而不必擔心物理內存的實際情況。

  2. 解耦進程管理和內存管理: 地址空間和頁表的存在使得進程管理和內存管理可以相互獨立地進行,進程的創建、銷毀和切換與物理內存的分配、回收和調度等操作是相互獨立的。這種解耦合的設計有助于提高系統的模塊化和可擴展性,使得系統更容易進行維護和升級。

  3. 保護內存安全: 頁表是保護內存安全的重要手段之一,它通過設置頁面的訪問權限和保護位,可以防止程序對內存的非法訪問和修改。例如,可以將某些頁面設置為只讀或只執行,防止程序對其進行寫操作或執行惡意代碼,從而提高了系統的安全性和穩定性。

  4. 內存管理的有效性: 通過地址空間和頁表,操作系統可以實現虛擬內存管理,將邏輯地址映射到物理內存中,實現了內存的動態分配和管理。通過頁面置換算法和頁面駐留策略,可以實現對內存資源的有效利用,提高了系統的性能和效率。

  5. 地址空間共享和隔離: 地址空間的存在允許多個進程共享相同的代碼和數據,從而節省內存空間,提高系統的資源利用率。同時,地址空間的隔離性保證了每個進程都擁有獨立的地址空間,互不干擾,確保了系統的穩定性和安全性。


3.創建進程

3.1fork()函數創建子進程補充

我們之前已經講了在代碼里可以使用fork()函數來。創建子進程規則是:子進程與父進程共享代碼,寫時拷貝

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

  • 分配新的內存塊和內核數據結構給子進程

  • 將父進程部分數據結構內容拷貝至子進程

  • 添加子進程到系統進程列表當中

  • fork()函數返回,開始調度器調度

當一個進程調用fork之后,就有兩個二進制代碼相同的進程。而且它們都運行到相同的地方。但每個進程都將可以開始它們自己的旅程

  1. 共享代碼怎么做到的?

    子進程創建后,會拷貝父進程的進程地址空間和頁表內容(相當于淺拷貝),頁表內容相同。那么映射到的物理內存也是相同的,這樣就做到了共享代碼

寫時拷貝

通常,父子代碼共享,父子再不寫入時,數據也是共享的,當任意一方試圖寫入,便以寫時拷貝的方式各自一份副本

Linux系統中,當使用fork()系統調用創建子進程時,子進程會繼承父進程的地址空間的一個副本。但實際上,fork()之后到子進程開始寫數據之前,父進程和子進程所共享的是同一個物理內存頁面。只有當其中一個進程嘗試修改寫入時,操作系統才會進行頁面復制,確保每個進程都有自己的數據副本,從而避免了不必要的內存復制開銷

在這里插入圖片描述

頁表除了有一系列對應的虛擬地址和物理地址以外,還有一列代表權限(針對物理地址的內容的權限)

具體來說,權限字段通常包含以下幾種權限:

  1. 讀權限(r):當某個頁表項的讀權限被設置時,擁有該頁表項的進程可以讀取該頁面上的數據。如果讀權限未被設置,任何試圖讀取該頁面的操作都會引發異常或錯誤。
  2. 寫權限(w):寫權限決定了進程是否可以修改頁面上的數據。如果頁表項的寫權限被設置,進程可以對該頁面進行寫操作。否則,任何寫操作都會被阻止。

除了讀和寫權限外,頁表的權限字段還可能包含其他類型的權限,例如執行權限(x),它決定了進程是否可以在該頁面上執行代碼。在某些系統中,還可能存在特殊的權限字段,如用于控制頁面共享、緩存策略等的字段。

所以上面寫時拷貝的過程里:可以看到在修改內容之前,數據段里的權限也都是只讀,這不對吧?(因為,全局變量我們是可以修改的啊)這是在創建子進程后,數據段的頁表映射權限由rw權限變為r

為什么要改啊:改后,如果我們嘗試寫入,會發生錯誤,這時操作系統就會來完成寫入拷貝,又發現你是數據段的本該可以寫入,就又把需要寫入的進程對應的頁表映射由r權限改為rw了


4.進程終止

4.1進程退出場景

  • 代碼運行完畢,結果正確
  • 代碼運行完畢,結果不正確
  • 代碼異常終止

退出碼

main函數的返回值通常被稱為進程退出碼或返回狀態碼。在C、C++等編程語言中,main函數是程序的入口點,當程序執行完畢后,main函數會返回一個整數值給操作系統,這個整數值就是進程退出碼。操作系統會根據這個退出碼來判斷程序是正常結束還是出現了某種錯誤。

我們自己寫main函數時,總是寫一個return 0

  • 返回0表示程序成功執行
  • 非0值表示出現了某種錯誤。具體的非0值可能由程序員定義,用于表示不同的錯誤類型或狀態。

Linux系統中,你可以使用echo $?命令來查看上一個執行的命令或進程的退出碼

在這里插入圖片描述

但是光看一個數字,我們怎么能知道錯誤的原因呢? 這就需要把錯誤碼轉換為錯誤描述

錯誤碼就是函數的

strerror()函數是一個C庫函數,用于將錯誤代碼轉換為對應的錯誤信息字符串。它接受一個整數參數errno,返回一個指向錯誤信息字符串的指針。strerror函數的在頭文件string.h中,

errno是一個全局變量,用于在C語言中表示發生錯誤時的錯誤碼。當函數或系統調用發生錯誤時,errno會被設置為相應的錯誤碼,以便程序可以根據錯誤碼進行適當的錯誤處理。error是最近一次函數進行調用的返回值

char *strerror(int errnum);

其中,errnum參數是一個整數,代表特定的錯誤碼。strerror函數會根據錯誤碼在系統的錯誤碼表中查找對應的錯誤信息,并將其作為字符串返回。

進程出現異常

進程出現異常說明進程收到了異常信號,每種信號都有自己的編號(宏編號),而不同的信號編號能表明異常的原因

kill -l 命令在 Unix 和 Linux 系統中用于列出所有可用的信號。執行這個命令將顯示系統支持的所有信號的列表以及它們的編號。這對于了解不同信號的含義和用途非常有用,特別是在處理進程和進程間通信時。

下面是一個 kill -l 命令的典型輸出示例:

$ kill -l  1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL  5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE  9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2  
13) SIGPIPE     14) SIGALRM     15) SIGTERM     16) SIGSTKFLT  
17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP  
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU  
25) SIGXFSZ     26) SIGVTALRM   27) SIGPROF     28) SIGWINCH  
29) SIGIO       30) SIGPWR      31) SIGSYS      34) SIGRTMIN  
35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3  ...  
...  
63) SIGRTMAX-1  64) SIGRTMAX

一些常見的信號及其用途包括:

  • SIGTERM:請求進程終止。進程可以捕獲這個信號并清理資源后正常退出。
  • SIGINT:通常由用戶按下 Ctrl+C 產生,用于中斷前臺進程。
  • SIGKILL:強制終止進程,不能被進程捕獲或忽略。
  • SIGHUP:當控制終端(controlling terminal)被關閉時發送給進程,常用于讓進程重新讀取配置文件。

4.2進程常見退出方法

4.2.1正常退出

  1. 正常的從main()函數返回

  2. 調用exit()函數

    #include <unistd.h>
    void exit(int status);
    

    參數status定義了進程的終止狀態,也就是程序的退出碼用于表示程序的執行狀態,并幫助調用程序理解程序結束的原因

    • 在進程代碼中,任意地方調用exit()函數都表示進程退出(不一定非要在main()函數里)
    #include<stdio.h>
    #include<unistd.h>
    #include<stdlib.h>void Print()
    {printf("Now it's calling the Print function\n");exit(10);
    }int main()
    {while(1){printf("I'm a process:%d\n",getpid());sleep(3);Print();}return 0;
    }
    

    在這里插入圖片描述

  3. 調用_exit()函數

    與exit()函數的不同:

    • exit()是一個標準C庫函數,而_exit()通常被視為一個系統調用(可以知道exit底層肯定封裝了__exti函數)
    • 調用exit()時,它會執行一些清理工作,包括檢測進程打開的文件情況,并將處于文件緩沖區的內容寫入到文件中,然后才退出
    • 而_exit()則直接退出,不會執行這些清理工作,也不會將緩沖區中的內容寫入文件

4.2.2異常退出

使用ctrl + c,能使異常信號終止

進程最終執行情況

Linux系統中,任何進程最終執行完畢后都會返回一個狀態碼,這個狀態碼通常被稱為“退出碼”或“返回碼”(exit code)。這個退出碼是一個整數,用于表示進程執行的結果或狀態。根據慣例,退出碼0通常表示成功,而非零值表示出現了某種錯誤。

Linux的上下文中,我們通常討論的是“信號”(signal),這些信號用于在進程之間傳遞信息或通知進程發生了某種事件(如中斷、終止等)

  • 退出碼(exit code):一個整數,用于表示進程執行的結果或狀態。0通常表示成功,非零值表示錯誤或異常情況。
  • 信號(signal):用于在進程之間傳遞信息或通知進程發生了某種事件的機制。進程可以發送和接收信號,并對某些信號進行特定的處理。(就是我們上面講的進程出現異常時收到的異常信號

4.3 OS會做什么

當進程創建和進程終止時,操作系統會執行一系列的操作來確保系統的穩定性和資源管理的有效性。

進程創建時:

  1. 資源分配:操作系統為新進程分配必要的資源,如內存空間、文件描述符、打開的文件等。
  2. 復制父進程數據:新創建的子進程是父進程的副本,所以操作系統會復制父進程的部分數據結構內容到子進程,包括代碼、數據、堆、棧等內容。然而,這種復制通常是“寫時復制”(Copy-On-Write)的,即實際的物理內存頁并不會立即復制,而是在子進程首次對這些頁進行修改時才會進行復制。
  3. 設置進程ID:操作系統為每個新進程分配一個唯一的進程ID(PID),用于在系統中唯一標識該進程。
  4. 添加到進程列表:新創建的進程會被添加到系統的進程列表中,以便操作系統可以對其進行管理和調度。
  5. 執行fork系統調用:當父進程調用fork()函數時,操作系統會處理這個系統調用,完成上述操作,并返回相應的值給父進程和子進程。父進程收到的是子進程的PID,而子進程收到的是0。

進程終止時:

  1. 執行清理工作:進程在終止前會執行一些清理工作,比如關閉打開的文件、釋放占用的內存等。如果進程是正常終止(比如調用exit()函數),操作系統還會捕獲進程的退出狀態碼。
  2. 回收資源:操作系統回收進程占用的所有資源,包括內存、文件描述符、信號處理程序等。
  3. 處理僵尸進程:當一個進程終止時,它并不會立即從系統中消失。相反,它會變成一個僵尸進程(Zombie Process),直到其父進程調用wait()或waitpid()系統調用來回收它。操作系統會維護這些僵尸進程的信息,直到它們被父進程回收。
  4. 更新進程列表:操作系統會從進程列表中移除已終止的進程。

5.進程等待

5.1必要性

  • 在Unix/Linux系統中,當子進程退出時,它的進程描述符仍然保留在系統中,直到父進程通過某種方式獲取其退出狀態。這個已經退出但進程描述符仍然保留在系統中的進程就被稱為“僵尸進程”
  • 一旦進程變成僵尸狀態,即使是使用kill -9這樣的強制終止命令也無法直接“殺死”它。因為僵尸進程本身已經終止,只是其退出狀態還沒有被父進程讀取
  • 而且父進程派給子進程的任務完成的如何,我們需要知道。如,子進程運行完成,結果對還是不對, 或者是否正常退出
  • 為了回收子進程的資源并獲取其退出信息,父進程需要調用wait()waitpid()系統調用(進行進程等待)。這些調用會阻塞父進程,直到有子進程退出,并返回已退出子進程的PID和退出狀態

5.2進程等待的方法

5.2.1 wait()方法

wait 方法在Linux 編程中是一個重要的系統調用,它主要用于監視先前啟動的進程,并根據被等待的進程的運行結果返回相應的 Exit 狀態。在父進程中,wait 方法常被用來回收子進程的資源并獲取子進程的退出信息,從而避免產生僵尸進程

wait 函數允許父進程等待其子進程結束,并可以獲取子進程的退出狀態。在C語言中的用法和參數:

函數原型

#include <sys/types.h>  
#include <sys/wait.h>  pid_t wait(int *status);

參數status:這是一個指向整數的指針,用于存儲子進程的退出狀態。如果父進程不關心子進程的退出狀態,可以將這個參數設為 NULL

返回值

  • 返回值大于零時成功,返回已終止子進程的進程ID。
  • 失敗時,返回 -1,并設置全局變量 errno 以指示錯誤原因。
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <sys/types.h>  
#include <sys/wait.h> int main()
{pid_t id = fork();//創建子進程if (id == 0){//這里面是子進程int count = 5;while (count--){printf("child is running. pid:%d , ppid:%d\n", getpid(), getppid());sleep(1);//這里循環5秒}printf("子進程將退出,馬上就變成僵尸進程\n");exit(0);//子進程退出了}//這里是父進程printf("父進程休眠\n");sleep(10);printf("父進程開始回收了\n");pid_t rid = wait(NULL);//讓父進程進程阻塞等待if (rid > 0){printf("wait successfully, rid:%d\n", rid);}printf("父進程回收了\n");sleep(5);return 0;
}

代碼一共15秒

  • 0~5秒內:子進程與父進程都存在,5秒后子進程結束
  • 5~10秒內:父進程正常運行,子進程在僵尸。10秒后父進程開始回收
  • 10~15秒:父進程正常運行,15秒后父進程結束

在這里插入圖片描述

5.2.2waitpid()方法

waitpid 是 Unix 和 Linux 系統編程中用于等待子進程結束并獲取其狀態的系統調用。它的原型如下:

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

返回值

  • 正常返回的時候waitpid返回收集到的子進程的進程ID

  • 如果 options 參數中設置了 WNOHANG,并且沒有已退出的子進程可收集,則 waitpid 返回0。

  • 如果調用中出錯,則返回-1,這時errno會被設置成相應的值以指示錯誤所在

參數

  1. pid:

    • Pid=-1,等待任一個子進程。與wait等效。

    • Pid>0.等待其進程ID與pid相等的子進程

  2. status:這是一個指向整數的指針,用于存儲子進程的退出狀態。如果不需要這個信息,可以傳遞 NULL

    • WIFEXITED(status):宏函數,如果子進程正常退出,返回非零值;否則返回0。
    • WEXITSTATUS(status):宏函數,如果 WIFEXITED(status) 為真,則返回子進程的退出碼。(后面就能理解這兩個用處)
  3. options:這是一個位掩碼,用于修改 waitpid 的行為。

    • WNOHANG:如果指定了此選項,waitpid不會阻塞,而是立即返回(父進程不會等待子進程了)。如果指定的子進程沒有結束,則 waitpid 返回0;如果子進程已結束,則返回子進程的ID。
    • 傳遞 0 作為 options 參數時,你實際上是在告訴 waitpid使用最傳統的阻塞方式等待子進程終止,并且只關心那些已經終止的子進程

如果子進程已經退出,調用wait/waitpid時,wait/waitpid會立即返回,并且釋放資源,獲得子進程退出信息。

如果在任意時刻調用wait/waitpid,子進程存在且正常運行,則進程可能阻塞。

如果不存在該子進程,則立即出錯返回。

獲取子進程status

  • wait和waitpid,都有一個status參數,該參數是一個輸出型參數,由操作系統填充

  • 如果傳遞NULL,表示不關心子進程的退出狀態信息

  • 否則,操作系統會根據該參數,將子進程的退出信息反饋給父進程

  • 我們上面說:任何進程最終的執行情況,我們可以使用兩個數字表明具體執行的情況——退出碼和退出信號。這里status就是將兩者結合起來的,使用位圖結合

  • status不能簡單的當作整形來看待,可以當作位圖來看待,具體細節如下圖:

在這里插入圖片描述

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){// childint cnt = 5;while(cnt){printf("Child is running, pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);cnt--;}exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0); // 阻塞等待if(rid > 0){printf("wait successfully, rid: %d, status: %d\n", rid, status);}return 0;
}

在這里插入圖片描述

在這里插入圖片描述

那我們怎么直接獲得退出碼和信號編號呢?

  1. 我們能自己針對status進行位運算

  2. 使用上面的WIFEXITED(status)、WEXITSTATUS(status)

    int main()
    {pid_t id = fork();if (id == 0){int cnt = 5;while (cnt){printf("Child is running, pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);cnt--;}exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0){if (WIFEXITED(status)){printf("wait successfully, rid: %d, status: %d\n", rid, status);printf("wait successfully, rid: %d, status: %d, exit code: %d\n", rid, status,WEXITSTATUS(status));}else{printf("wait wrongly\n");}}return 0;
    }
    

    在這里插入圖片描述

5.3阻塞等待與非阻塞等待

阻塞等待(wait()與waitpid( , , 0)):

  • 當進程執行某個操作時,如果該操作需要等待子進程結束,進程會進入阻塞狀態。
  • 在阻塞狀態下,進程會暫停執行,釋放CPU資源,將進程狀態保存起來,以便在條件滿足后能夠恢復執行。
  • 阻塞等待期間,進程無法執行其他任務,只能等待條件滿足或事件發生。

非阻塞等待

  • 與阻塞等待不同,非阻塞等待允許進程在等待子進程結束期間繼續執行其他任務。
  • 非阻塞等待通常通過輪詢或異步通知機制實現,進程會定期檢查條件是否滿足,或者在條件滿足時接收通知。
int main()
{pid_t id = fork();if (id == 0){int cnt = 5;while (cnt){printf("Child is running, pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);cnt--;}exit(1);}int status = 0;pid_t rid = waitpid(id, &status, WNOHANG);while (1){if (rid > 0){//子進程結束了printf("wait successfully, rid: %d, status: %d, exit code: %d\n",rid, status, WEXITSTATUS(status));break;}else if (rid = 0){//子進程還沒結束//這里能寫希望在子進程沒結束期間希望父進程干什么}else{//到這里就說明出錯了perror("waitpid");break;}}return 0;
}

今天的內容也是不少了,累死了。感謝大家支持!!!

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

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

相關文章

NDIS小端口驅動開發(三)

微型端口驅動程序處理來自過度驅動程序的發送請求&#xff0c;并發出接收指示。 在單個函數調用中&#xff0c;NDIS 微型端口驅動程序可以指示具有多個接收 NET_BUFFER_LIST 結構的鏈接列表。 微型端口驅動程序可以處理對每個NET_BUFFER_LIST結構上具有多個 NET_BUFFER 結構的多…

JAVA -- > 初識JAVA

初始JAVA 第一個JAVA程序詳解 public class Main {public static void main(String[] args) {System.out.println("Hello world");} }1.public class Main: 類型,作為被public修飾的類,必須與文件名一致 2.public static 是JAVA中main函數準寫法,記住該格式即可 …

python皮卡丘動畫代碼

在Python中&#xff0c;我們可以使用多種方法來創建皮卡丘的動畫&#xff0c;例如使用matplotlib庫。 解決方案1&#xff1a;使用matplotlib庫 以下是一個使用matplotlib庫創建皮卡丘動畫的例子&#xff1a; import matplotlib.pyplot as plt import matplotlib.animation …

Slash后臺管理系統代碼閱讀筆記 如何實現環形統計圖表卡片?

目前&#xff0c;工作臺界面的上半部分已經基本梳理完畢了。 接下來&#xff0c;我們看看這個環形圖卡片是怎么實現的&#xff1f; 具體代碼如下&#xff1a; {/*圖表卡片*/} <Row gutter{[16, 16]} className"mt-4" justify"center">{/*環形圖表…

U盤引導盤制作Rufus v4.5.2180

軟件介紹 Rufus小巧實用開源免費的U盤系統啟動盤制作工具和格式化U盤的小工具&#xff0c;它可以快速將ISO鏡像文件制作成可引導的USB啟動安裝盤&#xff0c;支持Windows或Linux啟動&#xff0c;堪稱寫入鏡像速度最快的U盤系統制作工具。 軟件截圖 更新日志 github.com/pbat…

嵌入式全棧開發學習筆記---C語言筆試復習大全24

目錄 內存管理 內存分配 堆和棧的區別&#xff1f;&#xff08;面試重點&#xff09; 申請內存的函數 malloc realloc free gcc工具鏈 編譯的過程&#xff08;面試重點&#xff09; 第一步&#xff0c;預處理&#xff1a; 第二步&#xff0c;編譯&#xff1a; 第三…

【Spring Boot】使用 Redis + Cafeine 實現二級緩存

使用 Redis Caffeine 實現二級緩存可以有效提升應用的性能和緩存的命中率。Caffeine 是一個高效的 Java 本地緩存庫&#xff0c;而 Redis 是一個分布式緩存解決方案。通過將兩者結合&#xff0c;Caffeine 作為一級緩存用于快速訪問常用數據&#xff0c;Redis 作為二級緩存用于…

解決LabVIEW通過OPC Server讀取PLC地址時的錯誤180121602

在使用LabVIEW通過OPC Server讀取PLC地址時&#xff0c;若遇到錯誤代碼180121602&#xff0c;建議檢查網絡連接、OPC Server和PLC配置、用戶權限及LabVIEW設置。確保網絡暢通&#xff0c;正確配置OPC變量&#xff0c;取消緩沖設置以實時讀取數據&#xff0c;并使用診斷工具驗證…

簡述vue常用指令

Vue.js 提供了許多內置指令&#xff0c;這些指令用于在模板中添加特殊功能。以下是一些 Vue 的常用內置指令的簡要說明&#xff1a; v-text&#xff1a; 更新元素的 textContent。示例&#xff1a;<span v-text"message"></span> v-html&#xff1a; 更…

2 使用香橙派AIpro報錯 No module named ‘acllite utils‘

當使用jupyter運行香橙派的notebooks下面的案例的時候啟動使用jupyter lab 然后自動跳轉到jupyter頁面。如下圖: 這是自動跳轉過來的。然后運行下面的包的導入后報錯: 報錯為No module named ‘acllite utils’,那么我們打開notebooks文件夾下面的start_notebooks.sh文件:…

【C++練級之路】【Lv.21】C++11——列表初始化和聲明

快樂的流暢&#xff1a;個人主頁 個人專欄&#xff1a;《算法神殿》《數據結構世界》《進擊的C》 遠方有一堆篝火&#xff0c;在為久候之人燃燒&#xff01; 文章目錄 引言一、列表初始化1.1 內置類型1.2 結構體或類1.3 容器 二、聲明2.1 auto2.2 decltype2.3 nullptr 三、STL的…

A*算法搜索的路徑是最優的么?

A * 算法&#xff08;A* Search Algorithm&#xff09;是一種啟發式搜索算法&#xff0c;它旨在找到從起點到終點的最短路徑。在滿足以下條件時&#xff0c;A*算法能夠保證找到最優路徑&#xff1a; 啟發式函數的一致性&#xff08;Consistency&#xff09;或可采納性&#xf…

從“反超”到“引領”,中國衛浴品牌憑何遙遙領先?

作者 | 曾響鈴 文 | 響鈴說 前不久&#xff0c;第28屆中國國際廚房、衛浴設施展覽會(以下簡稱“中國國際廚衛展”)在上海如期舉行&#xff0c;就結果來說真的讓人大開眼界。 沖水聲比蚊子聲更小的馬桶、能化身無感交互平臺的魔鏡柜、可以語音交互的淋浴器&#xff0c;這些“…

Keli5燒寫STM32程序時出現ST-LINK USB communication error錯誤(USB 通信錯誤)

1錯誤原圖 2錯誤原因 前提驅動安裝正確 原因1 usb接觸不良&#xff08;極少出現&#xff09; 解決方法 更換USB線 還不行連下載器一起更換 原因2&#xff08;出現概率比較大&#xff09; 下載器的固件出現問題或下載器固件版本與Keli5的版本不匹配 解決方法 在Keli5的…

[音視頻]ffmepg常用命令

ffmpeg 在音視頻的世界里&#xff0c;ffmpeg可是如雷貫耳的存在&#xff0c;學習音視頻開發&#xff0c;ffmpeg是必須掌握的技能 常用命令 保存m3u8文件 ffmpeg -i http://xxxxx/test.m3u8 -c copy result.mp4

今日早報 每日精選15條新聞簡報 每天一分鐘 知曉天下事 5月26日,星期日

每天一分鐘&#xff0c;知曉天下事&#xff01; 2024年5月26日 星期日 農歷四月十九 1、 醫保局&#xff1a;支持將符合條件的村衛生室納入醫保定點&#xff0c;方便農村居民就醫。 2、 網傳養老金儲備嚴重不足&#xff1f;央視辟謠&#xff1a;這筆錢二十多年來從未動用過&a…

搭建企業級AI應用的流程

搭建企業級AI應用的流程是一個復雜且系統化的工程&#xff0c;它需要從多個維度出發&#xff0c;確保最終的應用既符合企業的業務需求&#xff0c;也具備高效、穩定和可擴展的特性。以下是詳細的步驟&#xff1a; 初步接觸與需求分析是整個項目的基礎。在這一階段&#xff0c;我…

【C++題解】1698. 請輸出帶有特殊尾數的數

問題&#xff1a;1698. 請輸出帶有特殊尾數的數 類型&#xff1a; 題目描述&#xff1a; 請輸出1~n 中所有個位為 1、3、5、7中任意一個數的整數&#xff0c;每行 1 個。( n<1000 ) 比如&#xff0c;假設從鍵盤讀入 20&#xff0c;輸出結果如下&#xff1a; 1 3 5 7 11 1…

LLMs之PEFT之Llama-2:《LoRA Learns Less and Forgets LessLoRA學得更少但遺忘得也更少》翻譯與解讀

LLMs之PEFT之Llama-2&#xff1a;《LoRA Learns Less and Forgets LessLoRA學得更少但遺忘得也更少》翻譯與解讀 導讀&#xff1a;該論文比較了LoRA與完全微調在代碼與數學兩個領域的表現。 背景問題&#xff1a;微調大規模語言模型需要非常大的GPU內存。LoRA這一參數高效微調方…

OpenStack平臺Keystone組件的使用

1. 規劃節點 安裝基礎服務的服務器規劃 IP地址 主機名 節點 192.168.100.10 controller Openstack控制節點 2. 基礎準備 使用機電云共享的單節點的openstack系統&#xff0c;自行修改虛擬網絡編輯器、網絡適配器&#xff0c;系統用戶名&#xff1a;root&#xff0c;密…