linux核心—– swtich_to 詳細分析

來源:互聯網
上載者:User

linux核心進程切換最重要的一個部分就是宏定義switch_to,下面從幾個方面來詳細講解一下:

(1)內嵌彙編

(2)memory 破壞描述符(編譯器最佳化)

(3)進程切換的標誌是什嗎?

(4)堆棧切換的標誌是什嗎?

(5)為什麼switch_to 提供了三個參數?

(6)彙編參數的傳遞?

 

帶著這幾個問題,先來大體瀏覽一下代碼

 

#define switch_to(prev, next, last)     /
do {         /
 /*        /
  * Context-switching clobbers(徹底擊敗) all registers, so we clobber /
  * them explicitly, via unused output variables.  /
  * (EAX and EBP is not listed because EBP is saved/restored /
  * explicitly for wchan access and EAX is the return value of /
  * __switch_to())      /
  */        /
 unsigned long ebx, ecx, edx, esi, edi;    /
         /
 asm volatile("pushfl/n/t"  /* save    flags */ /
       "pushl %%ebp/n/t"  /* save    EBP   */ /
       "movl %%esp,%[prev_sp]/n/t" /* save    ESP   */ /
       "movl %[next_sp],%%esp/n/t" /* restore ESP   */ /
       "movl $1f,%[prev_ip]/n/t" /* save    EIP   */ /
       "pushl %[next_ip]/n/t" /* restore EIP   */ /
       "jmp __switch_to/n" /* regparm call  */ /
       "1:/t"      /
       "popl %%ebp/n/t"  /* restore EBP   */ /
       "popfl/n"   /* restore flags */ /
         /
       /* output parameters */                       /
       : [prev_sp] "=m" (prev->thread.sp),  /
       /*m表示把變數放入記憶體,即把[prev_sp]儲存的變數放入記憶體,最後再寫入prev->thread.sp*//
         [prev_ip] "=m" (prev->thread.ip),  /
         "=a" (last),                                           /
         /*=表示輸出,a表示把變數last放入ax,eax = last*/         /
         /
         /* clobbered output registers: */  /
         "=b" (ebx), "=c" (ecx), "=d" (edx),  /
         /*b 變數放入ebx,c表示放入ecx,d放入edx,S放入si,D放入edi*//
         "=S" (esi), "=D" (edi)    /
                /
         /* input parameters: */    /
       : [next_sp]  "m" (next->thread.sp),  /
       /*next->thread.sp 放入記憶體中的[next_sp]*//
         [next_ip]  "m" (next->thread.ip),  /
                /
         /* regparm parameters for __switch_to(): */ /
         [prev]     "a" (prev),    /
         /*eax = prev  edx = next*//
         [next]     "d" (next)    /
         /
       : /* reloaded segment registers */   /
   "memory");     /
} while (0)

 

以上代碼,主要是內嵌彙編,這裡先簡單介紹一下:

     1 內嵌彙編文法

 __asm__ __violate__ ("movl %1,%0" : "=r" (result) : "m" (input));

 

 __asm__ __violate__("指令模板" : 輸出部 : 輸入部);

 

“movl %1,%0”是指令模板;“%0”和“%1”代表指令的運算元,稱為預留位置,內嵌匯
編靠它們將C 語言運算式與指令運算元相對應。指令模板後面用小括弧括起來的是C語言表
達式,本例中只有兩個:“result”和“input”,他們按照出現的順序分別與指令運算元“%0”,
“%1”對應;注意對應順序:第一個C 運算式對應“%0”;第二個運算式對應“%1”,依次類
推,運算元至多有10 個,分別用“%0”,“%1”….“%9”表示。在每個運算元前面有一個
用引號括起來的字串,字串的內容是對該運算元的限制或者說要求。“result”前面的
限制字串是“=r”,其中“=”表示“result”是輸出運算元,“r”表示需要將“result”
與某個通用寄存器相關聯,先將運算元的值讀入寄存器,然後在指令中使用相應寄存器,而
不是“result”本身,當然指令執行完後需要將寄存器中的值存入變數“result”,從表面
上看好像是指令直接對“result”進行操作,實際上GCC做了隱式處理,這樣我們可以少寫
一些指令。“input”前面的“r”表示該運算式需要先放入某個寄存器,然後在指令中使用
該寄存器參加運算。

(2)memory 破壞描述符(編譯器最佳化)

