本文爲基於Linux kernel 2.6進行深刻源碼的進程模型分析,進程是操做系統的核心概念之一,進程是系統實現的重要途徑,因此,在此進行進程的相關分析,以此增強對操做系統的學習。linux
附上Linux kernel 2.6 源碼下載地址:https://mirrors.edge.kernel.org/pub/linux/kernel/v2.6/算法
進程(Process)是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操做系統結構的基礎。在早期面向進程設計的計算機結構中,進程是程序的基本執行實體;在當代面向線程設計的計算機結構中,進程是線程的容器。程序是指令、數據及其組織形式的描述,進程是程序的實體。數組
接下來是維基百科關於進程的解釋:數據結構
In computing, a process is an instance of a computer program that is being executed. It contains the program code and its current activity. Depending on the operating system (OS), a process may be made up of multiple threads of execution that execute instructions concurrently.併發
在計算中,進程是正在執行的計算機程序的一個實例。它包含程序代碼及其當前活動。根據操做系統(OS),一個進程可能由多個執行線程併發執行指令組成。異步
在對進程有了一些基礎的瞭解以後, 咱們即可以進行下一步的分析了函數
/linux/include/linux/sched.h
頭文件中被定義爲task_struct
, 它是一個結構體, 一個它的實例化即爲一個進程, task_struct
由許多元素構成, 下面列舉一些重要的元素進行分析。
標識符:與進程相關的惟一標識符,用來區別正在執行的進程和其餘進程。
狀態:描述進程的狀態,由於進程有掛起,阻塞,運行等好幾個狀態,因此都有個標識符來記錄進程的執行狀態。
優先級:若是有好幾個進程正在執行,就涉及到進程被執行的前後順序的問題,這和進程優先級這個標識符有關。
程序計數器:程序中即將被執行的下一條指令的地址。
內存指針:程序代碼和進程相關數據的指針。
上下文數據:進程執行時處理器的寄存器中的數據。
I/O狀態信息:包括顯示的I/O請求,分配給進程的I/O設備和被進程使用的文件列表等。
記帳信息:包括處理器的時間總和,記帳號等等學習
/* * pointers to (original) parent process, youngest child, younger sibling, * older sibling, respectively. (p->father can be replaced with * p->parent->pid) */ struct task_struct *real_parent; /* real parent process (when being debugged) */ struct task_struct *parent; /* parent process */ /* * children/sibling forms the list of my children plus the * tasks I‘m ptracing. */ 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 */
在Linux系統中,全部進程之間都有着直接或間接地聯繫,每一個進程都有其父進程,也可能有零個或多個子進程。擁有同一父進程的全部進程具備兄弟關係。優化
real_parent指向其父進程,若是建立它的父進程再也不存在,則指向PID爲1的init進程。 parent指向其父進程,當它終止時,必須向它的父進程發送信號。它的值一般與 real_parent相同。 children表示鏈表的頭部,鏈表中的全部元素都是它的子進程(進程的子進程鏈表)。 sibling用於把當前進程插入到兄弟鏈表中(進程的兄弟鏈表)。 group_leader指向其所在進程組的領頭進程。this
在task_struct結構體中, 定義進程的狀態語句爲
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
valatile關鍵字的做用是確保本條指令不會因編譯器的優化而省略, 且要求每次直接讀值, 這樣保證了對進程實時訪問的穩定性。
進程在/linux/include/linux/sched.h 頭文件中咱們能夠找到state的可能取值以下
/*
* Task state bitmask. NOTE! These bits are also
* encoded in fs/proc/array.c: get_task_state().
* We have two separate sets of flags: task->state
* is about runnability, while task->exit_state are
* the task exiting. Confusing, but this way
* modifying one set can‘t modify the other one by
* mistake.
*/
define TASK_RUNNING 0
define TASK_INTERRUPTIBLE 1
define TASK_UNINTERRUPTIBLE 2
define TASK_STOPPED 4
define TASK_TRACED 8
/* in tsk->exit_state */
define EXIT_ZOMBIE 16
define EXIT_DEAD 32
/* in tsk->state again */
define TASK_NONINTERACTIVE 64
define TASK_DEAD 128
根據state後面的註釋, 能夠獲得當state<0時,表示此進程是處於不可運行的狀態, 當state=0時, 表示此進程正處於運行狀態, 當state>0時, 表示此進程處於中止運行狀態。
如下列舉一些state的經常使用取值
| 狀態 | 描述 |
| :---------------------- | :----------------------------------------------------------- |
| 0(TASK_RUNNING) | 進程處於正在運行或者準備運行的狀態中 |
| 1(TASK_INTERRUPTIBLE) | 進程處於可中斷睡眠狀態, 可經過信號喚醒 |
| 2(TASK_UNINTERRUPTIBLE) | 進程處於不可中斷睡眠狀態, 不可經過信號進行喚醒 |
| 4( TASK_STOPPED) | 進程被中止執行 |
| 8( TASK_TRACED) | 進程被監視 |
| 16( EXIT_ZOMBIE) | 殭屍狀態進程, 表示進程被終止, 可是其父程序還未獲取其被終止的信息。 |
| 32(EXIT_DEAD) | 進程死亡, 此狀態爲進程的最終狀態 |
(圖片來源於網上)
在瞭解進程是如何進行調度以前, 咱們須要先了解一些與進程調度有關的數據結構。
在/kernel/sched.c
文件下, 可運行隊列被定義爲struct rq
, 每個CPU都會擁有一個struct rq
, 它主要被用來存儲一些基本的用於調度的信息, 包括及時調度和CFS調度。在Linux kernel 2.6中, struct rq
是一個很是重要的數據結構, 接下來咱們介紹一下它的部分重要字段:
/* 選取出部分字段作註釋 */ //runqueue的自旋鎖,當對runqueue進行操做的時候,須要對其加鎖。因爲每一個CPU都有一個runqueue,這樣會大大減小競爭的機會 spinlock_t lock; // 此變量是用來記錄active array中最先用完時間片的時間 unsigned long expired_timestamp; //記錄該CPU上就緒進程總數,是active array和expired array進程總數和 unsigned long nr_running; // 記錄該CPU運行以來發生的進程切換次數 unsigned long long nr_switches; // 記錄該CPU不可中斷狀態進程的個數 unsigned long nr_uninterruptible; // 這部分是rq的最最最重要的部分, 我將在下面仔細分析它們 struct prio_array *active, *expired, arrays[2];
Linux kernel 2.6版本中, 在rq中多加了兩個按優先級排序的數組active array
和expired array
。
這兩個隊列的結構是struct prio_array
, 它被定義在/kernel/sched.c
中, 其數據結構爲:
struct prio_array { unsigned int nr_active; // DECLARE_BITMAP(bitmap, MAX_PRIO+1); /* include 1 bit for delimiter */ /*開闢MAX_PRIO + 1個bit的空間, 當某一個優先級的task正處於TASK_RUNNING狀態時, 其優先級對應的二進制位將會被標記爲1, 所以當你須要找此時須要運行的最高的優先級時, 只須要找到bitmap的哪一位被標記爲1了便可*/ struct list_head queue[MAX_PRIO]; // 每個優先級都有一個list頭 };
Active array
表示的是CPU選擇執行的運行進程隊列, 在這個隊列裏的進程都有時間片剩餘, *active
指針老是指向它。Expired array
則是用來存放在Active array
中使用完時間片的進程, *expired指針老是指向它。
一旦在active array
裏面的某一個普通進程的時間片使用完了, 調度器將從新計算該進程的時間片與優先級, 並將它從active array
中刪除, 插入到expired array
中的相應的優先級隊列中 。
當active array內的全部task都用完了時間片, 這時只須要將*active
與*expired
這兩個指針交換下, 便可切換運行隊列。
schedule
函數存在/kernel/sched.c
中, 是Linux kernel很重要的一個函數, 它的做用是用來挑選出下一個應該執行的進程, 而且完成進程的切換工做, 是進程調度的主要執行者。
何爲O(1)算法: 該算法總可以在有限的時間內選出優先級最高的進程而後執行, 而無論系統中有多少個可運行的進程, 所以命名爲O(1)算法。
在前面咱們提到了兩個按優先級排序的數組active array
和expired array
, 這兩個數組是實現O(1)算法的關鍵所在。
O(1)調度算法每次都是選取在active array數組中且優先級最高的進程來運行。
那麼該算法如何找到優先級最高的進程呢? 你們還記得前面prio_array
內的DECLARE_BITMAP(bitmap, MAX_PRIO+1);
字段嗎?這裏它就發揮出做用了(詳情看代碼註釋), 這裏只要找到bitmap
內哪個位被設置爲了1, 便可獲得當前系統所運行的task的優先級(idx, 經過sehed_find_first_bit()方法實現), 接下來找到idx所對應的進程鏈表(queue), queue內的全部進程都是目前可運行的而且擁有最高優先級的進程, 接着依次執行這些進程,。
該過程定義在schedule
函數中, 主要代碼以下:
struct task_struct *prev, *next; struct list_head *queue; struct prio_array *array; int idx; prev = current; array = rq->active; idx = sehed_find_first_bit(array->bitmap); //找到位圖中第一個不爲0的位的序號 queue = array->queue + idx; //獲得對應的隊列鏈表頭 next = list_entry(queue->next, struct task_struct, run_list); //獲得進程描述符 if (prev != next) //若是選出的進程和當前進程不是同一個,則交換上下文 context_switch();