《Linux核心設計與實現》 讀書筆記(3)--Linux的程序
《Linux核心設計與實現》 讀書筆記(3)--Linux的程序
程序是所有作業系統的核心概念,同樣在linux上也不例外。
主要內容:
- 程序和執行緒
- 程序的生命週期
- 程序的建立
- 程序的終止
1. 程序和執行緒
程序和執行緒是程式執行時狀態,是動態變化的,程序和執行緒的管理操作(比如,建立,銷燬等)都是有核心來實現的。
Linux中的程序於Windows相比是很輕量級的,而且不嚴格區分程序和執行緒,執行緒不過是一種特殊的程序。
所以下面只討論程序,只有當執行緒與程序存在不一樣的地方時才提一下執行緒。
程序提供2種虛擬機制:虛擬處理器和虛擬記憶體
每個程序有獨立的虛擬處理器和虛擬記憶體,
每個執行緒有獨立的虛擬處理器,同一個程序內的執行緒有可能會共享虛擬記憶體。
核心中程序的資訊主要儲存在task_struct中(include/linux/sched.h)
程序標識PID和執行緒標識TID對於同一個程序或執行緒來說都是相等的。
Linux中可以用ps命令檢視所有程序的資訊:
ps -eo pid,tid,ppid,comm
2. 程序的生命週期
程序的各個狀態之間的轉化構成了程序的整個生命週期。
3. 程序的建立
Linux中建立程序與其他系統有個主要區別,Linux中建立程序分2步:fork()和exec()。
fork: 通過拷貝當前程序建立一個子程序
exec: 讀取可執行檔案,將其載入到記憶體中執行
建立的流程:
- 呼叫dup_task_struct()為新程序分配核心棧,task_struct等,其中的內容與父程序相同。
- check新程序(程序數目是否超出上限等)
- 清理新程序的資訊(比如PID置0等),使之與父程序區別開。
- 新程序狀態置為 TASK_UNINTERRUPTIBLE
- 更新task_struct的flags成員。
- 呼叫alloc_pid()為新程序分配一個有效的PID
- 根據clone()的引數標誌,拷貝或共享相應的資訊
- 做一些掃尾工作並返回新程序指標
建立程序的fork()函式實際上最終是呼叫clone()函式。
建立執行緒和程序的步驟一樣,只是最終傳給clone()函式的引數不同。
比如,通過一個普通的fork來建立程序,相當於:clone(SIGCHLD, 0)
建立一個和父程序共享地址空間,檔案系統資源,檔案描述符和訊號處理程式的程序,即一個執行緒:clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0)
在核心中建立的核心執行緒與普通的程序之間還有個主要區別在於:核心執行緒沒有獨立的地址空間,它們只能在核心空間執行。
這與之前提到的Linux核心是個單核心有關。
4. 程序的終止
和建立程序一樣,終結一個程序同樣有很多步驟:
子程序上的操作(do_exit)
- 設定task_struct中的標識成員設定為PF_EXITING
- 呼叫del_timer_sync()刪除核心定時器, 確保沒有定時器在排隊和執行
- 呼叫exit_mm()釋放程序佔用的mm_struct
- 呼叫sem__exit(),使程序離開等待IPC訊號的佇列
- 呼叫exit_files()和exit_fs(),釋放程序佔用的檔案描述符和檔案系統資源
- 把task_struct的exit_code設定為程序的返回值
- 呼叫exit_notify()向父程序傳送訊號,並把自己的狀態設為EXIT_ZOMBIE
- 切換到新程序繼續執行
子程序進入EXIT_ZOMBIE之後,雖然永遠不會被排程,關聯的資源也釋放掉了,但是它本身佔用的記憶體還沒有釋放,
比如建立時分配的核心棧,task_struct結構等。這些由父程序來釋放。
父程序上的操作(release_task)
父程序受到子程序傳送的exit_notify()訊號後,將該子程序的程序描述符和所有程序獨享的資源全部刪除。
從上面的步驟可以看出,必須要確保每個子程序都有父程序,如果父程序在子程序結束之前就已經結束了會怎麼樣呢?
子程序在呼叫exit_notify()時已經考慮到了這點。
如果子程序的父程序已經退出了,那麼子程序在退出時,exit_notify()函式會先呼叫forget_original_parent(),然後再呼叫find_new_reaper()來尋找新的父程序。
find_new_reaper()函式先在當前執行緒組中找一個執行緒作為父親,如果找不到,就讓init做父程序。(init程序是在linux啟動時就一直存在的)