第一次作業:基於Linux系統深入源碼分析進程模型
1.前言
本文主要基於Linux 2.6源代碼分析進程模型。源代碼下載地址:https://elixir.bootlin.com/linux/v2.6.39/source
2.進程
定義:進程是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操作系統結構的基礎。
3.Linux系統進程的組織
進程是由進程控制塊(PCB)、程序段、數據段三部分組成。
3.1 進程控制塊
進程控制塊(Processing Control Block),是操作系統核心中一種數據結構,主要表示進程狀態,是系統為了管理進程設置的一個專門的數據結構。在Linux中,這個結構叫做task_struct。task_struct被定義在/include/linux/sched.h中。
源代碼地址
PCB包含信息:
- 進程標識符:每個進程都必須有一個唯一的標識符
pid_t pid; //進程的唯一標識 pid_t tgid; //線程組的領頭線程的pid成員的值
- 進程狀態
volatile long state;
- 進程優先級
int prio, static_prio, normal_prio; unsigned int rt_priority;
prio表示進程的動態優先級,static_prio表示進程的靜態優先級,normal_prio表示基於進程的靜態優先級和調度策略計算出的優先級,rt_priority表示實時進程的優先級。
- CPU現場保護區
- 進程相應的程序和數據地址
- 進程資源清單
- 信號處理信息
- 文件系統的信息
- 與進程有關的其他信息
3.2 程序段
程序段:被CPU執行的程序代碼
3.3 數據段
數據段:進程對應的程序中原來的數據或程序執行後產生的結果。
4.Linux系統中進程的狀態及轉換
4.1 進程的狀態
- 進程的三種基本狀態:
運行態(該時刻進程實際占用CPU)
就緒態(可運行,但因為其他進程正在運行而暫時停止)
阻塞態(除非某種外部事件發生,否則進程不能運行)
- 在Linux系統中,進程的狀態有以下幾種,定義在/include/linux/sched.h中。
源代碼地址:https://elixir.bootlin.com/linux/v2.6.37/source/include/linux/sched.h#L182
#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
TASK_RUNNING是就緒態,進程當前只等待CPU資源。
TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE都是阻塞態,進程當前正在等待除CPU外的其他系統資源;前者可以被信號喚醒,後者不可以。
ZOMBIE是僵屍進程,進程已經結束運行,但是進程控制塊尚未註銷。
TASK_STOPPED是掛起狀態,主要用於調試目的。進程接收到SIGSTOP信號後會進入該狀態,在接收到SIGCONT後又會恢復運行。
4.2 進程的創建
在Linux中主要提供了fork()、vfork()、clone()三個進程創建方法。
在Linux系統中可以使用fork()來創建一個進程,fork()函數用於從已存在的進程中創建一個新的進程。新進程為子進程,原進程為父進程。使用fork()函數得到的子進程時父進程的一個復制品,它從父進程處繼承了整個進程的地址空間,包括進程上下文,代碼段,進程堆棧,內存信息,文件描述符,信號控制設定,進程優先級,進程組號,當前工作目錄,根目錄,資源限制和控制終端等,而子進程所獨有的只有它的進程號,資源使用和計時器等子進程幾乎是父進程的完全復制,所以父子進程會同時運行一個程序。vfork()函數則只復制task_struct和內核堆棧。
4.3 狀態的轉換
進程狀態轉換圖如下圖所示:
1.進程因為等待輸入而被阻塞
2.調度程序選擇另一個進程
3.調度程序選擇這個進程
4.出現有效輸入
5.進程結束
4.4進程的終止
- 正常退出
- 出錯退出
- 嚴重錯誤
- 被其他進程殺死
5.進程的調度
當計算機系統是多道程序設計系統時,通常就會有多個進程通時競爭CPU。只要有兩個或更多的進程處於就緒狀態,這種情形就會發生。如果只有一個CPU可用,那麽就必須選擇下一個要運行的進程。完成選擇工作的部分稱為調度,使用的算法稱為調度算法。
5.1 何時調度
有關調度的一個關鍵問題是何時進行調度決策。
- 在創建一個新進程之後,需要決定是運行父進程還是運行子進程。這兩種進程都處於就緒狀態,可以任意決定。
- 在一個進程退出時必須做出調度決策。從就緒進程中選擇某個進程,如果沒有就緒的進程,通常會運行一個系統提供的空閑進程。
- 當一個進程阻塞在I/O和信號量上或由於其他原因阻塞時,必須選擇另一個進程運行。
- 在一個I/O中斷發生時,必須做出調度決策。
5.2 如何調度
schedule():進程調度函數,由它來完成進程的調度。該函數的主要流程如下:先關閉內核搶占,找到當前CPU上的就緒隊列,檢查prev的狀態,如果是非運行狀態的,且在內核中沒有被搶占,從隊列rq中刪除。但如果prev有掛起信號,設置其狀態為TASK_RUNNING狀態,保留在隊列rq中。然後挑選優先級高的下一個進程,並且通知調度器即將進行切換,更新隊列中保存的進程信息,最後通知調度類,完成進程切換。
源代碼地址:https://elixir.bootlin.com/linux/v2.6.39/source/kernel/sched.c
asmlinkage void __sched schedule(void) { struct task_struct *prev, *next; //當前進程、下一個進程的結構體 unsigned long *switch_count; //進程切換次數 struct rq *rq; //就緒隊列 int cpu; need_resched: preempt_disable(); //關閉內核搶占 cpu = smp_processor_id(); //找到當前CPU上的就緒隊列rq rq = cpu_rq(cpu); rcu_note_context_switch(cpu); prev = rq->curr; //將正在運行的進程保存在prev schedule_debug(prev); if (sched_feat(HRTICK)) hrtick_clear(rq); raw_spin_lock_irq(&rq->lock); switch_count = &prev->nivcsw; //切換次數記錄 if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) { //當前進程非運行狀態,並且非內核搶占 if (unlikely(signal_pending_state(prev->state, prev))) { //若不是非掛起信號,設置進程為就緒狀態 prev->state = TASK_RUNNING; } else { //若為非掛起信號,則將其從隊列中移出 /* * If a worker is going to sleep, notify and * ask workqueue whether it wants to wake up a * task to maintain concurrency. If so, wake * up the task. */ if (prev->flags & PF_WQ_WORKER) { struct task_struct *to_wakeup; to_wakeup = wq_worker_sleeping(prev, cpu); if (to_wakeup) try_to_wake_up_local(to_wakeup); } deactivate_task(rq, prev, DEQUEUE_SLEEP); /* * If we are going to sleep and we have plugged IO queued, make * sure to submit it to avoid deadlocks. */ if (blk_needs_flush_plug(prev)) { raw_spin_unlock(&rq->lock); blk_schedule_flush_plug(prev); raw_spin_lock(&rq->lock); } } switch_count = &prev->nvcsw; } pre_schedule(rq, prev); //通知調度器,即將發生進程切換 if (unlikely(!rq->nr_running)) idle_balance(cpu, rq); put_prev_task(rq, prev); //通知調度器,即將用另一個進程替換當前進程 next = pick_next_task(rq); //挑選可運行的任務 clear_tsk_need_resched(prev); //清除pre的TIF_NEED_RESCHED標誌 rq->skip_clock_update = 0; if (likely(prev != next)) { //如果不是同一個進程 rq->nr_switches++; rq->curr = next; //將當前進程切換成挑選的那個進程 ++*switch_count; //切換次數更新 context_switch(rq, prev, next); /* unlocks the rq */ //進程上下文切換 /* * The context switch have flipped the stack from under us * and restored the local variables which were saved when * this task called schedule() in the past. prev == current * is still correct, but it can be moved to another cpu/rq. */ cpu = smp_processor_id(); rq = cpu_rq(cpu); } else raw_spin_unlock_irq(&rq->lock); post_schedule(rq); //通知調度類,完成進程切換 preempt_enable_no_resched(); if (need_resched()) //如果該進程被其他進程設置了TIF_NEED_RESCHED標誌,則函數重新執行進行調度 goto need_resched; }
6.對操作系統模型的看法
操作系統是實現資源管理,程序控制和人機交互的計算機程序。系統的設備資源和信息資源是操作系統根據用戶需求按一定的策略來進行分配和調度的 ;一個用戶程序的執行自始至終是在操作系統控制下進行的;操作系統的人機交互功能可以使計算機系統更“友善”。7.參考資料
https://baike.baidu.com/item/%E8%BF%9B%E7%A8%8B/382503?fr=aladdin
https://blog.csdn.net/u013592097/article/details/52530129
https://blog.csdn.net/hzk8656511/article/details/52204016
http://blog.sina.com.cn/s/blog_9ca3f6e70102wkwq.html
第一次作業:基於Linux系統深入源碼分析進程模型