2.進程管理
進程是Unix操作系統(tǒng)最基本的抽象之一。
定義: 進程就是處于執(zhí)行期的程序胰耗,以及它所包含的資源的總稱
包含:
可執(zhí)行程序代碼 (Unix稱為代碼段 Text section)
存放全局變量的數(shù)據(jù)段 Data section
打開文件侥蒙、掛起的信號
地址空間一個或多個執(zhí)行線程 threads of execution
另一個名字:任務(wù)task峰弹,內(nèi)核通常把進程也叫做任務(wù)
2.1 進程描述符及任務(wù)隊列
+-------------------------+
+--> | struct task_struct |
+-----+-------------------+-----+
+-> | struct task_struct | |
+---+---------------------+---+ |
+--> | struct task_struct | | |
+-----+-------------------+-----+ | |
| struct task_struct | | | |
+-------------------------+ | | |
| unsigned long state; | | | |
| int prio; | | | |
| unsigned long policy | | | <---+
| struct task_struct *parent | |
| struct list_head tasks;| | |
| pid_t pid; | |<--+
| ... | |
| | |
| 進程描述符 | <---+
| |
+-------------------------+
| 任務(wù)鏈表 |
| |
+--------------------------------------------+
2.1.1 分配進程描述符
Linux通過slab分配器分配task_struct 結(jié)構(gòu)(預(yù)先分配和重復(fù)使用task_struct赘理,避免動態(tài)分配的資源消耗座韵,所以進程創(chuàng)建迅速)
進程內(nèi)核棧
+------------------------+ 最高的內(nèi)存地址
| |
| |
| |
+------------------------+ 棧指針
| |
| |
+------------------------+
| |
| |
| struct thread_struct |
+----+-------------------+ 最低的內(nèi)存地址 current_thread_info()-
|
| thread info 有一個指向進程描述符的指針
|
|
v-----------------> 進程的task_struct結(jié)構(gòu)
進程描述符及內(nèi)核棧
struct thread_info {
struct task_struct *task;
struct exec_domain *exec_domain;
unsigned long flags;
_u32 cpu
_s32 preempt_count;
mm_segment_t addr_limit;
u8 supervisor_stack[0];
}
2.1.2 進程描述符的存放
PID pid_t
內(nèi)核中,訪問任務(wù)通常需要獲得指向其task_struct指針腹备,內(nèi)核大部分處理進程的代碼都是直接通過task_struct進行的
x86 寄存器并不富余衬潦,只能在內(nèi)核在的尾端創(chuàng)建thread_info結(jié)構(gòu),通過計算偏移間接地查找task_struct
2.1.3 進程狀態(tài)
創(chuàng)建新進程 任務(wù)被終止
+------------------+ +------------------+
| | | |
| fork() | | TASK_ZOMBIE |
| a new task | | |
+-----+------------+ +------------------+
|
| ^
| 調(diào)度程序?qū)⑷蝿?wù)投入運行 |
| schedule()函數(shù)調(diào)用 |
| context_switch() |
| +---------------------+ | 任務(wù)通過do_exit()函數(shù)退出
v | | |
| v |
+-----------+------+ +-+-----+----------+
| TASK_RUNNING | | TASK_RUNNING |
| | | | 正在運行
+------------------+ +---+-------+------+
準備就緒 ^ | |
還未投入運行 | | |
^ | | |
| +-----<-------------+ |
| 任務(wù)被優(yōu)先級更高 |
| 的任務(wù)搶占 |
| |
| |為了等待特定事件
| |任務(wù)在等待隊列上睡眠
| |
| +---------------------+ |
| |TASK_INTERRUPTIBLE | |
+--------+or | |
|TASK_UNINTERRUPTIBLE | <---+
等待的事件發(fā)生后 | |
任務(wù)被喚醒 +---------------------+
重新進入運行隊列 等待
進程上下文
當一個程序執(zhí)行了系統(tǒng)調(diào)用或者觸發(fā)了某個異常植酥,它就陷入了內(nèi)核空間
此時,我們稱內(nèi)核"代表進程執(zhí)行"弦牡,并處于進程上下文中友驮。
除非在此間隙有更高優(yōu)先級的進程需要執(zhí)行并由調(diào)度器做出了調(diào)整,否則在內(nèi)核退出時候驾锰,程序恢復(fù)在用戶空間繼續(xù)執(zhí)行
進程只有通過系統(tǒng)接口(系統(tǒng)調(diào)用和異常) 才能陷入內(nèi)核
+--------+ +--------+
| | | |
+----------+parent | <----+parent |
| |children+----> |children|
v +--------+ +--------+
+--------+ +--------+ +--------+
| | | | | |
| init | <----+parent | <----+parent |
| | |children+----> |children|
+--------+ +--------+ +--------+
2.2進程創(chuàng)建
fork() 復(fù)制當前進程創(chuàng)建子進程
exec() 讀取可執(zhí)行文件卸留,并將其載入地址空間開始運行
linux 的fork,并不會立即復(fù)制整個進程地址空間椭豫,而是讓父進程和子進程共享一個拷貝
在需要寫入的時候耻瑟,數(shù)據(jù)才會被復(fù)制
而 fork 之后立即調(diào)用exec() 旨指,他們就無需復(fù)制,可以避免大量復(fù)制父進程數(shù)據(jù)
普通fork()
clone(SIGCHLD,0);
vfork()
clone(CLONE_VFORK | CLONE_VM | CLONE_SIGHAND, 0);
2.3 線程在Linux 中的實現(xiàn)
線程提供了在同一程序內(nèi)共享內(nèi)存地址空間運行的一組線程
線程可以共享打開的文件和其他資源喳整,線程機制支持并發(fā)程序設(shè)計技術(shù)谆构,在多處理器上,它能保證真正的并行處理
linux實現(xiàn)比較獨特框都。內(nèi)核的角度來說搬素,他們有線程的該你那,把所有的線程都當做進程來實現(xiàn)魏保。
線程僅僅被視為一個使用某些共享資源的進程熬尺。
每個線程都擁有唯一隸屬于自己的task_struct,在內(nèi)核中谓罗,它看起來像是一個普通的進程
該進程和其他一些進程共享某些資源粱哼,比如地址空間
線程的創(chuàng)建和普通進程的創(chuàng)建類似,只不過在調(diào)用clone()時候需要傳遞一些參數(shù)標志來指明需要共享的資源
clone(CLONE_VM | COLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);
共享地址檩咱,共享文件資源揭措,共享文件描述符,共享信號
參數(shù)標記 | 含義 |
---|---|
CLONE_CLEARTID | 清除TID |
CLONE_DETACHED | 父進程不需要子進程退出時發(fā)送SIGCHLD |
CLONE_FILES | 父進程共享打開的文件 |
CLONE_FS | 父子進程共享文件系統(tǒng)信息 |
CLONE_IDLETASK | 將PID設(shè)置為0(只提供idle進程使用) |
CLONE_NEWNS | 為子進程創(chuàng)建新的命名空間 |
CLONE_PARENT | 指定子進程與父進程擁有同一個父進程 |
CLONE_PTRACE | 繼續(xù)調(diào)試子進程 |
CLONE_SETTID | 將TID回寫到用戶空間 |
CLONE_SETTLS | 為子進程創(chuàng)建新的TLS |
CLONE_SIGHAND | 父子進程共享信號處理函數(shù) |
CLONE_SYSVSEM | 父子進程共享System V SEM_UNDO 語義 |
CLONE_THREAD | 父子進程放入相同的線程組 |
CLONE_VFORK | 調(diào)用vfork(),所以父進程準備睡眠等待子進程將其喚醒 |
CLONE_VM | 父子進程共享地址空間 |
2.3.1 內(nèi)核線程
內(nèi)核需要在后臺執(zhí)行一些操作税手,這種任務(wù)可以通過內(nèi)核線程 kernel thread完成
獨立運行在內(nèi)核空間的標準進程
內(nèi)核線程和普通進的區(qū)別在于內(nèi)核線程沒有獨立的地址空間 實際上他的 mm 指針被設(shè)置NULL
2.4 進程終結(jié)
所有進程的終止都是由do_exit()函數(shù)來處理的蜂筹,這個函數(shù)從內(nèi)核數(shù)據(jù)結(jié)構(gòu)中刪除對終止進程的大部分引用
- 把進程task_struct的flag字段設(shè)置為PF_EXITING標志,以表示進程正在被刪除芦倒。
- 如果BSD的進程記賬功能是開啟的艺挪,要調(diào)用accp_process() 來輸出記賬信息
- 調(diào)用_exit_mm()函數(shù)放棄進程占用的mm_struct,如果沒有別的進程使用它們(如果沒有別的進程使用它們兵扬,也就是說麻裳,它們沒有被共享),就徹底釋放它們
- 如果需要器钟,通過函數(shù)del_timer_sync()從動態(tài)定時器隊列中刪除進程描述符津坑。
- 分別調(diào)用exit_mm()、exit_sem()傲霸、__exit_files()疆瑰、__exit_fs()、exit_namespace()和exit_thread()函數(shù)從進程描述符中分離出與分頁昙啄、信號量穆役、文件系統(tǒng)、打開文件描述符梳凛、命名空間以及I/O權(quán)限位圖相關(guān)的數(shù)據(jù)結(jié)構(gòu)耿币。如果沒有其它進程共享這些數(shù)據(jù)結(jié)構(gòu),那么這些函數(shù)還刪除所有這些數(shù)據(jù)結(jié)構(gòu)中韧拒。
- 如果實現(xiàn)了被殺死進程的執(zhí)行域和可執(zhí)行格式的內(nèi)核函數(shù)包含在內(nèi)核模塊中淹接,則函數(shù)遞減它們的使用計數(shù)器十性。
- 把進程描述符的exit_code字段設(shè)置成進程的終止代號,這個值要么是_exit()或exit_group()系統(tǒng)調(diào)用參數(shù)塑悼,要么是由內(nèi)核提供的一個錯誤代碼劲适。
- 調(diào)用exit_notify()函數(shù),向父進程發(fā)送信號,將子進程的父進程重新設(shè)置為線程組中的其他線程或init進程, 狀態(tài) TASK_ZOMBIE
- 調(diào)用 schedule()切換到其他進程
具體exit_notify
a. 更新父進程和子進程的親屬關(guān)系拢肆。如果同一線程組中有正在運行的進程减响,就讓終止進程所創(chuàng)建的所有子進程都變成同一線程組中另外一個進程的子進程,否則讓它們成為init的子進程
b. 檢查被終止進程其進程描述符的exit_signal字段是否不等于-1郭怪,并檢查進程是否是其所屬進程組的最后一個成員支示。在這種情況下,函數(shù)通過給正被終止進程的父進程發(fā)送一個信號鄙才,以通知父進程子進程死亡颂鸿。
c. 否則,也就是exit_signal字段等于-1攒庵,或者線程組中還有其它進程嘴纺,那么只要進程正在被跟蹤,就向父進程發(fā)送一個SIGCHLD信號浓冒。
d. 如果進程描述符的exit_signal字段等于-1栽渴,而且進程沒有被跟蹤,就把進程描述符的exit_state字段置為EXIT_DEAD稳懒,然后調(diào)用release_task()回收進程的其它數(shù)據(jù)結(jié)構(gòu)占用的內(nèi)存闲擦,并遞減進程描述符的使用計數(shù)器,以使進程描述符本身正好不會被釋放场梆。
e. 否則墅冷,如果進程描述符的exit_signal字段不等于-1,或進程正在被跟蹤或油,就把exit_state字段置為EXIT_ZOMBIE寞忿。
f. 把進程描述符的flags字段設(shè)置為PF_DEAD標志。
調(diào)用schedule()函數(shù)選擇一個新進程運行顶岸。調(diào)度程序忽略處于EXIT_ZOMBIE狀態(tài)的進程腔彰,所以這種進程正好在schedule()中的宏switch_to被調(diào)用之后停止執(zhí)行。
2.4.1 刪除進程描述符
調(diào)用 do_exit()之后辖佣,盡管線程已經(jīng)僵尸不能運行萍桌,但是系統(tǒng)還保留他的進程描述符
父進程獲得已終結(jié)的子進程的信息后,子進程task_struct結(jié)構(gòu)才能釋放
wait() 這一組函數(shù)都是通過唯一的一個系統(tǒng)調(diào)用wait4()實現(xiàn)的凌简,他的標準動作是掛起調(diào)用它的進程,直到其中的一個子進程退出恃逻,此時函數(shù)會返回該子進程PID雏搂。此外藕施,調(diào)用該函數(shù)時提供的指針會包含子函數(shù)退出是的退出代碼
release_task()
- free_uid() 減少該進程擁有者的進程使用計數(shù)。
linux用一個單用戶告訴緩存統(tǒng)計和記錄每個用戶占用的進程數(shù)目凸郑、文件數(shù)目裳食。如果這些數(shù)目都為0,表明這個用戶沒有使用任何進程和文件芙沥,那么這塊緩存可以銷毀
- unhash_process() 從pidhash上刪除該進程诲祸,同時也要從task_list中刪除該進程
- 如果這個進程正在被ptrace追蹤,將跟蹤進程的父進程重設(shè)為其最初的父進程并將它從ptrace list上刪除
- put_task_struct()釋放進程內(nèi)核棧和thread_info結(jié)構(gòu)所占用的頁而昨,并釋放task_struct所占用的slab高速緩存
2.4.2 孤兒進程
如果父進程在子進程之前推出救氯,必須有機制來保證子進程能找到一個新的父親,否則這些孤兒的進程就會在推出時候永遠處于僵尸狀態(tài)歌憨,白白消耗內(nèi)存着憨。