前面我們模擬了一個帶有時鐘中斷的x86 CPU並初始化了系統變數
只需要在mymain.c
基礎上完成程序描述符PCB和程序連結串列管理,在myinterrupt.c
中完成程序切換程式碼,即可完成一個可執行的OS kernel
定義程序描述符PCB
linux核心中程序PCB由資料結構struct task_struct
定義
定義mykernel
的程序控制塊mykernel/mypcb.h
定義入口函式my_start_kernel
linux核心入口函式為init/main.c:start_kernel()
修改mymain.c:my_start_kernel()
,定義mykernel
的入口,負責初始化工作
先將程序thread.ip
初始化為工作函式地址my_process
將程序thread.sp
初始化為棧頂地址,由於棧空,所以棧頂和棧底地址一樣,都是&task[pid].stack[KERNEL_STACK_SIZE-1]
啟動第一個程序的關鍵彙編程式碼
asm volatile(
"movl %1, %%esp\n\t"
"pushl %1\n\t"
"pushl %0\n\t"
"ret\n\t"
"popl %%ebp\n\t"
:
: "c" (task[pid].thread.ip), "d" (task[pid].thread.sp)
);
堆疊和暫存器的變化過程如下:
高地址
-------
| ebp |
| eip |<- esp
| |
低地址
movl %1, %%esp
將程序堆疊棧頂地址task[pid].thread.sp
放入ESP暫存器
pushl %1
因為是空棧,所以ESP和EBP值一樣
直接使用堆疊棧頂地址task[pid].thread.sp
初始化EBP
pushl %0
將當前程序thread.ip
入棧,相應的ESP暫存器指向的位置也發生變化
這裡是my_process(void)
函式的位置
ret
將EIP暫存器指向程序thread.ip
,即my_process(void)
位置
popl %%ebp
這裡不會被執行,只是與push
指令配套出現,編碼習慣
0號程序啟動,開始執行my_process(void)
定義排程函式my_schedule
linux核心中,程序排程函式為schedule(void)
修改myinterrupt.c
,增加mykernel
的程序上下文切換程式碼my_schedule()
若程序next曾經執行過,則進入if
語句
asm volatile (
"pushl %%ebp\n\t" // save %ebp
"movl %%esp, %0\n\t" // save %esp
"movl %2, %%esp\n\t" // restore %esp
"movl $1f, %1\n\t" // save %eip
"pushl %3\n\t"
"ret\n\t" // restore %eip
"1:\t" // next process start here
"popl %%ebp\n\t"
: "=m" (prev->thread.sp), "=m" (prev->thread.ip)
: "m" (next->thread.sp), "m" (next->thread.ip)
);
說明如下:
pushl %%ebp
將當前EBP暫存器入棧
movl %%esp, %0
將當前程序ESP暫存器的值儲存到程序PCB
movl %2, %%esp
載入next程序的堆疊棧頂地址到ESP暫存器,ESP暫存器指向next程序棧頂
movl $1f, %1
儲存$1f
到程序PCB中,下次恢復程序後將在標號1開始執行
pushl %3
將next程序繼續執行的程式碼位置(標號1)入棧
1:
標號1,即next程序開始執行的位置
if
語句中有標號1,else
中沒有
else
中的$1f
只是將其存入PCB的prev->thread.ip
中,並沒有使用
當程序被重新排程時,prev->thread.ip
變成next->thread.ip
此時進入if
程式碼塊將next->thread.ip
入棧,並由ret
出棧到EIP暫存器,此時才使用了$1f
因此執行if
程式碼塊中的1:
程式碼,所以else
中沒有標號1
popl %%ebp
恢復EBP暫存器值
若程序next第一次執行,則進入else
語句
asm volatile(
"pushl %%ebp\b\t" // save %ebp
"movl %%esp, %0\n\t" // save %esp
"movl %2, %%esp\n\t" // restore %esp
"movl %2, %%ebp\n\t" // restore %ebp
"movl $1f, %1\n\t" // save %eip
"pushl %3\n\t"
"ret\n\t" // restore %eip
: "=m" (prev->thread.sp), "=m" (prev->thread.ip)
: "m" (next->thread.sp), "m" (next->thread.ip)
);
說明如下:
pushl %%ebp
儲存當前程序上下文,將當前程序EBP入棧儲存
movl %%esp, %0
儲存當前程序上下文,將當前程序ESP儲存到程序PCB
movl %2, %%esp
切換到next程序,載入next程序的棧頂地址next->thread.sp
到ESP暫存器
movl %2, %%ebp
載入next程序的堆疊基地址next->thread.sp
到EBP暫存器
next是一個新的程序,堆疊為空,棧底和棧頂地址相同,都用next->thread.sp
表示
movl $1f, %1
將$1f
儲存到程序PCB,這裡$1f
是if
語句裡的標號1:
pushl %3
把即將執行的程序next的程式碼入口地址入棧
ret
出棧程序next的程式碼入口地址到EIP暫存器
開始執行程序next
程式碼示例
點選檢視程式碼