《深入理解Linux核心3rd》學習筆記——進程切換(上):相關知識

來源:互聯網
上載者:User

   進程切換(process switch),作為搶佔式多任務OS中重要的一個功能,其實質就是OS核心掛起正在啟動並執行進程A,然後將先前被掛起的另一個進程B恢複運行。

 

硬體上下文

  每個進程都有自己的地址空間,但是所有進程在物理上共用著CPU的寄存器,因此,當恢複一個進程執行前,OS核心必須要將掛起該進程時寄存器的值裝入CPU寄存器。進程恢複執行前必須裝入寄存器的一組資料就叫做“硬體上下文”(hardware context),它是進程執行內容的子集,後者是進程執行時需要的所有資訊(如地址空間中的資料等)。

  Linux中,TSS儲存著部分的進程的硬體上下文(如ss、esp等寄存器的值),剩餘部分儲存在核心堆棧中(如eax、ebx等通用資料寄存器的值)。

  進程切換隻發生在核心態,在進程切換之前,使用者態使用的寄存器內容都已儲存在核心堆棧上,如ss、esp等。

 任務狀態段(TSS)

  80x86體繫結構中有個特殊的段——TSS,用來存放硬體上下文。Linux為每個CPU分配一個TSS。這樣,當一個CPU從使用者態切換到核心態時,就從TSS中得到核心態的堆棧地址,如果使用者態程式試圖用in或out指令訪問I/O裝置時,CPU就可以訪問在TSS中的I/O許可位元影像(I/O Permission Bitmap)來檢查該操作是否合法。

  tss_struct結構描述TSS的格式。系統中有一個全域數組——init_tss,裡面儲存著每個不同CPU的TSS(n個CPU就有n個TSS)。由此可見,TSS表示了CPU上當前進程的資訊,沒有必要為每個進程都分配TSS。

  Linux建立的TSSD(任務狀態段描述符)存放在GDT中,GDT的基地址儲存在每個CPU的gdtr寄存器裡。每個CPU的tr寄存器裡有相應的TSS的TSSD的選擇子,這可以用來在GDT中定位TSSD,從而得到TSS。CPU中有兩個不可程式化的寄存器,存放TSSD的Base欄位和Limit欄位,這樣CPU可以快速地對TSS定址,而不需經過GDT。

  因為Linux為每個CPU分配TSS,而不是每個進程分配TSS,因此,被替換的進程的硬體上下文必須儲存在別處,不能存在TSS中。每個進程描述符中有一個欄位thread——一個thread_struct類型的欄位,使用它可以儲存部分硬體上下文。該結構中包含了大部分的CPU寄存器(如esp、eip等),但不包含eax、ebx之類的通用寄存器,因為它們儲存在進程核心堆棧中。

 

執行進程切換

  進程切換髮生在schedule()函數中。進程切換分為兩個步驟:

  1. 切換頁全域目錄(Page Global Directory)來載入一個新的地址空間,實際上就是載入新進程的cr3寄存器值。
  2. 切換核心堆棧和硬體上下文,這些包含了核心執行一個新進程的所有資訊,包含了CPU寄存器。

  現在假設prev表示即將被替換的進程的描述符,next表示即將執行的進程的描述符。其實,prev和next都是schedule()函數的局部變數。

 

switch_to宏

  這裡討論進程切換的第2個步驟,該步驟通過switch_to宏來實現。

  switch_to宏它有三個參數:prev、next、last。prev和next不需要解釋,last參數是幹什麼的呢?實際上,任何進程切換涉及3個進程,不僅僅是2個。

  假設核心決定將進程A掛起,執行進程B,那麼在schedule()函數中,prev就是進程A的描述符地址,next就是進程B的描述符地址,一旦switch_to掛起A,那麼進程A就凍結了。後來,當核心想重新執行進程A,它必須通過switch_to宏來掛起進程C(通常不是進程B),此時prev代表C、next代表A。當A恢複執行,它得到它原來的核心堆棧,在這個原來的核心堆棧裡,prev代表A,next代表B。此時,代表進程A的核心代碼失去了對進程C的引用,就找不到進程C了。事實證明,這個引用對於完成進程切換是有用的。

  switch_to的last參數是一個輸出參數,表示宏把進程C的描述符地址寫在記憶體的某個地址(這是在A恢複執行後完成的)。在進程切換之前,switch_to把prev的值寫入eax。在A恢複執行後,此時還是在switch_to宏代碼中,A得到它原來的核心堆棧,prev是A的描述符地址,注意,因為CPU內eax寄存器的值不會因為切換而變化,因此,eax裡存的是進程C的描述符地址,switch_to會將eax的值寫入到last中,原來last指向進程A的prev就被C的描述符地址覆蓋了。

  關於switch_to宏的分析,請見下一篇。

 

__switch_to函數

  switch_to宏裡有一句“jmp __switch_to”,即跳轉到__switch_to函數開始執行。該函數完成進程切換第2步驟的大部分工作。該函數是FASTCALL調用方式(利用關鍵字__attribute__(regparm(3))聲明),因此參數用通用資料寄存器傳遞——eax傳遞prev_p、edx傳遞next_p。

  關於__switch_to函數的分析,請見下一篇。

 

儲存和載入FPU、MMX和XMM寄存器

  從Intel 80486DX開始,FPU(算術浮點單位)被整合到了CPU中,浮點算術功能用ESCAPE指令來執行,操縱CPU中的浮點寄存器集。顯然,當一個進程正在使用ESCAPE指令,那麼浮點寄存器的內容就屬於它的硬體上下文。

  為了加速多媒體程式的執行,Intel在微處理器中引入了新的指令集——MMX,MMX指令也作用於FPU的浮點寄存器。這樣,MMX就不能和FPU指令混用,但是OS核心就可以忽略新的MMX指令集,因為儲存浮點寄存器的功能代碼也能夠應用於MMX的狀態。

  MMX使用SIMD(單指令多資料)流水線,Pentium III增強了這種SIMD能力,引入SSE(Streaming SIMD Extensions)擴充。該功能增強了8個128位寄存器(XMM寄存器)的功能,這些寄存器不和FPU/MMX寄存器重疊,因此能夠與FPU/MMX指令混用。

  Pentium IV還引入了SSE2擴充,支援高精度浮點值,SSE2和SSE使用同一個XMM寄存器組。

  80x86微處理器不在TSS中儲存FPU、MMX和XMM寄存器的值,不過還是提供了一些支援,能夠在需要時儲存它們。cr0寄存器有一個TS(Task-Switching)標誌位,每當執行硬體環境切換時,TS置位,每當TS被置位後進程執行ESCAPE、MMX、SSE或SSE2指令,控制器就產生一個“Device not available”異常。這樣,TS標誌位就能夠讓OS核心只有在真正需要時才儲存或恢複FPU、MMX和XMM寄存器。

  假設進程A使用了數學副處理器,那麼當進程A被切換出去的時候,核心設定TS並將浮點寄存器的內容儲存到進程A的TSS中(原著這麼寫,但是應該是儲存到進程A描述符的一個欄位中,TSS是與CPU關聯的,進程沒有TSS)。

  如果新進程B不使用數學副處理器,那麼核心就不需要恢複浮點寄存器的內容,但是,一旦進程B執行FPU、MMX等指令,CPU就產生一個“Device not available”異常,相應的例外處理常式就會用儲存在進程B中的相關值來恢複浮點寄存器。

  處理FPU、MMX和XMM寄存器的資料結構存放在進程描述符的thread欄位的i387子欄位中(即thread.i387),由i387_union聯合體描述,其格式如下:

union i387_union {
        struct i387_fsave_struct    fsave; /* 儲存FPU、MMX寄存器的內容 */
        struct i387_fxsave_struct   fxsave;/* 儲存SSE和SSE2寄存器內容 */
        struct i387_soft_struct     soft;  /* 由無數學副處理器的老式CPU模型使用 */
    };

  此外,進程描述符中還包含了兩個附加的標誌:

  • thread_info結構中status欄位的TS_USEDFPU標誌,表示進程當前執行過程中是否使用過FPU、MMX和XMM寄存器。
  • task_struct結構的flags欄位的PF_USED_MATH標誌,表示thread.i387的內容是否有意義。

  儲存和載入FPU、MMX和XMM寄存器主要用到__unlazy_fpu宏,該宏在__switch_to函數中使用,下一篇會對其進行分析。

 

核心態使用FPU、MMX和XMM寄存器

  OS核心也可以使用FPU、MMX和XMM寄存器,當然,這麼做的時候應該避免幹擾使用者態進程。因此,Linux使用如下方法來解決:

  • 在核心使用副處理器之前,如果使用者態進程使用了FPU(TS_USEDFPU標誌為1),核心就要調用kernel_fpu_begin()函數,該函數裡又調用save_init_fpu()來儲存寄存器內容,然後重新設定cr0寄存器的TS標誌。
  • 使用完副處理器之後,核心調用kernel_fpu_end宏設定cr0寄存器的TS標誌。
  • 當使用者態進程恢複執行時,math_state_restore()函數將恢複FPU、MMX和XMM寄存器的內容。

  需要注意的是,如果目前使用者態進程有在用數學副處理器時,kernel_fpu_begin()函數的執行時間比較長,甚至無法通過FPU、MMX或XMM達到加速的目的。因此,核心只在有限的場合使用FPU、MMX或XMM指令,比如移動或清除大記憶體區欄位、計算校正和等。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.