Linux 系統中堆疊的使用方法

2022-04-30 19:30:08 字數 4610 閱讀 1343

本節內容概要描述了linux核心從開機引導到系統正常執行過程中對堆疊的使用方式。這部分內容的說明與核心**關係比較密切,可以先跳過。在開始閱讀相應**時再回來仔細研究。

linux 0.12系統中共使用了4種堆疊。第1種是系統引導初始化時臨時使用的堆疊;第2種是進入保護模式之後提供核心程式初始化使用的堆疊,位於核心**位址空間固定位置處。該堆疊也是後來任務0使用的使用者態堆疊;第3種是每個任務通過系統呼叫,執行核心程式時使用的堆疊,我們稱之為任務的核心態堆疊。每個任務都有自己獨立的核心態堆疊;第4種是任務在使用者態執行的堆疊,位於任務(程序)邏輯位址空間近末端處。

使用多個棧或在不同情況下使用不同棧的主要原因有兩個。首先是由於從實模式進入保護模式,使得cpu對記憶體定址訪問方式發生了變化,因此需要重新調整設定棧區域。另外,為了解決不同cpu特權級共享使用堆疊帶來的保護問題,執行0級的核心**和執行3級的使用者**需要使用不同的棧。當乙個任務進入核心態執行時,就會使用其tss段中給出的特權級0的堆疊指標tss.ss0、tss.esp0,即核心棧。原使用者棧指標會被儲存在核心棧中。而當從核心態返回使用者態時,就會恢復使用使用者態的堆疊。下面分別對它們進行說明。

(1)開機初始化時(bootsect.s,setup.s)

當bootsect**被rom bios引導載入到物理記憶體0x7c00處時,並沒有設定堆疊段,當然程式也沒有使用堆疊。直到bootsect被移動到0x9000:0處時,才把堆疊段暫存器ss設定為0x9000,堆疊指標esp暫存器設定為0xff00,即堆疊頂端在0x9000:0xff00處,參見boot/bootsect.s第61、62行。setup.s程式中也沿用了bootsect中設定的堆疊段。這就是系統初始化時臨時使用的堆疊。

(2)進入保護模式時(head.s)

從head.s程式起,系統開始正式在保護模式下執行。此時堆疊段被設定為核心資料段(0x10),堆疊指標esp設定成指向user_stack陣列的頂端(參見head.s,第31行),保留了1頁記憶體(4kb)作為堆疊使用。user_stack陣列定義在sched.c的67~72行,共含有1024個長字。它在物理記憶體中的位置示意圖可參見圖5-23。此時該堆疊是核心程式自己使用的堆疊。其中給出的位址是大約值,它們與編譯時的實際設定引數有關。這些位址位置是從編譯核心時生成的system.map檔案中查到的。

圖5-23  剛進入保護模式時核心使用的堆疊示意圖

(3)初始化時(main.c)

在init/main.c程式中,在執行move_to_user_mode()**把控制權移交給任務0之前,系統一直使用上述堆疊。而在執行過move_to_user_mode()之後,main.c的**被「切換」成任務0中執行。通過執行fork()系統呼叫,main.c中的init()將在任務1中執行,並使用任務1的堆疊。而main()本身則在被「切換」成為任務0後,仍然繼續使用上述核心程式自己的堆疊作為任務0的使用者態堆疊。關於任務0所使用堆疊的詳細描述見後面說明。

每個任務都有兩個堆疊,分別用於使用者態和核心態程式的執行,並且分別稱為使用者態堆疊和核心態堆疊。除了處於不同cpu特權級中,這兩個堆疊之間的主要區別在於任務的核心態堆疊很小,所儲存的資料量最多不能超過4096 – 任務資料結構塊個位元組,大約為3kb。而任務的使用者態堆疊卻可以在使用者的64mb空間內延伸。

(1)在使用者態執行時

