linux内核学习之四:进程切换简述
?進程是現(xiàn)代操作系統(tǒng)的核心概念之一,用于分配系統(tǒng)(CPU,內(nèi)存)資源的使用。了解linux進程及進程切換的知識,首先要理解進程與程序的區(qū)別,進程是執(zhí)行流,是動態(tài)概念;程序是數(shù)據(jù)與指令序列的集合,是靜態(tài)概念。進程作為動態(tài)的執(zhí)行流,可以用execv系統(tǒng)調(diào)用自由選擇一個程序(只要有權限)來執(zhí)行的,理解這一點很重要。在閱讀本書的第三章《進程》中,有兩個地方比較難于理解的。
?
1 switch_to宏的last參數(shù)
? ? ?書中討論switch_to宏(第110頁)時,提到,該宏有3個參數(shù):prev,next和last。前兩個分別是當前進程描述符地址和待切換的進程描述符的地址,相信大家對這兩個參數(shù)都不會有疑問,prev就是從current得到的,而next則是schedule()函數(shù)在根據(jù)調(diào)度算法從進程等待隊列中挑選的。關鍵是第三個last參數(shù),為什么需要這么一個參數(shù)呢?書中的描述比較難理解。它的意思是說,A切換到B時,prev=A,next=B, 經(jīng)過一定時間后,A被重新調(diào)度到CPU上執(zhí)行時,A需要知道從哪個進程切換過來的,需要從last參數(shù)得到。實際上我們只需要關注A->B這一個過程就可以理解last參數(shù)的使用了。下面我們用圖片記錄每個步驟:
(1) 在進程切換之前,A是當前進程,esp寄存器指向A的內(nèi)核棧,prev,next這兩個局部變量保存在棧中,也就是在A的內(nèi)核棧中。那么B的內(nèi)核棧有沒有這兩個參數(shù)呢?當然也有,因為B既然是在等待隊列中,很可能B也經(jīng)歷過被其他進程切換出去這一個過程,在那個過程中,B的內(nèi)核棧同樣保存了這兩個變量(如果B是新創(chuàng)建的進程,可以在創(chuàng)建時,或在schedule函數(shù)中將兩個值壓入內(nèi)核棧),但是這兩個值肯定跟A中的prev=A,next=B不同,因為那個過程中,B是被切換的,因此,這時,B的內(nèi)核棧中應該是prev=B.
? ? ?為了在切換到B進程執(zhí)行時,prev參數(shù)是正確的,就需要借助于第三個參數(shù)last 。在schedule函數(shù)中(它挑選的B,當然也知道B進程描述符的地址),它從B的進程描述符中得到B的內(nèi)核棧的地址(書中是thread_info參數(shù),3.2.54版本代碼中改成了stack參數(shù),原理是一樣的),從而得到B的prev參數(shù)的地址,作為第三個參數(shù)傳給switch_to宏。switch_to宏還將A的進程描述符地址加載到EAX寄存器中,而在進程切換過程中,EAX寄存器內(nèi)容是不會改變的。
(2) 執(zhí)行進程切換,主要是內(nèi)核棧的切換,因為內(nèi)核實現(xiàn)中,將thread_info結構與內(nèi)核棧放在一起,esp改變了,current參數(shù)得到的當前進程描述符地址也跟著改變。這時,當前進程變成了B進程,并在B的內(nèi)核棧上工作。注意,這時B內(nèi)核棧的prev參數(shù)還是不正確的,它指向的依然是B。
(3) 將EAX寄存器內(nèi)容復制到last指向的內(nèi)存,即B內(nèi)核棧的prev參數(shù)所在的地址。這樣,B內(nèi)核棧上的prev參數(shù)就指向了正確的A進程描述符的地址。
?
2 進程切換過程中進程棧
? ? ?書中對進程切換的描述中,對進程的棧的描述是零散的,很容易讓人犯糊途。棧是進程中的重要數(shù)據(jù)結構,在函數(shù)調(diào)用中起到核心作用,關于棧的詳細描述可以參閱《深入理解計算機系統(tǒng)》。下面描述進程切換過程中,進程的棧的變遷。
? ? ?linux的進程有兩種棧,用戶棧和內(nèi)核棧,它們在不同的內(nèi)存區(qū)域,用戶棧在用戶態(tài)中使用,在用戶地址空間分配(0~3G),內(nèi)核棧在內(nèi)核態(tài)中使用,在內(nèi)核地址空間分配(3G~4G)。用戶棧主要用于函數(shù)調(diào)用和存儲局部變量,內(nèi)核棧除此之外還要保存進程切換額外的信息,如通用寄存器等。不管是用戶棧還是內(nèi)核棧,CPU都是用ESP寄存器保存棧頂?shù)刂?#xff0c;因此早在進程切換前,進程進入內(nèi)核態(tài)后,用戶棧就需要被切換出去,整個切換過程,都是在內(nèi)核棧上工作,因而用戶棧與進程切換無關。另一方面,內(nèi)核的實現(xiàn)中,將thread_info結構與內(nèi)核棧放在一起,內(nèi)核棧改變了,current參數(shù)得到的當前進程描述符地址也跟著改變,因此進程切換,就是由內(nèi)核棧切換來完成的。整個完整的進程切換可以分為三個部分,以下假設從進程A切換到進程B:
(1) ?A的用戶態(tài)-->A的內(nèi)核態(tài)
? ? ?這一過程是由中斷,異常或系統(tǒng)調(diào)用實現(xiàn)的,書中的后面章節(jié)會有介紹,以后再詳談。這里只討論幾個要點,每次從用戶態(tài)切換到內(nèi)核態(tài),內(nèi)核棧都會被清空,ESP直接指向內(nèi)核棧的棧底,而用戶棧的信息則會保存到內(nèi)核棧中。清空內(nèi)核棧的設計估計是考慮到經(jīng)過了用戶態(tài)的操作后,以前內(nèi)核棧的調(diào)用信息沒有用處了,沒有必要再保存,畢竟內(nèi)核棧只分配了8K或4K的空間。那么,切換到內(nèi)核態(tài)之前,內(nèi)核怎么知道進程的內(nèi)核棧地址呢,進程描述符雖然保存有內(nèi)核棧的地址(stack變量),但是進程描述符位于動態(tài)內(nèi)態(tài)中,從內(nèi)存讀取的效率太低了。實現(xiàn)上,它是從TSS中獲取的。
? ? ?書中“任務狀態(tài)段”一節(jié)(第108頁)對TSS進行比較詳細的描述,每個CPU都有一個TSS,CPU可以快速訪問它。TSS的一個最重要的功能就是在用戶態(tài)轉為內(nèi)核態(tài)時供CPU讀取內(nèi)核棧地址,即是init_tss[cpu]->sp0字段(3.2.54版本的代碼),實際上,它存儲的是棧底地址,因此一加載到ESP中,就同時清空了內(nèi)核棧。
(2) A的內(nèi)核態(tài)->B的內(nèi)核態(tài)
這一階段實現(xiàn)的是進程間的內(nèi)核棧切換,同時也實現(xiàn)進程切換。與此過程關系最密切的是task_struct的thread變量,thread變量的類型是thread_struct,可稱為線程描述符,用于保存進程切換的硬件上下文(書中第109頁)。書中的switch_to和__switch_to函數(shù)詳細描述了進程切換過程中的每一個步驟,與內(nèi)核棧相關的有:
- 保存A的內(nèi)核棧棧頂?shù)刂?#xff0c;即ESP寄存器的內(nèi)容到A_task->thread->sp。(switch_to的第3步,變量名根據(jù)3.2.54版本中的代碼)
- 將B_task->thread->sp內(nèi)容加載到ESP。(switch_to的第4步,這步完成了內(nèi)核棧的切換)
- 將B_task->thread->sp0加載到init_tss[cpu]->sp0字段(__switch_to的第3步),這一步與(1)的描述對應,以后B在運行期間,用戶態(tài)切換到內(nèi)核態(tài)時,ESP寄存器總是從init_tss[cpu]->sp0字段獲取內(nèi)核棧的地址,這一操作同時清空了內(nèi)核棧內(nèi)容。(thread_struct結構有sp0,sp1變量,sp0保存內(nèi)核棧棧底地址,sp保存棧頂?shù)刂?#xff09;。
(3)B的內(nèi)核態(tài)->B用戶態(tài)
? ? ? 執(zhí)行與(1)相反的過程,從內(nèi)核棧中取出(1)中保存的用戶棧信息,裝載相應寄存器,切換到用戶棧,內(nèi)核棧信息不必保存,因為(2)中已保存了棧底地址,下次進入內(nèi)核棧時直接將其加載到ESP寄存器中即可(將棧底地址作為棧頂使用)。這一過程書中后面的章節(jié)同樣會有詳細描述。
?
? ? ?有關進程的內(nèi)容遠不止這些,例如,進程的創(chuàng)建與清除,進程隊列,進程調(diào)度等,總之要理解linux內(nèi)核的進程管理,必須將《深入理解linux內(nèi)核》一書相關章節(jié)逐句逐句細細品讀。
轉載于:https://www.cnblogs.com/wuchanming/p/4807416.html
總結
以上是生活随笔為你收集整理的linux内核学习之四:进程切换简述的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Vim安装与设置
- 下一篇: Android Configuratio