#### 设计目标

- 可以在运行时配置I/O设备
- 让主要的系统组件成为与内核分开的独立进程

#### 重要概念

##### minix3的四层结构

| 层级         | 进程类型 | 包含的进程                                         |
| ------------ | -------- | -------------------------------------------------- |
| 4 (用户模式) | 用户进程 | init进程, 若干用户进程                             |
| 3 (用户模式) | 服务进程 | 进程管理器，文件系统，信息服务程序，网络服务程序等 |
| 2 (用户模式) | 设备驱动 | TTY驱动程序，以太网驱动程序等                      |
| 1 (内核模式) | 内核进程 | 内核，时钟任务，系统任务                           |

##### 各种调用

- 系统调用

  在宏内核里，系统调用指内核提供的所有调用。

  在Minix3里，系统调用在形式上和宏内核是一样的，但它不直接向内核请求服务，而是向服务进程发消息。服务进程之间，服务进程与驱动、内核之间也是通过消息来通信的。

- 内核调用

  直接向内核请求服务的调用。用户进程无法进行内核调用，服务进程和驱动程序才能进行内核调用。内核调用和系统调用常有类似的名字，因为有些操作只能在内核里进行。

- 消息原语

  用于进程间通信，如send、receive、notify等。有时也被叫**IPC原语**或**自陷**。它们确实调用了系统，但即不应称为系统调用也不应称为内核调用。

##### 系统任务接收的消息

其实就是内核调用，共有28个。

- 进程管理

  sys_fork, sys_exec, sys_exit, sys_trace : 与POSIX系统调用相关的内核调用

  sys_nice：设置进程的调度优先级

  sys_privctl : 再生服务程序RS用它来改变进程的特权。驱动程序和不在启动镜像里的服务程序通过`/etc/rc`脚本启动时，将会需要特权转换。

- 信号

  sys_kill : 与系统调用`kill`相关

  sys_getksig, sys_endksig, sys_sigsend, sys_sigreturn : 进程管理器用它们来操作信号

- 设备驱动的支持

  sys_irqctl : 开启、关闭或配置中断

  sys_devio ：读写I/O端口

  sys_sdevio：从I/O端口读写字符串。例如访问串口时会用到它。

  sys_vdevio：执行一个I/O所请求的向量。向量指的是一串(port, value)对。

- 关于内存

  sys_newmap：当进程的内存发生改变时，进程管理器调用它来更新内核里的进程表。

  sys_regctl：设备驱动用它来获取一个段选择子，这样就可以访问I/O设备占用的内存区域了(0xa0000~0xfffff)。

  sys_memset：服务进程用它来写数据到不属于自己的内存里。当新进程创建时，进程管理器用它来为新进程清空内存。

- 复制内存

  sys_umap：把虚拟地址转换为物理地址。

  sys_vircopy, sys_physcopy：使用虚拟或物理地址复制内存。

  sys_virvcopy, sys_physcopy：使用向量化的I/O请求，它们可以向系统任务请求一系统的内存复制操作。

- 其它

  sys_times：对应了`times`系统调用。

  sys_setalarm：与`alarm`系统调用相关。

  sys_abort：当要求关闭系统或产生panic之后，进程管理器会产生此内核调用；当用户按下`Ctrl-Alt_Del`组合键后，tty设备驱动也会产生此内核调用。

  sys_getinfo：获取内核的信息。在`include/minix/syslib.h`里定义了获取内核信息的各种宏，它们都是使用的此内核调用。

#### 进程调度

Minix3使用的多级调度算法。具体过程是：先找到优先级最高的非空的队列，然后选择顶头的进程开始执行。`IDLE`进程在最低优先级队列里，且始终是处于就绪的状态，这就保证没有其它进程时至少还有一个进程可以跑。

初始状态下，时钟和系统任务在第1级，拥有最高的权限。设备驱动在第2级。服务进程在第3级。用户进程在更低的层级，但可以通过`nice`命令来调整。

时钟任务用于监控所有进程的时间。

任务、驱动程序和服务程序除非被阻塞，否则应该是一直运行的，它们会有大的时间片。它们如果运行的太久，也是可能被抢占的，这种机制可以防止有问题的高优先级进程锁死系统。

调度器管理着16个就绪队列。数组`rdy_head`保存了每个就绪队列的头，而数组`rdy_tail`保存了每个就绪队列的尾。

每个队列上使用的是轮转调度算法。时间片用完的进程会调度到队列的末尾，时间片没用完但被阻塞的进程再唤醒时会放在队列的顶头。

#### 驱动程序注册中断的过程

1. 一个用户态的、由中断驱动的设备驱动程序，当它需要注册一个中断处理例程时，会向系统任务发出`sys_irqctl`调用。
2. 系统任务再调用`put_irq_handler`。但是在中断例程的字段里保存的将是系统任务所在内核空间的`generic_handler`的地址，而不是驱动程序所在用户空间的中断处理例程的地址。
3. `generic_handler`用钩子结构体里的进程号字段定位此驱动程序在`priv`表里的入口，此中断在驱动程序的挂起中断位图里的对应位会置1。
4. `generic_handler`向驱动程序发一个通知。此通知被识别为来自HARDWARE，驱动程序的挂起中断位图也包含在此消息里。
5. 钩子结构体里的policy字段决定中断是立即打开还是保持关闭。如果是保持关闭，则驱动程序还需要发出一个内核调用`sys_irqenable`来开打中断。

#### 系统任务

系统任务是从内核中独立出来的进程，它不能像内核函数那样自由，不能进行实际的I/O，也不能操作内核表。

那么系统任务(驱动和服务程序)怎么和内核交互呢？答案是内核向它们提供一组服务。这些服务对普通用户是不可见的，系统任务通过这些服务进行实际的I/O，操作内核表等。

系统任务的工作就是就是接收上层对内核服务的请求并执行它们。上层的进程由于在用户态，无法访问内核里的数据结构，但系统任务可以。

系统任务的主程序在做完必要的初始化后就会进入一个循环。获取消息，发给合适的服务例程，然后发送一个回复。主文件`system.c`里有一些的通用支持函数，但处理内核调用是在`kernel/system`目录下进行的。