我們把正在執行的程序稱爲進程,這是一種廣義的定義。更具體一點來說,進程是由正文端 (text) 、用戶數據段 (usr segment) 以及系統數據段 (system segment)共同組成的一個程序執行環境。
進程和程序
程序
程序是一個包含及其代碼指令和數據的可執行文件,這個文件一般儲存在磁盤上(儲存設備)上,所以,程序是一個靜態的實體。比如,我們用 C 語言寫了一個C源程序,這個程序要經過 “預處理-編譯-彙編-鏈接”這一過程才能生成可執行文件,這個可執行文件在 Windows 下一般爲xxx.exe
,該可執行文件纔是我們所說的程序。進程
我們可以認爲上述所說的程序是:你期望完成某項任務的方法和步驟,它只浮現在紙面上,等待去實現。而這個實現過程就是由進程來完成的,進程可以認爲是運行中的程序。它除了包含程序中的所有數據之外,還包含一些額外數據。
當程序被裝入內存中並且獲取到所需資源後就可運行了:在程序計數器 (PC) 和其它一些寄存器的控制下,機器指令被取至 cpu 運行。
下圖是其大概情況:
進程在運行過程中,還需要一些系統資源。其中最重要的就是 cpu 資源了,除此之外還包括但不限於物理內存(以容納進程本身和其有關數據)、打印機、鍵盤等等。
由上可見進程是一個動態的實體,它每時每刻都在發生着變化。那麼如何管理和描述這個動態的進程呢?請看下面詳情。
進程控制塊
在 Linux 中每個進程由一個 task_struct
結構體來描述,該結構體也被稱爲進程控制塊(PCB)。它被定義於 include/linux/sched.h
(Linux源碼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行代碼),有興趣的朋友可以自行研究。
參考資料
- linux 源碼參考
【作者:果凍 http://blog.csdn.net/jelly_9】