task_struct結構體(PCB)描述
task_struct結構描述
在linux 中每一個程序都由task_struct 資料結構來定義. task_struct就是我們通常所說的PCB.她是對程序控制的唯一手段也是最有效的手段. 當我們呼叫fork() 時, 系統會為我們產生一個task_struct結構。然後從父程序,那裡繼承一些資料, 並把新的程序插入到程序樹中, 以待進行程序管理。因此瞭解task_struct的結構對於我們理解任務排程(在linux 中任務和程序是同一概念)的關鍵。
1. 程序狀態(State)
程序執行時,它會根據具體情況改變狀態 。程序狀態是排程和對換的依據。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()等。
·暫停狀態
此時的程序暫時停止執行來接受某種特殊處理。通常當程序接收到SIGSTOP、SIGTSTP、SIGTTIN或 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所示。
不管對核心還是普通使用者來說,怎麼用一種簡單的方式識別不同的程序呢?這就引入了程序識別符號(PID:process identifier),每個程序都有一個唯一的識別符號,核心通過這個識別符號來識別不同的程序,同時,程序識別符號PID也是核心提供給使用者程式的介面,使用者程式通過PID對程序發號施令。PID是32位的無符號整數,它被順序編號:新建立程序的PID通常是前一個程序的PID加1。然而,為了與16位硬體平臺的傳統Linux系統保持相容,在Linux上允許的最大PID號是32767,當核心在系統中建立第32768個程序時,就必須重新開始使用已閒置的PID號。
表4.4 各種識別符號
域名 |
含義 |
Pid |
程序識別符號 |
Uid、gid |
使用者識別符號、組識別符號 |
Euid、egid |
有效使用者識別符號、有效組識別符號 |
Suid、sgid |
備份使用者識別符號、備份組識別符號 |
Fsuid、fsgid |
檔案系統使用者識別符號、檔案系統組識別符號 |
另外,每個程序都屬於某個使用者組。task_struct結構中定義有使用者識別符號和組識別符號。它們同樣是簡單的數字,這兩種識別符號用於系統的安全控制。系統通過這兩種識別符號控制程序對系統中檔案和裝置的訪問,其它幾個識別符號將在檔案系統中討論。
4. 程序通訊有關資訊(IPC:Inter_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結構就構成了一棵程序樹,這棵程序樹的根就是初始化程序init的task_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 08、INT 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中,每個定時器都有兩個域來表示到期時間和定時間隔:value和incr,二者的單位都是時鐘滴答,和jiffies的單位是一致的,Linux所有的時間應用都建立在jiffies之上。虛擬定時器和概況定時器到期時由核心傳送相應的訊號,而實時定時器比較特殊,它由核心機制提供支援,我們將在後面討論這個問題。
每個時鐘中斷,當前程序所有和時間有關的資訊都要更新: 當前程序耗費的CPU時間要更新,以便於最後的計費;時間片計數器counter要更新,如果counter<=0,則要執行排程程式;程序申請的延時要更新,如果延時時間到了,則喚醒該程序;所有的定時器都要更新,Linux核心檢測這些定時器是否到期,如果到期,則執行相應的操作。在這裡,“更新”的具體操作是不同的:對counter,核心要對它減值,而對於所有的定時器,就是檢測它的值,核心把系統當前時間和其到期時間作一比較,如果到期時間小於系統時間,則表示該定時器到期。但為了方便,我們把這些操作一概稱為“更新”,請讀者注意。
請特別注意上面三個定時器的更新時間。實時定時器不管其所屬的程序是否執行都要更新,所以,時鐘中斷來臨時,系統中所有程序的實時定時器都被更新,如果有多個程序的實時定時器到期,則核心要一一處理這些定時器所觸發的事件。而虛擬定時器和概況定時器只在程序執行時更新,所以,時鐘中斷來臨時,只有當前程序的概況定時器得到更新,如果當前程序運行於使用者態,則其虛擬定時器也得到更新。
此外,Linux核心對這三種定時器的處理是不同的,虛擬定時器和概況定時器到期時,核心向當前程序傳送相應的訊號:SIGVTALRM 、SIGPROF ;而實時定時器要執行的操作由real_timer決定,real_time是timer_list型別的變數(定義:struct timer_list real_timer),其中容納了實時定時器的到期時間、定時間隔等資訊,我們將在下一章詳細討論這些內容。
7. 檔案系統資訊(File System)
程序可以開啟或關閉檔案,檔案屬於系統資源,Linux核心要對程序使用檔案的情況進行記錄。task_struct結構中有兩個資料結構用於描述程序與檔案相關的資訊。其中,fs_struct中描述了兩個VFS索引節點(VFS inode),這兩個索引節點叫做root和pwd,分別指向程序的可執行映象所對應的根目錄(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.4對SMP進行了全面的支援,表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,並且把控制權轉給排程程式。
(2)Struct rlimit rlim[RLIM_NLIMITS];
每一個程序可以通過系統呼叫setlimit和getlimit來限制它資源的使用。
(3)Int exit_code exit_signal;
程式的返回程式碼以及程式異常終止產生的訊號,這些資料由父程序(子程序完成後) 輪流查詢。
(4)Char comm[16]
這個域儲存程序執行的程式的名字,這個名字用在除錯中。
(5)Unsigned 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_XENIX和PER_MASK等,參見include/Linux/personality.h>。
(6) int did_exec:1;
按POSIX要求設計的布林量,區分程序正在執行老程式程式碼,還是用系統呼叫execve()裝入一個新的程式。
(7)struct linux_binfmt *binfmt
指向程序所屬的全域性執行檔案格式結構,共有a.out、script、elf、java等四種。
綜上所述,我們對程序的task_struct結構進行了歸類討論,還有一些域沒有涉及到,在第六章程序的建立與執行一節幾乎涉及到所有的域,在那裡可以對很多域有更深入一步的理解。task_struct結構是程序實體的核心,Linux核心通過該結構來控制程序:首先通過其中的排程資訊決定該程序是否執行;當該程序執行時,根據其中儲存的處理機狀態資訊來恢復程序執行現場,然後根據虛擬記憶體資訊,找到程式的正文和資料;通過其中的通訊資訊和其他程序實現同步、通訊等合作。幾乎所有的操作都要依賴該結構,所以,task_struct結構是一個程序存在的唯一標誌。
相關推薦
task_struct結構體(PCB)描述
task_struct結構描述 在linux 中每一個程序都由task_struct 資料結構來定義. task_struct就是我們通常所說的PCB.她是對程序控制的唯一手段也是最有效的手段. 當我們呼叫fork() 時, 系統會為我們產生一個task_struct結構
C語言結構體(Struct)
C語言結構體(Struct) 在C語言中,可以使用結構體(Struct)來存放一組不同型別的資料。結構體的定義形式為: struct 結構體名{ 結構體所包含的變數或陣列 }; 結構體是一種集合,它裡面包含了多個變數或陣列,它們的型別可以相同,也可
go——結構體(二)
Go語言是一種靜態型別的程式語言。這意味著,編譯器需要在編譯時知曉程式裡每個值的型別。 如果提前知道型別資訊,編譯器就可以確保程式合理的使用值。 這有助於減少潛在的記憶體異常和bug,並且使編譯器有機會對程式碼進行一些效能優化,提高執行效率。 值的型別給編譯器提供了兩部分資訊:第一部分,需要分配多少記憶
結構體(struct)與類(class)
在 C++ 的範疇裡,除了 struct 結構體不支援訪問許可權修飾符(private、protected、public)外,已不再區分二者的其他方面語法上的差異。 因此,在 C++ 程式設計中,如果一個類是由 struct 來定義,說明其所有成員都可直接訪問
C# 之 結構體(struct)---複合型別的資料結構
例題: /*建立學生結構體,輸出最高分數*/ /*使用結構體陣列 -----複合型別(string int float等) student[] students=ne
聯合體(union)和結構體(struct)的區別
1. 聯合說明和聯合變數定義 聯合也是一種新的資料型別, 它是一種特殊形式的變數。 聯合說明和聯合變數定義與結構十分相似。其形式為: union 聯合名{ 資料型別 成員名; 資料型別 成員名; ... } 聯合變數名; 聯合表示幾個變數公
swift類和結構體(一)
An instance of a class is traditionally known as an object. However, Swift classes and structures are much closer in functionality
Linux 驅動之塊裝置結構體 (二)
上回最後面介紹了相關資料結構,下面再詳細介紹 塊裝置物件結構 block_device 核心用結構block_device例項代表一個塊裝置物件,如:整個硬碟或特定分割槽。如果該結構代表一個分割槽,則其成員bd_part指向裝置的分割槽結構。如果該結構代表裝置,則其成員
c語言基礎語法六——結構體(完結)
1;關於c語言結構體的引入; 在前面已經介紹了整形(int,long,….),浮點型(flaot,double),字元型(char),還介紹了陣列(儲存一組具有相同型別的資料),字串。但是在實際問題中只有這些資料型別是不夠的,有時候我們需要其中的幾種一起來修飾
【C#基礎】列舉(Enum)、結構體(Struct)、委託(Delegate)
1.列舉(Enum) 確定數量,確定取值。方向(東南西北),性別(男女) 語法:[public] enum 列舉名 { 值1, 值2, 值3,
C語言結構體(struct)常見使用方法
今天覆習一下struct,順便挖掘一下以前沒注意的小細節: 基本定義:結構體,通俗講就像是打包封裝,把一些有共同特徵(比如同屬於某一類事物的屬性,往往是某種業務相關屬性的聚合)的變數封裝在內部,通過一定方法訪問修改內部變數。 結構體定義: 第一種:只有結構體定義
C++學習筆記之五 複合型別之結構體(struct)
結構宣告的位置很重要。可以有兩種宣告位置:一種是可以將宣告放在main()函式中,緊跟在開始括號的後面。另一種是將宣告放在main()函式的前面,這裡採用的就是這種方式,位於函式外面的宣告稱為外部宣告。對於只有一個主函式的程式來說放在外面和裡面沒有區別,但是如果有多個函式,外部宣告可以被其後面的任何函式使用,
結構體(struct)的不同寫法和tag字首
結構體有多種不同的宣告定義寫法,且在C、C++中使用略有差別,再者用typedef帶來方便的同時,也造成了更多寫法的混淆。本文的目的就是總結struct的各種宣告定義方法,理清思路,避免混淆。總結有誤之處,請大家指正。 一、struct宣告定義總結 結構體宣告由三部分組成,tag, member
C語言結構體(struct)常見定義和使用-struct
一、簡介 本文介紹如何在C語言結構體型別的定義與應用。 二、實驗平臺 1:本文如下實驗所用的上位機軟體為 VS2010 2:需要本文工程原始碼或有疑惑,請加群84342712進入群共享下載 三、 宣告:喝水不忘挖井人,轉載請註明出處。 原文
Unity中的結構體(C#)
usingUnityEngine; usingSystem.Collections; public class Foo { public Vector3 pos { get{ return _pos; } set{ _pos = value; } } private Vector3 _
結構體(Union)記憶體分配
#include <stdio.h> #include <iostream> using namespace std; union U1 { char s[9]; //偏移為
go遍歷結構體(struct)欄位對應的值,切片(slice),字典(map)
一、遍歷結構體欄位: eg1: package main import ( "fmt" "reflect" ) type person struct { name string age int } func main()
Linux進程描述符task_struct結構體詳解--Linux進程的管理與調度(一)【轉】
mage ase 設備 兩種 等價 而且 當前 結構體 技術 Linux內核通過一個被稱為進程描述符的task_struct結構體來管理進程,這個結構體包含了一個進程所需的所有信息。它定義在include/linux/sched.h文件中。 談到task_struct結構體
小白學開發(iOS)OC_ 經常使用結構體(2015-08-14)
轉換成 tracking 小白 epo idt rgb 表示範圍 dsm tor // // main.m // 經常使用結構體 // // Created by admin on 15/8/13. // Copyright (c) 2015年 admin.
jchdl - RTL例項 - And2(結構體的使用)
https://mp.weixin.qq.com/s/qTgeBF9N0mx5UK3xWDb3jg jchdl對Verilog做了增強,增加了使用者自定義結構體型別。使用自定義結構體,可以對輸入和輸出介面進行分類,並簡化模組輸入輸出介面的定義。 參考連