linux 程序切換,使用者態程序,核心態程序

2021-08-21 01:19:32 字數 4687 閱讀 3629

linux-程序切換,使用者態程序,核心態程序

程序切換

一開始我並不想寫這個筆記,因為太過複雜,我一直想以簡單的方式理解核心,只從概念,避免涉及過多的**。實際上,我寫筆記的時候,書已經看到很後面了,因為總要理解更多才能理解之前看似簡短實際複雜的內容。但最後發現實際上任何內容都沒有辦法跳過,即便不想看,也需要了解基本的概念,所以依舊不會拿大段**,但總會拿少量**。

為了控制程序的執行,核心必須有能力掛起正在cpu上執行的程序,並恢復以前掛起的某個程序的執行。這種行為被稱為程序切換(process switch)、任務切換(task switch)或上下文切換(content switch)。

儘管每個程序都有自己的位址空間,但所有程序必須共享cpu暫存器。因此,在恢復乙個程序的執行之前,核心必須確保每個暫存器裝載了掛起程序時所需要的值。

程序恢復執行前必須裝入暫存器的一組資料成為硬體上下文(hardware context)。硬體上下文是程序可執行上下文的乙個自己,因為可執行上下文包含程序執行時所需要的所有資訊。在linux中,程序硬體上下午的一部分存放在tss段,而剩餘部分存放在核心態堆疊中。

在下面描述中,假定用prev區域性變數表示切換出的程序描述符,next表示切換進的程序描述符。因此,我們把程序切換定義為這樣的行為:儲存prev硬體上下文,用next硬體上下文代替prev。因為程序切換經常發生,因此減少儲存和裝入硬體上下文所話費的時間是非常重要的。

早期linux版本利用80x86體系結構所需提供的硬體支援,並通過far jmp1

指令跳到next程序tss描述符的選擇符來執行程序切換。當執行這條指令時,cpu通過自動儲存原來的硬體上下文,裝入新的硬體上下文來執行硬體上下文切換。但linux2.6使用軟體執行程序切換,原因有:

通過一組mov指令逐步執行切換,這樣能較好地控制所裝入的資料的合法性,一面被惡意使用者偽造。far jmp指令不會有這樣的檢查。

舊方法和新方法所需時間大致相同。

程序切換值發生在核心態,在執行程序切換之前,使用者態程序使用的所有暫存器內容已儲存在核心堆疊上,這也包括ss和esp這對暫存器的內容。

80x86體系結構包含了乙個特殊的段型別,叫任務狀態段(task state segment,tss)來存放硬體上下文,儘管linux並不使用硬體上下文切換,但是強制它為系統中每個不同的cpu建立乙個tss,這樣做主要有兩個理由:

當80x86的乙個cpu從使用者態切換到核心態時,它就從tss中後去核心態堆疊的位址。

當使用者態程序試圖通過in或out指令訪問乙個i/o埠時,cpu需要訪問存放在tss中的i/o許可點陣圖以檢查該程序是否有訪問埠的權利。

更確切的說,當程序在使用者態執行in或out指令時,控制單元執行下列操作:

檢查eflags暫存器中的2位iopl欄位,如果欄位的值為3,控制單元就執行i/o指令。否則,執行下乙個檢查。

訪問tr暫存器以確定當前的tss和相應的i/o許可權位圖。

檢查i/o指令中指定的i/o埠在i/o許可權點陣圖中對應的位,如果該位清,這條指令就執行,否則控制單元產生乙個異常。

tss_struct結構描述tss的格式,init_tss陣列為系統上每個不同的cpu存放乙個tss。在每次程序切換時,核心都更新tss的某些字段以便相應的cpu控制單元可以安全地檢索到它需要的資訊。因此,tss反映了cpu上當前程序的特權級,但不必為沒有在執行的程序保留tss。

每個tss有它自己8位元組的任務狀態段描述符(task state segment descriptor,tssd)。這個描述符包括指向tss起始位址的32位base欄位,20位limit欄位。tssd的s標誌位被清0,以表示相應的tss時系統段的事實。

type欄位被置位11或9以表示這個段實際上是乙個tss。在intel的原始設計中,系統中的每個程序都應當指向自己的tss;type欄位的第二個有效位叫busy位;如果程序正由cpu執行,則該位置1,否則為0。在linux的設計中,每個cpu只有乙個tss,因此busy位總是為1.

由linux建立的tssd存放在全域性描述符表(gdt)中,gdt的基位址存放在每個cpu的gdtr暫存器中。每個cpu的tr暫存器包含相應tss的tssd選擇符,也包含了兩個隱藏的非程式設計字段:tssd的base欄位和limit欄位。這樣,處理器就能夠直接tss定址而不需要從gdt中檢索tss位址。

在每次程序切換時,被替換的程序的硬體上下文必須儲存在別處。不能像intel原始設計那樣儲存在tss中,因為linux為每個處理器而不是為每個程序使用tss。

因此,每個程序描述符包含乙個型別為thread_struct的thread欄位,只要程序被切換出去,核心就把其硬體上下文儲存在這個結構中。隨後可以看到,這個資料結構包含的字段涉及大部分cpu暫存器,但不包括eax、ebx等等這些通用暫存器。它們的值保留在核心堆疊中。

程序切換由兩步組成:

切換頁全域性目錄以安裝乙個新的位址空間。

切換核心態堆疊和硬體上下文,因為硬體上下文提供了核心執行新程序所需要的所有資訊,包含cpu暫存器。

