第2章 操作系统是如何工作的
一、学习笔记
1. 计算机的三大法宝:存储程序计算、函数调用机制、中断。
2. 堆栈的具体作用有:记录函数调用框架、传递函数参数、保存返回值的地址、提供函数内部局部变量的存储空间等。
3. 堆栈相关的寄存器:
ESP:堆栈指针(stack pointer)
EBP:基址指针(base pointer),在C语言中用作记录当前函数调用基址。
4. 对于X86体系结构来讲,堆栈空间是从高地址向低地址增长的,如图所示:

5. 堆栈操作:
push:栈顶地址减少4个字节(32位),并将操作数放入栈顶存储单元。
pop:栈顶地址增加4个字节(32位),并将栈顶存储单元的内容放入操作数。
6. 其它一些指令:
顺序执行:总是指向地址连续的下一条指令。
跳转/分支:执行这样的指令时,CS:EIP的值会根据程序需要被修改。
call:将当前CS:EIP 的值压入栈顶,CS:EIP指向被调用函数的入口地址。
ret:从栈顶弹出原来保存在这样的CS:EIP的值,放入CS:EIP中。
二、试验记录
1.进程初始化代码
asm volatile(
"movl %1,%%esp\n\t" //将进程原堆栈栈顶的地址(这里是初始化的值)存入ESP寄存器。
"pushl %1\n\t" //将当前EBP寄存器值入栈。
"pushl %0\n\t" //将当前进程的EIP(这里是初始化的值)入栈。
"ret\n\t" //ret命令正好可以让入栈的进程EIP保存到EIP寄存器中。
"popl %%ebp\n\t" //与前面push指令结对出现。
2.进程0启动,开始执行my_process(void)函数的代码。
if(next->state==0)//next->state==0对应进程next对应进程曾经执行过。
{
//进行进程调度关键代码。
asm volatile(
"pushl %%ebp\n\t" //保存当前EBP到堆栈中。
"movl %%esp,%0\n\t" //保存当前ESP到当前PCB中。
"movl %2,%%esp\n\t" //将next进程的堆栈栈顶的值存到ESP寄存器。
"movl $1f,%1\n\t" //保存当前进程的EIP值,下次恢复进程后将在标号1开始执行。
"pushl %3\n\t" //将next进程继续执行的代码位置(标号1)压栈。
"ret\n\t" //出栈标号1到EIP寄存器。
"1:\t" //标号1,即next进程开始执行的位置。
"pop1 %%ebp\n\t" //恢复EBP寄存器的值。
: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
: "m" (next->thread.sp),"m" (next->thread.ip)
);
my_current_task=next;
printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
}
else
{
next-state=0;
my_current_task=next;
printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
//转换到新的进程中
asm volatile(
"pushl %%ebp\n\t" //保存当前EBP到堆栈中。
"movl %%esp,%0\n\t" //保存当前ESP到当前PCB中。
"movl %2,%%esp\n\t" //载入next进程的栈顶地址到ESP寄存器。
"movl %2,%%ebp\n\t" //载入next进程的堆栈基地址到EBP寄存器
"movl $1f,%1\n\t" //保存当前EIP寄存器值到PCB,这里$1f是指上面的标号1。
"push %3\n\t" //把即将执行的进程的代码入口地址入栈。
"ret\n\t" //出栈进程的代码入口地址到EIP寄存器。
: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
: "m" (next->thread.sp),"m" (next->thread.ip)
);
}
3.对上述代码的堆栈调用过程进行分析。
为了分析简单,假设系统只有两个进程,分别是进程0和进程1.进程0由内核启动时初始化执行,然后需要进程调度,开始执行进程1。下面从进程1被调度开始分析堆栈变化,因为进程1从来没有被执行过,是第一次被调度执行,此时执行else中的代码。

到这里开始执行进程1,如果进程1执行的过程中发生了进程调度,进程0重新被调度执行了,应该执行前述if中的代码,具体的堆栈变化与上图所示过程相似。
总结
本章内容最重要的是进程的切换,进程在执行过程中,当时间片用完需要进程切换时,需要先保存当前的进程执行的上下文环境,下次进程被调度时,需要恢复进程的上下文环境。这样实现多道程序的并发执行。