Linux內核學習筆記(3)-- 進程的創建和終結

来源:https://www.cnblogs.com/tongye/archive/2018/08/29/9517935.html
-Advertisement-
Play Games

一、 進程創建: Unix 下的進程創建很特別,與許多其他操作系統不同,它分兩步操作來創建和執行進程: fork() 和 exec() 。首先,fork() 通過拷貝當前進程創建一個子進程;然後,exec() 函數負責讀取可執行文件並將其載入地址空間開始運行。 1、fork() :kernel/fo ...


一、 進程創建:

  Unix 下的進程創建很特別,與許多其他操作系統不同,它分兩步操作來創建和執行進程: fork() 和 exec() 。首先,fork() 通過拷貝當前進程創建一個子進程;然後,exec() 函數負責讀取可執行文件並將其載入地址空間開始運行。

1、fork() :kernel/fork.c

  在Linux系統中,通過調用fork()來創建一個進程。調用 fork() 的進程稱為父進程,新產生的進程稱為子進程。在該調用結束時,在返回點這個相同的位子上,父進程恢復執行,子進程開始執行。fork()系統調用從內核返回兩次:一次返回到父進程,另一次返回到新產生的子進程。使用fork()創建新進程的流程如下:

  1)fork() 調用clone;

  2)clone() 調用 do_fork();

  3)do_fork() 調用 copy_process() 函數,copy_process() 函數將完成第 4-11 步;

  4)調用 dup_task_struct() 為新進程創建一個內核棧、thread_info結構和task_struct,這些值與當前進程的值相同;

  5)檢查並確保新創建這個子進程後,當前用戶所擁有的進程數目沒有超出給它分配的資源的限制;

  6)清理子進程進程描述符中的一些成員(清零或初始化,如PID),以使得子進程與父進程區別開來;

  7)將子進程的狀態設置為 TASK_UNINTERRUPTIBLE,保證它不會投入運行;

  8)調用 copy_flags() 以更新 task_struct 的 flags 成員;

  9)調用 alloc_pid() 為新進程分配一個有效的 PID;

10)根據傳遞給clone() 的參數標誌,copy_process() 拷貝或共用打開的文件、文件系統信息、信號處理函數、進程地址空間和命名空間等;

11)做一些掃尾工作並返回一個指向子進程的指針。

12)回到 do_fork() 函數,如果 copy_process() 函數成功返回,新創建的子進程將被喚醒並讓其投入運行。

  下麵用一段簡單的代碼演示一下 fork() 函數:

  1 #include <unistd.h>
  2 #include <stdio.h>
  3 
  4 int main(){
  5     pid_t fpid;
  6     int count= 0;
  7     fpid = fork();              // fpid 為fork()的返回值
  8     if(fpid < 0){               // 當fork()的返回值為負值時,表明調用 fork() 出錯
  9         printf("error in fork!");
 10     }
 11     else if(fpid  == 0){        // fork() 返回值為0,表明該進程是子進程
 12         printf("this is a child process, the process id is %d\n",getpid());
 13         count++;
 14     }
 15     else{                       // fork() 返回值大於0,表明該進程是父進程,這時返回值其實是子進程的PID
 16         printf("this is a father process, the process id is %d\n",getpid());
 17         count++;
 18     }
 19     printf("計數 %d 次\n",count);
 20     return 0;                                                                          
 21 }

輸出結果:

  可以看到,調用 fork() 函數後,原本只有一個進程,變成了兩個進程。這兩個進程除了 fpid 的值不同外幾乎完全相同,它們都繼續執行接下來的程式。由於 fpid 的值不同,因此會進入不同的判斷語句,這也是為什麼兩個結果有不同之處的原因。另外,可以看到,父進程的 PID 剛好比子進程的 PID 小1。 fork()  的返回值有以下三種:

a)在父進程中,fork() 返回新創建子進程的 PID;

b)在子進程中,fork() 返回0;

