2.6 Code: starting xv6, the first process and system call

启动代码:
entry.S 为每个CPU设置堆栈,然后跳进start.c的start函数。

	# qemu -kernel loads the kernel at 0x80000000
        # and causes each CPU to jump there.
        # kernel.ld causes the following code to
        # be placed at 0x80000000.
.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 # CPUs 0~7 has hartid from 0~7
        addi a1, a1, 1
        mul a0, a0, a1
        add sp, sp, a0
	# jump to start() in start.c
        call start
spin:
        j spin

start.c 调用mret, 从machine mode进入supervisor mode。跳转至mepc中存入的main函数地址。

void start()
{
	unsigned long x = r_mstatus();
	x &= ~MSTATUS_MPP_MASK;
	x |= MSTATUS_MPP_S;
	w_mstatus(x);

	w_mepc((uint64)main);
	// ...
	asm volatile("mret");
}

main.c userinit()中创建第一个进程。

void main()
{
	// ...
	userinit();
}

userinit.c uvminituchar initcode[]user/initcode.S编译出来的initcode可执行程序加载进进程的页表中。

void userinit(void)
{
	struct proc *p;
	p = allocproc();

	uvminit(p->pagetable, initcode, sizeof(initcode));
}

接着initcode会执行系统调用exec执行/init用户程序。

initcode.S

# exec(init, argv)
.globl start
start:
        la a0, init
        la a1, argv
        li a7, SYS_exec
        ecall

最终在init.c中调用fork()在子进程中执行shell进程exec("sh", argv);,父进程则进入死循环。

4.3 Code: Calling system calls

initcode.S 把执行exec的参数存放在a0,a1寄存器中, 把system call编号存在a7寄存器中。随后ecall指令会trap into kernel, 导致进入uservecusertrap函数, 最后调用到sys_exec函数。

4.4 Code: System call arguments

User space通过系统调用进入kernel space, 参数保存在current process当前进程的trap frame中。
比如exit(0), 0会被保存到a0寄存器,随后会被kernel保存到p->trapframe->a0

argint, argaddr, argfd函数从当前进程的trap frame中读取传入的参数。


如果user space传入指针,会有两个问题。

  1. user space可能传入invalid address或者是企图访问kernel memory的恶意指针。
  2. xv6 kernel page table和user space的page table mapping不同,所以同一个虚拟地址对应的物理地址是不同的。

这时候需要用copyinstr函数。