scheduler线程分析
1. scheduler函数
当检索到有一个空闲的PCB后,将PCB的地址记录在c->proc成员中。接着调用switchuvm。
2. switchuvm函数
pushcli和popcli实现如下功能:
- 如果在调用pushcli之前,中断是关的,则调用popcli后中断还是关的。
- 如果在调用pushcli之前,中断是开的,则调用pushcli关中断,调popcli之后开中断。
2.1 设置TSS描述符
接着设置了任务段TSS描述符,TSS段描述符内容如下:
TSS段用于恢复一个任务执行的处理器状态信息。TSS段详细内容如下:
从代码中可以看出,每个cpu结构中都有一个TSS段,TSS段中的SS和ESP用来表示一个进程的内核栈地址。
2.2 设置TR寄存器
TR寄存器描述如下:
通过ltr指令加载段选择子到TR寄存器中。
2.3 切换进程页表
最后将CR3寄存器切换到当前进程的页表,switchuvm做的事情总结如下:
- 设置当前cpu中的GDT表中的TSS段描述符。
- 设置TSS段中的ss和esp为要运行进程的内核栈地址。
- 切换CR3寄存器为要运行进程的页目录地址。
3. 进程切换
接着设置进程的状态为RUNNING,然后调用swtch切换到第一个进程initcode中执行。
swtch的原型如下:
void swtch(struct context**, struct context*);
第一个参数用来保存old的context在old栈中的地址,是出参。第二个参数用来表示new的context在new栈中的地址,是入参。
具体实现如下:
首先保存老的上下文在老的栈中,来看看在scheduler线程中swtch的调用:
可以看出,老的上下文就是指scheduler线程的上下文,新的上下文就是指即将要运行的进程的上下文,这个上下文在创建新进程时通过allocproc在新的进程的内核栈中构造好的。swtch的流程如下;
- 老的上下文保存在老的内核栈中。
- 切换内核栈指针。
- 恢复新栈中的上下文。
- 最后通过ret返回。
注意,如果新进程是第一次运行,那么ret返回到forkret函数中执行。
我们来看看新进程被切换运行的全过程:
当新进程的四个寄存器被弹出栈空间时,栈中的eip指向的是进程之前调用swtch()函数的下一条指令的地址,当调用ret时,ret指令将esp指向的栈中保存的eip值赋值给eip寄存器,然后cpu就从新的eip地址执行指令,也就相当于新进程运行了。
评论