Bo2SS

Bo2SS

3 多進程

課程內容#

什麼是進程#

  • 進程是程序在內存中的鏡像,是正在運行的程序,是程序的實例化,是一個複雜的集合體
    • 包含開辟的內存空間、用戶信息、組信息、權限、佔用的資源、正在跑的代碼、打開的文件等等
  • 與之對應
    • ① 什麼是程序
      • 程序是編譯好的可執行的二進制文件,放在磁碟上
        • 就是一個普通文件,有 x 權限
      • 程序的集合是應用
    • ② 什麼是線程
      • 線程代表一系列有順序的、需要 CPU 執行的指令
      • 一個進程可能由一個或多個線程組成,同時執行指令
    • [PS] 進程是 CPU資源分配的基本單位,線程是 CPU調度的基本單位

fork#

創建一個子進程 [進程接口]

  • man fork
  • 原型 + 描述
    • 圖片
    • 返回值 [類型:pid_t]:進程 id
    • 通過複製調用 fork 的進程 [父進程] 創建一個新進程 [子進程],父進程和子進程運行在相互獨立的內存空間
      • fork 完成時,兩者有一樣的內容,之後內存的寫、文件映射不會互相影響
      • 當內存發生變化時,才會發生真正的拷貝 [寫拷貝的概念]
        • 否則共用的是同一份內存空間
    • image-20210120105027238
    • 父子主要有以下的不同點:
      • 孩子有自己唯一的 PID,並且不和任何已存在的 PID 相同
      • 孩子認為的父親 PID [getppid] 與真實的父親 PID 相同
      • 孩子不會繼承父親的內存鎖
      • 孩子的資源使用量和 CPU 使用時間都會重置為 0
      • 孩子不會繼承待執行的信號、信號量、記錄鎖、計時器、異步 IO 操作
  • 返回值
    • 圖片
    • 成功:在父進程中返回孩子的 PID,子進程中返回 0
      • 父親無法再通過其他方式獲得該孩子的 PID,兒子可通過 getppid 獲得父親的 PID
    • 失敗:返回 - 1,並設置 errno [沒有創建子進程]

wait#

等待進程狀態的改變

  • man wait
  • 原型
    • 圖片
    • wstatus [int *]:返回子進程的狀態
      • 如子進程中 return、exit 的值
      • 需要使用宏來解析,如 WIFEXITED (wstatus),詳見代碼演示 —wait— 二
  • 描述
    • 圖片
    • 等待對象:調用進程的孩子
    • 狀態改變情況:孩子被終止、被信號中斷、被信號喚醒
    • 當有被終止的孩子時,
      • wait 命令可以使系統釋放孩子相關的資源
      • 否則 [不執行 wait 命令],被終止的子進程就會變成殭屍進程[👇]
        • 死了的孩子沒有被父進程察覺,其資源沒有被釋放
        • 可使用 top 查看
        • 圖片
        • zombie 即殭屍進程
    • 只要一個孩子已經改變狀態,wait 命令就會被立馬返回
      • 否則,會阻塞直到有孩子改變狀態或者信號中斷
  • 返回值
    • image
    • 返回被終止的孩子 PID 或者 - 1 [出錯,並設置 errno]

exec 族#

執行一個文件 [一切皆文件]

  • man exec
  • 原型
    • 圖片
    • 有很多兄弟
  • 描述
    • 圖片
    • 圖片
    • 將用一個全新的進程鏡像【替換】當前的進程鏡像
      • [讓孩子有一個全新的世界]
    • 第一個參數都是要執行文件的名字
      • path:完整路徑
      • file:可以是 PATH 環境變量裡的命令或完整路徑
    • 整個族可概括為:"exec + l/v + p/e/pe"
      • 參數名 arg,表示是前面參數 path 的參數
      • l-list,所有參數放入一整個字符串中 [參數的傳遞方式]
        • 按慣例,arg0 應與要執行文件的名字有關聯
        • 必須以 (char *) NULL 結尾
      • v-vector,所有參數放入一個字符串數組中 [參數的傳遞方式]
        • 必須以 null 指針結尾
      • p-path,可執行文件的查找範圍包括 PATH 環境變量
        • 複製了 Shell 查找命令的過程
      • e-env,允許指定環境變量
        • 變量 - 數值對
  • 返回值
    • 圖片
    • 只在錯誤發生時返回 - 1

flock#

在打開的文件上操作建議鎖

[本質上是為了保護數據]

  • man 2 flock
  • 原型 + 描述
    • 圖片
    • 通過文件描述符 fd 操作
    • 主要三種操作
      • LOCK_SH:共享鎖
      • LOCK_EX:互斥鎖
        • 互斥鎖:如果有一個人訪問,其他人就不能訪問了
        • 舉例:很多人上一個衛生間
      • LOCK_UN:解鎖
  • 返回值
    • 圖片
    • 0,成功;-1,失敗

代碼演示#

fork#

