「程序」有諸多的定義,在許多的教材資料上,其定義是乙個程式的執行例項,這不無道理,也有的人認為它是程式處理所描述的所有資料結構的集合。這裡不深究其定義,如果換個角度而言,程序就好像我們人類,他們被產生,它們有自己的生命週期,儘管生命週期的長短不一,從幾毫秒至幾秒,甚至幾個月,幾年。與人類的真正區別就在於它們沒有性別之分。
從linux核心觀點上來看,程序是核心所分配的系統資源的乙個實體(如cpu時間片,記憶體等)。需要了解的是,早期的linux核心是不支援多執行緒應用的程序,因為從當初的核心角度來看,多執行緒應用也僅僅是乙個正常的程序。總之早期的應用不太盡如人意,現在linux核心使用輕量級執行緒來支援多執行緒應用,實際上,兩個輕量級執行緒可以共享一些資源,如位址空間,開啟的檔案等等。當其中乙個修改了共享資源,另乙個馬上就能看到它與之間的變化,在訪問共享資源時,它們之間需要進行同步操作。
linux系統上執行著許多的程序,核心如何對它們進行高效的管理呢?這是我們想要弄明白的。在閱讀了一些linux核心關於程序管理方面的源**後,作了一些思考與回顧。要想弄明白linux核心對程序的管理,首先應該了解它內部的一些重要的資料結構,這是最基本的。
程序的資料結構--程序描述符
為了管理程序,核心必須有乙個非常清晰的關於每個程序正在做什麼的巨集偉藍圖,比如,它必須知道程序的優先順序,它是否在cpu上執行或者在等待什麼事件,哪塊位址空間被賦予了哪個程序,它允許訪問哪些檔案等等,所以有關程序的事務核心必須一清二楚。程序描述符(乙個task_struct型別的資料結構)內的每個字段包含了每個程序相關的所有資訊。也就是說,核心對程序的管理主要是通過從程序描述符獲取豐富的資訊進行的。可以想象,程序描述符內肯定也包含了其它的資料結構型別,各種資料型別互相穿插,複雜而有序地進行資料資訊的傳輸。說了這麼多,不知道程序描述符到底長啥樣,廬山真面目如下:
struct task_struct ;
以上我只列出程序描述符中的一小部分**, 給我的第一感覺是,它很龐大,不知道如何下手。想一下子搞清楚每個欄位的含義以及它是怎麼工作是不可能的。不過我們可以從list_head task欄位開始著手。但是在分析之前必須牢記task_struct程序描述符是描述乙個程序所擁有的各種詳細資訊,按我們正常的理解來看,乙個task_struct程序描述符本身就代表了乙個程序。它就是乙個程序的實體,這樣理解我認為也是可以的。
注意以上標註為「紅色」的字段,有很大一部分都是struct list_head結構體,這個結構體的作用是非常巨大的,它的作用好比是針線,可以將許多相關的原材料串起來,對,它就是鍊錶結構。list_head結構如下:
struct list_head ;
這只是乙個雙向鍊錶結點,有乙個next和prev指標,這麼看來,linux核心對程序描述符的管理方式大概就能猜到了。
所以從上圖就可以看到,通過將list_head作為其中乙個字段嵌入到task_struct結構體中,便使得task_struct具有雙向鍊錶的特性,這樣就可以方便地遍歷的尋找所有的程序了。完成這個功能的就是list_head task欄位。但是,task欄位的連線只是邏輯上將所有的程序串起來,此時雙向鍊錶結點的總數即為當前程序的總量。它並未考慮進一步的情況,比如,兄弟關係,父子關係等。下面來看看另外兩個同樣的字段:
struct list_head children; /* list of my children */
struct list_head sibling; /* linkage in my parent's children list */
children欄位的主要作用是將所有的孩子程序連線起來,這時候通過這個欄位就可以尋找到自己的程序孩子結點,sibling欄位主要作用是將兄弟程序連線起來。舉個例子,如程序p0建立了三個子程序分別為p1,p2,p3,然後p3再建立乙個子程序為p4,這們之間的關係如下圖所示:
p0程序的children欄位只記錄它的第乙個孩子p1的位置,通過這個孩子的sibling.next就可以找著p0的逐個孩子p2,p3,孩子兄弟間是通過slibing來訪問的,每個孩子可以通過parent欄位尋找到它們的父親,p0的child.prev可以尋找到它的最後乙個孩子程序,即p3,所以它們都是雙向鍊錶性質。以p3為例再進行分析,p3建立了程序p4,所以p3的children.next指向p4,由於p4是它唯一乙個孩子,p3的chindren.prev還是指向p4,由於p4沒有兄弟程序,所以它們的sibing.prev和sibling.next均指向p3程序。
所以稍微總結下,程序描述符中的task欄位是將所有的程序串塊一塊,相當於將某一型別的資料進行歸類,但是並未細分,但是children和sibling欄位在此基礎上進一步加工,將這一類的資料的內部邏輯關係進行了整合。這就好比一群人被集中起來,然後通過血緣的紐帶關係我們可以很快地尋找到自己所屬的那個羈絆。
程序是如何進行遍歷的?
從上面分析可知,遍歷當然是靠鍊錶,不過這裡主要講述核心遍歷程序時的介面,先來討論鍊錶的遍歷介面,然後再講述程序的遍歷介面,思路基本相同,linux核心對鍊錶的遍歷或操作主要通過巨集來進行:
list_add(n,p): 從p前面插入結點資料n,所以如果要把n插入到煉表頭,設定p為頭結點
list_add_tail(n,p):與list_add(n,p)基本一致,區別在於插入的位置是在p的後面,所以如果要把n插入到鍊錶尾部,設定p為頭結點
list_entry(p,t,m):返回資料結構型別t所在的位址,注意,t內包含list_head型別的字段m,併此這個欄位的位址為p
list_for_each(p,h):掃瞄以h為頭結點的鍊錶元素,每次迭代,p中存放指向lis_head資料結構欄位的位址
list_for_each_entry(p,h,m):同list_for_each有點類似,但是返回的p是包含list_head欄位的所在資料結構的位址
linux核心就是憑藉以上的鍊錶介面來進行操作。對程序而言,程序的頭結點即為init_task描述符,它也是task_struct結構型別,將init_task的task->prev位址作為引數p傳入list_add_tail中,就可以將包含task_struct型別的程序描述符插入到鍊錶的末尾。對程序而言set_links和remove_links巨集用於插入或刪除乙個程序描述符結點。另乙個有用的程序巨集是for_each_process,用於掃瞄整個程序列表。使用如下:
#define for_each_process(p) \
for (p = &init_task; ( p=list_entry( (p)->tasks.next, struct task_struct, tasks) ) != &init_task;)
核心如何尋找需要執行的程序?當核心需要尋找乙個新的程序到cpu上執行時,核心必須只考慮狀態為可執行(task__running)的程序。
早期linux版本將所有可執行的程序放置於同乙個鍊錶執行佇列中。但是維護它的代價實在是太高,並且隨著程序數量的增多效能下降很快。從linux2.6開始實現了不同的執行佇列。它的目標是允許排程器在同乙個時間值內尋找到可執行的程序,並且完全不受程序數量的依賴。它的實現技巧為對每個優先順序k都有對應的可執行佇列,在程序描述符中有乙個list_head run_list,這個域將相同優先順序的可執行程序鏈結起來,而且在多核系統中,每個cpu也將有它自己的執行佇列。這是一種通過增加資料結構的複雜性來提高效能的經典案例:它使得排程器操作更加高效,執行佇列被分散成140個不同的鍊錶。
Linux核心 程序管理
注 對linux系統來說,執行緒只是一種特殊的程序,linux不區分執行緒和程序 程序 處於執行期的程式 包含的資源 一 程序描述符及任務結構 程序存放在任務佇列 task list 的雙向迴圈鍊錶中 鍊錶中每一項型別均為task struct 程序描述符,也可表示為task t 該結構定義在inc...
Linux核心 程序管理
1 程序生命週期 某個程序通過系統呼叫fork,建立乙個用於執行程式的程序。生成此程序的程序稱為父程序,被生成的程序稱為子程序。子程序通過複製父程序的資料得以建立 父程序執行系統呼叫wait,等待子程序結束 子程序通過系統呼叫exec將程式讀取到記憶體並開始執行 當程式執行完畢後,子程序通過系統呼叫...
Linux核心之程序管理
linux核心之程序管理 支援執行緒的計算機系統裡面,程序作為資源分配的基本單位而存在,執行緒作為排程的基本單位而存在.執行緒僅擁有必不可少的一些資源,如 一組暫存器,堆疊資訊等等和其他執行緒共享同乙個程序的所有資源.所以,在同乙個程序的執行緒切換時不需要大量的儲存和恢復工作,同時由於共享同乙個儲存...