更詳細的講解和代碼調試演示過程,請參看視頻
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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。