xv6 系统启动过程

1、硬件上电

硬件上电后,将会运行一个只读的boot loader 程序,这个程序会将xv6的内核加载进内存中。
程序将会被加载到物理地址0x80000000处,前面的物理地址被IO设备占用。

2、执行 entry 代码

进入机器模式,xv6将从 entry 处开始执行。

//kernel/_entry.S
  1         # qemu -kernel loads the kernel at 0x80000000
  2         # and causes each hart (i.e. CPU) to jump there.
  3         # kernel.ld causes the following code to
  4         # be placed at 0x80000000.
  5 .section .text
  6 .global _entry
  7 _entry:
  8         # set up a stack for C.
  9         # stack0 is declared in start.c,
 10         # with a 4096-byte stack per CPU.
 11         # sp = stack0 + (hartid * 4096)
 12         la sp, stack0
 13         li a0, 1024*4
 14         csrr a1, mhartid
 15         addi a1, a1, 1
 16         mul a0, a0, a1
 17         add sp, sp, a0
 18         # jump to start() in start.c
 19         call start
 20 spin:
 21         j spin
  • entry 将设置一个栈stack0以供xv6运行C代码
  • csrr a1, mhartid,//将当前硬件线程的 ID(hartid)加载到寄存器 a1 中。mhartid 是 RISC-V 中的一个特权级 CSR(Control and Status Register)寄存器,用于获取硬件线程 ID。
  • addi a1, a1, 1,这行代码将寄存器 a1 中的值增加 1。
  • mul a0, a0, a1,这行代码将寄存器 a0 中的值与寄存器 a1 中的值相乘,结果保存在寄存器 a0 中。
  • add sp, sp, a0,这行代码将栈指针 sp 向上移动,移动的距离是寄存器 a0 中的值。

3、entry 跳转到/kernel/start.c

执行start()函数,该函数执行一些机器模式下的配置任务。

 19 // entry.S jumps here in machine mode on stack0.
 20 void
 21 start()
 22 {
 23   // set M Previous Privilege mode to Supervisor, for mret.
 24   unsigned long x = r_mstatus();  //读取状态
 25   x &= ~MSTATUS_MPP_MASK;      //将 `x` 中表示 Previous Privilege Mode 的位清零。
 26   x |= MSTATUS_MPP_S;          //将 `x` 中表示 Previous Privilege Mode 的位设置为 Supervisor Mode。
 27   w_mstatus(x);                //将修改后的状态值写回 `mstatus` CSR 寄存器。
 28
 29   // set M Exception Program Counter to main, for mret.
 30   // requires gcc -mcmodel=medany
 31   w_mepc((uint64)main); //将 `main` 函数的地址写入 Exception Program Counter (EPC) 寄存器,以便在异常处理完成后跳转到 `main` 函数执行。
 32
 33   // disable paging for now.
 34   w_satp(0);   //将页表寄存器(SATP)设置为零,暂时禁用分页机制。
 35
 36   // 将所有的中断和异常委托给 Supervisor Mode 处理。
 37   w_medeleg(0xffff);
 38   w_mideleg(0xffff);
 39   w_sie(r_sie() | SIE_SEIE | SIE_STIE | SIE_SSIE);
 40
 41   // configure Physical Memory Protection to give supervisor mode
 42   // access to all of physical memory.
 43   w_pmpaddr0(0x3fffffffffffffull);
 44   w_pmpcfg0(0xf);
 45
 46   // ask for clock interrupts.初始化时钟中断。
 47   timerinit();
 48
 49   // keep each CPU's hartid in its tp register, for cpuid().
 50   int id = r_mhartid();
 51   w_tp(id);
 52
 53   // switch to supervisor mode and jump to main().
 54   asm volatile("mret"); //执行 `mret` 汇编指令,将处理器从机器模式切换到 supervisor 模式,并跳转到 `main()` 函数执行。
 55 }

4、start函数跳转到/kernel/main.c

main执行一些初始化工作

  9 // start() jumps here in supervisor mode on all CPUs.
 10 void
 11 main()
 12 {
 13   if(cpuid() == 0){
 14     consoleinit();
 15     printfinit();
 16     printf("\n");
 17     printf("xv6 kernel is booting\n");
 18     printf("\n");
 19     kinit();         // physical page allocator
 20     kvminit();       // create kernel page table
 21     kvminithart();   // turn on paging
 22     procinit();      // process table
 23     trapinit();      // trap vectors
 24     trapinithart();  // install kernel trap vector
 25     plicinit();      // set up interrupt controller
 26     plicinithart();  // ask PLIC for device interrupts
 27     binit();         // buffer cache
 28     iinit();         // inode table
 29     fileinit();      // file table
 30     virtio_disk_init(); // emulated hard disk
 31     userinit();      // 产生第一个用户进程,第一个进程执行用RISCV汇编写的,将产生第一个系统调用initcode.S
 32     __sync_synchronize();  //同步内存,确保之前的操作在多核环境中可见。
 33     started = 1;   //代表前述初始化完成
 34   } else {
 35     while(started == 0)  //等待初始化完成
 36       ;
 37     __sync_synchronize(); 
 38     printf("hart %d starting\n", cpuid());
 39     kvminithart();    // turn on paging
 40     trapinithart();   // install kernel trap vector
 41     plicinithart();   // ask PLIC for device interrupts
 42   }
 43
 44   scheduler();   //进入调度器,开始调度进程。
 45 }

5、main函数跳转到/user/initcode.S

首先准备好执行 /init 程序的参数,然后调用 exec 系统调用执行 /init。(即执行sys_exec系统调用)

  1 # Initial process that execs /init.
  2 # This code runs in user space.
  3
  4 #include "syscall.h"
  5
  6 # exec(init, argv)
  7 .globl start    //定义全局标签 `start`,表示程序的入口点。
  8 start:
  9         la a0, init   //将字符串 `/init` 的地址加载到寄存器 `a0` 中。
 10         la a1, argv   //将参数数组 `argv` 的地址加载到寄存器 `a1` 中。
 11         li a7, SYS_exec  //将 `exec` 系统调用编号加载到寄存器 `a7` 中。
 12         ecall   //触发系统调用 `exec`,执行 `/init` 程序。
 13
 14 # for(;;) exit();
 15 exit:
 16         li a7, SYS_exit  //将 `exit` 系统调用编号加载到寄存器 `a7` 中。
 17         ecall    //触发系统调用 `exit`,退出当前进程。
 18         jal exit   //跳转并链接到 `exit` 标签,形成一个无限循环以防止进程返回到调用者。
 19
 20 # char init[] = "/init\0";
 21 init:   //定义字符串 `init`:
 22   .string "/init\0"
 23
 24 # char *argv[] = { init, 0 };
 25 .p2align 2
 26 argv:   //定义参数数组 `argv`:
 27   .long init
 28   .long 0

6、initcode.S跳转到/kernel/syscall.c

(sys_exec系统调用)

//syscall.h
 #define SYS_exec    7
//syscall.c
[SYS_exec]    sys_exec,
 extern uint64 sys_exec(void);
//通过defs.h 查找,可知位于exec.c文件中
 26 // exec.c
 27 int             exec(char*, char**);

7、syscall.c跳转到/kernel/exec.c

  • exec() 函数的作用是在当前进程的上下文中执行一个新的程序。具体来说,exec() 会用指定的程序替换当前进程的地址空间,包括代码段、数据段、堆和栈,从而执行新的程序。

8、exec.c返回到/user/init.c

  • exec()结束后,将返回到 /init进程(user/init.c)(若有需要产生一个新的控制台设备文件并以描述符0,1,2打开这个文件)
  • 最后在控制台上启动shell

系统启动完成

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值