記憶體訪問速度遠不及CPU處理速度,為提高機器整體效能,在硬體上引入硬體快取
Cache,加速對記憶體的訪問。另外在現代CPU中指令的執行並不一定嚴格按照順序執行,沒
有相關性的指令可以亂序執行,以充分利用CPU的指令流水線,提高執行速度。以上是硬體
層級的最佳化。再看軟體一級的最佳化:一種是在編寫代碼時由程式員最佳化,另一種是由編譯器
進行最佳化。編譯器最佳化常用的方法有:將記憶體變數緩衝到寄存器;調整指令順序充分利用
CPU指令流水線,常見的是重新排序讀寫指令。

對常規記憶體進行最佳化的時候,這些最佳化是透明的,而且效率很好。由編譯器最佳化或者硬
件重新排序引起的問題的解決辦法是在從硬體(或者其他處理器)的角度看必須以特定順序
執行的操作之間設定記憶體屏障(memory barrier),linux 提供了一個宏解決編譯器的執行
順序問題。
void Barrier(void)
這個函數通知編譯器插入一個記憶體屏障,但對硬體無效,編譯後的代碼會把當前CPU寄存器
中的所有修改過的數值存入記憶體,需要這些資料的時候再重新從記憶體中讀出。

Memory描述符告知GCC:
l  1)不要將該段內嵌彙編指令與前面的指令重新排序;也就是在執行內嵌彙編代碼
之前,它前面的指令都執行完畢
l  2)不要將變數緩衝到寄存器,因為這段代碼可能會用到記憶體變數,而這些記憶體變
量會以不可預知的方式發生改變,因此GCC插入必要的代碼先將緩衝到寄存器的變
量值寫回記憶體,如果後面又訪問這些變數,需要重新訪問記憶體。
如果彙編指令修改了記憶體,但是GCC 本身卻察覺不到,因為在輸出部分沒有描述,此時
就需要在修改描述部分增加“memory”,告訴GCC 記憶體已經被修改,GCC 得知這個資訊後,
就會在這段指令之前,插入必要的指令將前面因為最佳化Cache 到寄存器中的變數值先寫回內
存,如果以後又要使用這些變數再重新讀取。

(3)進程切換的標誌-----sp指標的切換

    因為進程切換也就是進程描述符的切換,現在讓我們來想一下我們是如何定位某個進程描述符的地址的,看下面的彙編代碼:

   mov $0xffffe000,%ecx

   andl %esp,%ecx

   movl %ecx,p

 

執行上面代碼後,p中即儲存當前運行進程的thread_info結構的地址,但是我們最長用的是進程描述符的地址,因此核心設計了current宏來計算指向進程描述符的指標:

 mov $0xfffe000,%ecx

 andl %esp,%ecx

 movl (%ecx),p

因為task欄位在thread_info中的位移量為0,所以執行上述三條指令後,p即是當前啟動並執行進程的描述符指標。

      我們可以看到,只要知道esp,那麼,進程就唯一確定了,所以說esp是進程切換的標誌

(4)堆棧切換的標誌 --- ebp (棧低指標)

    毋庸置疑,棧底指標肯定是堆棧切換的標誌

(5)switch_to 三個參數

進程切換一般都涉及三個進程,如進程a切換成進程b,b開始執行,但是當a恢複執行的時候往往是通過一個進程c,而不是進程b。注意switch_to的調用: switch_to(prev,next,prev), 可以看到last就是prev 調用方法如下:進程A->進程B switch_to(A,B,A)主要有三個參數:
輸入參數兩個:prev:切換前的進程,next:切換後的進程,輸出參數一個:last:切換前進程。 

注意這三個變數都是局部變數,在系統棧中,所以切換到另一進程後變數的值不會改變。
進程a切換b之前,eax的值為prev,也就是A;edx的值為next,也就是B,eax的值為last,也就是A
當不考慮第三個參數時,從C切換成A,核心棧切換成A的棧,這時A中的prev和nexxt分別指向A和B,進程C的引用丟失了。這時第三個參數就派上用場了。C切換進程A後,將C存入eax中,切換到A後,由於輸出部"=a" (last)會將eax的值寫入last中,也就是prev中,所以此時prev和next的值就是C和B了。

(6)彙編參數的傳遞?

彙編是通過寄存器傳遞參數的,這裡用了eax和edx,這樣jmp就和call差不多了,但是jmp和call的區別是,call會有硬體自動壓棧一些寄存器的,比如cs:ip ,這裡是通過手工壓棧ip,類比了call,在__switch_to中,用return 返回。我們又會想到為什麼不用call呢?原因是進入__switch_to後,我們是為運行別的進程做準備,也就是說當返回的時候應該是運行next進程而不是當前進程。如果用call的話,壓棧的是當前進程的ip,那麼__switch_to返回後就運行當前進程了,這與我們想運行next的進程的想法是不一致的,因此,我們手工壓棧的是next進程的ip,那麼,當__switch_to返回後自然出棧的就是next的ip,也就是運行next進程了。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.