Java開發作業系統核心 實現程序的優先順序切換

2021-09-23 07:02:26 字數 3984 閱讀 1661

為了保護系統核心不受惡意程式的破壞,我們原來的做法是專門為應用程式分配單獨使用的記憶體,使得應用程式對資料的讀寫都限制在核心給他分配的記憶體段內。程式對記憶體段的讀寫,完全是由ds暫存器指向的全域性描述符決定的,如果惡意程式通過修改ds暫存器的值,使得它在執行時,讓ds暫存器指向核心資料段的全域性描述符,那麼惡意程式就可以讀寫核心的資料了,為了防範出現這種情況,我們要做的是讓應用程式沒有讀寫段暫存器的權力,因此我們就必須設定應有程式的優先順序。

在x86架構下,程式可以分為4個等級,分別是0,1,2,3.級別數越低,它的許可權就越高,系統核心是許可權最高的,因此它執行的優先順序為0,為了防止應用程式作亂,我們在啟動它之前,必須把它的優先順序設定為最低階,也就是3.

為了讓應用程式執行在低特權級上,核心在啟動應用程式前,必須把應用程式**所在的記憶體段的級別設定為3,在乙個級別為3的**段上執行指令時,如果指令的優先順序高於3,例如讀寫段暫存器等,那麼就會觸發cpu錯誤,根據上篇文章講過的核心異常處理機制,核心就會把應用程式的殺掉。於是我們修改核心啟動應用程式的相關**,在write_vga_desktop.c中:

void cmd_hlt()
上面**跟以前相比,差別在於set_segmdesc呼叫中,設定記憶體段屬性時,我們多加了0x60,加上0x60的目的是,把該描述符所指向的記憶體其優先順序設定為3.這樣一來,應用程式一旦指向高優先順序的cpu指令,例如move ds, ax這種讀寫段暫存器的指令時,就會引發cpu異常。

同時我們通過呼叫task_now()獲得當前正在執行的程序物件,每個程序物件都含有乙個tss資料結構,其內容如下(在multi_task.h中):

struct tss32 ;
tss結構我們在早前講到程序切換的章節裡有過對它的詳細解讀,這裡我們需要注意它的幾個變數:esp0, ss0, esp1, ss1, esp2, ss2。乙個程序在執行時,它可以執行在不同優先順序下,在不同優先順序下執行時,它必須使用不同的堆疊,這些變數就是用於儲存不同優先順序下對應的堆疊段和堆疊指標的。如果程序要切換到優先順序0,那麼cpu會自動從esp0和ss0中讀取堆疊指標和堆疊記憶體段的全域性描述符,如果程序要從優先順序0切換到優先順序1,那麼我們核心需要自己把對應優先順序0的堆疊指標和堆疊段描述符的值存入到esp0和ss0。也就是說如果程序從低優先順序切換到高優先順序時,cpu會自動幫我們從tss中讀取對應的堆疊段全域性描述符和堆疊指標,實現相應的堆疊切換。如果程序從高優先順序切換到低優先順序時,需要程序自己把高優先順序的堆疊段描述符和堆疊指標儲存到tss中的相應位置。

pushad

mov eax, [esp+52]

mov [eax], esp

mov [eax+4], ss

mov eax, [esp+36] ;eip

mov ecx, [esp+40] ;cs

mov edx, [esp+44] ;esp

mov ebx, [esp+48] ;ds

mov ds, bx

mov es, bx

or ecx,3

or ebx, 3

push ebx

push edx

push ecx

push eax

retf

上面**中,有兩條指令特別值得我們注意,他們是:

or ecx, 3

or ebx, 3

ecx暫存器儲存的是應用程式的**段,ebx暫存器儲存的是應有程式的記憶體段。我們以前講過,在把全域性描述符賦值給段暫存器時,需要把該描述符對應在全域性描述符表中的下標乘以8後再傳給段暫存器,為何要乘以8呢?假設某個全域性描述符它的下標是1,乘以8相當於左移三位:

00000001  ->  00001000
左移三位後會在右邊空出3個0,這三個0是有專門作用的,前兩個0用於表示對應記憶體段的優先順序,也叫請求優先順序,當核心要執行應用程式的**時,我們需要把應用程式**段賦值給暫存器cs,把應用程式的記憶體段賦值給ds,如果要把優先順序從0切換成3時,我們需要把請求優先順序也設定為3,這就是前面兩條指令的作用:

