天天看點

linux核心學習之四:程序切換簡述【轉】

在講述專業知識前,先講講我學習linux核心使用的入門書籍:《深入了解linux核心》第三版(英文原版叫《Understanding the Linux Kernel》),不過這本書不一定對每個人都适合,大家可以根據自己的情況選擇适合的入門書籍。看了前面幾章,感覺這本書的語言極其精練,沒有一句多餘的,必須慢慢讀。可能我以前習慣了粗略浏覽的閱讀方式,讀這本書時經常看着看着就迷糊了,不得不回到前面重新讀起,如此反反複複。關于程序的一章更是深奧難懂,前前後後翻了十幾遍才明白個大概。另外說明下,我用來驗證代碼的核心版本為官方的linux-3.2.54核心,而系統是用debian7.3_i386CD光牒安裝的。

     程序是現代作業系統的核心概念之一,用于配置設定系統(CPU,記憶體)資源的使用。了解linux程序及程序切換的知識,首先要了解程序與程式的差別,程序是執行流,是動态概念;程式是資料與指令序列的集合,是靜态概念。程序作為動态的執行流,可以用execv系統調用自由選擇一個程式(隻要有權限)來執行的,了解這一點很重要。在閱讀本書的第三章《程序》中,有兩個地方比較難于了解的。

1 switch_to宏的last參數

     書中讨論switch_to宏(第110頁)時,提到,該宏有3個參數:prev,next和last。前兩個分别是目前程序描述符位址和待切換的程序描述符的位址,相信大家對這兩個參數都不會有疑問,prev就是從current得到的,而next則是schedule()函數在根據排程算法從程序等待隊列中挑選的。關鍵是第三個last參數,為什麼需要這麼一個參數呢?書中的描述比較難了解。它的意思是說,A切換到B時,prev=A,next=B, 經過一定時間後,A被重新排程到CPU上執行時,A需要知道從哪個程序切換過來的,需要從last參數得到。實際上我們隻需要關注A->B這一個過程就可以了解last參數的使用了。下面我們用圖檔記錄每個步驟:

linux核心學習之四:程式切換簡述【轉】

(1) 在程序切換之前,A是目前程序,esp寄存器指向A的核心棧,prev,next這兩個局部變量儲存在棧中,也就是在A的核心棧中。那麼B的核心棧有沒有這兩個參數呢?當然也有,因為B既然是在等待隊列中,很可能B也經曆過被其他程序切換出去這一個過程,在那個過程中,B的核心棧同樣儲存了這兩個變量(如果B是新建立的程序,可以在建立時,或在schedule函數中将兩個值壓入核心棧),但是這兩個值肯定跟A中的prev=A,next=B不同,因為那個過程中,B是被切換的,是以,這時,B的核心棧中應該是prev=B.

     為了在切換到B程序執行時,prev參數是正确的,就需要借助于第三個參數last 。在schedule函數中(它挑選的B,當然也知道B程序描述符的位址),它從B的程序描述符中得到B的核心棧的位址(書中是thread_info參數,3.2.54版本代碼中改成了stack參數,原理是一樣的),進而得到B的prev參數的位址,作為第三個參數傳給switch_to宏。switch_to宏還将A的程序描述符位址加載到EAX寄存器中,而在程序切換過程中,EAX寄存器内容是不會改變的。

(2) 執行程序切換,主要是核心棧的切換,因為核心實作中,将thread_info結構與核心棧放在一起,esp改變了,current參數得到的目前程序描述符位址也跟着改變。這時,目前程序變成了B程序,并在B的核心棧上工作。注意,這時B核心棧的prev參數還是不正确的,它指向的依然是B。

(3) 将EAX寄存器内容複制到last指向的記憶體,即B核心棧的prev參數所在的位址。這樣,B核心棧上的prev參數就指向了正确的A程序描述符的位址。

