模拟存储程序计算机工作模型二:进程初始化

前面我们模拟了一个带有时钟中断的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)
);

关键字volatile:编译器不再优化访问该变量,总是重新从内存中读取数据,即使刚刚使用过该变量

堆栈和寄存器的变化过程如下:

高地址
-------
| 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

代码示例

点击查看代码

posted @ 2024-11-06 10:34  sgqmax  阅读(3)  评论(0编辑  收藏  举报