## 实现系统调用

目前，我们实现 `sys_read` `sys_write` 和 `sys_exit` 三个简单的系统调用。通过学习它们的实现，更多的系统调用也并没有多难。

### 用户程序中调用系统调用

在用户程序中实现系统调用比较容易，就像我们之前在操作系统中使用 `sbi_call` 一样，只需要符合规则传递参数即可。而且这一次我们甚至不需要参考任何标准，每个人都可以为自己的操作系统实现自己的标准。

例如，在实验指导中，系统调用的编号使用了 musl 中的编码和参数格式。但实际上，在实现操作系统的时候，编码和参数格式都可以随意调整，只要在用户程序中的调用和操作系统中的解释相符即可。

{% label %}代码示例{% endlabel %}
```rust
// musl 中的 sys_read 调用格式
llvm_asm!("ecall" :
    "={x10}" (/* 返回读取长度 */) :
    "{x10}" (/* 文件描述符 */),
    "{x11}" (/* 读取缓冲区 */),
    "{x12}" (/* 缓冲区长度 */),
    "{x17}" (/* sys_read 编号 63 */) ::
);
// 一种可能的 sys_read 调用格式
llvm_asm!("ecall" :
    "={x10}" (/* 现在的时间 */),
    "={x11}" (/* 今天的天气 */),
    "={x12}" (/* 读取一个字符 */) :
    "{x20}" (/* sys_read 编号 0x595_7ead */) ::
);
```

实验指导提供了第一种无趣的系统调用格式。

### 避免忙等待

在常见操作系统中，一些延迟非常大的操作，例如文件读写、网络通讯，都可以使用异步接口来进行。但是为了实现更加简便，我们的读写系统调用都是阻塞的。在 `sys_read` 中，使用了 `loop` 来保证仅当成功读取字符时才返回。

此时，如果用户程序需要获取从控制台输入的字符，但是此时并没有任何字符到来。那么，程序将被阻塞，而操作系统的职责就是尽量减少线程执行无用阻塞占用 CPU 的时间，而是将这段时间分配给其他可以执行的线程。具体的做法，将会在后面**条件变量**的章节讲述。

### 操作系统中实现系统调用

在操作系统中，系统调用的实现和中断处理一样，有同样的入口，而针对不同的参数设置不同的处理流程。为了简化流程，我们不妨把系统调用的处理结果分为三类：

- 返回一个数值，程序继续执行
- 程序进入等待
- 程序将被终止

#### 系统调用的处理流程

- 首先，从相应的寄存器中取出调用代号和参数
- 根据调用代号，进入不同的处理流程，得到处理结果
  - 返回数值并继续执行：
    - 返回值存放在 `x10` 寄存器，`sepc += 4`，继续此 `context` 的执行
  - 程序进入等待
    - 同样需要更新 `x10` 和 `sepc`，但是需要将当前线程标记为等待，切换其他线程来执行
  - 程序终止
    - 不需要考虑系统调用的返回，直接删除线程

#### 具体的调用实现

那么具体该如何实现读 / 写系统调用呢？这里我们会利用文件的统一接口 `INode`，使用其中的 `read_at()` 和 `write_at()` 接口即可。下一节就将讲解如何处理文件描述符。
