1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
state域能夠取5個互為排斥的值(通俗一點就是這五個值任意兩個不能一起使用,隻能單獨使用)。系統中的每個程序都必然處于以上所列程序狀态中的一種。
狀态
描述
task_running
表示程序要麼正在執行,要麼正要準備執行(已經就緒),正在等待cpu時間片的排程
task_interruptible
程序因為等待一些條件而被挂起(阻塞)而所處的狀态。這些條件主要包括:硬中斷、資源、一些信号……,一旦等待的條件成立,程序就會從該狀态(阻塞)迅速轉化成為就緒狀态task_running
task_uninterruptible
意義與task_interruptible類似,除了不能通過接受一個信号來喚醒以外,對于處于task_uninterrupible狀态的程序,哪怕我們傳遞一個信号或者有一個外部中斷都不能喚醒他們。隻有它所等待的資源可用的時候,他才會被喚醒。這個标志很少用,但是并不代表沒有任何用處,其實他的作用非常大,特别是對于驅動刺探相關的硬體過程很重要,這個刺探過程不能被一些其他的東西給中斷,否則就會讓進城進入不可預測的狀态
task_stopped
程序被停止執行,當程序接收到sigstop、sigttin、sigtstp或者sigttou信号之後就會進入該狀态
task_traced
表示程序被debugger等程序監視,程序執行被調試程式所停止,當一個程序被另外的程序所監視,每一個信号都會讓進城進入該狀态
exit_zombie
程序的執行被終止,但是其父程序還沒有使用wait()等系統調用來獲知它的終止資訊,此時程序成為僵屍程序
exit_dead
程序的最終狀态
而int exit_code, exit_signal;我們會在後面程序介紹
參見 <a href="http://www.ibm.com/developerworks/cn/linux/l-task-killable/index.html">task_killable:linux 中的新程序狀态</a>
如前所述,程序狀态 task_uninterruptible 和 task_interruptible 都是睡眠狀态。現在,我們來看看核心如何将程序置為睡眠狀态。
linux 核心提供了兩種方法将程序置為睡眠狀态。
将程序置為睡眠狀态的普通方法是将程序狀态設定為 task_interruptible 或 task_uninterruptible 并調用排程程式的 schedule() 函數。這樣會将程序從 cpu 運作隊列中移除。
如果程序處于可中斷模式的睡眠狀态(通過将其狀态設定為 task_interruptible),那麼可以通過顯式的喚醒呼叫(wakeup_process())或需要處理的信号來喚醒它。
但是,如果程序處于非可中斷模式的睡眠狀态(通過将其狀态設定為 task_uninterruptible),那麼隻能通過顯式的喚醒呼叫将其喚醒。除非萬不得已,否則我們建議您将程序置為可中斷睡眠模式,而不是不可中斷睡眠模式(比如說在裝置 i/o 期間,處理信号非常困難時)。
當處于可中斷睡眠模式的任務接收到信号時,它需要處理該信号(除非它已被屏弊),離開之前正在處理的任務(此處需要清除代碼),并将 -eintr 傳回給使用者空間。再一次,檢查這些傳回代碼和采取适當操作的工作将由程式員完成。
是以,懶惰的程式員可能比較喜歡将程序置為不可中斷模式的睡眠狀态,因為信号不會喚醒這類任務。
但需要注意的一種情況是,對不可中斷睡眠模式的程序的喚醒呼叫可能會由于某些原因不會發生,這會使程序無法被終止,進而最終引發問題,因為惟一的解決方法就是重新開機系統。一方面,您需要考慮一些細節,因為不這樣做會在核心端和使用者端引入 bug。另一方面,您可能會生成永遠不會停止的程序(被阻塞且無法終止的程序)。
現在,我們在核心中實作了一種新的睡眠方法
linux kernel 2.6.25 引入了一種新的程序睡眠狀态,
task_killable
當程序處于這種可以終止的新睡眠狀态中,它的運作原理類似于 task_uninterruptible,隻不過可以響應緻命信号
它定義如下:
換句話說,task_uninterruptible + task_wakekill = task_killable。
而task_wakekill 用于在接收到緻命信号時喚醒程序
新的睡眠狀态允許 task_uninterruptible 響應緻命信号
程序狀态的切換過程和原因大緻如下圖
unix系統通過pid來辨別程序,linux把不同的pid與系統中每個程序或輕量級線程關聯,而unix程式員希望同一組線程具有共同的pid,遵照這個标準linux引入線程組的概念。一個線程組所有線程與領頭線程具有相同的pid,存入tgid字段,getpid()傳回目前程序的tgid值而不是pid的值。
在config_base_small配置為0的情況下,pid的取值範圍是0到32767,即系統中的程序數最大為32768個。
在linux系統中,一個線程組中的所有線程使用和該線程組的領頭線程(該組中的第一個輕量級程序)相同的pid,并被存放在tgid成員中。隻有線程組的領頭線程的pid成員才會被設定為與tgid相同的值。注意,getpid()系統調用傳回的是目前程序的tgid值而不是pid值。
對每個程序,linux核心都把兩個不同的資料結構緊湊的存放在一個單獨為程序配置設定的記憶體區域中
一個是核心态的程序堆棧,
另一個是緊挨着程序描述符的小資料結構thread_info,叫做線程描述符。
linux把thread_info(線程描述符)和核心态的線程堆棧存放在一起,這塊區域通常是8192k(占兩個頁框),其實位址必須是8192的整數倍。
在linux/arch/x86/include/asm/page_32_types.h中,
出于效率考慮,核心讓這8k空間占據連續的兩個頁框并讓第一個頁框的起始位址是213的倍數。
核心态的程序通路處于核心資料段的棧,這個棧不同于使用者态的程序所用的棧。
使用者态程序所用的棧,是在程序線性位址空間中;
而核心棧是當程序從使用者空間進入核心空間時,特權級發生變化,需要切換堆棧,那麼核心空間中使用的就是這個核心棧。因為核心控制路徑使用很少的棧空間,是以隻需要幾千個位元組的核心态堆棧。
需要注意的是,核心态堆棧僅用于核心例程,linux核心另外為中斷提供了單獨的硬中斷棧和軟中斷棧
下圖中顯示了在實體記憶體中存放兩種資料結構的方式。線程描述符駐留與這個記憶體區的開始,而棧頂末端向下增長。 下圖摘自ulk3,程序核心棧與程序描述符的關系如下圖:
但是較新的核心代碼中,程序描述符task_struct結構中沒有直接指向thread_info結構的指針,而是用一個void指針類型的成員表示,然後通過類型轉換來通路thread_info結構。
在這個圖中,esp寄存器是cpu棧指針,用來存放棧頂單元的位址。在80x86系統中,棧起始于頂端,并朝着這個記憶體區開始的方向增長。從使用者态剛切換到核心态以後,程序的核心棧總是空的。是以,esp寄存器指向這個棧的頂端。一旦資料寫入堆棧,esp的值就遞減。
架構
定義連結
x86
<a href="http://lxr.free-electrons.com/source/arch/x86/include/asm/thread_info.h?v=4.5#l55">linux-4.5/arch/x86/include/asm/thread_info.h, line 55</a>
arm
<a href="http://lxr.free-electrons.com/source/arch/arm/include/asm/thread_info.h#l49">linux-4.5arch/arm/include/asm/thread_info.h, line 49</a>
arm64
<a href="http://lxr.free-electrons.com/source/arch/arm64/include/asm/thread_info.h#l47">linux/4.5/arch/arm64/include/asm/thread_info.h, line 47</a>
linux核心中使用一個聯合體來表示一個程序的線程描述符和核心棧:
下面來說說如何通過esp棧指針來擷取目前在cpu上正在運作程序的thread_info結構。
實際上,上面提到,thread_info結構和核心态堆棧是緊密結合在一起的,占據兩個頁框的實體記憶體空間。而且,這兩個頁框的起始起始位址是213對齊的。
早期的版本中,不需要對64位處理器的支援,是以,核心通過簡單的屏蔽掉esp的低13位有效位就可以獲得thread_info結構的基位址了。
我們在下面對比了,擷取正在運作的程序的thread_info的實作方式
版本
實作方式
思路解析
<a href="http://lxr.free-electrons.com/ident?v=3.14;i=current_thread_info">3.14</a>
<a href="http://lxr.free-electrons.com/source/arch/x86/include/asm/thread_info.h#l164">current_thread_info(void)</a>
return (struct thread_info *)(sp & ~(thread_size - 1));
屏蔽了esp的低十三位,最終得到的是thread_info的位址
<a href="http://lxr.free-electrons.com/ident?v=3.15;i=current_thread_info">3.15</a>
<a href="http://lxr.free-electrons.com/source/arch/x86/include/asm/thread_info.h?v=3.15#l163">current_thread_info(void)</a>
ti = (void *)(this_cpu_read_stable(kernel_stack) + kernel_stack_offset - thread_size);
<a href="http://lxr.free-electrons.com/ident?v=4.1&i=current_thread_info">4.1</a>
<a href="http://lxr.free-electrons.com/source/arch/x86/include/asm/thread_info.h?v=4.1#l182">current_thread_info(void)</a>
(struct thread_info *)(current_top_of_stack() - thread_size);
早期版本 目前的棧指針(current_stack_pointer == sp)就是esp, thread_size為8k,二進制的表示為0000 0000 0000 0000 0010 0000 0000 0000。 ~(thread_size-1)的結果剛好為1111 1111 1111 1111 1110 0000 0000 0000,第十三位是全為零,也就是剛好屏蔽了esp的低十三位,最終得到的是thread_info的位址。
這個定義是體系結構無關的,當然linux也為各個體系結構定義了更加友善或者快速的current
4.5
<a href="http://lxr.free-electrons.com/source/arch/x86/include/asm/page_32_types.h?v=4.5#l20">arch/x86/include/asm/page_32_types.h, line 20</a>
define thread_size_order 1
__get_free_pages函數配置設定2個頁的記憶體(它的首位址是8192位元組對齊的)
x86_64
<a href="http://lxr.free-electrons.com/source/arch/x86/include/asm/page_64_types.h?v=4.5#l10">arch/x86/include/asm/page_64_types.h, line 10</a>
define thread_size_order (2 + kasan_stack_order)
反應程序狀态的資訊,但不是運作狀态,用于核心識别程序目前的狀态,以備下一步操作
flags成員的可能取值如下,這些宏以pf(processflag)開頭
例如 pf_forknoexec 程序剛建立,但還沒執行。 pf_superpriv 超級使用者特權。 pf_dumpcore dumped core。 pf_signaled 程序被信号(signal)殺出。 pf_exiting 程序開始關閉。
在linux系統中,所有程序之間都有着直接或間接地聯系,每個程序都有其父程序,也可能有零個或多個子程序。擁有同一父程序的所有程序具有兄弟關系。
字段
real_parent
指向其父程序,如果建立它的父程序不再存在,則指向pid為1的init程序
parent
指向其父程序,當它終止時,必須向它的父程序發送信号。它的值通常與real_parent相同
children
表示連結清單的頭部,連結清單中的所有元素都是它的子程序
sibling
用于把目前程序插入到兄弟連結清單中
group_leader
指向其所在程序組的領頭程序
ptrace 提供了一種父程序可以控制子程序運作,并可以檢查和改變它的核心image。
它主要用于實作斷點調試。一個被跟蹤的程序運作中,直到發生一個信号。則程序被中止,并且通知其父程序。在程序中止的狀态下,程序的記憶體空間可以被讀寫。父程序還可以使子程序繼續執行,并選擇是否是否忽略引起中止的信号。
成員ptrace被設定為0時表示不需要被跟蹤,它的可能取值如下:
<a href="http://lxr.free-electrons.com/source/include/linux/ptrace.h?v=4.5#l20">http://lxr.free-electrons.com/source/include/linux/ptrace.h?v=4.5#l20</a>
performance event是一款随 linux 核心代碼一同釋出和維護的性能診斷工具。這些成員用于幫助performanceevent分析程序的性能問題。
static_prio
用于儲存靜态優先級,可以通過nice系統調用來進行修改
rt_priority
用于儲存實時優先級
normal_prio
的值取決于靜态優先級和排程政策
prio
用于儲存動态優先級
實時優先級範圍是0到max_rt_prio-1(即99),而普通程序的靜态優先級範圍是從max_rt_prio到max_prio-1(即100到139)。值越大靜态優先級越低。
policy
排程政策
sched_class
排程類
se
普通程序的調用實體,每個程序都有其中之一的實體
rt
實時程序的調用實體,每個程序都有其中之一的實體
cpus_allowed
用于控制程序可以在哪裡處理器上運作
policy表示程序的排程政策,目前主要有以下五種:
所在排程器類
sched_normal
(也叫sched_other)用于普通程序,通過cfs排程器實作。sched_batch用于非互動的處理器消耗型程序。sched_idle是在系統負載很低時使用
cfs
sched_batch
sched_normal普通程序政策的分化版本。采用分時政策,根據動态優先級(可用nice()api設定),配置設定 cpu 運算資源。注意:這類程序比上述兩類實時程序優先級低,換言之,在有實時程序存在時,實時程序優先排程。但針對吞吐量優化
sched_idle
優先級最低,在系統空閑時才跑這類程序(如利用閑散計算機資源跑地外文明搜尋,蛋白質結構分析等任務,是此排程政策的适用者)
sched_fifo
先入先出排程算法(實時排程政策),相同優先級的任務先到先服務,高優先級的任務可以搶占低優先級的任務
sched_rr
輪流排程算法(實時排程政策),後 者提供 roound-robin 語義,采用時間片,相同優先級的任務當用完時間片會被放到隊列尾部,以保證公平性,同樣,高優先級的任務可以搶占低優先級的任務。不同要求的實時任務可以根據需要用sched_setscheduler()api 設定政策
sched_deadline
新支援的實時程序排程政策,針對突發型計算,且對延遲和完成時間高度敏感的任務适用。基于earliest deadline first (edf) 排程算法
sched_class結構體表示排程類,目前核心中有實作以下四種:
排程器類
idle_sched_class
每個cup的第一個pid=0線程:swapper,是一個靜态線程。排程類屬于:idel_sched_class,是以在ps裡面是看不到的。一般運作在開機過程和cpu異常的時候做dump
stop_sched_class
優先級最高的線程,會中斷所有其他線程,且不會被其他任務打斷。作用:1.發生在cpu_stop_cpu_callback 進行cpu之間任務migration;2.hotplug_cpu的情況下關閉任務。
rt_sched_class
rt,作用:實時線程
fair_sched_class
cfs(公平),作用:一般正常線程
目前系統中,scheduling class的優先級順序為stoptask > realtime > fair > idletask
開發者可以根據己的設計需求,來把所屬的task配置到不同的scheduling class中.
mm
程序所擁有的使用者空間記憶體描述符,核心線程無的mm為null
active_mm
active_mm指向程序運作時所使用的記憶體描述符, 對于普通程序而言,這兩個指針變量的值相同。但是核心線程kernel thread是沒有程序位址空間的,是以核心線程的tsk->mm域是空(null)。但是核心必須知道使用者空間包含了什麼,是以它的active_mm成員被初始化為前一個運作程序的active_mm值。
brk_randomized
rss_stat
用來記錄緩沖資訊
是以如果目前核心線程被排程之前運作的也是另外一個核心線程時候,那麼其mm和avtive_mm都是null
exit_code
用于設定程序的終止代号,這個值要麼是_exit()或exit_group()系統調用參數(正常終止),要麼是由核心提供的一個錯誤代号(異常終止)。
exit_signal
被置為-1時表示是某個線程組中的一員。隻有當線程組的最後一個成員終止時,才會産生一個信号,以通知線程組的領頭程序的父程序。
pdeath_signal
用于判斷父程序終止時發送信号。
personality
in_execve
in_iowait
用于判斷是否進行iowait計數
sched_reset_on_fork
用于判斷是否恢複預設的優先級或排程政策
36
37
38
39
40
41
42
43
44
utime/stime
用于記錄程序在使用者态/核心态下所經過的節拍數(定時器)
prev_utime/prev_stime
utimescaled/stimescaled
用于記錄程序在使用者态/核心态的運作時間,但它們以處理器的頻率為刻度
gtime
以節拍計數的虛拟機運作時間(guest time)
nvcsw/nivcsw
是自願(voluntary)/非自願(involuntary)上下文切換計數
last_switch_count
nvcsw和nivcsw的總和
start_time/real_start_time
cputime_expires
用來統計程序或程序組被跟蹤的處理器時間,其中的三個成員對應着cpu_timers[3]的三個連結清單
signal
指向程序的信号描述符
sighand
指向程序的信号處理程式描述符
blocked
表示被阻塞信号的掩碼,real_blocked表示臨時掩碼
pending
存放私有挂起信号的資料結構
sas_ss_sp
是信号處理程式備用堆棧的位址,sas_ss_size表示堆棧的大小
在執行do_fork()時,如果給定特别标志,則vfork_done會指向一個特殊位址。
如果copy_process函數的clone_flags參數的值被置為clone_child_settid或clone_child_cleartid,則會把child_tidptr參數的值分别複制到set_child_tid和clear_child_tid成員。這些标志說明必須改變子程序使用者态位址空間的child_tidptr所指向的變量的值。
在ubuntu 11.04上,執行cat獲得程序1的i/o計數如下:
輸出的資料項剛好是task_io_accounting結構體的所有成員。
轉載:http://blog.csdn.net/gatieme/article/details/51383272