操作系统(2) 系统调用

402 阅读5分钟

系统接口

我们使用计算机时软硬件布局如下:

image.png

操作系统隔开了上层应用软件和底层硬件,上层应用不能绕过操作系统直接访问硬件。比如应用想要保存数据到txt文件,它不能直接到磁盘中创建文件并写入数据,只能"告诉"操作系统它要创建文件,将文件格式,要保存的数据等参数交给操作系统由系统完成磁盘中文件的创建和数据写入并返回结果给上层应用

上层应用和操作系统互动的过程是通过调用系统接口来实现的,如下所示系统接口就是一系列操作系统提供的函数,详情可参考POSIX (Portable Operating System Interface of UNIX)

此处开始本文引用图片均来自 李治军: 操作系统32讲

image.png

当应用调用open函数系统就打开文件,调用fork函数系统就创建一个进程,系统调用其实就是调用操作系统提供的接口函数实现特定功能的过程

值得一提的是,一套标准接口可以使应用开发者极大地降低开发成本且开发的应用可以获得源码级别的可移植性

内核与用户

系统将内存分为内核段用户段,前者保存系统的重要数据例如用户密码,后者保存上层应用的数据。同时,为了保护数据操作系统将运行特权级划分为内核态用户态

image.png

内核态下当前指令可以访问任意数据,而用户态下不能访问内核段的数据,上层应用运行的时候处于用户态,这样可以避免恶意应用窃取系统数据

内核态用户态针对于当前执行的指令,我们知道当前指令由CSIP寄存器共同指定,所以CS寄存器的最后两位被用于确定当前的系统特权级(CPL (Current Privilege Level): 0表示内核态,3表示用户态

操作系统将内存按照段的形式划分,每一段内存在System模块的Head.s创建GDT时都会给该段内存对应的表项(如下图)设定DPL(Descriptor Privilege Level),根据操作系统本身所占据的内存位置内核段内存的GDT表项的DPL都会被设定为0,其他内存设定为3

image.png

当应用访问内存时,操作系统会比较CPL和目标内存段的DPL,只有当DPL大于等于CPL时才允许访问

真正的调用手段——中断

系统接口的具体实现函数的代码和数据毫无疑问是放在内核段的,此时处于用户态的应用为什么能直接调用系统接口呢?

其实系统接口只是一个空壳,本身并不负责实现相应的功能,它只是为具体实现功能的函数准备数据(例如参数)以及为用户设定中断号,最后通过中断指令让操作系统调用位于内核段的真正实现功能的函数

中断是应用实现系统调用的唯一方式,通过中断指令系统会短暂的进入内核态,当调用完成后系统又会自动回到用户态

中断其实相当于告诉系统我要进行系统调用,系统收到中断信号就会去处理相应的中断请求,利用中断实现系统调用的流程大概为以下几步:

  1. 应用中有一段调用系统接口的代码,系统接口函数的实现是一段包含int指令的代码
  2. 操作系统收到中断信号并根据系统接口设置在eax寄存器的值获取目标中断处理程序编号
  3. 系统根据处理程序的编号执行保存在内核段中的相应函数的代码

值得一提的是操作系统规定系统调用只能通过int 0x80这个中断来实现,也正因为此,所以需要把目标中断处理程序的编号放在eax中,否则系统无法区分该中断使想要实现open还是fork或别的操作。为了让用户态下的应用能够调用0x80号中断,在IDT中该中断对应表项的DPL被设置为3

image.png

调用0x80中断后系统会根据段选择符来设置CSIP0x80中断对应表项的段选择符为8),所以此时CS被赋值8最低两位为0系统进入内核态,然后执行0x80中断的处理函数system_call

system_call根据eax中的值(目标中断处理程序编号)查sys_call_table(函数指针数组),找到目标中断处理程序的地址执行函数并返回执行结果,至此,一个系统调用就完成了

image.png

image.png

调用例子

以c语言常用的printf函数为例简述系统调用的整个流程

首先有一段包含printf的代码

#include<stdio.h>
int main(){
    printf("Hello World\n");
    return 0;
}

printf函数是由c语言的标准库提供的,进入标准库的print函数实现会发现它调用另一个库函数write来实现打印功能,再进入write的实现可以看到它有一段宏展开的包含int 0x80指令的汇编代码(c语言内嵌汇编),这里的int真正开始了系统调用

操作系统收到0x80中断信号后进入内核态并调用该中断的处理函数system_callsystem_call根据eax寄存器中的值查询函数表sys_call_table找到目标中断处理函数并执行然后返回执行结果且回到用户态,至此系统调用结束

image.png image.png

参考资料