0%

xv6-riscv调研

https://github.com/mit-pdos/xv6-riscv/
一个简单,类UNIX的 MIT(麻省理工) 教学用操作系统

用户态 syscall

Xv6内核提供了Unix内核传统上提供的服务和系统调用的子集

系统调用 描述
int fork() 创建一个进程,返回子进程的PID
int exit(int status) 终止当前进程,并将状态报告给wait()函数。无返回
int wait(int *status) 等待一个子进程退出; 将退出状态存入*status; 返回子进程PID。
int kill(int pid) 终止对应PID的进程,返回0,或返回-1表示错误
int getpid() 返回当前进程的PID
int sleep(int n) 暂停n个时钟节拍
int exec(char *file, char *argv[]) 加载一个文件并使用参数执行它; 只有在出错时才返回
char *sbrk(int n) 按n 字节增长进程的内存。返回新内存的开始
int open(char *file, int flags) 打开一个文件;flags表示read/write;返回一个fd(文件描述符)
int write(int fd, char *buf, int n) 从buf 写n 个字节到文件描述符fd; 返回n
int read(int fd, char *buf, int n) 将n 个字节读入buf;返回读取的字节数;如果文件结束,返回0
int close(int fd) 释放打开的文件fd
int dup(int fd) 返回一个新的文件描述符,指向与fd 相同的文件
int pipe(int p[]) 创建一个管道,把read/write文件描述符放在p[0]和p[1]中
int chdir(char *dir) 改变当前的工作目录
int mkdir(char *dir) 创建一个新目录
int mknod(char *file, int, int) 创建一个设备文件
int fstat(int fd, struct stat *st) 将打开文件fd的信息放入*st
int stat(char *file, struct stat *st) 将指定名称的文件信息放入*st
int link(char *file1, char *file2) 为文件file1创建另一个名称(file2)
int unlink(char *file) 删除一个文件

用户态的基础接口

  • 文件操作 open read write close link unlink stat fstat mkdir chdir
  • 进程相关 fork dup wait pipe getpid exit kill
  • log相关 printf
  • 内存 malloc free
  • 时间相关 uptime(获取当前的tick) sleep

文件系统

提供了一个精简版的文件系统, 支持ramdisk, 支持文件读写

可改code, 将用户态程序打包进文件系统, 将文件系统镜像融入到 kernel, 使之成为 kernel .rodata段的内容, 作为ramdisk使用

不依赖加载器, 可以将用户态应用程序和 little kernel 打包到一起

cpu mode

从M-mode 启动到 S-mode 的xv6 little kernel, 最后启动到 U-mode 的sh 终端

smp

支持多核启动

内存布局

启用mmu

kernel 为线性映射 VA 等同于 PA

Xv6 为每个进程维护一个用于描述进程的用户地址空间的页表,外加一个单独的描述内核地址空间的页表。内核配置其地址空间的布局,使其能够通过可预测的虚拟地址访问物理内存和各种硬件资源。

image-20230222143152947

​ 内核地址空间

内核对RAM和内存映射的设备寄存器使用“直接映射”,也就是将这些资源映射到和它们物理地址相同的虚拟地址上。例如,内核本身在虚拟地址空间和物理内存中的位置都是KERNBASE=0x80000000。直接映射简化了读/写物理内存的内核代码。例如,当 fork 为子进程分配用户内存时,分配器返回该内存的物理地址;fork 在将父进程的用户内存复制到子进程时,直接使用该地址作为虚拟地址。

有几个内核虚拟地址不是直接映射:

  • trampoline 页。它被映射在虚拟地址空间的顶端;用户页表也有这个映射。Xv6在内核页表和每个用户页表中的同一个虚拟地址上映射了trampoline页
  • 内核栈页。每个进程都有自己的内核栈,内核栈被映射到高地址处,所以 xv6 可以在它后面留下一个未映射的守护页。守护页的 PTE 是无效的(不设置 PTE_V 位),这样如果内核栈溢出,很可能会引起异常,内核会报错。如果没有防护页,栈溢出时会覆盖其他内核内存,导致不正确的操作。

内核通过高地址映射使用它的栈空间,栈空间也可以通过直接映射的地址被内核访问。

每个用户态进程都有一个单独的页表,当 xv6 在进程间切换时,也会改变页表

image-20230222143445243

​ 用户态进程地址空间

为了检测用户栈溢出分配的栈内存,xv6 会在 stack 的下方放置一个无效的保护页。如果用户栈溢出,而进程试图使用栈下面的地址,硬件会因为该映射无效而产生一个缺页异常。

用户页表并不映射内核, 因为RISC-V硬件在trap过程中不切换页表,所以用户页表必须包含uservec的映射,即stvec指向的trap处理程序地址。uservec必须切换satp,使其指向内核页表;为了在切换后继续执行指令,uservec必须被映射到内核页表与用户页表相同的地址。

调度

轮询调度

xv6周期性地强制切换,以应对长时间不进行sleep操作的计算进程, 用定时器中断来驱动上下文切换

sleepwakeup允许一个进程放弃CPU,并睡眠等待某一事件,并允许另一个进程将睡眠的进程唤醒

优点

文件结构简单, 资料丰富, 编出的文件小, 不依赖加载器, 支持比较常用的posix api.