2 程序切換過程中程序棧

     書中對程序切換的描述中,對程序的棧的描述是零散的,很容易讓人犯糊途。棧是程序中的重要資料結構,在函數調用中起到核心作用,關于棧的較長的描述可以參閱《深入了解計算機系統》。下面描述程序切換過程中,程序的棧的變遷。

     linux的程序有兩種棧,使用者棧和核心棧,它們在不同的記憶體區域,使用者棧在使用者态中使用,在使用者位址空間配置設定(0~3G),核心棧在核心态中使用,在核心位址空間配置設定(3G~4G)。使用者棧主要用于函數調用和存儲局部變量,核心棧除此之外還要儲存程序切換額外的資訊,如通用寄存器等。不管是使用者棧還是核心棧,CPU都是用ESP寄存器儲存棧頂位址,是以早在程序切換前,程序進入核心态後,使用者棧就需要被切換出去,整個切換過程,都是在核心棧上工作,因而使用者棧與程序切換無關。另一方面,核心的實作中,将thread_info結構與核心棧放在一起,核心棧改變了,current參數得到的目前程序描述符位址也跟着改變,是以程序切換,就是由核心棧切換來完成的。整個完整的程序切換可以分為三個部分,以下假設從程序A切換到程序B:

(1)  A的使用者态-->A的核心态

     這一過程是由中斷,異常或系統調用實作的,書中的後面章節會有介紹,以後再詳談。這裡隻讨論幾個要點,每次從使用者态切換到核心态,核心棧都會被清空,ESP直接指向核心棧的棧底,而使用者棧的資訊則會儲存到核心棧中。清空核心棧的設計估計是考慮到經過了使用者态的操作後,以前核心棧的調用資訊沒有用處了,沒有必要再儲存,畢竟核心棧隻配置設定了8K或4K的空間。那麼,切換到核心态之前,核心怎麼知道程序的核心棧位址呢,程序描述符雖然儲存有核心棧的位址(stack變量),但是程序描述符位于動态内态中,從記憶體讀取的效率太低了。實作上,它是從TSS中擷取的。

     書中“任務狀态段”一節(第108頁)對TSS進行比較詳細的描述,每個CPU都有一個TSS,CPU可以快速通路它。TSS的一個最重要的功能就是在使用者态轉為核心态時供CPU讀取核心棧位址,即是init_tss[cpu]->sp0字段(3.2.54版本的代碼),實際上,它存儲的是棧底位址,是以一加載到ESP中,就同時清空了核心棧。

(2) A的核心态->B的核心态

這一階段實作的是程序間的核心棧切換,同時也實作程序切換。與此過程關系最密切的是task_struct的thread變量,thread變量的類型是thread_struct,可稱為線程描述符,用于儲存程序切換的硬體上下文(書中第109頁)。書中的switch_to和__switch_to函數較長的描述了程序切換過程中的每一個步驟,與核心棧相關的有:

儲存A的核心棧棧頂位址,即ESP寄存器的内容到A_task->thread->sp。(switch_to的第3步,變量名根據3.2.54版本中的代碼)

将B_task->thread->sp内容加載到ESP。(switch_to的第4步,這步完成了核心棧的切換)

将B_task->thread->sp0加載到init_tss[cpu]->sp0字段(__switch_to的第3步),這一步與(1)的描述對應,以後B在運作期間,使用者态切換到核心态時,ESP寄存器總是從init_tss[cpu]->sp0字段擷取核心棧的位址,這一操作同時清空了核心棧内容。(thread_struct結構有sp0,sp1變量,sp0儲存核心棧棧底位址,sp儲存棧頂位址)。

(3)B的核心态->B使用者态

      執行與(1)相反的過程,從核心棧中取出(1)中儲存的使用者棧資訊,裝載相應寄存器,切換到使用者棧,核心棧資訊不必儲存,因為(2)中已儲存了棧底位址,下次進入核心棧時直接将其加載到ESP寄存器中即可(将棧底位址作為棧頂使用)。這一過程書中後面的章節同樣會有較長的描述。

     有關程序的内容遠不止這些,例如,程序的建立與清除,程序隊列,程序排程等,總之要了解linux核心的程序管理,必須将《深入了解linux核心》一書相關章節逐句逐句細細品讀。

【新浪微網誌】 張昺華--sky

【twitter】 @sky2030_

【facebook】 張昺華 zhangbinghua

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利.

繼續閱讀