關(guān)注+星標(biāo)公眾號,程管程不錯過精彩內(nèi)容
作者 |?吳偉東
轉(zhuǎn)自 |?嵌入式Hacker
目的理何:
初步了解進(jìn)程描述符 task_struct。
目錄:
Linux 的描述進(jìn)程 Linux 的進(jìn)程描述符
task_struct 內(nèi)核如何找到 task_struct task_struct 的分配和初始化
環(huán)境:
Linux-4.14 + ARMv7
1. Linux 的進(jìn)程
進(jìn)程的術(shù)語是 process,是個進(jìn) Linux 最基礎(chǔ)的抽象,另一個基礎(chǔ)抽象是核進(jìn)文件。
最簡單的程管程理解,進(jìn)程就是理何執(zhí)行中 (executing, 不等于running) 的程序。
更準(zhǔn)確一點的描述理解,進(jìn)程包括執(zhí)行中的個進(jìn)程序以及相關(guān)的資源(包括cpu狀態(tài)、打開的核進(jìn)文件、掛起的程管程信號、tty、理何內(nèi)存地址空間等)。描述
一種簡潔的個進(jìn)說法:進(jìn)程 = n*執(zhí)行流 + 資源,n>=1。
Linux 進(jìn)程的特點:
通過系統(tǒng)調(diào)用 fork() 創(chuàng)建進(jìn)程,fork() 會復(fù)制現(xiàn)有進(jìn)程來創(chuàng)建一個全新的進(jìn)程。
內(nèi)核里,并不嚴(yán)格區(qū)分進(jìn)程和線程。
從內(nèi)核的角度看,調(diào)度單位是線程 (即執(zhí)行流)。可以把線程看做是進(jìn)程里的一條執(zhí)行流,1個進(jìn)程里可以有1個或者多個線程。
內(nèi)核里,常把進(jìn)程稱為 task 或者 thread,這樣描述更準(zhǔn)確,因為許多進(jìn)程就只有1條執(zhí)行流。
內(nèi)核通過輕量級進(jìn)程 (lightweight process) 來支持多線程。1個輕量級進(jìn)程就對應(yīng)1個線程,輕量級進(jìn)程之間可以共享打開的文件、地址空間等資源。
2. Linux 的進(jìn)程描述符
2.1 task_struct
內(nèi)核里,通過 task_struct 結(jié)構(gòu)體來描述一個進(jìn)程,稱為進(jìn)程描述符 (process descriptor),它保存著支撐一個進(jìn)程正常運行的所有信息。
每一個進(jìn)程,即便是輕量級進(jìn)程(即線程),都有1個 task_struct。
sched.h?(include\linux)
struct?task_struct?{
????struct?thread_info?thread_info;
????volatile?long?state;
????void?*stack;
????[...]
????struct?mm_struct?*mm;
????[...]
????pid_t?pid;
????[...]
????struct?task_struct?*parent;
????[...]
????char?comm[TASK_COMM_LEN];
????[...]
?struct?files_struct?*files;
?[...]
?struct?signal_struct?*signal;
}
這是一個龐大的結(jié)構(gòu)體,不僅有許多進(jìn)程相關(guān)的基礎(chǔ)字段,還有許多指向其他數(shù)據(jù)結(jié)構(gòu)的指針。
它包含的字段能完整地描述一個正在執(zhí)行的程序,包括 cpu 狀態(tài)、打開的文件、地址空間、掛起的信號、進(jìn)程狀態(tài)等。
作為初學(xué)者,先簡單地了解部分字段就好::
struct thread_info thread_info:進(jìn)程底層信息,平臺相關(guān),下面會詳細(xì)描述。
long state:進(jìn)程當(dāng)前的狀態(tài),下面是幾個比較重要的進(jìn)程狀態(tài)以及它們之間的轉(zhuǎn)換流程。
void *stack:指向進(jìn)程內(nèi)核棧,下面會解釋。
struct mm_struct *mm:與進(jìn)程地址空間相關(guān)的信息都保存在一個叫內(nèi)存描述符 (memory descriptor) 的結(jié)構(gòu)體 (mm_struct) 中。
pid_t pid:進(jìn)程標(biāo)識符,本質(zhì)就是一個數(shù)字,是用戶空間引用進(jìn)程的唯一標(biāo)識。
struct task_struct *parent:父進(jìn)程的 task_struct。
char comm[TASK_COMM_LEN]:進(jìn)程的名稱。
struct files_struct *files:打開的文件表。
struct signal_struct *signal:信號處理相關(guān)。
其他字段,等到有需要的時候再回過頭來學(xué)習(xí)。
2.2 當(dāng)發(fā)生系統(tǒng)調(diào)用或者進(jìn)程切換時,內(nèi)核如何找到 task_struct ?
對于 ARM 架構(gòu),答案是:通過內(nèi)核棧 (kernel mode stack)。
為什么要有內(nèi)核棧?
因為內(nèi)核是可重入的,在內(nèi)核中會有多條與不同進(jìn)程相關(guān)聯(lián)的執(zhí)行路徑。因此不同的進(jìn)程處于內(nèi)核態(tài)時,都需要有自己私有的進(jìn)程內(nèi)核棧 (process kernel stack)。
當(dāng)進(jìn)程從用戶態(tài)切換到內(nèi)核態(tài)時,所使用的棧會從用戶棧切換到內(nèi)核棧。
至于是如何切換的,關(guān)鍵詞是系統(tǒng)調(diào)用,這不是本文關(guān)注的重點,先放一邊,學(xué)習(xí)內(nèi)核要懂得恰當(dāng)?shù)臅r候忽略細(xì)節(jié)。
當(dāng)發(fā)生進(jìn)程切換時,也會切換到目標(biāo)進(jìn)程的內(nèi)核棧。
同上,關(guān)鍵詞是硬件上下文切換 (hardware context switch),忽略具體實現(xiàn)。
無論何時,只要進(jìn)程處于內(nèi)核態(tài),就會有內(nèi)核棧可以使用,否則系統(tǒng)就離崩潰不遠(yuǎn)了。
ARM 架構(gòu)的內(nèi)核棧和 task_struct 的關(guān)系如下:
內(nèi)核棧的長度是 THREAD_SIZE,對于 ARM 架構(gòu),一般是 2 個頁框的大小,即 8KB。
內(nèi)核將一個較小的數(shù)據(jù)結(jié)構(gòu) thread_info 放在內(nèi)核棧的底部,它負(fù)責(zé)將內(nèi)核棧和 task_struct 串聯(lián)起來。thread_info 是平臺相關(guān)的,在 ARM 架構(gòu)中的定義如下:
//?thread_info.h?(arch\arm\include\asm)
struct?thread_info?{
?unsigned?long?flags;??/*?low?level?flags?*/
?int?preempt_count;?/*?0?=>?preemptable,?<0?=>?bug?*/
?mm_segment_t?addr_limit;?/*?address?limit?*/
?struct?task_struct?*task;??/*?main?task?structure?*/
????[...]
?struct?cpu_context_save?cpu_context;?/*?cpu?context?*/
?[...]
};
thread_info 保存了一個進(jìn)程能被調(diào)度執(zhí)行的最底層信息(low level task data),例如struct cpu_context_save cpu_context 會在進(jìn)程切換時用來保存/恢復(fù)寄存器上下文。
內(nèi)核通過內(nèi)核棧的棧指針可以快速地拿到 thread_info:
//?thread_info.h?(include\linux)
static?inline?struct?thread_info?*current_thread_info(void)
{
????//?current_stack_pointer?是當(dāng)前進(jìn)程內(nèi)核棧的棧指針
?return?(struct?thread_info?*)
??(current_stack_pointer?&?~(THREAD_SIZE?-?1));
}
然后通過 thread_info 找到 task_struct:
//?current.h?(include\asm-generic)
#define?current?(current_thread_info()->task)
內(nèi)核里通過 current 宏可以獲得當(dāng)前進(jìn)程的 task_struct。
2.3 task_struct 的分配和初始化
當(dāng)上層應(yīng)用使用 fork() 創(chuàng)建進(jìn)程時,內(nèi)核會新建一個 task_struct。
進(jìn)程的創(chuàng)建是個復(fù)雜的工作,可以延伸出無數(shù)的細(xì)節(jié)。這里我們只是簡單地了解一下 task_struct 的分配和部分初始化的流程。
fork() 在內(nèi)核里的核心流程:
dup_task_struct() 做了什么?
至于設(shè)置內(nèi)核棧里做了什么,涉及到了進(jìn)程的創(chuàng)建與切換,不在本文的關(guān)注范圍內(nèi),以后再研究了。
3. 實驗:打印 task_struct / thread_info / kernel mode stack
實驗?zāi)康模?/strong>
梳理 task_struct / thread_info / kernel mode stack 的關(guān)系。
實驗代碼:
#include?
#include?
#include?
static?void?print_task_info(struct?task_struct?*task)
{
????printk(KERN_NOTICE?"%10s?%5d?task_struct?(%p)?/?stack(%p~%p)?/?thread_info->task?(%p)",
????????task->comm,?
????????task->pid,
????????task,
????????task->stack,
????????((unsigned?long?*)task->stack)?+?THREAD_SIZE,
????????task_thread_info(task)->task);
}
static?int?__init?task_init(void)
{
????struct?task_struct?*task?=?current;
????printk(KERN_INFO?"task?module?init\n");
????print_task_info(task);
????do?{
????????task?=?task->parent;
????????print_task_info(task);
????}?while?(task->pid?!=?0);
????return?0;
}
module_init(task_init);
static?void?__exit?task_exit(void)
{
????printk(KERN_INFO?"task?module?exit\n?");
}
module_exit(task_exit);
運行效果:
task?module?init
????insmod??3123?task_struct?(edb42580)?/?stack(ed46c000~ed474000)?/?thread_info->task?(edb42580)
??????bash??2393?task_struct?(eda13e80)?/?stack(c9dda000~c9de2000)?/?thread_info->task?(eda13e80)
??????sshd??2255?task_struct?(ee5c9f40)?/?stack(c9d2e000~c9d36000)?/?thread_info->task?(ee5c9f40)
??????sshd???543?task_struct?(ef15f080)?/?stack(ee554000~ee55c000)?/?thread_info->task?(ef15f080)
???systemd?????1?task_struct?(ef058000)?/?stack(ef04c000~ef054000)?/?thread_info->task?(ef058000)
在程序里,我們通過 task_struct 找到 stack,然后通過 stack 找到 thread_info,最后又通過 thread_info->task 找到 task_struct。
4. 相關(guān)參考
Linux 內(nèi)核設(shè)計與實現(xiàn) / 第 3.1 章節(jié)
深入理解 Linux 內(nèi)核 / 3
Linux 內(nèi)核深度解析 / 2.5.1
深入Linux 內(nèi)核架構(gòu) / 2.3
點擊“閱讀原文”查看更多分享,歡迎點分享、收藏、點贊、在看。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!