一、複製緩衝區、行緩衝

  • 圖片
  • 輸出結果
    • 圖片
    • ❗ fork 後面已經沒有輸出函數了,為什麼輸入 suyelu,會輸出兩個suyelu?
    • 【事實】雖然 fork 後代碼複製了一份給子進程,但是子進程只會執行 fork 後的代碼
    • 【關鍵】緩衝區被複製了,裡面還存有 suyelu
      • printf 中沒有換行符,而標準 I/O 是行緩衝I/O,第 13 行執行完並不會刷新緩衝區
      • 當程序結束時,才觸發刷新緩衝區的條件
    • [PS] zsh 下可能只會輸出一次 suyelu,可能是 zsh 的優化?bash 下有兩個

二、父子進程相互獨立

  • 圖片
  • 輸出結果
    • 圖片
    • ❗ 父進程一定先執行嗎?
      • 不一定,父子進程完全獨立,不相干,本質上誰先執行是由內核調度決定
      • 但父進程極大概率先執行,因為內核調度的每個進程有一個運行時間,父進程生了孩子後,它的運行時長還沒到
  • [PS]
    • 1 號進程 <pid 為 1 的進程> 是 init 進程,其它進程都是由它生出來的
    • 與人類世界相反,計算機世界的第一個進程一直活著,等著給子進程收屍

三、創建 10 個子進程,並打印自己的序號

10 個子進程是親兄弟

  • 圖片
  • 如果不加 18 行的 break
    • 將產生 2^10 個進程:1 -> 2 -> 4 -> 8 -> 16 -> 32 -> ... -> 2^10
    • 統計運行的父、子進程數量:ps -ef | grep -v grep | grep Ten | wc -l
      • [Ten 為可執行程序名]
  • sleep 時長不會疊加
    • 進程遇到 sleep,系統調度換到其它進程運行,最後等待時間只體現約 10s
  • i 變量被子進程帶走後就獨立了,不會因父進程中 i 變量的改變而改變

wait#

一、製造殭屍進程

  • 圖片
  • 不用 wait 感知子進程的終止,即會產生殭屍進程
  • 用戶有多種查看殭屍進程的方式 [讓程序在後台運行:./a,out &]
    • 基於 ps,查看有 defunct 或 Z 標記的進程
    • 圖片
    • 基於 top
    • 圖片
    • 利用 pstree 可以看到殭屍進程的血緣關係
    • 圖片
  • [PS] 殺殭屍進程需要殺其父進程;該父進程的父進程是 zsh,程序結束後,zsh 會告知系統對父子進程收屍

二、感知子進程返回狀態

  • 圖片
  • 程序在運行約 2s 後,輸出如下:
  • 圖片
  • ❗ 為什麼子進程 return 1,父進程 wait 得到的 status 是 256
    • 16 位 int 型變量值為 256 👉 其二進制對應第 8 位為 1、其餘位均為 0
    • 再參考下圖 [Linux-UNIX 系統編程手冊 (上冊)—26.1.3 節],問題有了答案
    • 圖片
    • 其實,在 man 手冊中提到了可使用宏來檢查狀態
    • 圖片
    • WEXITSTATUS (wstatus) 則可以解析退出狀態
    • 在源碼中,每個宏對應了下面的位操作
    • 圖片
    • 所以在 printf 狀態時,根據需求,通過宏處理以下即可

exec 族#

【替換為全新的進程】

  • 圖片
  • 子進程在第 17 行被替換為全新的進程 [vim],之後的代碼永遠不會被執行
    • fork 後直接 exec:不會在 fork 時複製父進程的內存空間又在 exec 時馬上啟用 [寫拷貝概念:當內存發生變化時,才會發生真正的拷貝]
  • wait (NULL) 負責收屍
  • execlp 的第二個參數可以任意,但和第一個參數有關聯更有意義
    • 在下面可以體現該參數的某方面意義
    • 如果將 exec 代碼換成第 17 行,第二個參數隨意取名
    • 圖片
    • 生成可執行文件 Test 的源文件 test.c 如下:
      • 圖片
      • 輸出 argv [0] 的值
    • 執行上下兩份代碼的結果如下:
      • 圖片
      • 可見,第二個參數體現在了 argv [0] 變量裡

附加知識點#

  • 使用 while (1){} 時在循環體裡加 sleep,對 CPU 更友好
    • 否則可能導致 CPU 利用率蹿升、空轉、過熱
  • pstree 可以方便地看到進程的繼承關係,-p 可以顯示 pid
  • 查看殭屍進程:ps、ps -aux、ps -ef、top 均可
  • 死鎖:兩個以上的運算單元,雙方都在等待對方停止運行,以獲取系統資源,但是沒有一方提前退出
  • 計算機中的同步與生活中的不太一樣
    • 不是做同樣的操作
    • 而是事件發生的順序是確定的,是有因果關係的

思考點#

Tips#

  • du [-h]:查看當前目錄以及所有子目錄的大小 [human-readable]
  • 對於多進程的輸出,使用 more 會將不同進程的輸出獨立顯示
  • 推薦電影:《她》2013
    • 圖片
    • 一個矽基生命與許多碳基生命的愛情故事,涉及高併發概念
    • 豆瓣百度雲,提取碼:8pic

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。