117 lines
6.3 KiB
Markdown
117 lines
6.3 KiB
Markdown
#### 设计目标
|
||
|
||
- 可以在运行时配置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`目录下进行的。 |