Linux中的task_struct結構體

        我們都知道,在廣義上,所有的進程信息都被放在一個叫做進程控制塊數據結構中,這個可以理解爲進程屬性的集合。這裏的進程控制塊也就是所謂的PCB,他是一個非常大的數據結構,那麼它到底有多大呢?如果沒什麼意外,這個結構體可能是這個宇宙中最大的單個變量了,一個結構體就有好幾k那麼大,想想他包含了一個進程的所有信息,這麼龐大也不足爲怪了。LINUX內核代碼紛繁複雜,千頭萬緒,這個結構體是系統進程在執行過程中所有涉及的方方面面的縮影,包括系統內存管理子系統、進程調度子系統、虛擬文件系統等等,以這個所謂的PCB爲切入點,是一個很好的研究內核的窗口。總之,當一個程序文件被執行的時候,內核將會產生這麼一個結構體,來承載所有該活動實體日後運行時所需要的所有資源,隨着進程的運行,各種資源被分配和釋放,是一個動態的過程。
下面我們來分析一下。
1、進程控制塊
     每個進程在內核中都有一個進程控制塊(PCB)來維護進程的相關信息,Linux內核的進程控制塊是task_struct結構體。它會被裝載到RAM裏並且包含着進程的信息,每個進程都會把它的進程的相關信息放在task_struct數據結構中,task_struct主要包含了下面這些內容:
標識符:描述本進程的唯一標識符,用來區別其他進程。
狀態:任務狀態、退出代碼、退出信號等。
優先級:相對於其他進程的優先級。
程序計數器(PC):程序中即將被執行的下一條指令的地址。
內存指針:包括程序代碼和進程相關數據的指針,還有和其它進程共享的內存塊的指針。
上下文數據:進程執行時處理器的寄存器中的數據。
I/O狀態信息:包括顯示的I/O請求,分配給進程的I/O設備和被進程使用的文件列表。
記賬信息:可能包括處理器時間總和,使用的時鐘數綜合、時間限制、記賬號等。
   下面是對它們的詳細說明:
·進程狀態
     進程執行時,它會根據具體情況改變狀態 。進程狀態是調度和對換的依據。Linux中的進程主要有如下狀態,如表4.1所示。

                    表4.1          Linux進程的狀態

 

內核表示

含義

TASK_RUNNING

可運行

TASK_INTERRUPTIBLE

可中斷的等待狀態

TASK_UNINTERRUPTIBLE

不可中斷的等待狀態

TASK_ZOMBIE

僵死

TASK_STOPPED

暫停

TASK_SWAPPING

換入/換出

 

·可運行狀態

處於這種狀態的進程,要麼正在運行、要麼正準備運行。正在運行的進程就是當前進程(由current所指向的進程),而準備運行的進程只要得到CPU就可以立即投入運行,CPU是這些進程唯一等待的系統資源。系統中有一個運行隊列(run_queue),用來容納所有處於可運行狀態的進程,調度程序執行時,從中選擇一個進程投入運行。在後面我們討論進程調度的時候,可以看到運行隊列的作用。當前運行進程一直處於該隊列中,也就是說,current總是指向運行隊列中的某個元素,只是具體指向誰由調度程序決定。

·等待狀態

處於該狀態的進程正在等待某個事件(event)或某個資源,它肯定位於系統中的某個等待隊列(wait_queue)中。Linux中處於等待狀態的進程分爲兩種:可中斷的等待狀態和不可中斷的等待狀態。處於可中斷等待態的進程可以被信號喚醒,如果收到信號,該進程就從等待狀態進入可運行狀態,並且加入到運行隊列中,等待被調度;而處於不可中斷等待態的進程是因爲硬件環境不能滿足而等待,例如等待特定的系統資源,它任何情況下都不能被打斷,只能用特定的方式來喚醒它,例如喚醒函數wake_up()等。

·暫停狀態

