java開發系統內核:進程切換

更詳細的講解和代碼調試演示過程,請參看視頻
Linux kernel Hacker, 從零構建本身的內核vue

上一節,咱們初步介紹了進程相關的具體概念,特別是講解了進程切換相關的數據結構,也就是TSS,也實現了進程的自我切換,本節,咱們看看如何從當前的進程切換到新進程,而後再切換回來,也就是:面試

進程A -切換->進程B-切換->進程A.算法

咱們先看看進程B的實現,一個進程主要包含一個主函數,咱們把進程B的主函數實現以下:微信

void task_b_main(void) {
   showString(shtctl, sht_back, 0, 144, COL8_FFFFFF, "enter task b");

    struct FIFO8 timerinfo_b;
    char timerbuf_b[8];
    struct TIMER *timer_b = 0;

    int i = 0;

    fifo8_init(&timerinfo_b, 8, timerbuf_b);
    timer_b = timer_alloc();
    timer_init(timer_b, &timerinfo_b, 123);

    timer_settime(timer_b, 500);

    for(;;) {

       io_cli();
        if (fifo8_status(&timerinfo_b) == 0) {
            io_sti();
        } else {
           i = fifo8_get(&timerinfo_b);
           io_sti();
           if (i == 123) {
               showString(shtctl, sht_back, 0, 160, COL8_FFFFFF, "switch back");
               taskswitch7();
           }

        }

    }

}

進程B函數的邏輯是這樣的,當進入到進程B後,經過它的主函數如今桌面上打印出一個字符串」enter task b」, 當這個字符串出如今桌面時,表示進程完成了切換。而後它初始化一個時鐘,這個時鐘超時是五秒,五秒事後,它調用函數taskswitch7從新切回到進程A.markdown

進程A就是主入口函數CMain. 既然要切換進程B,那顯然,咱們須要一個描述進程B的TSS結構,並進行相應的初始化,代碼以下:數據結構

int addr_code32 = get_code32_addr();
 tss_b.eip =  (task_b_main - addr_code32);
    tss_b.eflags = 0x00000202; 
    tss_b.eax = 0;
    tss_b.ecx = 0;
    tss_b.edx = 0;
    tss_b.ebx = 0;
    tss_b.esp = 1024;//tss_a.esp;
    tss_b.ebp = 0;
    tss_b.esi = 0;
    tss_b.edi = 0;
    tss_b.es = tss_a.es;
    tss_b.cs = tss_a.cs;//6 * 8;
    tss_b.ss = tss_a.ss;
    tss_b.ds = tss_a.ds;
    tss_b.fs = tss_a.fs;
    tss_b.gs = tss_a.gs;

上面的代碼須要詳細解釋下,首先咱們把tss_b.eflags設置成0x202,這個值能夠當作一個寫死的值,而後,咱們把進程B的段寄存器設置成跟A同樣,咱們看看進程A的各個段寄存器分別指向哪一個全局描述符,tss_a.cs 的值是8,對應全局描述符表的下標就是1(數值要除以8,上一節講解過)。下標爲1的描述符是這樣的:app

LABEL_DESC_CODE32:  Descriptor        0,      0fffffh,       DA_CR | DA_32 | DA_LIMIT_4K

這個描述符指向一段內存,這段內存的性質是可執行代碼段,這段內存的起始地址在內核的彙編部分進行了初始化,以下:機器學習

xor   eax, eax
     mov   ax,  cs
     shl   eax, 4
     add   eax, LABEL_SEG_CODE32
     mov   word [LABEL_DESC_CODE32 + 2], ax
     shr   eax, 16
     mov   byte [LABEL_DESC_CODE32 + 4], al
     mov   byte [LABEL_DESC_CODE32 + 7], ah

上面的代碼把描述符指向的內存地址的起始位置設置爲LABEL_SEG_CODE32,
tss_a.ds 的值爲24,除以8後爲3,也就是對應描述符在全局描述符表中的下標是3,這個描述符內容以下:函數

LABEL_DESC_VRAM:    Descriptor        0,         0fffffh,            DA_DRWA | DA_LIMIT_4K

這個描述符指向的內存起始地址是0,長度爲0fffffh, 這段內存的性質是可讀寫數據段,也就是從0到0fffffh這段長度的內存是可讀寫的數據。學習

tss_a.ss 的值是32,除以8後得4,所以對應的是下標爲4的描述符,該描述符的內容以下:

