【Linux系统编程】Linux进程管理
00. 目錄
文章目錄
- 00. 目錄
- 01. 概述
- 02. 進程相關概念
- 03. 進程的狀態
- 04. 進程調度
- 05. 程序調度函數
- 06. 附錄
01. 概述
在Linux的內核的五大組成模塊中,進程管理模塊時非常重要的一部分,它雖然不像內存管理、虛擬文件系統等模塊那樣復雜,也不像進程間通信模塊那樣條理化,但作為五大內核模塊之一,進程管理對我們理解內核的運作、對于我們以后的編程非常重要。同時,作為五大組成模塊中的核心模塊,它與其他四個模塊都有聯系。下面就對進程模塊進行想寫的介紹,首先要了解進程及其相關的概念。其次介紹進程的創建、切換、撤銷等基本操作。除此之外,還給出了Linux內核是如何對進程進行調度管理的。
02. 進程相關概念
進程:進程可以理解為程序執行的一個實例,它包括可執行程序以及與其相關的系統資源,比如打開的文件、掛起的信號、內核內部數據、處理器狀態、內存地址空間及包含全局變量的數據段等。從內核的角度看,進程也可以稱為任務。
進程描述符:與進程相關的事情非常多,比如進程的狀態、進程的優先級、進程的地址空間、允許該進程訪問的文件等等,Linux內核為此專門設計了一個類型為task_struct的結構體,稱之為進程描述符。進程描述符中包含了內核管理進程的所有信息,可以說,只要得到一個進程的進程描述符,就可以知道一個進程的所有信息。
進程狀態:進程描述符task_struct結構體中有一個state字段,表示進程當前的所處狀態。從進程的創建到進程的刪除,它可以經過5種不同的狀態,分別是可運行狀態、可中斷的等待狀態、不可中斷的等待狀態、暫停狀態、跟蹤狀態。除此之外,當進程被終止時,還可能會變為僵死狀態、僵死撤消狀態。內核可以使用宏set_current_state(state)設置當前進程的狀態,用set_task_state(task,state)設置某進程的狀態。
進程號:進程描述task_struct結構體中的pid字段可以標識唯一標識一個進程,稱之為進程標識符PID。當創建一個新進程時,PID是按照順序從小到大分配給新進程的。內核通過管理一個pidmap_array位圖來表示當前已分配的PID和閑置的PID號。注意:在多線程組中,所有的線程共享相同的PID。除了進程標識符外,內核對進程的大部分訪問時通過進程描述符指針進行的。
進程關系:進程之間的關系有親屬關系和非親屬關系。親屬關系包括父子關系和兄弟關系等。其中由tast_struct結構體中的parent/children/real_parent/sibling等字段描述。除了親屬關系外,還有其他關系,比如,一個進程是一個進程組或登錄會話的領頭進程,可能是一個線程組的領頭進程,這些關系由group_leader/tgid/signal->pgrp等字段描述。
進程資源:為了防止進程過度的使用系統資源,內核為每個進程使用資源的數量進行了一些限制。其中包括進程地址空間的最大數、進程使用CPU的最大時間、堆的最大值、文件大小的最大值、文件鎖數量的最大值、消息隊列的最大字節數、打開文件描述符的最大數、進程擁有的頁框最大數等。
03. 進程的狀態
進程的創建:在Linux環境編程時,一般采用fork()函數來創建新的進程,當然,那是在用戶空間的函數,它會調用內核中的clone()系統調用,由clone()函數繼續調用do_fork()完成進程的創建。
傳統Unix系統中,創建的子進程復制父進程所擁有的資源,這種方法效率低,因為子進程需要拷貝父進程的整個地址空間。但是,子進程幾乎不必讀或修改父進程擁有的所有資源,因為很多情況下,子進程創建后會立即調用exec()族的函數,并清除父進程仔細拷貝過來的地址空間。
現代Unix系統用三種方式解決了這個問題:
1、寫實復制技術允許父子進程讀相同的物理頁。
2、輕量級進程允許父子進程共享每進程在內核的很多數據結構。
3、vfork()系統調用創建的進程能共享父進程的內存地址空間,為了防止父進程重寫子進程需要的數據,阻塞父進程的執行,一直到子進程退出或執行一個新的程序為止。整個進程創建過程可能涉及到如下函數:
fork()/vfork()/_clone----------->clone()--------->do_fork()---------->copy_process()
上面的創建過程結束之后,就有了處于可運行狀態的完整的子進程,新的子進程有了PID、進程描述符等各種數據結構,要想實際運行它,還需要調度程序把CPU交給新創建的子進程。
除了進程外,還有內核線程(用kernet_thread創建)的概念。在Linux中,內核線程與普通進程存在以下兩個方面的不同:
1、內核線程只運行在內核態,而普通進程既可以運行在內核態,也可運行在用戶態。
2、因為內核線程只運行在內核態,它只使用大于PAGE_OFFSET的線性地址空間。另一方面,不管在用戶態還是在內核態,普通進程可以用4GB的線性地址空間。
撤銷進程:進程終止后,需要通知內核以便內核釋放進程所擁有的資源,包括內存、打開文件以及其他資源,如信號量。進程終止的一般方式是調用exit()庫函數,該函數釋放C函數庫所分配的資源,執行編程者所注冊的每個函數,并結束從系統回收進程的那個系統調用。
除了進程自己終止自己外,內核可以有選擇地強迫整個線程組死掉。這發生在:當進程接收到一個不能處理或忽視的信號時,或者當內核正在代表進程運行時再內核態產生一個不可恢復的CPU異常時。
有兩個終止用戶態應用的系統調用:exit_group()系統調用,它終止整個線程組,即整個基于多線程的應用。do_group_exit()是實現這個系統調用的主要內核函數。exit()系統調用,它終止一個線程,而不管該線程所屬線程組中的所有其他進程。do_exit()是實現這個系統調用的主要內核函數。
進程切換:進程切換又稱為任務切換、上下文切換。它是這樣一種行為,為了控制進程的執行,內核掛起當前在CPU上運行的進程,并恢復以前掛起的某個進程的執行。
跟函數的調用類似,進程切換時,一般要在CPU上裝載要執行進程的進程上下文。進程的硬件上下文指:可執行程序上下文的一個子集,是進程恢復執行前裝入寄存器的一組數據。其中一部分放在TSS段,即任務狀態段,剩余部分存放在內核態堆棧中。進程的切換只發生在內核態,在執行進程切換之前,用戶態進程使用的所有寄存器內容都已保存在內核態堆棧上。
進程的切換有兩種方法,一種是硬件切換,一種是軟件切換。軟件切換就是利用程序逐步執行切換,它的優點是,可以對切換時裝入的數據進行合法性檢查,執行時間雖與硬件切換大致相同,但仍有可改進的地方。
進程切換使用schedule()函數完成,在本質上,每個進程切換由兩部分組成:
1、切換頁全局目錄以安裝一個新的地址空間。
2、切換內核態堆棧和硬件上下文,因為硬件上下文提供了內核執行新進程所需要的所有信息,包括CPU寄存器,主要有switch_to函數完成。
04. 進程調度
調度策略:調度策略就是這樣一組規則:決定什么時候以怎樣的方式選擇一個新進程運行的規則。Linux的調度基于分時技術:多個進程以“時間多路復用”方式運行,因為CPU的時間被分成“片”,給每個可運行進程分配一片。調度策略也是根據進程的優先級對它們進行分類。在Linux中,進程的優先級是動態的。調度程序跟蹤進程正在做什么,并周期性地調整它們的優先級。根據不同的分類標準,可以把進程分成不同的類型。比如可以把一個進程看作是“I/O受限”或“CPU受限”。也可把進程區分為以下三類:交互式進程、批處理進程、實時進程。Linux的進程是搶占式的,無論是處于內核態還是用戶態。時間片的長短對系統性能是很關鍵的:它既不能太長也不能太短。如果平均時間片太短,由進程切換引起的系統額外開銷就變得非常高。如果平均時間片太長,進程看起來就不再是并發執行的。對時間片大小的選擇始終是一種折中。Linux采用單憑經驗的方法,即選擇盡可能長、同時能保持良好響應時間的一個時間片。
調度算法:早起的Linux中,調度算法是根據進程的優先級選擇“最佳”進程來執行,它的缺點是時間開銷與“可運行進程數量”有關?,F代的Linux中,調度算法可以在固定時間內(與可運行進程數量無關)選中要運行的進程。首先,我們必須知道進程可以分為實時進程與普通進程。每個LInux進程總是按照如下的調度類型被調度:先進先出的實時進程、時間片輪轉的實時進程、普通的分時進程。調度算法根據進程是普通進程還是實時進程而有很大不同。
普通進程的調度:每個普通進程都有它自己的靜態優先級(值是從100到139),調度程序使用靜態優先級來估價系統中這個進程與其他普通進程之間調度的程度。靜態優先級決定進程的基本時間片,即進程用完了以前的時間片時,系統分配給進程的時間片長度。普通進程除了靜態優先級,還有動態優先級。動態優先級是調度程序在選擇新進程來運行的時候使用的數。平均睡眠時間是進程在睡眠狀態所消耗的平均納秒數。即使具有較高靜態優先級的普通進程獲得了較大的CPU時間片,也不應該使靜態優先級較低的進程無法運行。為了避免這個問題,提出了活動進程和過期進程的概念,活動進程指進程的時間片還未用完,過期進程指進程的時間片以用完,即使過期進程的優先級更高,也不能繼續運行,除非等到所有活動進程都過期以后。
實時進程的調度:每個實時進程都與一個試試優先級相關,實時優先級是一個范圍從1到99的值。跟普通進程不同,實時進程總是被當作活動進程。
調度程序所使用的主要數據結構:數據結構runqueue和進程描述符
數據結構runqueue:runqueue數據結構中最重要的字段是與可運行進程 的鏈表相關的字段。其中的arrays字段是活動進程和過期進程的兩個集合,active字段是指向活動進程鏈表的指針,expired字段是指向過期進程鏈表的指針。
進程描述符:每個進程描述符都包括幾個與調度相關的字段。其中的time_slice字段是在進程的時間片中還剩余的時鐘節拍數。它由copy_process函數設置:父進程的剩余節拍數被劃分為兩等分,一份給父進程,一份給子進程。
05. 程序調度函數
調度程序依靠幾個函數來完成調度工作,其中最重要的函數如下:
try_to_wake_up()函數通過把進程狀態設置為TASK_RUNNING,并把該進程插入本地CPU的運行隊列來喚醒睡眠或停止的進程。
recalc_task_prio()函數更新進程的平均睡眠時間和動態優先級。
schedule()憾事實現調度程序,它的任務時從運行隊列的鏈表中找到一個進程,并隨后將CPU分配給這個進程。schedule()可以由幾個內核控制路徑調用,可以采用直接調用或延遲調用的方式
06. 附錄
6.1 參考博客: Linux內核之進程管理
總結
以上是生活随笔為你收集整理的【Linux系统编程】Linux进程管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Linux系统编程】POSIX有名信号
- 下一篇: 【Linux系统编程】Linux进程调度