目录
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