linux核心如何管理進程

來源:互聯網
上載者:User

        “進程”有諸多的定義,在許多的教材資料上,其定義是一個程式的執行執行個體,這不無道理,也有的人認為它是程式處理所描述的所有資料結構的集合。這裡不深究其定義,如果換個角度而言,進程就好像我們人類,他們被產生,它們有自己的生命週期,儘管生命週期的長短不一,從幾毫秒至幾秒,甚至幾個月,幾年。與人類的真正區別就在於它們沒有性別之分。

        從linux核心觀點上來看,進程是核心所分配的系統資源的一個實體(如CPU時間片,記憶體等)。需要瞭解的是,早期的linux核心是不支援多線程應用的進程,因為從當初的核心角度來看,多線程應用也僅僅是一個正常的進程。總之早期的應用不太盡如人意,現在linux核心使用輕量級線程來支援多線程應用,實際上,兩個輕量級線程可以共用一些資源,如地址空間,開啟的檔案等等。當其中一個修改了共用資源,另一個馬上就能看到它與之間的變化,在訪問共用資源時,它們之間需要進行同步操作。

        linux系統上運行著許多的進程,核心如何對它們進行高效的管理呢?這是我們想要弄明白的。在閱讀了一些linux核心關於進程管理方面的原始碼後,作了一些思考與回顧。要想弄明白linux核心對進程的管理,首先應該瞭解它內部的一些重要的資料結構,這是最基本的。

        進程的資料結構--進程描述符

        為了管理進程,核心必須有一個非常清晰的關於每個進程正在做什麼的宏偉藍圖,比如,它必須知道進程的優先順序,它是否在CPU上運行或者在等待什麼事件,哪塊地址空間被賦予了哪個進程,它允許訪問哪些檔案等等,所以有關進程的事務核心必須一清二楚。進程描述符(一個task_struct類型的資料結構)內的每個欄位包含了每個進程相關的所有資訊。也就是說,核心對進程的管理主要是通過從進程描述符擷取豐富的資訊進行的。可以想象,進程描述符內肯定也包含了其它的資料結構類型,各種資料類型互相穿插,複雜而有序地進行資料資訊的傳輸。說了這麼多,不知道進程描述符到底長啥樣,廬山真面目如下:

       

struct task_struct {volatile long state;/* -1 unrunnable, 0 runnable, >0 stopped */void *stack;atomic_t usage;unsigned int flags;/* per process flags, defined below */unsigned int ptrace;int lock_depth;/* BKL lock depth */int prio, static_prio, normal_prio;unsigned int rt_priority;const struct sched_class *sched_class;struct sched_entity se;struct sched_rt_entity rt;......struct list_head tasks;struct plist_node pushable_tasks;struct mm_struct *mm, *active_mm;/* Revert to default priority/policy when forking */unsigned sched_reset_on_fork:1;pid_t pid;pid_t tgid;/*  * pointers to (original) parent process, youngest child, younger sibling, * older sibling, respectively.  (p->father can be replaced with  * p->real_parent->pid) */struct task_struct *real_parent; /* real parent process */struct task_struct *parent; /* recipient of SIGCHLD, wait4() reports *//* * children/sibling forms the list of my natural children */struct list_head children;/* list of my children */struct list_head sibling;/* linkage in my parent's children list */struct task_struct *group_leader;/* threadgroup leader *//* PID/PID hash table linkage. */struct pid_link pids[PIDTYPE_MAX];struct list_head thread_group;/* CPU-specific state of this task */struct thread_struct thread;/* filesystem information */struct fs_struct *fs;/* open file information */struct files_struct *files;#ifdef CONFIG_AUDITSYSCALLuid_t loginuid;unsigned int sessionid;#endifstruct prop_local_single dirties;#ifdef CONFIG_LATENCYTOPint latency_record_count;struct latency_record latency_record[LT_SAVECOUNT];#endif/* * time slack values; these are used to round up poll() and * select() etc timeout values. These are in nanoseconds. */unsigned long timer_slack_ns;unsigned long default_timer_slack_ns;struct list_head*scm_work_list;        ........};

       以上我只列出進程描述符中的一小部分代碼, 給我的第一感覺是,它很龐大,不知道如何下手。想一下子搞清楚每個欄位的含義以及它是怎麼工作是不可能的。不過我們可以從list_head task欄位開始著手。但是在分析之前必須牢記task_struct進程描述符是描述一個進程所擁有的各種詳細資料,按我們正常的理解來看,一個task_struct進程描述符本身就代表了一個進程。它就是一個進程的實體,這樣理解我認為也是可以的。

        注意以上標註為“紅色”的欄位,有很大一部分都是struct list_head結構體,這個結構體的作用是非常巨大的,它的作用好比是針線,可以將許多相關的原材料串起來,對,它就是鏈表結構。list_head結構如下:

struct list_head {struct list_head *next, *prev;};

        這隻是一個雙向鏈表結點,有一個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_del(p):刪除P所指向的地址結點

        list_empty(p):判斷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個不同的鏈表。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.