Linux下如何從使用者態切換到核心態?
首先我們得明白什麼是使用者態和核心態?
明白這兩個概念之前,我們得知道使用者空間和核心空間。
使用者空間:指的就是使用者可以操作和訪問的空間,這個空間通常存放我們使用者自己寫的資料等等;而核心空間則是系統核心來操作的一塊空間,這塊空間裡面存放系統核心的函式、介面等。
不管對於Linux還是Windows, 他們都具有自己使用者空間和核心空間。當一個程式執行時,如果它是在使用者空間下執行,我們把此時執行得程式的這種狀態成為使用者態,而當這段程式執行在核心的空間執行時,這種狀態稱為核心態。
Linux嚴格意義上說的是一個作業系統,我們稱之為“核心 "但我們一般使用者是,不能直接使用kernel,而是通過kernel的“外殼“程式,也就是說shell,來於kernel溝通。
那什麼是Shell?
Shell:命令列直譯器(command Interpreter),它主要包含:
1.將使用者的命令翻譯給核心(kernel)來處理。
2.將核心(kernel)的處理結果翻譯給使用者。
那麼對比Windows GUI,我們也不是直接操作Windows核心,而是通過GUI的圖形介面,點選,從而完成我們的操作(比如進入D盤的操作,我們通常時雙擊D盤碟符,此時雙擊的操作就是通過GUI來將指令解析給Windows核心 ,從而完成操作)。
關於使用者不能直接操作核心,其根本原因是為了對系統核心進行“保護”。這裡我在系統呼叫說過。
明白上面的概念之後,現在的問題就是如何切換?
答案是軟體中斷
具體是怎麼切換 ,我們再來詳細分析下。
我們所說的使用者態到核心態的切換,其實就是一個程序通過系統呼叫到核心的一些介面。從而實現切換。而該系統呼叫切換時通過軟體中斷來完成,該中斷是程式人員自己開發出的一種正常的異常,那麼在Linux下,這個異常具體就是呼叫int $0x80的彙編指令,這條彙編指令將產生向量為0x80的程式設計異常。
之所以系統呼叫需要藉助這個中斷異常來實現,是因為這個異常實際上就是通過系統門陷入核心(除了int 0x80外使用者空間還可以通過int3——向量3、into——向量4 、bound——向量5等異常指令進入核心,而其他異常無法被使用者空間程式利用,都是由系統使用的)。
好了,現在我們知道是先通過軟體中斷呼叫了0x80的這個程式設計異常,這個程式設計異常對應的是中斷描述符表IDT中的第128項——也就是對應的系統門描述符。門描述符中含有一個預設的核心空間地址,它指向了系統呼叫處理程式:system_call()(別和系統呼叫服務程式混淆,這個程式在entry.S檔案中用匯編語言編寫)。走到這一步,我們應該畫個圖來理解。
現在我們就知道通過系統描述符中的核心空間來找到系統呼叫程式,從而進入到核心態。可很顯然,所有的系統呼叫都會統一地轉到這個地址,但Linux一共有319個系統呼叫都從這裡進入核心後又該如何派發到它們到各自的服務程式去呢?
解決這個問題的方法很簡單就是: 首先Linux為每個系統呼叫都進行了編號 (0—NR_syscall),同時在核心中儲存了一張系統呼叫表 ,該表中儲存了系統呼叫編號和其對應的服務例程,因此在系統調入通過系統門陷入核心前,需要把系統呼叫號一併傳入核心。在x86上,這個傳遞動作是通過在執行int0x80前把呼叫號裝入eax暫存器實現的。這樣系統呼叫處理程式一旦執行,就可以從eax中得到資料,然後再去系統呼叫表中尋找相應服務例程了。為了方便理解,我把這些操作用圖表示出來。
除了需要傳遞系統呼叫號以外,許多系統呼叫還需要傳遞一些引數到核心,比如sys_write(unsigned int fd, const char * buf, size_t count)呼叫就需要傳遞檔案描述符fd、要寫入的內容buf、以及寫入位元組數count等幾個內容到核心。碰到這種情況,Linux會有6個暫存器可被用來傳遞這些引數:eax (存放系統呼叫號)、 ebx、ecx、edx、esi及edi來存放這些額外的引數(以字母遞增的順序)。具體做法是在system_call( )中使用SAVE_ALL巨集把這些暫存器的值儲存在核心態堆疊中。
做好上述工作之後於,再執行系統呼叫處理程式,這樣就從使用者態切換到核心態。
以上均為本人理解,如有不正,歡迎評論指正。共同學習!