模擬儲存程式計算機工作模型二:程序初始化

sgqmax發表於2024-11-06

前面我們模擬了一個帶有時鐘中斷的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,這裡$1fif語句裡的標號1:

pushl %3
把即將執行的程序next的程式碼入口地址入棧

ret
出棧程序next的程式碼入口地址到EIP暫存器
開始執行程序next

程式碼示例

點選檢視程式碼

相關文章