進程是任何多通道程序設計的操做系統中的基本概念,進程一般被定義爲程序執行時的一個實例,在 Liunx 的源代碼中,進程一般被稱爲 「任務」。shell
進程描述符的做用是爲了管理進程,內核必須對每一個進程所作的事情進行清除的描述,例如,內核必須知道進程的優先級、進程狀態、爲它分配什麼樣的地址空間、容許訪問那些文件等等;數組
進程描述符是 task_struct 類型結構,它的域包含了與一個進程相關的全部信息。數據結構
進程描述符中的狀態域描述了進程當前所處的狀態:app
可運行狀態(TASK_RUNNING):進程要麼在 CPU 上執行,要麼準備執行;spa
可中斷的等待狀態(TASK_INTERRUPTIBLE):進程被掛起(睡眠),直到一些條件變爲 真(產生一個硬件中斷,釋放進程正等待的系統資源,或傳遞一個信號,都能喚醒進程,回到 TASK_RUNNING;操作系統
不可終端的等待狀態(TASK_UNINTERRPTIBLE):與前一個狀態相似, 但有一個例外,把信號傳遞到睡眠的進程不能改變它的狀態,用在進程必須等待,不能被中斷,知道給定的事件發生;設計
暫停狀態(TASK_STOPPED):進程的執行被暫停,收到 SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU 信號,進人暫停狀態,當一個進程被另外一個進程監控時,任何信號均可以把這個進程置於 TASK_STOPPED 狀態;指針
僵死狀態(TASK_ZOMBIE):進程的執行被終止,可是父進程尚未發佈 wait() 類系統調用以返回有關死進程的信息,發佈 wait() 類系統調用前,內核不能丟棄包含在死進程描述符中的數據,由於父進程可能還須要它;隊列
Linux 進程能共享內核大部分數據結構(經過輕量級進程);進程
Linux 能處理多達 NR_TASKS 個進程,內核在本身的地址空間保存了一個全局靜態數組 task,大小爲 NR_TASKS,數組中的元素就是進程描述符指針,空指針表示數組項中沒有進程描述符。
進程描述符的存放:
task 數組僅僅包含進程描述符的指針,而不是描述符自己,由於進程是動態實體,所以,進程描述符被存放在動態內存中,而不是存放永久的分配給內核的內存區。
內核態的進程訪問包含在內核數據段中的棧,內存區中存放進程描述符和內核態的進程棧:
esp 寄存器是 CPU 棧指針,用來存放棧頂的地址,從用戶態切換到內核態之後,進程的內核態堆棧老是空的,所以,esp 寄存器直接指向這個內存區的頂端。
C 語言中用聯合結構表示這個混合結構:
union task_union {
struct task_struct task;
unsigned long stack[2048];
};
current 宏
進程描述符與內核堆棧之間的配對:內核很容易從 esp 寄存器的值得到當前在 CPU 上正在運行的進程描述符指針。
假設內存區是 8KB(2^13)長,內核必須讓 esp 至少有 13 位有效位,以得到進程描述符的基礎址,由 current 宏完成:
movl $0xffffe000, %ecx
andl %esp, %ecx
movl %ecx, p
執行三條指令後,局部變量 p 包含了在 CPU 上運行的進程描述符指針;
進程鏈表
爲了對給定類型的進程進行有效的搜索,內核創建了幾個進程鏈表,每一個進程鏈表由指向進程描述符的指針組成;
一個雙向循環鏈表把全部現有的進程聯繫起來,稱爲進程鏈表(process list),每一個進程的 prev_task 和 next_task 域用來實現鏈表,鏈表的頭是 init_task 描述符,由 task 數組的第一個元素指向,是進程的祖先,叫作進程 0 或 swapper,init_task 的 rev_task 域指向鏈表中最後插入的進程描述符。
SET_LINKS、REMOVE_LINKS 宏用來分別在進程鏈表中插入和刪除一個進程描述符,for_each_task 宏掃描整個進程鏈表:
#define for_each_task(p) \
for (p = &init_task; (p = p->next_task) != &init_task ;)
TASK_RUNNING 狀態的進程有獨立的雙向循環鏈表:運行隊列(runqueue)。
pidhash 表及鏈接表:從進程的 PID 導出對應的進程描述符指針。
task 空閒表項的鏈表:進程建立或撤銷都要更新。
進程 0 和進程 1 由內核建立,進程 1(init)是全部進程的祖先,一個進程 P 的描述符包含下列域:
p_opptr —— 祖先(original parent):p_opptr 指向建立了進程 P 的進程描述符,若是父進程不存在,則指向 1,當一個 shell 用戶啓動一個後臺進程並從 shell 退出時,後臺進程變成 init 的子進程;
p_pptr —— 父進程(parent):p_pptr 指向 P 的當前父進程,值一般與 p_opptr 一致,但偶爾不一樣,當另外一個進程發佈 parace() 系統調用請求監控 P 時;
p_cptr —— 子進程(child):p_cptr 指向 P 年齡最小的子進程的描述符,即指向剛剛由 P 建立的進程的進程描述符
p_ysptr —— 弟進程(younger sibling):p_ysptr 指向在 P 以後由 P 的父進程立刻建立的進程的進程描述符
p_osptr —— 兄進程(younger sibling):p_ysptr 指向在 P 以前由 P 的父進程立刻建立的進程的進程描述符
把 TASK_INTERRUPTIBLE 或 TASK_UINTERRUPTIBLE 狀態的進程分紅不少類,每一類對應一個特定的事件,進程狀態提供的信息知足不了快遞檢索進程,引入了另外的進程鏈表 —— 等待隊列(wait queue)
等待隊列對中斷處理、進程同步及定時用處很大,進程必須常常等待某些事件的發生,等待隊列實如今事件上的條件等待:但願等待特定事件的進程把本身放進合適的等待隊列,並放棄控制權,所以等待隊列表示一組睡眠的進程,當某一條件變爲真時,由內核喚醒它們;
等待隊列中每一個元素都是 wait_queue 類型:
struct wait_queue {
struct task_struct * task;
struct wait_queue * next;
}
每個等待隊列由一個等待隊列指針來標識,等待隊列指針或者指向鏈表中第一個元素地址,wait_queue 數據結構的 next 域指向鏈表中的下一個元素;
進程與一組使用限制(usage limit)相關聯,使用限制指定了進程能使用的系統資源數量:
RLIMIT_CPU:進程使用 CPU 的最長時間,若是進程超過了這個限制,內核就向它發一個 SIGXCPU 信號,若是進程還不終止,再發一個 SIGKILL 信號;
RLIMIT_FSIZE:容許文件大小的最大值,若是進程試圖把文件的大小託充到大於這個值,內核就給這個進程發 SIGXFSZ 信號;
RLIMIT_DATA、RLIMIT_STACK、RLIMIT_CORE、RLIMIT_RSS、RLIMIT_NPROC、RLIMIT_NOFILE、RLIMIT_MEMLOCK、RLIMIT_MEMLOCK、RLIMIT_AS 等等限制;
使用限制被存放在進程描述符的 rlim 域:
struct limit {
long rlim_cur;
long rlim_max;
}
rlim_cur 域是資源當前使用限制,例如:current->rlim[RLIMIT_CPU].rlim_cur 表示在 CPU 上正在容許進程所花時間的當前限制;
rlim_max 域是資源限制所容許的最大值,利用 getrlimit() 和 setrlimit() 系統調用,能夠把 rlim_cur 增長到 rlim_max;
單獨博客