java開發(fā)系統(tǒng)內(nèi)核:進程切換

更詳細的講解和代碼調(diào)試演示過程,請參看視頻
Linux kernel Hacker, 從零構(gòu)建自己的內(nèi)核

上一節(jié),我們初步介紹了進程相關(guān)的具體概念,特別是講解了進程切換相關(guān)的數(shù)據(jù)結(jié)構(gòu),也就是TSS,也實現(xiàn)了進程的自我切換,本節(jié),我們看看如何從當前的進程切換到新進程,然后再切換回來,也就是:

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

我們先看看進程B的實現(xiàn),一個進程主要包含一個主函數(shù),我們把進程B的主函數(shù)實現(xiàn)如下:

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函數(shù)的邏輯是這樣的,當進入到進程B后,通過它的主函數(shù)現(xiàn)在桌面上打印出一個字符串"enter task b", 當這個字符串出現(xiàn)在桌面時,表示進程完成了切換。然后它初始化一個時鐘,這個時鐘超時是五秒,五秒過后,它調(diào)用函數(shù)taskswitch7重新切回到進程A.

進程A就是主入口函數(shù)CMain. 既然要切換進程B,那顯然,我們需要一個描述進程B的TSS結(jié)構(gòu),并進行相應(yīng)的初始化,代碼如下:

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設(shè)置成0x202,這個值可以當做一個寫死的值,然后,我們把進程B的段寄存器設(shè)置成跟A一樣,我們看看進程A的各個段寄存器分別指向哪個全局描述符,tss_a.cs 的值是8,對應(yīng)全局描述符表的下標就是1(數(shù)值要除以8,上一節(jié)講解過)。下標為1的描述符是這樣的:

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

這個描述符指向一段內(nèi)存,這段內(nèi)存的性質(zhì)是可執(zhí)行代碼段,這段內(nèi)存的起始地址在內(nèi)核的匯編部分進行了初始化,如下:

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

上面的代碼把描述符指向的內(nèi)存地址的起始位置設(shè)置為LABEL_SEG_CODE32,
tss_a.ds 的值為24,除以8后為3,也就是對應(yīng)描述符在全局描述符表中的下標是3,這個描述符內(nèi)容如下:

LABEL_DESC_VRAM:    Descriptor        0,         0fffffh,            DA_DRWA | DA_LIMIT_4K

這個描述符指向的內(nèi)存起始地址是0,長度為0fffffh, 這段內(nèi)存的性質(zhì)是可讀寫數(shù)據(jù)段,也就是從0到0fffffh這段長度的內(nèi)存是可讀寫的數(shù)據(jù)。

tss_a.ss 的值是32,除以8后得4,因此對應(yīng)的是下標為4的描述符,該描述符的內(nèi)容如下:

LABEL_DESC_STACK:   Descriptor        0,             LenOfStackSection,        DA_DRWA | DA_32

它描述的是一段32位可讀寫的內(nèi)存,長度為LenOfStackSection,它對應(yīng)的這段內(nèi)存是我們在內(nèi)核的匯編部分分配的內(nèi)存,具體如下:

[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字節(jié),總共1024字節(jié)的內(nèi)存,LABEL_STACK將會設(shè)置成下標為4的描述符所對應(yīng)內(nèi)存的起始地址,第一個512字節(jié),作為進程A的堆棧,第二個512字節(jié),將作為進程B的堆棧,上面tss_b的初始化代碼中有這么一句:
tss_b.esp = 1024;
它的作用就是讓進程把把堆棧指針指向第二個512字節(jié)的末尾處,大家要記得,堆棧是有高地址向低地址生長的,所以設(shè)置堆棧指針時,要把它指向內(nèi)存的末尾。

在內(nèi)核的匯編部分,有代碼將下標為4的描述符對應(yīng)的內(nèi)容起始地址設(shè)置為了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,設(shè)置好,其余寄存器,設(shè)置成跟進程A一樣即可,接下來最重要的設(shè)置是eip指針,這個指針將指向要執(zhí)行代碼的首地址,我們要執(zhí)行的函數(shù)是task_b_main ,因此eip應(yīng)該指向這個函數(shù),但注意,我們不能直接把這個函數(shù)的地址直接賦值給eip, eip指向的是相對于代碼段起始地址的偏移,當前代碼段的其實地址是LABEL_SEG_CODE32, 因此我們需要把task_b_main的地址減去LABEL_SEG_CODE32,所得的結(jié)果就是相對偏移了,這也是eip初始化的邏輯:
tss_b.eip = (task_b_main - addr_code32);

get_code32_addr是內(nèi)核的匯編部分實現(xiàn)的行數(shù),目的就是返回LABEL_SEG_CODE32對應(yīng)的地址,實現(xiàn)如下:

get_code32_addr:
        mov  eax, LABEL_SEG_CODE32
        ret

上一節(jié),我們已經(jīng)看到,我們通過代碼,講一個描述符指向結(jié)構(gòu)tss_b了,代碼如下:

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

指向tss_b結(jié)構(gòu)的描述符下標是9,初始化好tss_b后,只要通過一個jmp語句,跳轉(zhuǎn)到下標為9的描述符,那么就能將當前指向進程切換成運行task_b_main的進程了,這個跳轉(zhuǎn)語句實現(xiàn)如下:

taskswitch9:
        jmp 9*8:0
        ret

進程A運行的是CMain函數(shù),它會創(chuàng)建一個5秒的計時器,一旦超時,則調(diào)用上面的函數(shù)實現(xiàn)任務(wù)切換:

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();
           }
.....
}

在跳轉(zhuǎn)前,我們會在桌面上打印出一句switch to task b表示即將進行任務(wù)切換,task_b_main的實現(xiàn)我們已經(jīng)看過了,進入task_b_main后,它會在桌面打印一條語句,表示跳轉(zhuǎn)成功,然后啟動一個5秒的計時器,五秒過后,通過taskswitch7重新跳轉(zhuǎn)回進程A.

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


這里寫圖片描述

一旦跳轉(zhuǎn)到task_b_main, 桌面會打印出相關(guān)字符串,然后光標會停止住,等5廟后,進程從task_b_main,切換回進程A,進程A恢復執(zhí)行,于是在卡死5秒后,在跳轉(zhuǎn)會進程A前,task_b_main會打印出一條語句"switch back",當這條語句出現(xiàn)在桌面上時,控制器轉(zhuǎn)回到進程A, 于是光標會重新開始閃爍。

這樣的話,我們就實現(xiàn)了進程從A切換到B再從B切換回A的整個流程:


這里寫圖片描述

更加詳細的講解和調(diào)試演示請參看視頻。

更多技術(shù)信息,包括操作系統(tǒng),編譯器,面試算法,機器學習,人工智能,請關(guān)照我的公眾號:


這里寫圖片描述
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容