程序切換的第二步由switch_to巨集執行。它是核心中與硬體關係最為密切的例程之一,必須下很多功夫了解。

/* context switching is now performed out-of-line in switch_to.s */

extern

struct

task_struct

*__switch_to

(struct

task_struct*,

struct

task_struct*);

#define switch_to(prev, next, last)\ do while (0)

首先,該巨集有三個引數,prev、next和last,prev和next的作用僅是區域性變數prev和next的佔位符,即它們是輸入引數,分別表示被替換程序和新程序描述符的位址在記憶體中的位置。

在任何程序切換中,涉及到的是三個程序而不是兩個。假設核心決定暫停程序a而啟用程序b,在schedule()函式中,prev指向a的描述符,而next指向b的程序描述符。switch_to巨集一旦使a暫停,a的執行流就被凍結。

隨後,當核心想再次啟用a,就必須暫停另乙個程序c,因為這通常不是b,因為b有可能被其他程序比如c切換。於是就要用prev指向c而next指向a來執行另乙個switch_to巨集。當a恢復它執行的流時,就會找到它原來的核心棧,於是prev區域性變數還是指向a的描述符而next指向b的描述符。此時,代表程序a執行的核心就失去了對c的任何引用。但引用對於完成程序切換是有用的,所以需要保留。

switch_to巨集的最後乙個引數是輸出引數,它表示巨集把程序c的描述符位址寫在記憶體的什麼位置了,不過,這個是在恢復a執行之後完成的。在程序切換之前,巨集把第乙個輸入引數prev表示的變數存入cpu的eax暫存器。在完成程序切換,a已經恢復執行時,巨集把cpu的eax暫存器的內容寫入由第三個引數last所指示的a在記憶體中的位置。因為cpu暫存器不會在切換點發生變化,所以c的描述符位址也存在記憶體的這個位置。在schedule()執行過程中,last引數指向a的區域性變數prev,所以prev被c的位址覆蓋。

__switch_to()函式執行大多數開始於switch_to()巨集的程序切換。這個函式作用於prev_p和next_p引數,這兩個引數表示前乙個程序和新程序。這個函式的呼叫不同於一般的函式呼叫。因為__switch_to()從eax和edx取引數prev_p和next_p,而不像大多數函式一樣從棧中取引數。

__switch_to

(struct

task_struct

*prev_p

,struct

task_struct

*next_p

)

這個函式執行步驟如下:

執行由__unlay_fpu()巨集**產生的**,以有選擇地儲存prev_p程序的fpu、mmx以及xmm暫存器的內容。

執行smp_processor_id()巨集獲得本地cpu的下表,即執行**的cpu。該巨集從當前程序的thread_info結構的cpu欄位獲得下標並儲存到cpu區域性變數。

把next_p->thread.esp0裝入對應於本地cpu的tss的esp0欄位。其實,任何由sysenter彙編指令產生的從使用者態到核心態的特權級轉換將把這個位址拷貝到esp暫存器中。

把next_p程序使用的執行緒區域性儲存(tls)段裝載入本地cpu的全域性描述符表。

把fs和gs段暫存器的內容分別存放在prev_p->thread.fs和prev_p->thread.gs中。esi暫存器指向prev_p->thread結構。

如果fs或gs段暫存器已經被prev_p或next_p程序中的任意乙個使用,則將next_p程序的thread_struct描述符中儲存的值裝入這些暫存器。

用next_p->thread.debugreg陣列內容裝載dr0…dr7中的6個除錯暫存器。只有在next_p被掛起時正在使用除錯暫存器,這種操作才能進行。

如果必要,則更新tss中的i/o點陣圖。然後終止,prev_p引數被拷貝到eax,因為預設情況下任何c函式的返回值被傳給eax暫存器。所以eax的值在呼叫__switch_to()的過程中被保護起來;這很重要,因為呼叫該函式時會假定eax總是用來存放將被替換的程序描述符位址。

組合語言指令ret把棧定儲存的返回位址裝入eip程式計數器。不過,__swtich_to()函式時通過簡單的跳轉被呼叫的。因此,ret彙編指令在棧中找到標號為1的指令位址,其中標號為1的位址是由switch_to()巨集推入堆疊的。

far jmp指令既修改cs暫存器,也修改eip暫存器,而簡單的jmp之類值修改eip暫存器。 ↩

linux-程序切換,使用者態程序,核心態程序

Linux 檔案操作函式 使用者核態切換

一 檔案操作函式 1 open 建立了一條到檔案或裝置的訪問路徑,成功將返回乙個可被read write其他系統呼叫使用的檔案描述符。include include includeint open char dpath,int flag,mode t mode 成功返回新得檔案描述符,失敗返回 1並...

linux中切換使用者

檢視當前登入使用的使用者名稱 檢視登入過的使用者名稱 登入的主機位址 時間 tt號 who從當前使用者切換到其他使用者 su 其他使用者名稱 從dsl使用者切換到admin使用者 注意輸入密碼處,要輸入你要切換到的使用者的密碼 dsl localhost admin su admin 從當前使用者切...

linux系統切換使用者

切換使用者的命令是su,su是 switch user 切換使用者的縮寫。通過su命令,可以從普通使用者切換到root使用者,也可以從root使用者切換到普通使用者。從普通使用者切換到root使用者需要密碼 該密碼是普通使用者的密碼 從root使用者切換到普通使用者不需要密碼。第一步 securec...