20135220谈愈敏Linux Book_3
第3章 進程管理
進程是Unix操作系統抽象概念中最基本的一種,進程管理是操作系統的心臟所在。
3.1 進程
- 進程:處于執行期的程序以及相關的資源的總稱。
- 線程:在進程中活動的對象,擁有獨立的程序計數器、進程棧和一組進程寄存器。
內核調度的對象是線程而不是進程。
存在包含多個線程的多線程程序 存在兩個或多個不同進程執行同一程序,并且可以共享資源現代操作系統中,進程提供兩種虛擬機制:
- 虛擬存儲器:給進程假象好像自己在獨享處理器,實際是很多進程在分享一個處理器。
虛擬內存:讓進程覺得自己擁有整個系統的內存資源
注意:線程之間可以共享虛擬內存,但每個都擁有自己的虛擬處理器。
創建進程:
fork()系統調用:通過復制一個現有進程來創建一個全新的進程。(進程在創建它的時候開始存活)
exec():創建新的地址空間,把新的程序載入其中。
exit():退出執行,終結程序并將其占用的資源釋放掉。
wait4():父進程調用它查詢子進程是否終結。
3.2 進程描述符及任務結構
進程列表存放在任務隊列(雙向循環鏈表)中,每一項都是進程描述符(類型為task_struct),包含了一個進程的所有信息:
- 它打開的文件
- 進程的地址空間
- 掛起的信號
- 進程的狀態
- 其他更多信息
分配進程描述符
以前:各個進程的task_struct存放在它們內核棧的尾端,只通過棧指針就能計算出它的位置,避免使用額外的寄存器。現在:用slab分配器動態生成task_struct,在內核棧的尾端創建一個新的結構struct thread_info,thread_info有一個指向進程描述符的指針(task域中存放指向實際task_struct的指針)進程描述符的存放
進程唯一的標識值PID(類型pid_t,實際是int類型),PID最大值默認設置為32768(short int型最大值,最大值可更改),PID最大值也表示系統中允許同時存在的進程的最大數目。
內核訪問任務的速度關鍵在于找到當前進程的進程描述符task_struct,通過current宏實現:
current宏針對不同硬件體系結構: 寄存器富余的直接拿出一個專門的寄存器存放指向當前進程task_struct的指針。 寄存器不富余的,在內核棧尾端創建thread_info結構,計算偏移間接找到task_struct。x86中,current把棧指針的后13位屏蔽掉來計算thread_info結構的偏移,通過current_thread_info()函數實現: movl $-8192,%eax //棧大小8KB,4KB->4096 andl $esp,%eax //計算偏移 current_thread_info() ->task //current從thread_info的task域中提取task_struct地址進程狀態
進程描述符stat域描述了進程當前狀態,進程狀態只有五種,必為其一:
TASK_RUNNING(運行):進程正在執行/在運行隊列中等待執行。 TASK_INTERRUPTIBLE(可中斷):進程正在睡眠(被阻塞),等待條件達成就變為執行狀態,也可被信號提前喚醒。 TASK_UNINTERRUPTIBLE(不可中斷):對信號不作響應,其余與可中斷狀態相同。 __TASK_TRACED:被其他進程跟蹤的進程。 __TASK_STOPPED:進程停止執行設置當前進程狀態
set_task_state(task,state); //將task狀態設置為state 必要時會設置內存屏障來強制其他處理器作重新排序,否則等價于:task->state = state進程上下文
可執行程序代碼是進程的重要組成部分,從可執行文件載入到進程的地址空間執行。
陷入內核:內核代表進程執行,從用戶空間到內核,此時內核處于進程上下文,current在此上下文中是有效的,內核退出->恢復上下文。
系統調用和異常處理程序是對內核明確定義的接口
進程家族樹
進程之間存在明顯的繼承關系,所有進程都是init進程(PID為1)的后代,系統啟動的最后階段就是啟動init進程。
每個進程必有一個父進程,也有0或多個子進程,進程間的關系存放在進程描述符中(task_struct):
parent指針:指向父進程tast_struct children:子進程鏈表init進程的進程描述符是作為init_task靜態分配的。
struct tsak_struct *task; for (task = current;task != &init_task;task = task->parent) //一直找尋當前進程的父進程,直到找到init進程實際上從系統的任意一個進程出發都能找到任意指定的其它進程。
獲取任務隊列的下一個進程:list_entry(task->tasks.next,struct task_struct,tasks) 獲取任務隊列的前一個進程:list_entry(task->tasks.prev,struct task_struct,tasks) //通過next_task(task)宏和prev_task(task)宏實現for_each_process(task)宏可依次訪問整個任務隊列,每次訪問都指向鏈表中的下一個元素,但遍歷所有進程代價大。3.3 進程創建
一般產生進程的機制:
- 在新的地址空間里創建進程
- 讀入可執行文件
- 開始執行
UNIX中:
fork():通過拷貝當前進程來創建一個新的子進程。子父進程區別:PID(進程唯一)、PPID(父進程進程號)、某些資源統計量。 exec():讀取可執行文件并將其載入新的地址空間開始運行。寫時拷貝
傳統的fork()系統調用直接把所有資源復制給新進程,這樣效率低。
Linux使用寫時拷貝頁:推遲甚至免除拷貝數據,父進程和子進程共享一個拷貝(只讀),在需要寫入的時候,數據才會被復制。
fork()的實際開銷:
復制父進程的頁表 給子進程創建唯一的進程描述符fork()
clone()系統調用通過一系列參數標志來指明父子進程需要共享的資源
創建進程:大部分靠do_fork()
fork()、vfork()、_clone()庫函數根據各自需要的參數標志去調用clone()->do_fork()完成創建的大部分工作->copy_process()函數返回指向子進程的指針->回到do_fork(),子進程被喚醒先執行。vfork()
除了不拷貝父進程的頁表項,與fork()功能相同。子進程作為父進程的一個單獨的線程在它的地址空間里運行,父進程堵塞直到子進程退出或執行exec().子進程不能向地址空間寫入,理想情況下,最好不用vfork()。3.4 線程在Linux中的實現
線程機制:現代編程技術中常用的一種抽象概念,提供在同一程序內共享內存地址空間運行的一組線程,還可共享打開的問價和其他資源。支持并發程序設計技術。
從內核角度來說,并沒有線程的概念,都是進程,只是線程可以和其他進程共享資源,每個線程都有自己的task_struct。
創建線程
線程創建和進程創建類似,只是在調用clone()時需要傳遞一些參數標志來指明需要共享的資源:
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND,0);父子共享地址空間、文件系統資源、文件描述符和信號處理程序。
普通fork()實現:clone(SIGCHLD,0); vfork()實現:clone(CLONE_VFORK | CLONE_VM | SIGCHLD,0);傳遞的參數標志決定了新創建進程的行為方式和父子進程之間共享資源的種類。書上有具體參數標志的含義。
內核線程
內核線程:獨立運行在內核空間的標準進程,沒有獨立的地址空間,只在內核空間運行,可以被調度或搶占。
ps -ef //看到內核線程內核線程只能由內核線程創建,都是從kthreadd內核線程中衍生出來的新內核線程。
創建新內核線程:kthread_create() 新進程處于不可運行狀態 明確喚醒進程,否則不會主動運行:wake_up_process() 讓線程運行起來:kthread_run() 線程自己調用退出:do_exit() 其他調用使線程退出:kthread_stop() 傳遞的參數為kthread_create()函數返回的task_struct結構的地址3.5 進程終結
進程終結時:內核釋放它占有的資源并告知其父進程。
1、清理工作
進程的析構:進程調用exit():
- 顯示調用
- 隱式的從某個程序的主函數返回
- 當進程接受到它既不能處理也不能忽略的信號或異常時,可能被動的終結。
進程終結:大部分靠do_exit()
do_exit()永不返回釋放掉與進程相關聯的資源,進程不可運行并處于EXIT_ZOMBIE退出狀態 占用的所有內存:內核棧、thread_info結構和tast_struct結構。 此時進程存在唯一目的就是向父進程提供信息。2、刪除進程描述符
do_exit()后,系統還保留了它的進程描述符,唯一目的就是向父進程提供信息,當父進程獲得已終結的子進程的信息后或者通知內核那是無關信息后,子進程的進程描述符才被釋放。
wait()這一族函數都是通過唯一的系統調用wait4()實現的,標準動作是將調用它的進程掛起,直到其中一個子進程退出。 會返回該子進程PID,調用該函數時提供的指針會包含子函數退出時的退出代碼。釋放進程描述符:release_task()
釋放進程內核棧、thread_info結構和tast_struct結構孤兒進程造成的進退維谷
如果父進程在子進程之前退出,必須給子進程找到一個新的父親,否則這些孤兒進程在退出時將永遠處于僵死狀態,耗費內存。
解決:尋父:
do_exit() -> exit_notify() -> forgrt_original_parent() -> find_new_reaper() 如果不行就讓init做它們的父進程遍歷子進程為它們設置新的父進程:
子進程鏈表子進程被跟蹤時,父進程設定為調試進程,此時父進程退出了,要為它和它的兄弟新找一個父進程 ptrace子進程鏈表:在其中搜索相關兄弟進程init進程會例行調用wait()來檢查其子進程,清除所有與其相關的僵死進程。
轉載于:https://www.cnblogs.com/tymjava/p/5339246.html
總結
以上是生活随笔為你收集整理的20135220谈愈敏Linux Book_3的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 阴阳师预约全是r原因 《阴阳师》手游官网
- 下一篇: oc常用字符串方法,数组方法,字典方法整