每個任務(除了任務0和任務1)有自己的64mb位址空間。當乙個任務(程序)剛被建立時,它的使用者態堆疊指標被設定在其位址空間的靠近末端(64mb頂端)部分。實際上末端部分還要包括執行程式的引數和環境變數,然後才是使用者堆疊空間,如圖5-24所示。應用程式在使用者態下執行時就一直使用這個堆疊。堆疊實際使用的物理記憶體則由cpu分頁機制確定。由於linux實現了寫時複製功能(copy on write),因此在程序被建立後,若該程序及其父程序都沒有使用堆疊,則兩者共享同一堆疊對應的物理記憶體頁面。只有當其中乙個程序執行堆疊寫操作(如push操作)時核心記憶體管理程式才會為寫操作程序分配新的記憶體頁面。而程序0和程序1的使用者堆疊比較特殊,見後面說明。

圖5-24  邏輯空間中的使用者態堆疊

(2)在核心態執行時

p->tss.esp0 = page_size + (long)p;

p->tss.ss0 = 0x10;

其中,p是新任務的任務資料結構指標,tss是任務狀態段結構。核心為新任務申請記憶體用作儲存其task_struct結構資料,而tss結構(段)是task_struct中的乙個字段。該任務的核心堆疊段值tss.ss0也被設定成為0x10(即核心資料段選擇符),而tss.esp0則指向儲存task_struct結構頁面的末端。如圖5-25所示。實際上tss.esp0被設定成指向該頁面(外)上一位元組處(圖中堆疊底處)。這是因為intel cpu執行堆疊操作時是先遞減堆疊指標esp值,然後在esp指標處儲存入棧內容。

圖5-25  程序的核心態堆疊示意圖

為什麼從主記憶體區申請得來的用於儲存任務資料結構的一頁記憶體也能被設定成核心資料段中的資料呢,即tss.ss0為什麼能被設定成0x10呢?這是因為使用者核心態棧仍然屬於核心資料空間。我們可以從核心**段的長度範圍來說明。在head.s程式的末端,分別設定了核心**段和資料段的描述符,段長度都被設定成了16mb。這個長度值是linux 0.12核心所能支援的最大物理記憶體長度(參見head.s,110行開始的注釋)。因此,核心**可以定址到整個物理記憶體範圍中的任何位置,當然也包括主記憶體區。每當任務執行核心程式而需要使用其核心棧時,cpu就會利用tss結構把它的核心態堆疊設定成由tss.ss0和tss.esp0這兩個值構成。在任務切換時,老任務的核心棧指標esp0不會被儲存。對cpu來講,這兩個值是唯讀的。因此每當乙個任務進入核心態執行時,其核心態堆疊總是空的。

(3)任務0和任務1的堆疊

任務0(空閒程序idle)和任務1(初始化程序init)的堆疊比較特殊,需要特別予以說明。任務0和任務1的**段和資料段相同,限長也都是640kb,但它們被對映到不同的線性位址範圍中。任務0的段基位址從線性位址0開始,而任務1的段基位址從64mb開始。但是它們全都對映到實體地址0~640kb範圍中。這個位址範圍也就是核心**和基本資料所存放的地方。在執行了move_to_user_mode()之後,任務0和任務1的核心態堆疊分別位於各自任務資料結構所在頁面的末端,而任務0的使用者態堆疊就是前面進入保護模式後所使用的堆疊,即sched.c的user_stack陣列的位置。由於任務1在建立時複製了任務0的使用者堆疊,因此剛開始時任務0和任務1共享使用同乙個使用者堆疊空間。但是當任務1開始執行時,由於任務1對映到user_stack處的頁表項被設定成唯讀,使得任務1在執行堆疊操作時將會引起寫頁面異常,從而核心會使用寫時複製機制(關於寫時複製技術的說明請參見第13章)為任務1另行分配主記憶體區頁面作為堆疊空間使用。只有到此時,任務1才開始使用自己獨立的使用者堆疊記憶體頁面。因此任務0的堆疊需要在任務1實際開始使用之前保持「乾淨」,即任務0此時不能使用堆疊,以確保複製的堆疊頁面中不含有任務0的資料。

