Xv6系统启动

阅读材料

  • Xv6代码:entry.S、start.c、main.c
  • 教材2.6节

执行的第一行汇编指令

链接器脚本文件(kernel.ld)指定了整个内核程序的入口点在 _entry

这段汇编代码为每个HART分配了 4KB 的栈空间,并且sp寄存器指向栈顶(栈是从高地址向低地址增长),然后跳转到 start 函数

这里 stack0 是一个全局变量(定义在start.c),是一个字节数组,因此位于内核源码链接后生成的可执行文件.bss段

.section .text
.global _entry
_entry:
        # set up a stack for C.
        # stack0 is declared in start.c,
        # with a 4096-byte stack per CPU.
        # sp = stack0 + (hartid * 4096)
        la sp, stack0
        li a0, 1024*4
        csrr a1, mhartid
        addi a1, a1, 1
        mul a0, a0, a1
        add sp, sp, a0
        # jump to start() in start.c
        call start
spin:
        j spin

start函数

首先设置 mstatus 寄存器中 MPP 字段,这样内核执行 mret 指令后返回S模式

	// set M Previous Privilege mode to Supervisor, for mret.
	unsigned long x = r_mstatus();
	x &= ~MSTATUS_MPP_MASK;
	x |= MSTATUS_MPP_S;
	w_mstatus(x);

设置 mepc 寄存器为 main 函数的地址,因为内核执行 mret 指令后,硬件会将 mepc 寄存器中的值加载到 PC 当中去 

	// set M Exception Program Counter to main, for mret.
	// requires gcc -mcmodel=medany
	w_mepc((uint64)main);

暂时禁用虚拟地址,因此现在访问的都是物理地址

	// disable paging for now.
	w_satp(0);

将所有中断和异常都代理给S模式(操作系统),并使能S模式下的外部中断、时钟中断、软件中断

	// delegate all interrupts and exceptions to supervisor mode.
	w_medeleg(0xffff);
	w_mideleg(0xffff);
	w_sie(r_sie() | SIE_SEIE | SIE_STIE | SIE_SSIE);

 配置 PMP 寄存器,让所有物理内存都具有读权限、写权限、执行权限。

  // configure Physical Memory Protection to give supervisor mode
  // access to all of physical memory.
  w_pmpaddr0(0x3fffffffffffffull);
  w_pmpcfg0(0xf);

调用 timerinit 函数,对每个核心的时钟初始化,为时钟中断做准备

通过启用 sstc 扩展,支持 stimecmp 寄存器,让S模式也能处理时钟中断。

// ask each hart to generate timer interrupts.
void timerinit()
{
	// enable supervisor-mode timer interrupts.
	w_mie(r_mie() | MIE_STIE);

	// enable the sstc extension (i.e. stimecmp).
	w_menvcfg(r_menvcfg() | (1L << 63));

	// allow supervisor to use stimecmp and time.
	w_mcounteren(r_mcounteren() | 2);

	// ask for the very first timer interrupt.
	w_stimecmp(r_time() + 1000000);
}

旧版本的Xv6时钟初始化比较复杂,新版本利用了RISC-V中一些新的特性(stimecmp),简化了代码逻辑

旧版本代码剖析参考:

3. 处理时钟中断 | XV6 源代码阅读指南 (gitbook.io)

将每个核心的 hartid 号存入到 tp 寄存器中,后面的代码经常会用到 hartid 号

	// keep each CPU's hartid in its tp register, for cpuid().
	int id = r_mhartid();
	w_tp(id);

通过汇编指令 mret 返回到S模式中,入口点为 main 函数 

	// switch to supervisor mode and jump to main().
	asm volatile("mret");

main函数

main函数之前执行的代码可以认为是配置支持操作系统的基本硬件环境,main函数中的的代码才是对操作系统组件的初始化

注意:此时每个HART的sp寄存器保存了栈地址,tp寄存器保存了HART ID

void main()
{
	if (cpuid() == 0)
	{
		consoleinit();
		printfinit();
		printf("\n");
		printf("xv6 kernel is booting\n");
		printf("\n");
		kinit();			// physical page allocator
		kvminit();			// create kernel page table
		kvminithart();		// turn on paging
		procinit();			// process table
		trapinit();			// trap vectors
		trapinithart();		// install kernel trap vector
		plicinit();			// set up interrupt controller
		plicinithart();		// ask PLIC for device interrupts
		binit();			// buffer cache
		iinit();			// inode table
		fileinit();			// file table
		virtio_disk_init(); // emulated hard disk
		userinit();			// first user process
		__sync_synchronize();
		started = 1;
	}
	else
	{
		while (started == 0)
			;
		__sync_synchronize();
		printf("hart %d starting\n", cpuid());
		kvminithart();	// turn on paging
		trapinithart(); // install kernel trap vector
		plicinithart(); // ask PLIC for device interrupts
	}

	scheduler();
}

具体函数不展开讲了,会在相应章节中分析。这里只需知道 hart0 是主核心,负责大部分的初始化工作,其他核心负责次要初始化工作。并且在初始化结束后,每个核心都各自调用 scheduler 函数,该函数会切换到应用进程,标志着操作系统正式启动起来了。

参考文献

1. 启动操作系统 | XV6 源代码阅读指南 (gitbook.io)

C语言__attribute__的使用_c attribute-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值