Linux 核心的 thread_info 結構

發表於2016-11-12

本文基於Linux 3.5.4原始碼。

對每個程式,Linux核心都把兩個不同的資料結構緊湊的存放在一個單獨為程式分配的記憶體區域中:一個是核心態的程式堆疊,另一個是緊挨著程式描述符的小資料結構thread_info,叫做執行緒描述符。在較新的核心程式碼中,這個儲存區域的大小通常為8192個位元組(兩個頁框)。在linux/arch/x86/include/asm/page_32_types.h中,

出於效率考慮,核心讓這8K空間佔據連續的兩個頁框並讓第一個頁框的起始地址是213的倍數。

核心態的程式訪問處於核心資料段的棧,這個棧不同於使用者態的程式所用的棧。使用者態程式所用的棧,是在程式執行緒地址空間中;而核心棧是當程式從使用者空間進入核心空間時,特權級發生變化,需要切換堆疊,那麼核心空間中使用的就是這個核心棧。因為核心控制路徑使用很少的棧空間,所以只需要幾千個位元組的核心態堆疊。需要注意的是,核心態堆疊僅用於核心例程,Linux核心另外提供了單獨的硬中斷棧和軟中斷棧。

下圖中顯示了在實體記憶體中存放兩種資料結構的方式。執行緒描述符駐留與這個記憶體區的開始,而棧頂末端向下增長。 下圖摘自ULK3,但是較新的核心程式碼中,程式描述符task_struct結構中沒有直接指向thread_info結構的指標,而是用一個void指標型別的成員表示,然後通過型別轉換來訪問thread_info結構。相關程式碼在include/linux/sched.h中:

0_1271584604XTJq.gif

在這個圖中,esp暫存器是CPU棧指標,用來存放棧頂單元的地址。在80×86系統中,棧起始於頂端,並朝著這個記憶體區開始的方向增長。從使用者態剛切換到核心態以後,程式的核心棧總是空的。因此,esp暫存器指向這個棧的頂端。

一旦資料寫入堆疊,esp的值就遞減。在Linux3.5.4核心中,thread_info結構是72個位元組長(ULK3時代的核心中,這個結構的大小是52個位元組),因此核心棧能擴充套件到8120個位元組。thread_info結構的定義如下:

Linux核心中使用一個聯合體來表示一個程式的執行緒描述符和核心棧:

下面來說說如何通過esp棧指標來獲取當前在CPU上正在執行程式的thread_info結構。實際上,上面提到,thread_info結構和核心態堆疊是緊密結合在一起的,佔據兩個頁框的實體記憶體空間。而且,這兩個頁框的起始起始地址是213對齊的。所以,核心通過簡單的遮蔽掉esp的低13位有效位就可以獲得thread_info結構的基地址了。在檔案linux/arch/x86/include/asm/thread_info.h中,有如下程式碼:

在上面的程式碼中,當前的棧指標current_stack_pointer就是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的地址。

程式最常用的是程式描述符結構task_struct而不是thread_info結構的地址。為了獲取當前CPU上執行程式的task_struct結構,核心提供了current巨集,該巨集本質上等價於current_thread_info()->task,在include/asm-generic/current.h中定義:

相關文章