此時的進程暫時停止運行來接受某種特殊處理。通常當進程接收到SIGSTOPSIGTSTPSIGTTIN SIGTTOU信號後就處於這種狀態。例如,正接受調試的進程就處於這種狀態。

·僵死狀態

進程雖然已經終止,但由於某種原因,父進程還沒有執行wait()系統調用,終止進程的信息也還沒有回收。顧名思義,處於該狀態的進程就是死進程,這種進程實際上是系統中的垃圾,必須進行相應處理以釋放其佔用的資源。

2. 進程調度信息

調度程序利用這部分信息決定系統中哪個進程最應該運行,並結合進程的狀態信息保證系統運轉的公平和高效。這一部分信息通常包括進程的類別(普通進程還是實時進程)、進程的優先級等等。如表4.2所示:

 

 
        表4.2           進程調度信息

域名

含義

need_resched

調度標誌

Nice

靜態優先級

Counter

動態優先級

Policy

調度策略

rt_priority

實時優先級

 

在下一章的進程調度中我們會看到,當need_resched被設置時,在“下一次的調度機會”就調用調度程序schedule() counter代表進程剩餘的時間片,是進程調度的主要依據,也可以說是進程的動態優先級,因爲這個值在不斷地減少;nice是進程的靜態優先級,同時也代表進程的時間片,用於對counter賦值,可以用nice()系統調用改變這個值;policy是適用於該進程的調度策略,實時進程和普通進程的調度策略是不同的;rt_priority只對實時進程有意義,它是實時進程調度的依據。

進程的調度策略有三種,如表4.3所示。

4.3          進程調度的策略

名稱

解釋

適用範圍

SCHED_OTHER

其他調度

普通進程

SCHED_FIFO

先來先服務調度

實時進程

SCHED_RR

時間片輪轉調度

 

只有root用戶能通過sched_setscheduler()系統調用來改變調度策略。

 

