xv6引导及初始化详解

时间:2022-05-08 19:33:27

    了解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框架就凸显出来了~