程序切換之 switch

2021-06-16 07:27:14 字數 3449 閱讀 6552

linux

核心程序切換最重要的乙個部分就是巨集定義switch_to,下面從幾個方面來詳細講解一下:

(1)內嵌彙編

(2)memory 破壞描述符(編譯器優化)

(3)程序切換的標誌是什麼?

(4)堆疊切換的標誌是什麼?

(5)為什麼switch_to 提供了三個引數?

(6)彙編引數的傳遞?

帶著這幾個問題,先來大體瀏覽一下**

#define switch_to(prev, next, last)     \

do while (0)

以上**,主要是內嵌彙編,這裡先簡單介紹一下:

1 內嵌彙編語法

__asm__ __violate__ ("movl %1,%0" : "=r" (result) : "m" (input));

__asm__ __violate__("指令模板" : 輸出部 : 輸入部);

「movl %1,%0」是指令模板;「%0」和「%1」代表指令的運算元,稱為佔位符,內嵌匯

編靠它們將c 語言表示式與指令運算元相對應。指令模板後面用小括號括起來的是c語言表

達式,本例中只有兩個:「result」和「input」,他們按照出現的順序分別與指令運算元「%0」,

「%1」對應;注意對應順序:第乙個c 表示式對應「%0」;第二個表示式對應「%1」,依次類

推,運算元至多有10 個,分別用「%0」,「%1」….「%9」表示。在每個運算元前面有乙個

用引號括起來的字串,字串的內容是對該運算元的限制或者說要求。「result」前面的

限制字串是「=r」,其中「=」表示「result」是輸出運算元,「r」表示需要將「result」

與某個通用暫存器相關聯,先將運算元的值讀入暫存器,然後在指令中使用相應暫存器,而

不是「result」本身,當然指令執行完後需要將暫存器中的值存入變數「result」,從表面

上看好像是指令直接對「result」進行操作,實際上gcc做了隱式處理,這樣我們可以少寫

一些指令。「input」前面的「r」表示該表示式需要先放入某個暫存器,然後在指令中使用

該暫存器參加運算。

(2)memory 破壞描述符(編譯器優化)

記憶體訪問速度遠不及cpu處理速度,為提高機器整體效能,在硬體上引入硬體快取記憶體

cache,加速對記憶體的訪問。另外在現代cpu中指令的執行並不一定嚴格按照順序執行,沒

有相關性的指令可以亂序執行,以充分利用cpu的指令流水線,提高執行速度。以上是硬體

級別的優化。再看軟體一級的優化:一種是在編寫**時由程式設計師優化,另一種是由編譯器

進行優化。編譯器優化常用的方法有:將記憶體變數快取到暫存器;調整指令順序充分利用

cpu指令流水線,常見的是重新排序讀寫指令。

對常規記憶體進行優化的時候,這些優化是透明的,而且效率很好。由編譯器優化或者硬

件重新排序引起的問題的解決辦法是在從硬體(或者其他處理器)的角度看必須以特定順序

執行的操作之間設定記憶體屏障(memory barrier),linux 提供了乙個巨集解決編譯器的執行

順序問題。

void barrier(void)

這個函式通知編譯器插入乙個記憶體屏障,但對硬體無效,編譯後的**會把當前cpu暫存器

中的所有修改過的數值存入記憶體,需要這些資料的時候再重新從記憶體中讀出。

memory描述符告知gcc:

l  1)不要將該段內嵌彙編指令與前面的指令重新排序;也就是在執行內嵌彙編**

之前,它前面的指令都執行完畢

l  2)不要將變數快取到暫存器,因為這段**可能會用到記憶體變數,而這些記憶體變

量會以不可預知的方式發生改變,因此gcc插入必要的**先將快取到暫存器的變

量值寫回記憶體,如果後面又訪問這些變數,需要重新訪問記憶體。

如果彙編指令修改了記憶體,但是gcc 本身卻察覺不到,因為在輸出部分沒有描述,此時

就需要在修改描述部分增加「memory」,告訴gcc 記憶體已經被修改,gcc 得知這個資訊後,