or ecx, 3

or ebx, 3

上面兩條指令執行後,最右邊的兩個0都會變成1,也就是把請求優先順序設定成了3。還值得注意的是,以前我們把cpu控制器交給應用程式時,使用的指令是call far,但如果跳轉時帶有優先順序切換,那麼cpu就不允許使用call far 或者是jmp far 這兩種指令,我也不知道英特爾為何這麼設計,要實現從優先順序0跳轉到優先順序3,必須先把優先順序3對應的堆疊和堆疊指標壓入當前堆疊,然後把優先順序3的**段描述符和ip指標壓入堆疊,然後再執行retf命令,這幾個步驟對應的正好是最後幾條指令:

push ebx

push edx

push ecx

push eax

retf

執行完上面幾條指令後,應用程式就可以執行起來了,並且應用程式是執行在優先順序為3的條件下,此時應用程式不執行執行任何許可權超過3的指令,例如儲存段暫存器相關的指令,如果應用程式執行類似指令:move ds, ax時,cpu會產生od異常,於是根據上一節內容,應用程式會被殺掉。

在應用程式執行過程中,如果它需要呼叫核心api,也就是需要執行核心**時,cpu會自動從tss中讀取esp0和ss0兩個變數的資訊,然後自動把堆疊段和堆疊指標切換到核心原來的堆疊段和堆疊指標,這樣可以省卻我們大量的麻煩,於是相關**便可以得到極大的精簡,例如實現api呼叫的02dh中斷的實現如下:

asm_cons_putchar:

asmconsputcharhandler equ asm_cons_putchar - $$

push ds

push es

pushad

pusdad

;把記憶體段切換到核心

mov ax, selectorvram

mov ds, ax

mov es, ax

call kernel_api

cmp eax, 0

popad

pop es

pop ds

iretd

mov esp, [eax]

popad

ret

相比於上個版本,**精簡了很多,那是因為我們不用再考慮應用程式切換到核心時堆疊如何切換,因為cpu已經幫我們處理了。這裡我們再看看kernel_api的實現:

int* kernel_api(int edi, int esi, int ebp, int esp,

int ebx, int edx, int ecx, int eax) else if (edx == 2) else if (edx == 4)

return 0;

}

最後我們再看看有個應用程式**的修改,在api_call.asm中:

[section .s32]

bits 32

call main

mov edx, 4 ;返回核心

int 02dh

api_putchar:

mov edx, 1

mov al, [esp + 4]

int 02dh

ret當**呼叫完main函式後,也就是應用程式執行完畢後,**把4賦值給edx暫存器,然後呼叫api中斷,根據前面的分析,中斷執行後cpu的控制權就交還給了核心。另外由於應用程式執行在優先順序3,它要呼叫核心中斷時,需要使用指令int 02dh來觸發中斷,我們必須把02dh號中斷的優先順序也設定成3,要不然應用程式就沒有資格呼叫02dh號中斷,於是在kernel.asm中做如下修改:

.2dh:

gate selectorcode32, asmconsputcharhandler,0, da_386igate+0x60

像前面說過的一樣,加上0x60就是把該中斷的優先順序設定為3.

完成所有**修改後,核心運**況如下:

雖然執行結果與往常一樣

但應用程式執行時的優先順序已經轉變為3,因此應用程式沒有了執行高階指令的許可權,因此核心得到了進一步的保護。

作業系統的核心

核心就是 kernel 它是作業系統最底層的東西,每個作業系統都有自己的核心,由它來掌管整個硬體資源的工作狀態。所以,當有新的硬體加入到作業系統中時,若核心並沒有支援它,這個新硬體就無法工作,因為控制它的核心並不認識它。一般來說,核心為了實現使用者所需要的正確運算結果,必須要管理的事項有以下幾項。1...

作業系統核心編譯

sudo apt get install libncurses5 dev libssl dev sudo apt get install build essential openssl sudo apt get install zlibc minizip sudo apt get install l...

linux CPU 作業系統核心

幾個cpu more proc cpuinfo grep physical id uniq wc l每個cpu是幾核 假設cpu配置相同 more proc cpuinfo grep physical id grep 0 wc l cat proc cpuinfo grep processor檢視物...