c)如果 fork() 調用出錯,則返回負值

 

 2、exec() :fs/exec.c (源程式 exec.c 實現對二進位可執行文件和 shell 腳本文件的載入與執行)

  通常,創建新的進程都是為了立即執行新的、不同的程式,而接著調用 exec() 這組函數就可以創建新的地址空間,並把新的程式載入其中

  exec() 並不是一個函數,而是一個函數簇,一共包含六個函數,分別為: execl、execlp、execle、execv、execvp、execve,定義如下:

#include <unistd.h>  
  
int execl(const char *path, const char *arg, ...);  
int execlp(const char *file, const char *arg, ...);  
int execle(const char *path, const char *arg, ..., char *const envp[]);  
int execv(const char *path, char *const argv[]);  
int execvp(const char *file, char *const argv[]);  
int execve(const char *path, char *const argv[], char *const envp[]);  

  這六個函數的功能其實差不多,只是接受的參數不同。exec() 函數的參數主要有3個部分:執行文件部分、命令參數部分和環境變數部分:

1)執行文件部分:也就是函數中的 path 部分,該部分指出了可執行文件的查找方式。其中 execl、execle、execv、execve的查找方式都是使用的絕對路徑,而 execlp和execvp則可以只給出文件名進行查找,系統會從環境變數 "$PATH"中查找相應的路徑;

2)命令參數部分:也就是函數中的 file 部分,該部分指出了參數的傳遞方式以及要傳遞哪些參數。這裡,"l"結尾的函數表示使用逐個列舉的方式傳遞參數;"v"結尾的表示將所有參數整體構造成一個指針數組進行傳遞,然後將該數組的首地址當做參數傳遞給它,數組中的最後一個指針要求為 NULL;

3)環境變數部分:exec() 函數簇使用了系統預設的環境變數,也可以傳入指定的環境變數。其中 execle 和execve 這兩個函數就可以在 envp[] 中指定當前進程所使用的環境變數。

·  當 exec() 執行成功時,exec() 函數會取代執行它的進程,此時,exec() 函數沒有返回值,進程結束。當 exec() 函數執行失敗時,將返回失敗信息(返回 -1),進程繼續執行後面的代碼

  通常,exec() 會放在 fork() 函數的子進程部分,來替代子進程繼續執行,exec() 執行成功後子進程就會消失,但是執行失敗的話,就必須要使用 exit() 函數來讓子進程退出。下麵用一段簡單的代碼來演示一下 exec() 函數簇中的一個函數的用法,其餘的參考:https://www.cnblogs.com/dongguolei/p/8098181.html

  1 #include <unistd.h>
  2 #include <stdio.h>
  3 #include <errno.h>
  4 #include <string.h>
  5 
  6 int main(){
  7     int childpid;
  8     pid_t fpid;
  9     fpid = fork();
 10     if(fpid == 0){                          // 子進程
 11         char *execv_str[] = {"ps","aux",NULL};      // 指令:ps aux 查看系統中所有進程 
 12         if( execv("/usr/bin/ps",execv_str) < 0 ){
 13             perror("error on exec\n");
 14             exit(0);
 15         }
 16     }
 17     else{
 18         wait(&childpid);
 19         printf("execv done\n");
 20     }
 21 }

 

   在這個程式中,使用 fork() 創建了一個子進程,隨後立即調用 exec() 函數簇中的 execv() 函數,execv() 函數執行了一條指令,顯示當前系統中所有的進程,結果如下(進程有很多,這裡只截了一部分):

  註意看最後兩個進程,分別是父進程和調用 fork() 後創建的子進程。

 

二、進程終結

  進程被創建後,最終要終結。當一個進程終結時,內核必須釋放它所占有的資源,並把這一消息告訴其父進程。系統通過 exit() 系統調用來處理終止和退出進程的相關工作,而大部分工作則由 do_exit() 來完成 (kernel/exit.c):

1)將task_struct 中的標誌成員設置為 PF_EXITING;

2)調用 del_timer_sync() 刪除任一內核定時器,以確保沒有定時器在排隊,也沒有定時器處理程式在運行;

