Linux内核设计与实现(四)一一系统调用

Linux系统调用

系统调用概念

操作系统提供了一种标准的服务来让程序员实现对底层硬件和服务的控制(比如文件系统),叫做系统调用(system calls)

  • 应用程序通过这组界面访问硬件和其他操作系统资源

  • 完成对硬件和资源的访问控制

  • 硬件设备的抽象(提供设备的独立性)

系统调用列表

Linux系统调用列表

其中常见的有fork(), exec(), open(), read(), write(), close()……

应用程序及系统调用的层次关系

应用程序通过在用户空间实现的 API 而不是直接通过系统调用来编程

如 调用 printf()函数时,应用程序、C 库和内核的关系

应用程序调用 printf()->C 库中的 printf()->C 库中的 write()->内核中的 write() 系统调用

系统调用实现原理

系统调用相关概念

int 80H

软中断,通知内核的机制是靠软中断实现的,第128号中断处理程序

IVT(Interrupt Vector Table)

中断向量表,包括所有中断程序入口地址,它固定存放于内存中(实模式下应用)

IDT(Interrupt Descriptor Table)

中断描述符表,不固定内存位置,通过 IDTR 寄存器定位该表(保护模式下应用,int 80H 占据其中一项)

syscall table

系统调用表

系统调用号

在 Linux 中,每个系统调用被赋予一个系统调用号,表示它在表中的编号

系统调用的加载

操作系统在加载时做的有关系统调用的加载:

  • int 80H 处理程序地址的加载:start_kernel()中的 trap_init()和 set_system_gate()

  • 各系统调用处理程序的加载(entry.s)

系统调用过程(以 x86 为例)

软中断->系统从用户态切换到内核态->系统读取寄存器的值来获取系统调用号->执行相应系统调用代码

首先,通过软中断陷入到 int 80h 中断中,促使系统切换到内核态去执行异常处理程序(系统调用处理程序);之后,系统通过读取 eax 寄存器的值来获取系统调用号;之后,系统通过读取寄存器来获取传递的参数(ebx, ecx, edx, esi, edi)按照顺序存放前五个参数,如果参数为6个或以上,则将其中一个寄存器的值指向内存空间;最后,执行相应系统调用代码,完成系统调用

系统调用的参数验证

系统调用必须仔细检查他们所有参数是否合法有效,如果用户将不合法的参数传递给内核,那么系统的安全和稳定将面临极大考验。

  • 权限验证:系统调用的调用者可以使用 capable() 函数来检查是否有权能对制定的资源进行操作

  • 指针合法性验证:在接受一个用户空间的指针之前,内核需要验证:

    指针指向的内存区域属于用户空间

    指针指向的内存区域在进程的地址空间里

    如果是读,该内存应被标记为可读;如果是写,该内存应被标记为可写;如果是可执行,进程决不能绕过内存访问限制

系统调用追踪工具strace

ptrace提供了一种使父进程得以监视和控制其它进程的方式,它还能够改变子进程中的寄存器和内核映像,因而可以实现断点调试和系统调用的跟踪。
使用ptrace,你可以在用户层拦截和修改系统调用(sys call)

操作系统提供了一种标准的服务来让程序员实现对底层硬件和服务的控制(比如文件系统),叫做系统调用(system calls)

http://www.kgdb.info/playing_with_ptrace_part_i/

编写我们自己的系统调用

首先添加系统调用号

然后声明我们的系统调用函数

实现我们的系统调用函数

编译内核

编译成功

编写测试文件

进行测试

测试成功

参考

读薄《Linux 内核设计与实现》(3) - 系统调用