進程控制塊(PCB) —— task_struct

我們把正在執行的程序稱爲進程,這是一種廣義的定義。更具體一點來說,進程是由正文端 (text) 、用戶數據段 (usr segment) 以及系統數據段 (system segment)共同組成的一個程序執行環境。

進程和程序

  • 程序
    程序是一個包含及其代碼指令和數據的可執行文件,這個文件一般儲存在磁盤上(儲存設備)上,所以,程序是一個靜態的實體。比如,我們用 C 語言寫了一個C源程序,這個程序要經過 “預處理-編譯-彙編-鏈接”這一過程才能生成可執行文件,這個可執行文件在 Windows 下一般爲 xxx.exe,該可執行文件纔是我們所說的程序。

  • 進程
    我們可以認爲上述所說的程序是:你期望完成某項任務的方法和步驟,它只浮現在紙面上,等待去實現。而這個實現過程就是由進程來完成的,進程可以認爲是運行中的程序。它除了包含程序中的所有數據之外,還包含一些額外數據。
    當程序被裝入內存中並且獲取到所需資源後就可運行了:在程序計數器 (PC) 和其它一些寄存器的控制下,機器指令被取至 cpu 運行。
    下圖是其大概情況:
    這裏寫圖片描述

進程在運行過程中,還需要一些系統資源。其中最重要的就是 cpu 資源了,除此之外還包括但不限於物理內存(以容納進程本身和其有關數據)、打印機、鍵盤等等。
由上可見進程是一個動態的實體,它每時每刻都在發生着變化。那麼如何管理和描述這個動態的進程呢?請看下面詳情。

進程控制塊

在 Linux 中每個進程由一個 task_struct 結構體來描述,該結構體也被稱爲進程控制塊(PCB)。它被定義於 include/linux/sched.hLinux源碼github地址)。

task_struct 容納了一個進程的所有信息,它是系統對進程進行控制的唯一手段,也是最有效的手段。

每當系統創建一個進程,就會給該進程動態的分配一個 task_struct 結構體對象。一個系統內所允許的最大進程個數一般由機器硬件 (物理內存) 決定。在一臺 IA32 體系結構中,內存爲 512M 的機器上所允許的最大進程數是 32k 。

總之包含進程所有信息的 task_struct 內容是比較龐大複雜的,我們將其部分內容羅列如下:

struct task_struct{
  ...
  // 進程標識符
  ...
  // 上下文信息
  ...
  // 進程狀態
  ...
  // 進程優先級
  ...
  // 進程通信有關信息 
  ...
  // 時間和定時器有關信息
  ...
  // 文件系統信息
  ...
  // 虛擬內存信息
  ...
  // 其它
  ...
};
  • 進程標識符

操作系統中有很多進程,不管對於用戶還是對於內核,如何用一種簡單的方式以區分不同進程呢?這就引入了進程標識符 (PID:process identifier),每個進程都擁有一個唯一的進程標識符,內核以此來區分不同進程,同時,用戶也可以通過此標識符來給具體進程發號施令。Linux 中我們可以通過以下幾種方式獲取 PID:

  • shell中:通過ps aux列出所有進程詳細信息,在其中我們可以看到進程的 PID, 也可以通過 ps aux | grep '進程名' 查看指定進程信息,除此之外還有 top 命令也可以查看進程信息。

查看所有進程信息:
這裏寫圖片描述
查看指定進程信息:
這裏寫圖片描述

  • C程序中相關係統調用函數
#include <unistd.h>
pid_t getpid();     //調用進程的進程id
pid_t getppid();    //調用進程的父進程id
uid_t getuid();     //調用進程的實際用戶的id
uid_t geteuid();    //調用進程的有效用戶id
gid_t getgid();     //調用進程的實際組id
gid_t getegid();    //調用進程的有效組id

這裏寫圖片描述

  • 也可以通過文件 /proc/ 文件來查看進程信息。

這裏寫圖片描述

