本文仅用作学习记录
具体实现在这里:yrzr21/xv6 (github.com),`请在参考源码前想想是否有必要这么做
用户程序将 exec 的参数放到寄存器 a0 和 a1 中,系统调用号放到 a7 中
调用 ecall 进入 kernel,执行:uservec, usertrap, and then syscall
syscall 中的 syscalls 利用 a7 调用相关的函数,函数返回时将返回值放到 a0 中
kernel/syscall.c
用户代码调用的是 system call wrapper functions,参数被放到约定的寄存器中
内核 trap 代码将用户寄存器保存到当前进程的 trap frame 中
内核使用指针读写用户内存,这带来了一些问题:
用户可能传递无效指针 / 有恶意行为,让内核访问内核内存而非用户内存
内核的页表与用户不同,不能使用普通指令从该指针指向的虚拟地址读取数据
内核实现了一些函数,可以在用户和内核间安全地传递参数
如 fetchstr,从用户空间检索字符串文件名,通过调用 copystr实现
copystr 从用户页表中的地址 addr 向 buf 复制最多 max 个字节的数据,它使用 walkaddr 遍历页表,以确定 addr 的物理地址
由于内核将所有的 PM 映射到相同的内核 VM,copystr 可以直接从 addr 的物理地址向 buf复制数据
walkaddr 会检查用户提供的地址是否属于用户地址空间
跟踪当前的操作状态:用户/内核,其位数为SXLEN。
SPP 表示表示进入内核模式前的特权级别,如果 trap 来自用户,则 SPP 被设为 0,否则为 1
当从 trap handler 返回时,
以下是这些术语的缩写及其解释:
SXLEN: Supervisor XLEN
SXLEN
表示监督模式下的字长(XLEN),通常是32位或64位,取决于处理器的架构。hart: Hardware Thread
hart
表示硬件线程(Hardware Thread),即处理器内的一个独立执行线程。SRET: Supervisor Return
SRET
是一条指令,用于从陷阱处理程序返回到监督模式之前的执行状态。SPP: Supervisor Previous Privilege
SPP
是sstatus
寄存器中的一位,表示进入监督模式之前的特权级别。SIE: Supervisor Interrupt Enable
SIE
是sstatus
寄存器中的一位,用于启用或禁用监督模式下的中断。SPIE: Supervisor Previous Interrupt Enable
SPIE
是sstatus
寄存器中的一位,表示进入监督模式之前中断是否启用。mstatus: Machine Status
mstatus
是机器模式状态寄存器,记录处理器的状态信息,包括中断使能和特权级别等。好的,下面是直接翻译:
sstatus
寄存器是一个SXLEN位的读/写寄存器,当SXLEN=32时其格式如图4.1所示,当SXLEN=64时其格式如图4.2所示。sstatus
寄存器记录处理器的当前运行状态。SPP位表示hart在进入监督模式之前所执行的特权级别。当发生陷阱时,如果陷阱源自用户模式,SPP被设置为0,否则设置为1。当执行SRET指令以从陷阱处理程序返回时,如果SPP位为0,特权级别被设置为用户模式;如果SPP位为1,特权级别被设置为监督模式;然后SPP被设置为0。
SIE位启用或禁用监督模式下的所有中断。当SIE位被清除时,在监督模式下不会接受中断。当hart运行在用户模式时,SIE的值被忽略,并且监督级别的中断是启用的。监督模式可以使用sie
CSR禁用单个中断源。
SPIE位表示在进入监督模式之前监督中断是否启用。当陷阱进入监督模式时,SPIE被设置为SIE,并且SIE被设置为0。当执行SRET指令时,SIE被设置为SPIE,然后SPIE被设置为1。
sstatus
寄存器是mstatus
寄存器的一个子集。
SXLEN-bit 长的读写寄存器,当从 trap 进入内核模式时,有一句代码写入 scause 指明 trap 的原因。否则永远不会被写入——但可能被软件写
WLRL:寄存器在写入操作后,字段的值不会被重置或改变,直到进行下一次写操作。且值必须在指定范围内,此处要求必须能保存值0-31(即必须实现bit 0-4),支持哪些异常取决于实现
1 | make qemu-gdb |
1 | riscv64-unknown-elf-gdb |
例如:
这个特权寄存器用于保存发生异常或中断的原因,查询risc-v文档可知 0x000000000000000d 表示Store/AMO access fault,即写入内存时发生了访问错误
保存了在监督模式下发生异常时的程序计数器值,可查看 kernel.asm 确认导致异常的指令,在此例中为:
用于保存与异常或中断相关的附加信息。对于一些异常类型,比如存储访问故障或地址错误,stval 可能包含导致异常的内存地址。
通过上面的sepc,可以通过gdb找到相应的指令,并在此处查看寄存器值:
可以看到内核 VM 0~0x02000000 并没有被映射到物理内存。查询scause表可以看到异常为:load page fault
有时需要更多信息,比如需要查看内核崩溃时的用户进程名字:
以及 pid 等等数据
一定要做以下步骤获取 trace.c 以及 grade 、sysinfo.h 文件:
不然就会像我一样想半天,然后自己写了个 trace.c 死活发现不对
可以参考下 trace.c 的源码:
这个点困扰了我很久,查了很多代码都没找到什么踪迹,很神奇,加了个 SYS_trace 宏,加了个函数 sys_trace ,加了个 trace 接口就可以了
猜测是因为 usys.pl 中的代码:
本质还是个数组
电脑性能原因?…我改成二分计算系统调用号都耗时太长,改成O(1)又会占用太多空间…由于局部性说不定会耗时更长,搞不懂为什么要传个掩码进来
需要去这里改评分时间:
核心是这几个结构体和变量:
上面那个链表采用头插法收集释放的页指针
一些小trick:
——需要锁
extern 只能用于声明变量或函数,而不能定义结构体,这样不行:
extern 的变量无法访问内部成员(下面的proc[i]还是可以的),这种要访问next的最好搞个公共头文件
这样可以:
头文件中还是别include其他的头文件了,很容易出现重复定义,在需要的.c文件中include就行
值得注意的是所有系统调用的都是以这种形式定义,并通过trapframe中的寄存器值作参数使用: