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
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...