除此之外的其它 id:
這裏寫圖片描述

  • 上下文信息
    上下文信息一般和處理器密切相關。進程作爲一個程序執行環境的綜合,當處理器調度執行某個程序時,需要將相關指令和數據加載到對應的寄存器和堆棧中,當進程暫停或者等時,必須將其對應的寄存器和堆棧信息暫存起來,以便稍後重新調度該進程時,將其回覆到暫停之前的狀態,那麼這一部分信息就是進程的上下文信息。

  • 進程狀態
    進程在執行時,會根據環境改變其狀態,進程狀態是進程調度的依據。在 Linux 中進程更主要有這些狀態:

/* Used in tsk->state: */
#define TASK_RUNNING            0x0000  //可運行
#define TASK_INTERRUPTIBLE      0x0001  //可中斷的等待
#define TASK_UNINTERRUPTIBLE    0x0002  //不可中斷的等待
#define __TASK_STOPPED          0x0004  //暫停
/* Used in tsk->exit_state: 退出狀態*/
#define EXIT_DEAD           0x0010  //死亡
#define EXIT_ZOMBIE         0x0020  //僵死

#define TASK_STATE_MAX      0x1000  //進程最大個數:8k
  • 可運行狀態:處於該狀態的進程,要麼正在運行,要麼準備運行(在等待cpu資源)。系統通過一個運行隊列 (run_queue) 來管理處於此狀態的進程。
  • 等待狀態:處於該狀態的進程在等待某個事件或某個資源(磁盤、打印機),這些進程位於系統中的等待隊列中(wait_queue),對於等待不同資源設置有不同等待隊列,比如,需要打印機的進程被置於打印機的等待隊列中,需要磁盤的進程被置於磁盤等待隊列中。可中斷的等待可以被信號喚醒,如果被喚醒,該進程就被加入到運行隊列中,等待被調度,不可中斷的等待是由於沒有所需的硬件而等待,需要的磁盤資源暫時被其它進程佔用,這一類進程不可以被信號喚醒,直到它獲取到需要的硬件資源。
  • 暫停狀態:此時進程停止運行等待接收某種處理通常進程接收到 SIGSTOP、SIGTSTP、SIGTTIN 或 SIGTTOU信號後就處於這種狀態。正在調試的進程就處於這種狀態,如下圖所示:
    這裏寫圖片描述
  • 僵死狀態:當子進程退出,而父進程沒有退出,也沒有讀取(wait()) 子進程退出狀態時,此時子進程就進入殭屍狀態 (殭屍進程詳情)。
  • 進程優先級
    調度程序依靠這一部分信心決定進程的執行順序,並結合進程的狀態信息保證系統運轉的公平和高效。

Linux通過以下幾種方式查看進程優先級:

  • top: 動態列出系統的整體運行情況;
  • ps -l:採用詳細格式顯示進程情況。

下面是用top 查看的詳情:
這裏寫圖片描述

如上圖,有兩列和進程優先級有關:

  • PR (priority):進程優先級,越小代表優先級越高;
  • NI (nice):優先級修正參數。

如何設置優先級?
在此之前我們先創建一個小腳本文件,它件循環執行累減操作,以達到佔用更多cpu資源的目的。然後我們再以此腳本文件爲例,來修改其優先級,觀察其達到的效果。

該腳本文件如下:
文件名: count.sh
文件內容:

#!/bin/bash
x=100000000
echo $(date)
while[ $x -gt 0 ]; do x=$((x-1)); done
echo $(date)

有了上面的腳本文件,我們就可以依次通過下圖命令行指令來執行該腳本文件(用 top 查看進程情況):
這裏寫圖片描述
顯然此時該進程優先級爲默認的20,而且該進程佔用了極高的cpu資源。

修改優先級的第一種方法是:在程序開始執行前,通過如下命令修改:

nice +n +5 -p fileName//在原來優先級基礎上+5

