模拟存储程序计算机工作模型二:进程初始化
前面我们模拟了一个带有时钟中断的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,这里$1f
是if
语句里的标号1:
pushl %3
把即将执行的进程next的代码入口地址入栈
ret
出栈进程next的代码入口地址到EIP寄存器
开始执行进程next