docs added and classified

This commit is contained in:
YongkangLi 2021-03-02 10:45:16 +08:00
parent 61b7b555b5
commit f72a91fdf7
13 changed files with 137 additions and 139 deletions

View File

@ -0,0 +1,39 @@
# System Call
System Call即系统调用以下简称syscall用于在用户程序中执行一些特权模式下才能完成的操作如I/O操作
对于处理器而言syscall是一种同步发生的事件在基于RISC-V的操作系统中由`ecall`指令Environment Call产生。
当系统调用事件结束后处理器将返回到ecall指令的下一条指令继续执行。
原版xv6的文档将“system call”、“exception”和“(device) interrupt”统称为“trap”。
在RISC-V指令集手册的1.6节中对“exception”、“interrupt”和“trap”的定义是这样的
> We use the term <u>*exception*</u> to refer to an unusual condition occurring at run time associated with
an instruction in the current RISC-V hart. We use the term <u>*interrupt*</u> to refer to an external
asynchronous event that may cause a RISC-V hart to experience an unexpected transfer of control.
We use the term <u>*trap*</u> to refer to the transfer of control to a trap handler caused by either an
exception or an interrupt.
从这个角度来看trap在RISC-V的语境下指的是控制权的转换也就是特权级的转换。这可能与我们平常使用的术语有一定出入需要注意。
## 在RISC-V中请求系统调用
在RISC-V的用户程序中使用`ecall`指令请求syscall。操作系统提供不同功能的syscall对应不同的请求调用号。
因此请求前,需要向`a7`寄存器中写入所请求的syscall的调用号。以下为一个请求8号调用的例子
```
li a7, 8
ecall
```
## 系统调用的处理过程
Syscall涉及到特权态的转换需要经过trap过程。对于用户程序而言一般是从用户模式U模式陷入到监管模式S模式
我们知道操作系统中有中断向量表保存着处理各类中断的中断例程的入口syscall的中断处理例程自然也在其中。
而对于不同类型的调用请求syscall的处理例程也有相应的系统调用表保存不同类型的系统调用功能函数的入口。
根据用户事先写入寄存器的系统调用号syscall的处理例程调用相应的功能函数完成系统调用随后返回至原先的特权态。
<br>
<br>
# 参考文档
+ [*xv6: a simple, Unix-like teaching operating system*](https://pdos.csail.mit.edu/6.S081/2020/xv6/book-riscv-rev1.pdf)
+ [*The RISC-V Instruction Set Manual Volume I: Unprivileged ISA*](https://github.com/riscv/riscv-isa-manual/releases/download/draft-20210212-c879d5a/riscv-spec.pdf)
+ [《RISC-V手册——一本开源指令集的指南》](http://riscvbook.com/chinese/RISC-V-Reader-Chinese-v2p1.pdf)

View File

@ -0,0 +1,8 @@
# 进程管理
## 自旋锁
讨论进程管理,就必须先搞懂自旋锁。
1. 为了避免死锁,加锁和释放锁中涉及开关中断,为了保证释放锁之后与加锁之前的中断使能标志保持一致,需要保存加锁之前关中断时的中断使能状态,在释放锁之后开中断时将中断使能恢复到之前所保存的状态。(即封装之后的push_off()和pop_off())
2. 由于现代编译器的代码优化和指令重排以及现代CPU的乱序执行(Out-of-Order Execution)在上锁期间需要额外的操作来保证读写操作的顺序一致性在RISC-V中可以用__sync_synchronize指令来保证严格的执行顺序。关于这个问题可以具体参考[这篇文章](https://www.0xffffff.org/2017/02/21/40-atomic-variable-mutex-and-memory-barrier/)

View File

@ -1,43 +1,3 @@
+ [System Call的执行](#Syscall在xv6-k210中的实现与执行)
+ [System Call的添加](#如何在xv6-k210中添加syscall)
<br>
<br>
# System Call
System Call即系统调用以下简称syscall用于在用户程序中执行一些特权模式下才能完成的操作如I/O操作
对于处理器而言syscall是一种同步发生的事件在基于RISC-V的操作系统中由`ecall`指令Environment Call产生。
当系统调用事件结束后处理器将返回到ecall指令的下一条指令继续执行。
原版xv6的文档将“system call”、“exception”和“(device) interrupt”统称为“trap”。
在RISC-V指令集手册的1.6节中对“exception”、“interrupt”和“trap”的定义是这样的
> We use the term <u>*exception*</u> to refer to an unusual condition occurring at run time associated with
an instruction in the current RISC-V hart. We use the term <u>*interrupt*</u> to refer to an external
asynchronous event that may cause a RISC-V hart to experience an unexpected transfer of control.
We use the term <u>*trap*</u> to refer to the transfer of control to a trap handler caused by either an
exception or an interrupt.
从这个角度来看trap在RISC-V的语境下指的是控制权的转换也就是特权级的转换。这可能与我们平常使用的术语有一定出入需要注意。
## 在RISC-V中请求系统调用
在RISC-V的用户程序中使用`ecall`指令请求syscall。操作系统提供不同功能的syscall对应不同的请求调用号。
因此请求前,需要向`a7`寄存器中写入所请求的syscall的调用号。以下为一个请求8号调用的例子
```
li a7, 8
ecall
```
## 系统调用的处理过程
Syscall涉及到特权态的转换需要经过trap过程。对于用户程序而言一般是从用户模式U模式陷入到监管模式S模式
我们知道操作系统中有中断向量表保存着处理各类中断的中断例程的入口syscall的中断处理例程自然也在其中。
而对于不同类型的调用请求syscall的处理例程也有相应的系统调用表保存不同类型的系统调用功能函数的入口。
根据用户事先写入寄存器的系统调用号syscall的处理例程调用相应的功能函数完成系统调用随后返回至原先的特权态。
<br>
<br>
# Syscall在xv6-k210中的实现与执行
Syscall是如何实现与执行的呢以下从用户程序出发分析在xv6-k210中系统调用的过程。
@ -202,101 +162,3 @@ static uint64 (*syscalls[])(void) = {
<br>
<br>
# 如何在xv6-k210中添加syscall
了解了Syscall在xv6-k210中的执行过程为系统添加一个新的syscall功能就不是什么难事了。添加新系统调用的步骤如下
1. 在xv6-uesr目录下
+ 在uesr.h文件中添加新系统调用封装后的函数声明假设其函数名为`mysyscall`。
+ 在usys.pl文件末尾添加如下行
```perl
......
entry("mysyscall");
```
2. 在kernel目录下
+ 在include/syscall.h文件中添加新系统调用号的宏定义
```C
......
#define SYS_mysyscall ?
```
其中,“?”为新的合法系统调用号,本质上是一个数组下标,可根据需要设置,建议按顺序递增添加。
+ 在syscall.c文件中添加功能函数的声明并更新系统调用表
```C
......
extern uint64 sys_mysyscall(void);
static uint64 (*syscalls[])(void) = {
......
[SYS_mysyscall] sys_mysyscall,
};
```
3. 根据该系统调用的功能,选择一个适合的内核模块的源文件,在其中实现`sys_mysyscall`函数的功能。
例如与文件相关的系统调用可以在kernel/sysfile.c中添加。
可以利用syscall.c文件中提供的相关函数在`sys_mysyscall`中获取用户进程传递的参数。
下面给出一个更具体的例子。在我们的移植工作中,我们需要测试用户进程的运行,使其向屏幕进行输出。
但当时用户态尚未支持`printf`函数我们便通过系统调用在S态进行一些输出。为此我们需要添加一个`test_proc`函数。
首先我们在xv6-uesr/uesr.h文件中添加函数声明为用户程序添加调用接口
```C
int test_proc(void);
```
在xv6-uesr/usys.pl中添加行:
```perl
entry("test_proc");
```
随后修改内核在kernel/include/syscall.h中添加调用号调用号可以自行选择这里我们选择22表示第22个系统调用。
```C
#define SYS_test_proc 22
```
最后在kernel/include/syscall.c中修改系统调用表以及添加函数`sys_test_proc`
```C
extern uint64 sys_test_proc(void);
static uint64 (*syscalls[])(void) = {
......
[SYS_test_proc] sys_test_proc,
};
uint64 sys_test_proc(void) {
printf("hello world from proc %d, hart %d\n", myproc()->pid, r_tp());
return 0;
}
```
至此,我们就已经成功添加了一个系统调用。在用户程序中可以使用这个系统调用,例如以下的用户程序实例:
```C
#include "kernel/include/types.h"
#include "kernel/include/stat.h"
#include "xv6-user/user.h"
int main()
{
int n = 10;
while (n--) {
test_proc();
}
exit(0);
}
```
编译运行后的结果如下图所示(编译用户程序的方法详见[此处](./fs.md)
![](/img/syscall_test.png)
<br>
<br>
# 参考文档
+ [*xv6: a simple, Unix-like teaching operating system*](https://pdos.csail.mit.edu/6.S081/2020/xv6/book-riscv-rev1.pdf)
+ [*The RISC-V Instruction Set Manual Volume I: Unprivileged ISA*](https://github.com/riscv/riscv-isa-manual/releases/download/draft-20210212-c879d5a/riscv-spec.pdf)
+ [《RISC-V手册——一本开源指令集的指南》](http://riscvbook.com/chinese/RISC-V-Reader-Chinese-v2p1.pdf)

View File

@ -0,0 +1,90 @@
# 如何在xv6-k210中添加syscall
了解了Syscall在xv6-k210中的执行过程为系统添加一个新的syscall功能就不是什么难事了。添加新系统调用的步骤如下
1. 在xv6-uesr目录下
+ 在uesr.h文件中添加新系统调用封装后的函数声明假设其函数名为`mysyscall`。
+ 在usys.pl文件末尾添加如下行
```perl
......
entry("mysyscall");
```
2. 在kernel目录下
+ 在include/syscall.h文件中添加新系统调用号的宏定义
```C
......
#define SYS_mysyscall ?
```
其中,“?”为新的合法系统调用号,本质上是一个数组下标,可根据需要设置,建议按顺序递增添加。
+ 在syscall.c文件中添加功能函数的声明并更新系统调用表
```C
......
extern uint64 sys_mysyscall(void);
static uint64 (*syscalls[])(void) = {
......
[SYS_mysyscall] sys_mysyscall,
};
```
3. 根据该系统调用的功能,选择一个适合的内核模块的源文件,在其中实现`sys_mysyscall`函数的功能。
例如与文件相关的系统调用可以在kernel/sysfile.c中添加。
可以利用syscall.c文件中提供的相关函数在`sys_mysyscall`中获取用户进程传递的参数。
下面给出一个更具体的例子。在我们的移植工作中,我们需要测试用户进程的运行,使其向屏幕进行输出。
但当时用户态尚未支持`printf`函数我们便通过系统调用在S态进行一些输出。为此我们需要添加一个`test_proc`函数。
首先我们在xv6-uesr/uesr.h文件中添加函数声明为用户程序添加调用接口
```C
int test_proc(void);
```
在xv6-uesr/usys.pl中添加行:
```perl
entry("test_proc");
```
随后修改内核在kernel/include/syscall.h中添加调用号调用号可以自行选择这里我们选择22表示第22个系统调用。
```C
#define SYS_test_proc 22
```
最后在kernel/include/syscall.c中修改系统调用表以及添加函数`sys_test_proc`
```C
extern uint64 sys_test_proc(void);
static uint64 (*syscalls[])(void) = {
......
[SYS_test_proc] sys_test_proc,
};
uint64 sys_test_proc(void) {
printf("hello world from proc %d, hart %d\n", myproc()->pid, r_tp());
return 0;
}
```
至此,我们就已经成功添加了一个系统调用。在用户程序中可以使用这个系统调用,例如以下的用户程序实例:
```C
#include "kernel/include/types.h"
#include "kernel/include/stat.h"
#include "xv6-user/user.h"
int main()
{
int n = 10;
while (n--) {
test_proc();
}
exit(0);
}
```
编译运行后的结果如下图所示(编译用户程序的方法详见[此处](./fs.md)
![](/img/syscall_test.png)
<br>
<br>

View File

@ -13,7 +13,6 @@
void freerange(void *pa_start, void *pa_end);
extern char kernel_end[]; // first address after kernel.
// defined by k210.ld.
struct run {
struct run *next;