詳情如下圖所示:
這裏寫圖片描述
可以看到當降低 (PR越大,優先級越小) 進程優先級後,其佔用 cpu 資源降低了很多。

修改優先的第二中方法:在程序執行時修改,其命令如下:

renice +5 -p pid//需要知道進程id

詳情如下圖:
這裏寫圖片描述

  • 進程通信有關信息
    爲了可以使進程相互協作完成任務,不同進程間必須進行通信,即交流數據。Linux 支持多種不同的通信機制。支持電信的 Unix 通信機制(IPC) ,比如管道、信號,也支持 System V 通信機制:共享內存、信號量和消息隊列。
  • 時間和定時器
    一個進程從創建到終止叫做該進程的生存期。一個進程在其生存期內使用 cpu 的時間系統都要進程記錄,以便進程統計、“計費”等操作。“時間”對於操作系統是及其重要的,比如在進程調度的時間片輪轉中,操作系統要根據當前進程在 cpu 上執行時間的長短以確定是否要將 cpu 分配給其它進程,這個關乎到系統對進程調度是否公平和高效。
    這裏寫圖片描述

  • 文件系統信息
    進程可以打開和關閉文件,文件屬於系統資源,Linux 內核需要記錄進程對文件的使用情況。task_struct 進程控制塊中有兩個結構體用於記錄文件相關信息。其中 fs_struct 中描述了兩個 VFS節點,分別是 root 和 pwd,一個指向根目錄,一個指向當前目錄。還有 files_struct 記錄進程打開的文件描述符。
    這裏寫圖片描述
    在文件系統中,每個 VFS 索引節點唯一描述一個文件或目錄,同時該節點也是向更低
    層的文件系統提供的統一的接口。

  • 虛擬內存信息
    除了內核線程(kernel thread),每個進程都擁有自己的地址空間(也叫虛擬空間),
    用 mm_struct 來描述。另外 Linux2.4 還引入了另外一個域 active_mm,這是爲內核線程而引
    入。因爲內核線程沒有自己的地址空間,爲了讓內核線程與普通進程具有統一的上下文切換
    方式,當內核線程進行上下文切換時,讓切換進來的線程的 active_mm 指向剛被調度出去
    的進程的 active_mm(如果進程的 mm 域不爲空,則其 active_mm 域與 mm 域相同)。
    這裏寫圖片描述

我們對進程的 task_struct 結構進行了歸類討論,還有一些域沒有涉及到。task_struct 結構是進程實體的核心,Linux 內核通過該結構來控制進程:首先通過其中的調度信息決定該進程是否運行;當該進程運行時,根據其中保存的處理機狀態信息來恢復進程運行現場,然後根據虛擬內存信息,找到程序的正文和數據;通過其中的通信信息和其他進程實現同步、通信等合作。幾乎所有的操作都要依賴該結構,所以,task_struct 結構是一個進程存在的唯一標誌。

task_struct 在內存中的存放

  • 進程內核棧
    每個進程都有自己的內核棧。當進程從用戶態進入內核態時,CPU 就自動地設置
    該進程的內核棧,也就是說,CPU 從任務狀態段中裝入內核棧指針 esp。
    這裏寫圖片描述

  • 當前進程
    當一個進程在某個 CPU 上正在執行時,內核如何獲得指向它的 task_struct 的指針?current 指針指向當前 cpu 上運行的進程。
    這裏寫圖片描述
    除此之外還有當前進程狀態相關信息:
    這裏寫圖片描述

進程的組織方式

  • 哈希表
    哈希表是一種查找效率極高的數據結構,Linux 中以哈希表來組織進程。
    這裏寫圖片描述

下面代碼判斷進程是否閒置(這個我也不太清楚)。
這裏寫圖片描述

上述我們討論進程控制塊的部分內容,有關該結構的內容實在是太多了(有大約1600行代碼),有興趣的朋友可以自行研究。


參考資料

【作者:果凍 http://blog.csdn.net/jelly_9

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章