1. 程式人生 > 其它 >《Linux核心設計與實現》 讀書筆記(3)--Linux的程序

《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: 讀取可執行檔案,將其載入到記憶體中執行

建立的流程:

  1. 呼叫dup_task_struct()為新程序分配核心棧,task_struct等,其中的內容與父程序相同。
  2. check新程序(程序數目是否超出上限等)
  3. 清理新程序的資訊(比如PID置0等),使之與父程序區別開。
  4. 新程序狀態置為 TASK_UNINTERRUPTIBLE
  5. 更新task_struct的flags成員。
  6. 呼叫alloc_pid()為新程序分配一個有效的PID
  7. 根據clone()的引數標誌,拷貝或共享相應的資訊
  8. 做一些掃尾工作並返回新程序指標

建立程序的fork()函式實際上最終是呼叫clone()函式。

建立執行緒和程序的步驟一樣,只是最終傳給clone()函式的引數不同。

比如,通過一個普通的fork來建立程序,相當於:clone(SIGCHLD, 0)

建立一個和父程序共享地址空間,檔案系統資源,檔案描述符和訊號處理程式的程序,即一個執行緒:clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0)

在核心中建立的核心執行緒與普通的程序之間還有個主要區別在於:核心執行緒沒有獨立的地址空間,它們只能在核心空間執行。

這與之前提到的Linux核心是個單核心有關。

4. 程序的終止

和建立程序一樣,終結一個程序同樣有很多步驟:

子程序上的操作(do_exit)

  1. 設定task_struct中的標識成員設定為PF_EXITING
  2. 呼叫del_timer_sync()刪除核心定時器, 確保沒有定時器在排隊和執行
  3. 呼叫exit_mm()釋放程序佔用的mm_struct
  4. 呼叫sem__exit(),使程序離開等待IPC訊號的佇列
  5. 呼叫exit_files()和exit_fs(),釋放程序佔用的檔案描述符和檔案系統資源
  6. 把task_struct的exit_code設定為程序的返回值
  7. 呼叫exit_notify()向父程序傳送訊號,並把自己的狀態設為EXIT_ZOMBIE
  8. 切換到新程序繼續執行

子程序進入EXIT_ZOMBIE之後,雖然永遠不會被排程,關聯的資源也釋放掉了,但是它本身佔用的記憶體還沒有釋放,
比如建立時分配的核心棧,task_struct結構等。這些由父程序來釋放。

父程序上的操作(release_task)

父程序受到子程序傳送的exit_notify()訊號後,將該子程序的程序描述符和所有程序獨享的資源全部刪除。

從上面的步驟可以看出,必須要確保每個子程序都有父程序,如果父程序在子程序結束之前就已經結束了會怎麼樣呢?

子程序在呼叫exit_notify()時已經考慮到了這點。

如果子程序的父程序已經退出了,那麼子程序在退出時,exit_notify()函式會先呼叫forget_original_parent(),然後再呼叫find_new_reaper()來尋找新的父程序。

find_new_reaper()函式先在當前執行緒組中找一個執行緒作為父親,如果找不到,就讓init做父程序。(init程序是在linux啟動時就一直存在的)