轉自:http://edsionte.com/techblog/archives/2198/comment-page-1
對於每一個進程而言,內核爲其單獨分配了一個內存區域,這個區域存儲的是內核棧和該進程所對應的一個小型進程描述符——thread_info結構。
02 |
struct task_struct
*task; |
03 |
struct exec_domain
*exec_domain; |
08 |
mm_segment_t
addr_limit; |
09 |
struct restart_block
restart_block; |
10 |
unsigned long previous_esp; |
11 |
__u8
supervisor_stack[0]; |
之所以將thread_info結構稱之爲小型的進程描述符,是因爲在這個結構中並沒有直接包含與進程相關的字段,而是通過task字段指向具體某個進程描述符。通常這塊內存區域的大小是8KB,也就是兩個頁的大小(有時候也使用一個頁來存儲,即4KB)。一個進程的內核棧和thread_info結構之間的邏輯關係如下圖所示:
從上圖可知,內核棧是從該內存區域的頂層向下(從高地址到低地址)增長的,而thread_info結構則是從該區域的開始處向上(從低地址到高地址)增長。內核棧的棧頂地址存儲在esp寄存器中。所以,當進程從用戶態切換到內核態後,esp寄存器指向這個區域的末端。
從代碼的角度來看,內核棧和thread_info結構是被定義在一個聯合體當中的:
3 |
struct thread_info
thread_info; |
4 |
unsigned long stack[THREAD_SIZE/ sizeof ( long )]; |
其中,THREAD_SIZE的值取8192時,stack數組的大小爲2048;THREAD_SIZE的值取4096時,stack數組的大小爲1024。現在我們應該思考,爲何要將內核棧和thread_info(其實也就相當於task_struct,只不過使用thread_info結構更節省空間)緊密的放在一起?最主要的原因就是內核可以很容易的通過esp寄存器的值獲得當前正在運行進程的thread_info結構的地址,進而獲得當前進程描述符的地址。
2 |
static inline struct thread_info
*current_thread_info( void ) |
4 |
struct thread_info
*ti; |
5 |
__asm__( "andl
%%esp,%0; " : "=r" (ti)
: "0" (~(THREAD_SIZE
- 1))); |
這條內聯彙編語句會屏蔽掉esp寄存器中的內核棧頂地址的低13位(或12位,當THREAD_SIZE爲4096時)。此時ti所指的地址就是這片內存區域的起始地址,也就剛好是thread_info結構的地址。但是,thread_info結構的地址並不會對我們直接有用。我們通常可以輕鬆的通過current宏獲得當前進程的task_struct結構,這個宏是如何實現的?
2 |
static inline struct task_struct
* get_current( void ) |
4 |
return current_thread_info()->task; |
6 |
#define
current get_current() |
通過上述源碼可以發現,current宏返回的是thread_info結構task字段。而task正好指向與thread_info結構關聯的那個進程描述符。得到current後,我們就可以獲得當前正在運行進程的描述符中任何一個字段了,比如我們通常所做的:current->pid。