1. 程式人生 > >解析Linux核心獲取當前程序指標的方法

解析Linux核心獲取當前程序指標的方法

【轉載】原文出處:http://tech.ccidnet.com/art/302/20061115/950395_1.html


一、記憶體資料表示:


        我們在教材或閱讀中,經常需要直觀的用圖示來展示資料在記憶體中的分佈,那麼資料是如何在記憶體中組織的呢?不同的機器有不同的表示法,我們以最常見的Intel X86系列計算機為例來說明這個問題。

        如上圖示記憶體示意圖:記憶體低址在上。記憶體高址在下,記憶體單位為16bit。對於基於intel i386架構的計算機,系統採用小端位元組序來存放資料,所謂小端位元組序是指低序位元組低地址,高序位元組高地址(記憶體地址增大方向),大端位元組序反之,給定系統所用的位元組序稱為主機位元組序;CPU也以小端位元組序形式讀取資料,如上圖所示,如果變數num是16位的short短整型別,則CPU從記憶體中讀出的num=0x1234;如果num是32位的int型別,則CPU從記憶體中讀出的是num=0x56781234,其中num地址是0x12345678,即&num=0x12345678。


二、linux核心獲取程序任務結構的指標


       明白了系統記憶體資料表示,我們現在來看看linux核心是如何獲取當前程序的任務結構指標的,以下程式碼均參照linux核心2.4.0的原始碼。


           在include\asm-i386\ current.h中


                                   #ifndef _I386_CURRENT_H
                                  #define _I386_CURRENT_H
                                  struct task_struct;
                                  static inline struct task_struct * get_current(void)
                                  {
                                             struct task_struct *current;
                                             __asm__("andl %%esp,%0; ":"=r" (current) : "0" (~8191UL));
                                            return current;
                                  }


                                  #define current get_current()
                                  #endif /* !(_I386_CURRENT_H) */


        每個程序都有一個task_struct任務結構,和一片用於系統空間堆疊的儲存空間,他們在實體記憶體空間中也是聯絡在一起的,當給程序申請task_struct任務結構空間時,系統將連同系統的堆疊空間一起分配,如下圖為某個程序切換時刻的記憶體圖:



          下面針對程式碼實現來分析一下系統如何通過一系列操作獲取程序在核心中的任務結構指標的:


          由於linux核心分配程序任務結構空間時,是以8KB(2個頁面空間,即2^1*4KB,linux對實體記憶體空間和虛擬記憶體空間管理時,均規定其頁面單位的尺寸為4KB)為單位來分配的,所以記憶體應用地址是8KB(2^13)的整數倍,即指標地址的低13位全為0,所以根據小端位元組序,分配記憶體返回地址應該是指向struct task_struct結構,如圖中的0xc2342000地址所指,至於為何採用程式碼中的做法而不是直接將此指標儲存在全域性變數中以供應用,核心是從其自身的效率方面來考慮的,我們在此只針對程式碼解釋:根據上圖,此刻記憶體esp內容必定在0xc2342000和0xc2344000之間的一個數值,我們假設取0xc2343ffe(即堆疊壓棧EIP、返回地址、內部資料等相關資料了,地址值要減小;只要符合0xc2342xxx 、0xc2343xxx的地址指標都是正確的),來通過程式碼運算看是否current的指標是0xc2342000。


         __asm__("andl %%esp,%0; ":"=r" (current) : "0" (~8191UL));語句的意思是將ESP的內容與8191UL的反碼按位進行與操作,之後再把結果賦值給current,其中8191UL=8192-1=2^13-1,計算過程如下:


                                   8192UL=2^13 0000 0000 0000 0000 0010 0000 0000 0000


                                  8191UL 0000 0000 0000 0000 0001 1111 1111 1111


                                  ~8191UL(反碼) 1111 1111 1111 1111 1110 0000 0000 0000


                                  0xc2343ffe 1100 0010 0011 0100 0011 1111 1111 1110


                                  andl結果: 1100 0010 0011 0100 0010 0000 0000 0000 


                                  || (對照著看)


                                  0x c 2 3 4 2 0 0 0
             所以按位與操作之後的結果位0xc2342000,正好是struct task_struct結構的地址指標.通過觀察可知,只要符合0xc2342xxx 、0xc2343xxx的地址指標經過相同的計算,都可以得到核心程序任務結構的指標。


           另外,在進入中斷或系統呼叫時所引用的巨集操作(include\asm-i386\ hw_irq.h):


                                   #define GET_CURRENT \


                                  "movl %esp, %ebx\n\t" \


                                  "andl $-8192, %ebx\n\t"
其原理與上述描述也是一致的。