本文主要是介绍xv6 系统启动过程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
- 1、硬件上电
- 2、执行 _entry_ 代码
- 3、_entry_ 跳转到/kernel/start.c
- 4、start函数跳转到/kernel/main.c
- 5、main函数跳转到/user/initcode.S
- 6、initcode.S跳转到/kernel/syscall.c
- 7、syscall.c跳转到/kernel/exec.c
- 8、exec.c返回到/user/init.c
- 系统启动完成
1、硬件上电
硬件上电后,将会运行一个只读的boot loader 程序,这个程序会将xv6的内核加载进内存中。
程序将会被加载到物理地址0x80000000处,前面的物理地址被IO设备占用。
2、执行 entry 代码
进入机器模式,xv6将从 entry 处开始执行。
//kernel/_entry.S1 # qemu -kernel loads the kernel at 0x800000002 # and causes each hart (i.e. CPU) to jump there.3 # kernel.ld causes the following code to4 # be placed at 0x80000000.5 .section .text6 .global _entry7 _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, stack013 li a0, 1024*414 csrr a1, mhartid15 addi a1, a1, 116 mul a0, a0, a117 add sp, sp, a018 # jump to start() in start.c19 call start20 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 void21 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 寄存器。2829 // set M Exception Program Counter to main, for mret.30 // requires gcc -mcmodel=medany31 w_mepc((uint64)main); //将 `main` 函数的地址写入 Exception Program Counter (EPC) 寄存器,以便在异常处理完成后跳转到 `main` 函数执行。3233 // disable paging for now.34 w_satp(0); //将页表寄存器(SATP)设置为零,暂时禁用分页机制。3536 // 将所有的中断和异常委托给 Supervisor Mode 处理。37 w_medeleg(0xffff);38 w_mideleg(0xffff);39 w_sie(r_sie() | SIE_SEIE | SIE_STIE | SIE_SSIE);4041 // configure Physical Memory Protection to give supervisor mode42 // access to all of physical memory.43 w_pmpaddr0(0x3fffffffffffffull);44 w_pmpcfg0(0xf);4546 // ask for clock interrupts.初始化时钟中断。47 timerinit();4849 // keep each CPU's hartid in its tp register, for cpuid().50 int id = r_mhartid();51 w_tp(id);5253 // 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 void11 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 allocator20 kvminit(); // create kernel page table21 kvminithart(); // turn on paging22 procinit(); // process table23 trapinit(); // trap vectors24 trapinithart(); // install kernel trap vector25 plicinit(); // set up interrupt controller26 plicinithart(); // ask PLIC for device interrupts27 binit(); // buffer cache28 iinit(); // inode table29 fileinit(); // file table30 virtio_disk_init(); // emulated hard disk31 userinit(); // 产生第一个用户进程,第一个进程执行用RISCV汇编写的,将产生第一个系统调用initcode.S32 __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 paging40 trapinithart(); // install kernel trap vector41 plicinithart(); // ask PLIC for device interrupts42 }4344 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.34 #include "syscall.h"56 # 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` 程序。1314 # for(;;) exit();15 exit:16 li a7, SYS_exit //将 `exit` 系统调用编号加载到寄存器 `a7` 中。17 ecall //触发系统调用 `exit`,退出当前进程。18 jal exit //跳转并链接到 `exit` 标签,形成一个无限循环以防止进程返回到调用者。1920 # char init[] = "/init\0";21 init: //定义字符串 `init`:22 .string "/init\0"2324 # char *argv[] = { init, 0 };25 .p2align 226 argv: //定义参数数组 `argv`:27 .long init28 .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.c27 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
系统启动完成
这篇关于xv6 系统启动过程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!