任務0的核心態堆疊是在其人工設定的初始化任務資料結構中指定的,而它的使用者態堆疊是在執行move_to_user_mode()時,在模擬iret返回之前的堆疊中設定的,參見圖5-22所示。我們知道,當進行特權級會發生變化的控制權轉移時,目的**會使用新特權級的堆疊,而原特權級**堆疊指標將保留在新堆疊中。因此這裡先把任務0使用者堆疊指標壓入當前處於特權級0的堆疊中,同時把**指標也壓入堆疊,然後執行iret指令即可實現把控制權從特權級0的**轉移到特權級3的任務0**中。在這個人工設定內容的堆疊中,原esp值被設定成仍然是user_stack中原來的位置值,而原ss段選擇符被設定成0x17,即設定成使用者態區域性表ldt中的資料段選擇符。然後把任務0**段選擇符0x0f壓入堆疊作為棧中原cs段的選擇符,把下一條指令的指標作為原eip壓入堆疊。這樣,通過執行iret指令即可「返回」到任務0的**中繼續執行了。

在linux 0.12系統中,所有中斷服務程式都屬於核心**。如果乙個中斷產生時任務正在使用者**中執行,那麼該中斷就會引起cpu特權級從3級到0級的變化,此時cpu就會進行使用者態堆疊到核心態堆疊的切換操作。cpu會從當前任務的任務狀態段tss中取得新堆疊的段選擇符和偏移值。因為中斷服務程式在核心中,屬於0級特權級**,所以48位的核心態堆疊指標會從tss的ss0和esp0欄位中獲得。在定位了新堆疊(核心態堆疊)之後,cpu就會首先把原使用者態堆疊指標ss和esp壓入核心態堆疊,隨後把標誌暫存器eflags的內容和返回位置cs、eip壓入核心態堆疊。

核心的系統呼叫是乙個軟體中斷,因此任務呼叫系統呼叫時就會進入核心並執行核心中的中斷服務**。此時核心**就會使用該任務的核心態堆疊進行操作。同樣,當進入核心程式時,由於特權級別發生了改變(從使用者態轉到核心態),使用者態堆疊的堆疊段和堆疊指標以及eflags會被儲存在任務的核心態堆疊中。而在執行iret退出核心程式返回到使用者程式時,將恢復使用者態的堆疊和eflags。這個過程如圖5-26所示。

圖5-26  核心態和使用者態堆疊的切換

如果乙個任務正在核心態中執行,那麼若cpu響應中斷就不再需要進行堆疊切換操作,因為此時該任務執行的核心**已經在使用核心態堆疊,並且不涉及優先順序別的變化,所以cpu僅把eflags和中斷返回指標cs、eip壓入當前核心態堆疊,然後執行中斷服務過程。

C Stack堆疊的使用方法

堆疊 stack 代表了乙個後進先出的物件集合。當您需要對各項進行後進先出的訪問時,則使用堆疊。當您在列表中新增一項,稱為推入元素,stack 類的方法和屬性 下表列出了stack類的一些常用的屬性 屬性描述 count 獲取 stack 中包含的元素個數。下表列出了stack類的一些常用的方法 序...

Linux系統中ls命令的使用方法

命令是linux下最常用的命令之一,ls跟dos下的dir命令是一 1.ls a 列出檔案下所有的檔案,包括以 開頭的隱藏檔案 linux下檔案隱藏檔案是以.開頭的,如果存在.代表存在著父目錄 2.ls l 列出檔案的詳細資訊,如建立者,建立時間,檔案的讀寫許可權列表等等。3.ls f 在每乙個檔案...

Linux中vim的使用方法

命令模式 預設模式,移動游標,剪下貼上文字 插入或編輯模式 修改文字 擴充套件命令模式 儲存 退出等 esc鍵 退出當前模式 esc鍵 esc鍵 總是返回命令模式 命令模式 插入模式關閉檔案 擴充套件模式 q 退出 q 強制退出,丟棄做出的修改 wq 儲存退出 x 儲存退出 命令模式 zz 儲存退出...