3)調用 exit_mm() 函數釋放進程占用的 mm_struct,如果沒有別的進程使用它們(地址空間被共用),就徹底釋放它們;

4)調用 sem__exit() 函數,如果進程排隊等候 IPC 信號,它則離開隊列;

5)調用 exit_files() 和 exit_fs(),以分別遞減文件描述符、文件系統數據的引用計數,若其中某個引用計數的值降至零,則表示沒有進程使用相應的資源,可以釋放掉進程占用的文件描述符、文件系統資源;

6)把 task_struct 的 exit_code 成員設置為進程的返回值;

7)調用 exit_notify() 向父進程發送信號,並把進程狀態設置為 EXIT_ZOMBIE;

8)調用 schedule() 切換到新的進程,繼續執行。由於 EXIT_ZOMBIE 狀態的進程不會被再調度,所以這是進程所執行的最後一段代碼, do_exit() 沒有返回值。

  至此,與進程相關聯的所有資源都被釋放掉了,進程不可運行並處於 EXIT_ZOMBIE 退出狀態。此時,進程本身所占用的記憶體還沒有釋放,如內核棧、thread_info 結構和 task_struct 結構等,它存在的意義是向父進程提供信息,當父進程收到信息後,或者通知內核那是無關的信息後,進程所持有的剩餘的記憶體將被釋放。父進程可以通過 wait4() 系統調用查詢子進程是否終結,這其實使得進程擁有了等待特定進程執行完畢的能力。

  系統通過調用 release_task() 來釋放進程描述符。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • JQuery ajax支持get方式的跨域,採用了jsonp來完成。完成跨域請求的有兩種方式實現。一種是使用Jquery ajax最底層的Api實現跨域的請求,而另一種則是JQuery ajax的高級封裝。 方式1:使用Jquery ajax方式。 1 $.ajax({ 2 url:'http:// ...
  • 前沿: 一般情況下,在我們做訪問許可權管理的時候,會把用戶的正確登錄後的基本信息保存在Session中,以後用戶每次請求頁面或介面數據的時候,拿到 Session中存儲的用戶基本信息,查看比較他有沒有登錄和能否訪問當前頁面。 Session的原理,也就是在伺服器端生成一個SessionID對應了存儲的 ...
  • 0.使用背景 因為現在的項目都是基於 .NET Core 的,但是某些需要調用第三方的 WebService 服務,故有了此文章。其基本思路是通過微軟提供的 Svcutil 工具生成代理類,然後通過 System.ServiceModel 來調用代理類所提供的對象與方法。 1.配置準備 1.1 新建 ...
  • 1. 批量插入 public async Task CreateBusinessItemAsync(IEnumerable<BusinessItemsEntity> businessItemsEntities) { var bizid = businessItemsEntities.First(). ...
  • 1 在主視窗中實例化子視窗 在主視窗中實例化子視窗,而不是在按鈕中實例化子視窗對象。 2 通過按鈕來顯示主視窗 在按鈕中需要實現的是視窗的顯示 3 關閉子視窗而不釋放子視窗對象的方法 4 在父視窗關閉時銷毀子視窗對象 由於需要在父視窗關閉是銷毀子視窗對象,因此,在父視窗的關閉動作FormClosed ...
  • 1 namespace RemoveTheSame 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 List list = new List() 8 { 9 new Use... ...
  • [TOC] CVE 2018 8120 分析 1、實驗環境 1.1、操作系統 windows 7 sp1 x86 未打補丁 "磁力鏈接" 1.2、用到的分析工具 windbg 32位 "下載地址" IDA pro 7.0 "正版鏈接" PCHunter "下載地址" ProcessHacker "下 ...
  • 1.什麼是進程的內核棧? 在內核態(比如應用進程執行系統調用)時,進程運行需要自己的堆棧信息(不是原用戶空間中的棧),而是使用內核空間中的棧,這個棧就是進程的內核棧 2.進程的內核棧在電腦中是如何描述的? linux中進程使用task_struct數據結構描述,其中有一個stack指針 task_ ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...