LABEL_DESC_STACK:   Descriptor        0,             LenOfStackSection,        DA_DRWA | DA_32

它描述的是一段32位可讀寫的內存,長度爲LenOfStackSection,它對應的這段內存是咱們在內核的彙編部分分配的內存,具體以下:

[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512  db 0
TopOfStack1  equ  $ - LABEL_STACK
times 512 db 0
TopOfStack2 equ $ - LABEL_STACK

LenOfStackSection equ $ - LABEL_STACK

上面分配了兩個512字節,總共1024字節的內存,LABEL_STACK將會設置成下標爲4的描述符所對應內存的起始地址,第一個512字節,做爲進程A的堆棧,第二個512字節,將做爲進程B的堆棧,上面tss_b的初始化代碼中有這麼一句:
tss_b.esp = 1024;
它的做用就是讓進程把把堆棧指針指向第二個512字節的末尾處,你們要記得,堆棧是有高地址向低地址生長的,因此設置堆棧指針時,要把它指向內存的末尾。

在內核的彙編部分,有代碼將下標爲4的描述符對應的內容起始地址設置爲了LABEL_STACK, 代碼以下:

    xor   eax, eax
     mov   ax,  cs
     shl   eax, 4
     add   eax, LABEL_STACK
     mov   word [LABEL_DESC_STACK + 2], ax
     shr   eax, 16
     mov   byte [LABEL_DESC_STACK + 4], al
     mov   byte [LABEL_DESC_STACK + 7], ah

最重要的三個段寄存器,cs, ds, ss,設置好,其他寄存器,設置成跟進程A同樣便可,接下來最重要的設置是eip指針,這個指針將指向要執行代碼的首地址,咱們要執行的函數是task_b_main ,所以eip應該指向這個函數,但注意,咱們不能直接把這個函數的地址直接賦值給eip, eip指向的是相對於代碼段起始地址的偏移,當前代碼段的其實地址是LABEL_SEG_CODE32, 所以咱們須要把task_b_main的地址減去LABEL_SEG_CODE32,所得的結果就是相對偏移了,這也是eip初始化的邏輯:
tss_b.eip =  (task_b_main - addr_code32);

get_code32_addr是內核的彙編部分實現的行數,目的就是返回LABEL_SEG_CODE32對應的地址,實現以下:

get_code32_addr:
        mov  eax, LABEL_SEG_CODE32
        ret

上一節,咱們已經看到,咱們經過代碼,講一個描述符指向結構tss_b了,代碼以下:

set_segmdesc(gdt + 9, 103, (int) &tss_b, AR_TSS32);

指向tss_b結構的描述符下標是9,初始化好tss_b後,只要經過一個jmp語句,跳轉到下標爲9的描述符,那麼就能將當前指向進程切換成運行task_b_main的進程了,這個跳轉語句實現以下:

taskswitch9:
        jmp 9*8:0
        ret

進程A運行的是CMain函數,它會建立一個5秒的計時器,一旦超時,則調用上面的函數實現任務切換:

for(;;) {
.....
else if (fifo8_status(&timerinfo) != 0) {
           io_sti();
           int i = fifo8_get(&timerinfo);
           if (i == 10) {
               showString(shtctl, sht_back, 0, 176, COL8_FFFFFF, "switch to task b");
                //switch task 
               taskswitch9();
           }
.....
}

在跳轉前,咱們會在桌面上打印出一句switch to task b表示即將進行任務切換,task_b_main的實現咱們已經看過了,進入task_b_main後,它會在桌面打印一條語句,表示跳轉成功,而後啓動一個5秒的計時器,五秒事後,經過taskswitch7從新跳轉回進程A.

從運行過程上看,當進程A運行時,有一個光標會在文本框中不斷的閃爍:

一旦跳轉到task_b_main, 桌面會打印出相關字符串,而後光標會中止住,等5廟後,進程從task_b_main,切換回進程A,進程A恢復執行,因而在卡死5秒後,在跳轉會進程A前,task_b_main會打印出一條語句」switch back」,當這條語句出如今桌面上時,控制器轉回到進程A, 因而光標會從新開始閃爍。

這樣的話,咱們就實現了進程從A切換到B再從B切換回A的整個流程:

更加詳細的講解和調試演示請參看視頻。

更多技術信息,包括操做系統,編譯器,面試算法,機器學習,人工智能,請關照個人公衆號:

本文分享自微信公衆號 - Coding迪斯尼(gh_c9f933e7765d)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索