了解xv6的引导及初始化部分格外重要,因为它奠定了整个xv6操作系统的基础和框架。
按照执行的顺序,整个引导及初始化的基本流程如下:
bootasm.S
处于实模式,设置并加载gdt、使能A20、设置PE,从16bit代码段跳转到32bit保护模式代码段 (bootmain) 开始执行。
-----------------------------------------------------------------------------------------------------------------
bootmain.c
已经进入了保护模式,主要作了如下工作:
1)从第1扇区将内核 (ELF文件) 加载到内存地址为640K处
2) 解析ELF格式的文件,将代码段、数据段等链接到相应的地址,即bootother.S、initcode.S以及kernel就可以链接到正确的地址了
3) 跳转到内核的入口地址 (main) 开始执行
--------------------------------------------------------------------------------------------------------------------
main.c
主要设置了BSP的数据以及一些控制器的初始化工作
ksegment(); //设置BSP的per-CPU数据(即填充cpu结构体数据,包含有gdt以及cpu->id等)
picinit(); //初始化8259A主从中断控制器
kinit(); //free了从end-16M的内存空间,构成了一个空闲内存链表,每个节点对应1页 (4K),头结点为kmem.freelist
接下来跳转到mainc继续执行
--------------------------------------------------------------------------------------------------------------------
mainc
继续一些初始化工作,并启动其他APs
kvmalloc(); //建立了一个二级页表,这个内核页表是所有处理器内核共享的。它将640k之上的空间全部直接映射。内核页表的基地址保存在kpgdir中。
pinit(); //初始化进程表
tvinit(); //初始化中断向量表,包括256个表项,其中第64号为系统调用,因此访问权限只需要DPL_USER,其他如硬件中断是硬件产生的以引起操作系统的注意;异常是由非法操作引起的。
timerinit(); //初始化时钟中断,设置为每秒100次中断,使能 IRQ_TIMER中断
userinit(); //建立第一个用户进程,分配了一个物理页,并将这个物理地址页映射到虚拟地址为0处,将原来的加载到物理地址为0处的initcode.S的代码加载到这个虚拟地址对应的物理页中。另外还设置进程的trapframe,以便能从内核态返回到用户态 (即虚拟地址为0处)
bootothers(); //启动其他处理器 (APs),执行bootother.S (已被链接到0x7000处) ,并设置返回地址为mpmain, 而BSP直接进入mpmain继续执行
--------------------------------------------------------------------------------------------------------------------
mpmain
现在所有的CPU都会进入到这个函数中执行,那看看这个函数会做什么~
ksegment(); //对所有APs设置per-CPU数据
vmenable(); //加载kpgdir,这样所有的内核共享相同的内核页表
idtinit(); //加载中断向量表,这样所有的内核共享相同的中断向量表
xchg(&cpu->booted, 1); //设置cpu为已启动
scheduler(); //进入进程调度程序,不会再回来了~
---------------------------------------------------------------------------------------------------------------------
scheduler
核心的进程调度程序,其实蛮简单的~
主要做了以下几件事
1)从进程表 (ptable) 找到一个已经处于就绪态 (p->state = RUNNABLE) 的进程
2) switchuvm(p); //加载这个进程的二级页表 (p->pgdir)
3) p->state = RUNNING; //设置这个进程已经从就绪态转为运行态了
4) swtch(&cpu->scheduler, proc->context); //最重要的就是这个了,上下文切换,将旧的上下文保存下来,并将新的
上下文加载CPU中,至此调度程序结束,开始运行这个进程了
沿着这个流程,整个xv6框架就凸显出来了~