9.6 系统调用的实现

L56

学习目标

命令接口、图形接口、联机命令;教学 Shell 循环读命令。 命令接口、图形接口、联机命令;教学 Shell 循环读命令。

  • Shell 是用户态/任务循环。
  • 本课可用串口简易命令(ps/help)规划。
  • FULL=任务+节拍+内存+IPC+HDF。
  • 对照教材功能表做一次验收。
  • trap/ecall。
  • HDF/设备访问也可视为一种接口。

原理 · 深入理解

9.6 系统调用的实现

本节概览:先建立「系统调用的实现」的框架,再依次展开下列小节。

  1. 系统调用的实现方法
  2. UNIX系统调用的实现
  3. Linux系统调用
  4. Win32的应用程序接口

9.6.1 系统调用的实现方法

系统调用号和参数的设置:一个系统中设置了许多条系统调用,每条赋予唯一的系统调用号。

传递方法有多种:有的系统直接把调用号放在系统调用命令(陷入指令)中,如 IBM 370 和早期 UNIX 把陷入指令的低 8 位用于存放调用号;有的系统把调用号装入指定寄存器或内存单元,如 MS-DOS 放在 AH 寄存器中,Linux 用 EAX 寄存器存放。

每条系统调用都含若干参数,需把这些参数传递给陷入处理机构和系统内部的子程序。常用实现方式有以下几种。

  1. 陷入指令自带方式:参数直接放在陷入指令中
  2. 直接将参数送入相应的寄存器中
  3. 参数表方式:将参数放在内存参数表中,把参数表首址送入某寄存器
图9-8 系统调用的参数形式 寄存器 参数表
图9-8 系统调用的参数形式

系统调用的处理步骤:设置好系统调用号和参数后,便可执行一条系统调用命令。不同系统采用不同执行方式:UNIX 执行 CHMK 命令,MS-DOS 执行 INT 21 软中断。

系统调用处理子程序的处理过程:系统调用的功能主要由系统调用子程序完成,对不同的系统调用,其处理程序执行不同功能。

9.6.2 UNIX系统调用的实现

CPU 环境保护:用户程序在用户态、执行系统调用命令(CHMK)之前,应在用户空间提供系统调用所需的参数表,并将该参数表地址送入 R0 寄存器。

执行 CHMK 后,处理机由用户态转为核心态,硬件自动把处理机状态长字 PSL、程序计数器 PC 和代码操作数 code 压入用户核心栈,继而从中断和陷入向量表取出 trap.S 的入口地址,转入中断和陷入总控程序 trap.S 执行。

系统调用返回前的公共处理:UNIX 进程调度的主要依据是进程的动态优先级。随着进程执行时间加长,优先级将逐步降低。每次执行完系统调用命令并由处理子程序返回 trap.C 后,都重新计算该进程优先级。

若在系统调用执行过程中发生错误使进程无法继续运行,系统会设置再调度标志。处理子程序计算优先级后检查该标志,若已设置便调用 switch 调度程序,从就绪进程中选择优先级最高的进程把处理机让给它运行。

图9-9 用户核心栈 高地址 参数 返回地址 局部量 低地址
图9-9

利用系统调用定义表转入相应处理程序:在 UNIX 中,对不同编号的系统调用都设置了相应的处理子程序。为方便转入,把各处理子程序的入口地址放入系统调用定义表 Sysent[] 中。

参数传送:trap.C 程序把系统调用参数表中的内容从用户区传送到 User 结构的 u.u_arg 中,供系统调用处理程序使用。确定系统调用号:先经硬件陷入机构处理,再进入 trap.S 保护 CPU 现场,然后调用 trap.C 继续处理。

图9-9 用户核心栈 高地址 参数 返回地址 局部量 低地址
图9-9 用户核心栈

利用系统调用定义表转入相应处理程序:UNIX 系统为不同编号的系统调用设置相应的处理子程序,将各处理子程序入口地址放入系统调用定义表 Sysent[] 中,便于转入。

参数传送

由 trap.C 程序将系统调用参数表中的内容从用户区传送到 User 结构的 u.u_arg 中,供处理程序使用。

确定系统调用号

中断和陷入发生后,先经硬件陷入机构处理,再进入总控程序 trap.S,保护 CPU 现场后调用 trap.C 继续处理。

系统调用返回前的公共处理:UNIX 中进程调度的主要依据是动态优先级,随执行时间加长优先级逐步降低。每次系统调用返回到 trap.C 后重新计算优先级;若执行中发生错误无法继续运行,系统会设置再调度标志。

处理子程序计算优先级后检查再调度标志:若已设置,便调用 switch 调度程序,从就绪进程中选择优先级最高的进程,把处理机让给该进程运行。

9.6.3 Linux系统调用

与 UNIX 相似,Linux 采用类似技术实现系统调用。Linux 在 CPU 保护模式下提供四个特权级别,目前内核只用其中两个:特权级 0(内核态)和特权级 3(用户态)。

用户对系统调用不能任意拦截和修改,以保证内核安全性。Linux 最多可以有 190 个系统调用。应用程序和 Shell 需通过系统调用机制访问 Linux 内核功能。

9.6.4 Win32的应用程序接口

首先说明 API 与系统调用的区别和联系:API 是一个函数的定义,说明如何获得给定服务;系统调用是通过中断向内核发出的请求。一个 API 函数可能不与任何系统调用对应,也可调用若干系统调用,不同 API 函数可能封装相同系统调用。

Windows 程序采用事件驱动方式,即主程序等待事件发生。通过调用 Win32 API 可创建各种核心对象,如文件、进程、线程、管道等。

做 · 交互动画

  • 根据「学习目标」列出 3 个关键词,对照原理段落解释给自己听。
  • 列出 FULL 包含的子系统,并在模块图上全部点亮。
  • 写对照表:教材章 ↔ 本课模块 ↔ 实验课号。

系统调用的实现

在终端中输入命令并回车。可试:ls、cat notes.txt、echo hi > a.txt、ls | cat、sleep 1 &。方向键可翻阅历史。

总结与提升

  • 能区分 Shell 与内核。
  • 能用自己的话复述学习目标,并指出概念与板上实验的对应。
  • 能背出 FULL 包含的子系统。
  • 能说明用户态不能直接访问设备寄存器的原因。

延伸思考

  • 图形 UI 在 SOWL TFT 上对应哪一层?
  • 如果把本节机制拿到 Linux 或完整 OpenHarmony 上,会多出哪些硬件假设?
  • 还缺消息队列时算不算「全功能」?
  • 全部函数都链进固件时,还有「用户/内核」边界吗?

← 本阶段封面 · 课程列表