3 .標識符(Identifiers

每個進程有進程標識符、用戶標識符、組標識符,如表4.4所示。

不管對內核還是普通用戶來說,怎麼用一種簡單的方式識別不同的進程呢?這就引入了進程標識符(PIDprocess identifier),每個進程都有一個唯一的標識符,內核通過這個標識符來識別不同的進程,同時,進程標識符PID也是內核提供給用戶程序的接口,用戶程序通過PID對進程發號施令。PID32位的無符號整數,它被順序編號:新創建進程的PID通常是前一個進程的PID1。然而,爲了與16位硬件平臺的傳統Linux系統保持兼容,在Linux上允許的最大PID號是32767,當內核在系統中創建第32768個進程時,就必須重新開始使用已閒置的PID號。

 

 

           表4.4                  各種標識符 

域名

含義

Pid、ppid
子進程標識符,父進程標識符

Uidgid

用戶標識符、組標識符

Euidegid

有效用戶標識符、有效組標識符

Suidsgid

備份用戶標識符、備份組標識符

Fsuidfsgid

文件系統用戶標識符、文件系統組標識符

 

另外,每個進程都屬於某個用戶組。task_struct結構中定義有用戶標識符和組標識符。它們同樣是簡單的數字,這兩種標識符用於系統的安全控制。系統通過這兩種標識符控制進程對系統中文件和設備的訪問,其它幾個標識符將在文件系統中討論。

4. 進程通信有關信息(IPCInter_Process Communication

爲了使進程能在同一項任務上協調工作,進程之間必須能進行通信即交流數據。

Linux支持多種不同形式的通信機制。它支持典型的Unix 通信機制(IPC Mechanisms):信號(Signals)、管道(Pipes),也支持System V 通信機制:共享內存(Shared Memory)、信號量和消息隊列(Message Queues,如表4.5

4.5                 進程通信有關信息

域名

含義

Spinlock_t sigmask_lock

信號掩碼的自旋鎖

Long blocked

信號掩碼

Struct signal  *sig

信號處理函數

Struct sem_undo *semundo

爲避免死鎖而在信號量上設置的取消操作

Struct sem_queue *semsleeping

與信號量操作相關的等待隊列

這些域的具體含義將在進程通信一章進行討論。

 

5. 進程鏈接信息(Links

程序創建的進程具有父/子關係。因爲一個進程能創建幾個子進程,而子進程之間有兄弟關係,在task_struct結構中有幾個域來表示這種關係。

Linux系統中,除了初始化進程init,其他進程都有一個父進程(parent process)或稱爲雙親進程。可以通過fork()或clone()系統調用來創建子進程,除了進程標識符(PID)等必要的信息外,子進程的task_struct結構中的絕大部分的信息都是從父進程中拷貝,或說“克隆”過來的。系統有必要記錄這種“親屬”關係,使進程之間的協作更加方便,例如父進程給子進程發送殺死(kill)信號、父子進程通信等,就可以用這種關係很方便地實現。

每個進程的task_struct結構有許多指針,通過這些指針,系統中所有進程的task_struct結構就構成了一棵進程樹,這棵進程樹的根就是初始化進程inittask_struct結構(init進程是Linux內核建立起來後人爲創建的一個進程,是所有進程的祖先進程)。表4.6是進程所有的鏈接信息。

4.6          進程鏈接信息

名稱

英文解釋

中文解釋 [指向哪個進程]

p_opptr

Original parent

祖先

p_pptr

Parent

父進程

p_cptr

Child

子進程

p_ysptr

Younger sibling

弟進程

p_osptr

Older sibling

兄進程

Pidhash_next

Pidhash_pprev

 

進程在哈希表中的鏈接

Next_task prev_task

 

進程在雙向循環鏈表中的鏈接

Run_list

 

運行隊列的鏈表

 

6. 時間和定時器信息(Times and Timers

一個進程從創建到終止叫做該進程的生存期(lifetime)。進程在其生存期內使用CPU的時間,內核都要進行記錄,以便進行統計、計費等有關操作。進程耗費CPU的時間由兩部分組成:一是在用戶模式(或稱爲用戶態)下耗費的時間、一是在系統模式(或稱爲系統態)下耗費的時間。每個時鐘滴答,也就是每個時鐘中斷,內核都要更新當前進程耗費CPU的時間信息。

“時間”對操作系統是極其重要的 。讀者可能瞭解計算機時間的有關知識,例如8353/8254這些物理器件,INT 08INT 1C等時鐘中斷等,可能有過編程序時截獲時鐘中斷的成就感,不管怎樣,下一章我們將用較大的篇幅儘可能向讀者解釋清楚操作系統怎樣建立完整的時間機制、並在這種機制的激勵下進行調度等活動。

建立了“時間”的概念,“定時”就是輕而易舉的了,無非是判斷系統時間是否到達某個時刻,然後執行相關的操作而已。Linux提供了許多種定時方式,用戶可以靈活使用這些方式來爲自己的程序定時。

4.7是和時間有關的域,上面所說的counter是指進程剩餘的CPU時間片,也和時間有關,所以這裏我們再次提及它。表4.8是進程的所有定時器。

  4.7        與時間有關的域

域名

含義

Start_time

進程創建時間

Per_cpu_utime

進程在某個CPU上運行時在用戶態下耗費的時間

Per_cpu_stime

進程在某個CPU上運行時在系統態下耗費的時間

Counter

進程剩餘的時間片

 

 

  4.8                      進程的所有定時器

定時器類型

解釋

什麼時候更新

用來表示此種定時器的域

ITIMER_REAL

實時定時器

實時更新,即不論該進程是否運行

it_real_value

it_real_incr

real_timer

ITIMER_VIRTUAL

虛擬定時器

只在進程運行於用戶態時更新

it_virt_value

it_virt_incr

ITIMER_PROF

概況定時器

進程運行於用戶態和系統態時更新

it_prof_value

it_prof_incr

 

進程有三種類型的定時器:實時定時器、虛擬定時器和概況定時器。這三種定時器的特徵共有三個:到期時間、定時間隔、要觸發的事件。到期時間就是定時器到什麼時候完成定時操作,從而觸發相應的事件;定時間隔就是兩次定時操作的時間間隔,它決定了定時操作是否繼續進行,如果定時間隔大於0,則在定時器到期時,該定時器的到期時間被重新賦值,使定時操作繼續進行下去,直到進程結束或停止使用定時器,只不過對不同的定時器,到期時間的重新賦值操作是不同的。在表4.8中,每個定時器都有兩個域來表示到期時間和定時間隔:valueincr,二者的單位都是時鐘滴答,和jiffies的單位是一致的,Linux所有的時間應用都建立在jiffies之上。虛擬定時器和概況定時器到期時由內核發送相應的信號,而實時定時器比較特殊,它由內核機制提供支持,我們將在後面討論這個問題。

每個時鐘中斷,當前進程所有和時間有關的信息都要更新: 當前進程耗費的CPU時間要更新,以便於最後的計費;時間片計數器counter要更新,如果counter<=0,則要執行調度程序;進程申請的延時要更新,如果延時時間到了,則喚醒該進程;所有的定時器都要更新,Linux內核檢測這些定時器是否到期,如果到期,則執行相應的操作。在這裏,“更新”的具體操作是不同的:對counter,內核要對它減值,而對於所有的定時器,就是檢測它的值,內核把系統當前時間和其到期時間作一比較,如果到期時間小於系統時間,則表示該定時器到期。但爲了方便,我們把這些操作一概稱爲“更新”,請讀者注意。

請特別注意上面三個定時器的更新時間。實時定時器不管其所屬的進程是否運行都要更新,所以,時鐘中斷來臨時,系統中所有進程的實時定時器都被更新,如果有多個進程的實時定時器到期,則內核要一一處理這些定時器所觸發的事件。而虛擬定時器和概況定時器只在進程運行時更新,所以,時鐘中斷來臨時,只有當前進程的概況定時器得到更新,如果當前進程運行於用戶態,則其虛擬定時器也得到更新。

       此外,Linux內核對這三種定時器的處理是不同的,虛擬定時器和概況定時器到期時,內核向當前進程發送相應的信號:SIGVTALRM  SIGPROF ;而實時定時器要執行的操作由real_timer決定,real_timetimer_list類型的變量(定義:struct timer_list real_timer),其中容納了實時定時器的到期時間、定時間隔等信息,我們將在下一章詳細討論這些內容。

7. 文件系統信息(File System

進程可以打開或關閉文件,文件屬於系統資源,Linux內核要對進程使用文件的情況進行記錄。task_struct結構中有兩個數據結構用於描述進程與文件相關的信息。其中,fs_struct中描述了兩個VFS索引節點(VFS inode),這兩個索引節點叫做rootpwd,分別指向進程的可執行映象所對應的根目錄(home directory)和當前目錄或工作目錄。file_struct結構用來記錄了進程打開的文件的描述符(descriptor)。如表4.9所示。

       4.9                      與文件系統相關的域

定義形式

解釋

Sruct fs_struct *fs

進程的可執行映象所在的文件系統

Struct files_struct *files

進程打開的文件

 

在文件系統中,每個VFS索引節點唯一描述一個文件或目錄,同時該節點也是向更低層的文件系統提供的統一的接口。

8. 虛擬內存信息(Virtual Memory

除了內核線程(kernel thread),每個進程都擁有自己的地址空間(也叫虛擬空間),用mm_struct來描述。另外Linux2.4還引入了另外一個域active_mm,這是爲內核線程而引入。因爲內核線程沒有自己的地址空間,爲了讓內核線程與普通進程具有統一的上下文切換方式,當內核線程進行上下文切換時,讓切換進來的線程的active_mm 指向剛被調度出去的進程的active_mm(如果進程的mm域不爲空,則其active_mm域與mm域相同)。內存信息如表4.10所示。

4.10             虛擬內存描述信息

定義形式

解釋

Struct mm_struct *mm

描述進程的地址空間

Struct mm_struct *active_mm

內核線程所借用的地址空間

 

9.頁面管理信息

   當物理內存不足時,Linux內存管理子系統需要把內存中的部分頁面交換到外存,其交換是以頁爲單位的。有關頁面的描述信息如表4.11

  4.11           頁面管理信息

  定義形式

解釋

Int swappable

進程佔用的內存頁面是否可換出

Unsigned long min_flat,maj_flt,nswap

進程累計的次(minor)缺頁次數、主(major)次數及累計換出、換入頁面數

Unsigned long cmin_flat,cmaj_flt,cnswap

本進程作爲祖先進程,其所有層次子進程的累計的次(minor)缺頁次數、主(major)次數及累計換出、換入頁面數

 

10.對稱多處理機(SMP)信息

Linux2.4SMP進行了全面的支持,表4.12是與多處理機相關的幾個域。

4.12 與多處理機相關的域

   定義形式

解釋

 Int has_cpu

 進程是否當前擁有CPU

 Int processor

 進程當前正在使用的CPU

 Int lock_depth

 上下文切換時內核鎖的深度

 

11 和處理器相關的環境(上下文)信息(Processor Specific Context

這裏要特別注意標題:和“處理器”相關的環境信息。進程作爲一個執行環境的綜合,當系統調度某個進程執行,即爲該進程建立完整的環境時,處理器(processor)的寄存器、堆棧等是必不可少的。因爲不同的處理器對內部寄存器和堆棧的定義不盡相同,所以叫做“和處理器相關的環境”,也叫做“處理機狀態”。當進程暫時停止運行時,處理機狀態必須保存在進程的task_struct結構中,當進程被調度重新運行時再從中恢復這些環境,也就是恢復這些寄存器和堆棧的值。處理機信息如表4.13所示。

 

4.13         與處理機相關的信息

定義形式

解釋

Struct thread_struct *tss

任務切換狀態

12.其它

1  struct wait_queue *wait_chldexit

 

  在進程結束時,或發出系統調用wait4時,爲了等待子進程的結束,而將自己(父進程)睡眠在該等待隊列上,設置狀態標誌爲TASK_INTERRUPTIBLE,並且把控制權轉給調度程序。

2Struct rlimit rlim[RLIM_NLIMITS];

 

     每一個進程可以通過系統調用setlimitgetlimit來限制它資源的使用。

 

     3Int exit_code exit_signal;

 

     程序的返回代碼以及程序異常終止產生的信號,這些數據由父進程(子進程完成後)     輪流查詢。

 

     4Char comm[16]

     這個域存儲進程執行的程序的名字,這個名字用在調試中。

 

     5Unsigned long personality;

    Linux可以運行X86平臺上其它Unix操作系統生成的符合iBCS2標準的程序, personality進一步描述進程執行的程序屬於何種Unix平臺的“個性”信息。通常有PER_Linux,PER_Linux_32BIT,PER_Linux_EM86,PER_SVR4,PER_SVR3,PER_SCOSVR3,PER_WYSEV386,PER_ISCR4,PER_BSD,PER_XENIXPER_MASK等,參見includeLinux/personality.h>

 

(6) int did_exec:1;

 

POSIX要求設計的布爾量,區分進程正在執行老程序代碼,還是用系統調用execve()裝入一個新的程序。

 

7struct linux_binfmt *binfmt

 

     指向進程所屬的全局執行文件格式結構,共有a.outscriptelfjava等四種。

 
綜上所述,我們對進程的task_struct結構進行了歸類討論,task_struct結構是進程實體的核心,Linux內核通過該結構來控制進程:首先通過其中的調度信息決定該進程是否運行;當該進程運行時,根據其中保存的處理機狀態信息來恢復進程運行現場,然後根據虛擬內存信息,找到程序的正文和數據;通過其中的通信信息和其他進程實現同步、通信等合作。幾乎所有的操作都要依賴該結構,所以,task_struct結構是一個進程存在的唯一標誌。
發佈了40 篇原創文章 · 獲贊 29 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章