2020301918-os/kern/process.c
2022-10-29 19:48:58 +08:00

104 lines
4.3 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <elf.h>
#include <mmu.h>
#include <string.h>
#include <x86.h>
#include <kern/trap.h>
#include <kern/pmap.h>
#include <kern/process.h>
#include <kern/protect.h>
#include <kern/syscall.h>
/*
* 这个函数是写给汇编函数用,
* 在汇编函数调用save函数保存完用户寄存器上下文后
* 需要切到进程内核栈中minios的进程栈是与pcb是绑定的。
* 相当于每个进程分配了一个大空间前面一小部分给了pcb
* 后面的全可以给进程栈使用目前进程占8KB进程栈占7KB多。
*
* 这个函数的目的就是切换到进程栈处由于每个进程的空间只有C语言这知道
* 所以直接在C语言里面写一个内联汇编实现这样更方便动态开内核栈空间不用顾忌到汇编。
*/
void
to_kern_stack(u32 ret_esp)
{
asm volatile (
"add %1, %0\n\t"
"mov %0, %%esp\n\t"
"push %2\n\t"
"jmp *4(%%ebp)\n\t":
: "a"((u32)p_proc_ready->_pad)
, "b"((u32)sizeof(p_proc_ready->_pad))
, "c"(ret_esp)
);
}
/*
* 调度函数
*/
void
schedule(void)
{
// 获取当前的eflags的中断位
u32 IF_BIT = read_eflags() & FL_IF;
// 如果中断没关,一定!一定!一定!要关中断保证当前执行流操作的原子性
if (IF_BIT != 0)
disable_int();
PROCESS *p_cur_proc = p_proc_ready;
PROCESS *p_next_proc = p_proc_ready + 1;
if (p_next_proc >= proc_table + PCB_SIZE) {
p_next_proc = proc_table;
}
p_proc_ready = p_next_proc;
// 切换进程页表和tss
lcr3(p_proc_ready->pcb.cr3);
tss.esp0 = (u32)(&p_proc_ready->pcb.user_regs + 1);
// 调度函数最精髓的部分,非常的有意思。
// 对于目前的os来说eax,ebx,ecx,edx,esi,edi,esp,ebp,eflags这九个寄存器就可以表达当前执行流的上下文了
// 所以这个函数干了个什么事呢它先将当前的esp保存到kern_regs中保存当前的栈信息
// 之后将esp移到当前kern_context栈顶然后按顺序依次将eax,ecx,edx,ebx,ebp,esi,edi,eflags压栈
// 这样就把当前进程寄存器上下文都保存下来了再之后esp移到目标kern_context栈底
// 再依次恢复eflags,edi,esi,ebp,ebx,edx,ecx,eax
// 最后一步恢复esp这个esp本身被push时保留的是进程栈中的esp这样就完成了执行流的切换。
// 从当前进程的角度上这就很有意思,相当于执行流进入了一个函数之后突然就被送往其他次元了,而且当前次元的时间静止了。
// 然后再之后突然又回来了,还是之前的那些寄存器信息,感觉这个函数啥都没干。
// 由于涉及到一大堆跟执行流切换相关的操作,所以一定!一定!一定!要关中断。
switch_kern_context(
&p_cur_proc->pcb.kern_regs,
&p_next_proc->pcb.kern_regs);
// 在传送回之后需要做的第一件事就是忘掉所有的全局变量的缓存
// 防止编译器优化导致某些变量被优化掉比如说某个全局变量a
// 你拿eax去获取a的值在发生调度之后a的值发生变化下面用到了a
// 然后编译器想都不想就直接用eax接着用了一个bug就这么来了
// 这玩意学术名词叫内存屏障,这还只是单核,如果多核就更加恐怖了,
// 你会看到内存屏障遍地插这也是为什么os难的一部分原因跟缓存斗跟硬件斗跟编译器斗跟并发斗其乐无穷。
// 要敬佩那些写系统软件的人,斗都要斗麻了的。
asm volatile ("":::"memory");
// 这里可能你会比较疑惑为什么还要开中断,
// 这个时候你需要要以单线程的思路去想,
// 上面的switch_kern_context在执行后执行流就交给了其他进程了
// 但是其他进程总是会通过调度调度回来,对于当前这个进程来说,
// 它相当于突然被送到其他次元了包还掉在地上关中断而且那个次元的时间就静止了The World
// 当在其他次元兜兜转转回来了之后,还得把包捡回来(开中断)。
// 不用去关心其他次元是怎么干的,但是对于这个次元来说还是得捡包的。
// 如果每个次元都遵守这个规则,就可以保证执行不会出问题。
if (IF_BIT != 0)
enable_int();
}
u32
kern_get_pid(PROCESS *p_proc)
{
return p_proc->pcb.pid;
}
ssize_t
do_get_pid(void)
{
return (ssize_t)kern_get_pid(p_proc_ready);
}