10 系统调用
文章目录
- OS为在
- 用户态运行的进程与硬件设备(CPU、磁盘、打印机)进行交互
- 提供一组接口。
- 在应用程序和硬件之间
- 设置一个额外层好啊。
- 使编程更易,把用户从学习硬件设备的低级编程特性中解放
- 提高系统安全性,因为内核在试图满足某个请求之前在
- 接口级就可检査这种请求的正确性。
- 这些接口使程序有可移植性,
- 只要内核提供的接口相同,
- 则在任一内核上就可正确地编译和执行程序。
Unix向内核发出系统调用
- 实现用户态进程和硬件设备之间的大部分接口。
- 讨论Linux内核如何实现这些
- 由用户态进程向内核发出的系统调用。
POSIX API和系统调用
- 应用编程接口(API)与系统调用不同。
- 前者函数定义
- 后者通过软中断向内核态发出明确请求。
- Unix给程序员提供很多API的库函数
- libc的标准C库所定义的一些API引用了封装例程( wrapper routine)(其唯一目的就是发布系统调用)。
- 每个系统调用对应一个封装例程,封装例程定义应用程序使用的API。
- 反之不然
- 一个API没必要对应一个特定的系统调用。
- API可能直接提供用户态的服务(如抽象的数学函数,没必要用系统调用)。
- 一个单独的API可能调用几个系统调用。
- 几个API可能调用封装了不同功能的同一系统调用。
- Linux的libc库函数实现malloc、calloc和free等 POSIX API,都用brk()系统调用来扩大或缩小进程的堆(见九章“堆的管理”)
POSIX标准针对API不针对系统调用。
- 判断一个系统是否与 POSIX兼容要看它
- 是否提供一组合适的应用程序接口,不管对应的函数是如何实现。
- 一些非Unix系统被认为是与POSIX兼容的,
- 因为它们在用户态的库函数中提供了传统Unix能提供的所有服务。
编程者观点看,
- API和系统调用之间的差别是没关系的
- 唯一相关的是函数名、参数类型及返回代码的含义
- 内核设计者观点看,这种差别有关系,
- 系统调用属于内核,用户态的库函数不属于内核。
大部分封装例程返回一个整数,值含义依赖相应的系统调用。
- -1:内核不能满足进程的请求。
- 系统调用处理程序的失败
- 由无效参数引起,也可能因为缺乏可用资源,或硬件出问题。
- libc库中定义的errno变量含特定的出错码
出错码都定义为:常量宏(产生一个相应的正整数值)。
- POSIX标准指定了很多出错码的宏名。
- 80x86的linux中,include/asm-i386/errno.h中定义这些宏。
- 为使各种Unix系统上的C程序可移植
- 在标准的C库头文件/usr/include/errno.h中也含include/asm-i386/errno.h
- 其他的系统有它们自己专门的头文件子目录。
系统凋用处理程序及服务例程
- 当用户态进程调用一个系统调用时,
- CPU切换到内核态并开始执行一个内核函数。
- 80x86体系结构中,两种不同的方式调用Linux的系统调用。
- 两种方式的最终结果都是
- 跳转到所谓
- 系统调用处理程序的汇编语言函数。
内核实现了不同的系统调用,
- 进程须传递一个名为系统调用号的参数来识别所需的系统调用
- eax寄作此目的
- 当调用一个系统调用时通常还要传递另外的参数。
系统调用都返整数。
- 与封装例程返回值的约定不同。
- 内核中,正数或0表示系统调用成功结束,负数:一个出错条件。
- 后一种情况,就是存放在errno变量中必须返回给应用程序的负出错码。
- 内核没有设置或使用errno变量,封装例程从系统调用返回后设置这个变量
系统调用处理程序与其他异常处理程序same
- 在内核态栈保存多数寄内容(此操作对所有的系统调用都通用,用汇编写)。
- 调用
- 名为系统调用服务例程( system call service routine)的
- 相应的C函数来处理系统调用。
- 退出系统调用处理程序:
- 用保存在内核栈中的值加载寄存器,
- CPU从态切回用态
- (所有的系统调用都要执行这一相同的,用汇编语言代码实现)。
把系统调用号与相应的服务例程关联,内核用了一个系统调用分派表
- 此表存放在sys_all_table数组中,有NR_ syscalls个表项(在
Linux2.6.11内核中是289) - 第n个表项含系统调用号为n的服务例程的地址
NR_syscalls宏对可实现的系统调用最大个数的限制,不表已实现的系统调用个数。
- 分派表中的任一个表项也可包含sys_ni_syscall()函数的地址,
- 这个函数是“未实现”系统调用的服务例程,
- 仅返回出错码- ENOSYS。
进入和退出系统调用
本地应用(注1)两种方式系统调用:
- int $0x80。
- 内核老版本,从用态切到内态的唯一
- sysenter
- Pentium II中引入
- 2.6内核支持这条指令。
内核可通过两方式从系统调用退出,使CPU切回用态
- iret
- sysexit,sysenter同时在Pentium II引入。
支持进入内核的两种不同方式并不像看起来那么简单,
- 内核既支持只用int $0x80指令的旧函数库,
- 也支持可用sysenter的新函数库。
- sysenter的标准库必须能处理仅支持int$0x80指令的旧内核。
- 内核和标准库须既能运行在不含sysenter的旧处理器上,
- 也能运行在含它的新处理器上。
本章“通过sysenter指令发出系统调用”一节,
- 看Linux内核是如何解决这些兼容性问题的。
int $0x 80发系统调用
- 调用系统调用的传统方法是
- 用汇编语言指令int,
- 在“中断和异常的硬件处理”曾论过
128(0x80)对应内核入口点。
-
在内核初始化期间调用的函数trap_init()
-
用下面的方式建立对应于向量128的
- 中断描述符表表项
-
set_system__gate(0x80, &system-call)
-
该调用把下列值存入这个门描述符的相应字段(见四章的“中断门、陷阱门及系统门”一节)
-
Segment Selector
-
内核代码段__KERNEL_CS的段选择符。
-
Offset
-
指向system_call()系统调用处理程序的指针,
-
Type
-
置为15。
-
表示这个异常是一个陷阱,相应的处理程序不禁止可屏蔽中断。
-
DPL(描述符特权级)
- 置为3。
- 这就允许用户态进程调用这个异常处理程序(见四章中的“中断和异
常的硬件处理”)。
-
当用户态进程发出int s0x80时,
- CPU切换到内核态并从system_call处开始执行。