docs added and classified
This commit is contained in:
parent
61b7b555b5
commit
f72a91fdf7
|
@ -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)
|
|
@ -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/))
|
||||
|
|
@ -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)
|
||||
|
|
@ -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>
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue