1. 程式人生 > >進程及進程調度

進程及進程調度

沒有 用戶 wake 進程 詳細 虛擬存儲器 區域 內存區域 服務

1 .1 進程結構

每個進程都具有自己的屬性,用一個task_struct數據結構來表示,它包含了進程的詳細信息,主要有進程標識符(PID)、進程所占的內存區域、相關文件描述符、安全信息、進程環境、信號處理、資源安排、同步處理狀態幾個方面。

數組task包含指向系統中所有task_struct結構的指針。創建進程時,Linux將從系統內存中分配一個task_struct結構,並將其加入task數組。操作系統初始化後,建立init進程,它建立一個task_struct數據結構INIT_TASK。當前運行進程的結構用current指針來指示。

進程切換包含三個層次:

1)用戶數據的保存 包括正文段、數據段(DATA,BSS)、堆棧段(STACK)、共享內存段(SHARED MEMORY)。

2)寄存器數據的保護 包含PC、PSW(處理器狀態字)、SP(棧指針)、PCBP(進程控制塊指針)、FP(指向棧中一個函數的local變量的首地址)、P(指向棧中調用函數的實參位置)、ISP(中斷棧指針),以及其他通用寄存器等。

3)系統級的保護 包括proc、u‘虛擬存儲器空間管理表格、中斷處理棧。

(PROC文件系統是Linux中的特殊文件系統,提供給用戶一個可以了解內核內部工作過程的可讀窗口,在運行時訪問內核內部數據結構、改變內核設置的機制。)

技術分享圖片

狀態為TASK_INTERRUPTBLE或TASK_UNINTERRUPTIBLE的睡眠進程得到它需要的資源被喚醒,通過schdule()進入TASK_RUNNING狀態。狀態TASK_UNINTERRUPTIBLE的睡眠進程,不能被信號或定時器中斷喚醒,只有它申請的資源有效時才能被喚醒。

進程執行do_exit()後進入狀態TASK_ZOMBILE,釋放所申請的資源。

1.2 進程的創建

1.2.1對象緩存的分配

在系統啟動時的啟動內核函數中,有進程管理的初始化函數,其中fork_init函數初始化線程數,分配進程結構的對象緩存。各個進程創建時進程結構對象從這裏分配空間。

1.2.1系統調用sys_fork

當系統調用sys_fork創建一個進程的時候,它直接調用了實現函數do_fork。do_fork函數拷貝父進程的相關數據,如文件、信號量、內存等。完成進程初始化後,由父進程調用wake_up_process()函數將其喚醒,狀態變為TASK_RUNNING,掛到就緒隊列,返回子進程的pid。(創建完成的子進程會掛到就緒隊列)

1.3 內核線程

一個進程可以擁有多個線程。如果進程運行在SMP機器上,多個CPU執行各個線程,這樣達到最大程度的並行。線程的上下文切換開銷就比進程要小多了,線程共享了進程中除CPU以外的其他資源。

線程有內核線程、輕量級進程和用戶線程三種,其中內核線程在內核調度,可並發使用多個處理器。用戶線程在用戶空間實現,它減少了上下文切換開銷,它並行處理一個進程中的多個事務。

1)內核線程

內核線程是由內核創建和撤銷的,用來執行一個指定的函數。內核線程共享內核的正文段內核全局數據,但各自具有自己的內核堆棧。它能夠被單獨調度,並且使用標準的內核同步機制,可以被單獨分配到一個處理器上運行。內核線程實際上是一個與父進程共享地址空間的進程。

2)輕量級進程

輕量級進程是內核支持的用戶線程。它在一個單獨的進程中提供多線程控制。這些輕量級進程被單獨調度,可以在多個處理器上運行,每一個輕量級進程都被綁定在一個內核線程上。輕量級進程不被獨立調度,並且共享地址空間和進程中的其他資源,但是每個輕量級進程都應該有自己的程序計數器、寄存器集合、核心棧和用戶棧。

3)用戶線程

用戶線程是通過線程庫實現的。它們是在沒有內核的參與下進行創建、釋放和管理的。線程庫提供了同步和調度的方法。用戶線程的上下文在沒有內核幹預的情況下保存和恢復。每個用戶線程可以有自己的用戶堆棧,一塊用來保存用戶級寄存器上下文,以及入如信號屏蔽等狀態信息的內存區。

內核只調度用戶線程下的進程,這些進程再通過線程庫函數來調度它們的線程。

1.4 工作隊列

工作隊列接口是用於調度內核工作任務的。每個工作隊列使用一個專門線程,所有來自運行隊列的工作任務在這個線程中運行,而線程是在進程的上下文中運行的。因此可在適當時間調度此線程來運行工作任務。

1.5 進程調度

在linux中,每一個CPU維護一個自己的runqueue結構的就緒隊列。

1.5.1 runqueue結構

runqueue結構是主要的CPU運行隊列數據結構。運行隊列用來按照優先級來管理進程,每個運行隊列代表一定優先級的進程的鏈表。與工作隊列的區別是:一個工作隊列運用一個線程來運行工作隊列中的各種工作任務,而內核線程實質也是一種進程,工作隊列與運行隊列是完全不相關的兩個概念。

1.5.2 進程調度初始化

linux的進程有schedule函數執行。它只在內核態運行,任何進程從系統調用返回時會轉入schedule()。大多數中斷服務程序在中斷響應完成後,也會轉入schedule().

1)調度器的初始化

函數sched_init初始化調度器

2)進程調度器相關環境的建立

函數sched_fork為進程p建立調度器相關環境,進程p是由當前進程用函數fork()新建的進程。

在進程創建fork系統調用中會調用do_fork函數,在do_fork函數中會調用到copy_process函數,而copy_process函數調用到了sched_fork函數,這說明進程創建時,就會建立調度器相關環境。shed_fork函數給進程p分配時間片,打上時間戳。

1.5.3 函數schedule分析

進程的調度有直接啟動調度和被動調度兩種方式,在不同的方式下調度執行的步驟是不一樣的:

1)直接啟動調度

直接啟動調度發生在當前進程因資源而需要進入被阻塞狀態時。調度程序執行的步驟如下:

a 把當前進程放到適當的等待隊列裏;

b 把當前進程的state設為TASK_INTERRUPTIBEL或者TASK_UNINTERRUPTIBEL;

c 調用schedule(),準備讓新的進程掌握CPU

d 檢查當前進程所需的資源是否可用,如果是,則把當前進程從等待隊列裏刪除。

2)被動調度

通過在當前進程的need_resched設為1來實現被動調度,每次調入一個用戶態進程之前,這個變量的值都會被檢查,來決定是否調用函數schedule()來實現調度

函數schedule的功能是選擇一個合適的進程在CPU上執行,它的基本流程分為五個操作步驟:

1)清理當前運行中的進程

2)選擇下一個投入運行的進程

3)設置新進程的運行環境

4)執行進程上下文切換

5)後期整理

1.6 Linux內核搶占

內核搶占就是讓調度程序能盡可能多地運行,從而減少了從一個事件發生到調度程序被執行的時間延遲。在當前進程具有被“安全”搶占條件,並且有一個等待處理的重新調度請求時,內核就調用調度程序來進行進程調度。

內核搶占要求內核中所有可能為一個以上進程共享的變量和數據結構都要通過互斥機制加以保護,或者說都要放在臨界區中。在搶占式內核中,認為如果內核不是在一個中斷處理程序中,並且不在spinlock保護的代碼中,就任務可以“安全”地進行切換。

搶占式內核實現的原理是在釋放spinlock時,或者當中斷返回時,如果當前執行進程的need_resched被標記,則進行搶占式調度。

進程及進程調度