Linux系統堆棧講解
Linux系統經過長時間的發展,很多用戶都很了解Linux系統了,這里我發表一下Linux系統中共使用了四種堆棧個人理解,和大家討論討論。
一 系統引導初始化臨時使用的堆棧
二 進入保護模式后提供內核程序始化使用的堆棧,該堆棧也是后來任務0使用的用戶態堆棧
三 每個任務通過系統調用,執行內核程序時使用的堆棧,稱之為任務的內核態堆棧,每個任務都有自己獨立的內核態堆棧
四 任務在用戶態執行的堆棧,位于任務(進程 )邏輯地址空間近末端處
使用多個?;蛟诓煌闆r下使用不同棧的主要原因
(一)由于從實模式進入保護模式,使得CPU對內存尋址訪問方式發生了變化,因此需要重新設置堆棧區域
(二) 為了解決不同CPU特權級共享使用堆棧帶來的保護問題,執行0級的內核代碼和執行3級的用戶代碼需要使用不同的棧。當一個任務進入內核態運行時,就會使用其TSS段中給出的特權級0的堆棧指針tss.ss0.tss.esp0,即內核棧,原用戶棧指針會保存在內核棧中,而當從內核態返回用戶態時,就會恢復使用用戶態的堆棧
以下分別說明。
開機初始化時(bootsect.s,setup.s)
當bootsect代碼被ROM BIOS引導加載到物理內存0x7c00處時,并沒有設置堆棧段,程序也沒有使用堆棧,直到bootsect被移動到0x9000:0處時,才把堆棧段寄存器SS設置為0x9000,堆棧指針esp寄存器設置為0xff00,所以堆棧堆棧在0x9000:0xff00處(boot/bootsect.s L61,62)setup.s也使用這個堆棧
進入保護模式時候(head.s,L31)
此時堆棧段被設置為內核數據段(0x10),堆棧指針esp設置成指向user_stack數組(sched.c L67~72)的頂端,保留了1頁內存作為堆棧使用
初始化時(main.c)
在執行move_to_user_mode()代碼把控制權移交給任務0之前,系統一直使用上述堆棧,而在執行過move_to_user_mode()之后,main.c的代碼被“切換”成任務0中執行。通過執行fork()系統調用,main.c中的init()將在任務1中執行,并使用任務1的堆棧,而main()本身則在被“切換”成為任務0后,仍熱繼續使用上述內核程序自己的堆棧作為任務0的用戶態堆棧。
任務的堆棧
每個任務都有兩個堆棧,分別用于用戶態和內核態程序的執行,并且分別稱為用戶態堆棧和內核態堆棧。
除了處于不同CPU特權級中,這兩個堆棧之間的主要區別在于任務的內核態堆棧很小,所保存的數據最多不能超過4096個字節,而任務的用戶態堆棧卻可以在用戶的64MB空間中延伸
在用戶態運行時
每個任務(除了任務0和任務1)有自己的64MB地址空間,當一個任務(進程)剛被創建時,它的用戶態堆棧指針被設置在其地址空間的靠近末端部分,應用程序在用戶態下運行時就一直使用這個堆棧,實際物理地址內存則由CPU分頁機制確定。
在內核態運行時
每個任務有其自己的內核態堆棧,用于任務在內核代碼中執行期間。其所在的線性地址中位置由該任務TSS段中ss0和esp0兩個字段指定,任務內核態堆棧被設置在位于其任務數據結構所在頁面的末端,即于任務的任務數據結構(task_struct)放在同一頁面中,參見kernel/fork.c L93
p->tss.esp0 = PAGE_SIZE + (long)p;
p->tss.ss0 = 0x10
*為什么從主存區申請得來的用于保存任務數據結構的一頁內存也能被設置成內核數據段中的數據呢?就是說tss.ss0為什么可以是0x10?
用戶內核態仍然屬于內核數據空間,在head.s中設置內核代碼段和數據段的描述符,段長度都設置成了16MB,這個長度值是Linux0.11內核所能支持的最大物理內存長度(head.s,110開始的注釋),所以,內核代碼可以尋址到整個物理內存范圍中的任何位置,當然也包括主存區,每當任務執行內核程序而需要使用其內核棧時,CPU就會利用TSS結構把它的內核態堆棧設置成由tss.ss0和tss.esp0這兩個值構成
任務0(空閑進程idle)和任務1(初始化進程init)的堆棧
任務0和任務1的代碼段和數據段相同,限長都是640KB,但它們被映射到不同的線性地址空間,任務0的段基址從線性地址0開始,而任務1的段基址從64MB開始,但他們全部映射到物理地址0~640KB范圍中,這個地址也就是內核代碼和基本數據所存放的地方,在執行了move_to_user_mode()后,任務0和任務1的內核態堆棧分別位于各自任務數據結構所在頁面的末端,而任務0的用戶態堆棧就是前面進入保護模式后使用的堆棧,即user_stack[]數組的位置,由于任務1在創建時復制了任務0的用戶堆棧,所以剛開始時任務0和任務1共享使用同一個用戶堆??臻g,但是當任務1開始運行時,寫時復制機制會為任務1另行分配主存區頁面作為堆棧空間使用,只有到這個時候,任務1才開始使用自己獨立的用戶堆棧內存頁面,因此任務0的堆棧需要在任務1實際開始使用之前保持干凈,即任務0此時不能使用堆棧,以確保復制的堆棧頁面中不含任務0的數據
這樣你就學會Linux系統中使用了四種堆棧知識了。
【編輯推薦】