就會在這段指令之前,插入必要的指令將前面因為優化cache 到暫存器中的變數值先寫回內

存,如果以後又要使用這些變數再重新讀取。

(3)程序切換的標誌-----sp指標的切換

因為程序切換也就是程序描述符的切換,現在讓我們來想一下我們是如何定位某個程序描述符的位址的,看下面的彙編**:

mov $0xffffe000,%ecx

andl %esp,%ecx

movl %ecx,p

執行上面**後,p中即儲存當前執行程序的thread_info結構的位址,但是我們最長用的是程序描述符的位址,因此核心設計了current巨集來計算指向程序描述符的指標:

mov $0xfffe000,%ecx

andl %esp,%ecx

movl (%ecx),p

因為task欄位在thread_info中的偏移量為0,所以執行上述三條指令後,p即是當前執行的程序的描述符指標。

我們可以看到,只要知道esp,那麼,程序就唯一確定了,所以說esp是程序切換的標誌

(4)堆疊切換的標誌 --- ebp (棧低指標)

毋庸置疑,棧底指標肯定是堆疊切換的標誌

(5)switch_to 三個引數

程序切換一般都涉及三個程序,如程序a切換成程序b,b開始執行,但是當a恢復執行的時候往往是通過乙個程序c,而不是程序b。注意switch_to的呼叫: switch_to(prev,next,prev), 可以看到last就是prev 呼叫方法如下:程序a->程序b switch_to(a,b,a)主要有三個引數:

輸入引數兩個:prev:切換前的程序,next:切換後的程序,輸出引數乙個:last:切換前程序。  

注意這三個變數都是區域性變數,在系統棧中,所以切換到另一程序後變數的值不會改變。

程序a切換b之前,eax的值為prev,也就是a;edx的值為next,也就是b,eax的值為last,也就是a

當不考慮第三個引數時,從c切換成a,核心棧切換成a的棧,這時a中的prev和nexxt分別指向a和b,程序c的引用丟失了。這時第三個引數就派上用場了。c切換程序a後,將c存入eax中,切換到a後,由於輸出部"=a" (last)會將eax的值寫入last中,也就是prev中,所以此時prev和next的值就是c和b了。

(6)彙編引數的傳遞?

彙編是通過暫存器傳遞引數的,這裡用了eax和edx,這樣jmp就和call差不多了,但是jmp和call的區別是,call會有硬體自動壓棧一些暫存器的,比如cs:ip ,這裡是通過手工壓棧ip,模擬了call,在__switch_to中,用return 返回。我們又會想到為什麼不用call呢?原因是進入__switch_to後,我們是為執行別的程序做準備,也就是說當返回的時候應該是執行next程序而不是當前程序。如果用call的話,壓棧的是當前程序的ip,那麼__switch_to返回後就執行當前程序了,這與我們想執行next的程序的想法是不一致的,因此,我們手工壓棧的是next程序的ip,那麼,當__switch_to返回後自然出棧的就是next的ip,也就是執行next程序了。

作業系統之程序切換

最近複習作業系統關於程序切換的一些記錄。程序切換指從正在執行的程序中收回處理器,讓待執行程序來占有處理器執行。實質上就是被中斷執行程序與待執行程序的上下文切換。二 模式切換 程序切換必須在作業系統核心模式下完成,這就需要模式切換。模式切換又稱處理器切換,即使用者模式和核心模式的互相切換。1 中斷 異...

程序切換和執行緒切換

為了控制程序的執行,核心必須有能力掛起正在cpu上執行的程序,並恢復以前掛起的某個程序的執行。這種行為被稱為程序切換 process switch 任務切換 task switch 或上下文切換 content switch 原文 程序切換分兩步 1.切換頁目錄以使用新的位址空間 2.切換核心棧和硬...

程序切換與執行緒切換

原文 為了控制程序的執行,核心必須有能力掛起正在cpu上執行的程序,並恢復以前掛起的某個程序的執行。這種行為被稱為程序切換 process switch 任務切換 task switch 或上下文切換 content switch 程序切換分兩步 1.切換頁目錄以使用新的位址空間 2.切換核心棧和硬...