系统调用(syscall)是操作系统提供给程序以请求内核服务的一种机制。和int 0x80
提供相同的服务。
在计算机系统中,操作系统控制着对硬件资源的访问。应用程序不能直接操作硬件,而是需要通过操作系统提供的接口。系统调用就是这些接口之一,允许应用程序执行诸如读写文件、发送网络请求、创建进程等操作。系统调用作为用户空间(应用程序执行的地方)和内核空间(操作系统核心部分执行的地方)之间的桥梁,确保资源使用的安全性和有效性。
以Linux操作系统中的read()系统调用为例。当一个程序想要从文件中读取数据时,它会执行以下步骤:
read()
函数,并提供文件描述符(文件的唯一标识)、存放数据的缓冲区地址和要读取的字节数。read()
函数将这些参数传递给操作系统内核。可以将系统调用比作餐厅里的服务员。就像顾客不能直接进入厨房取菜,而需要通过服务员传达他们的需求一样,应用程序也不能直接访问硬件资源,而需要通过系统调用来请求操作系统执行特定的任务。
系统调用使应用程序能够执行文件操作、进程控制、通信等操作,同时确保了操作系统能够控制和管理资源访问,保证系统的稳定性和安全性。
安全性: 隔离用户程序和内核,防止直接的硬件访问导致的安全问题。
易用性: 提供了标准化的接口,简化了程序对硬件的操作。
兼容性: 应用程序通过系统调用与操作系统交互,减少了对特定硬件的依赖。
系统调用是操作系统提供给应用程序的一种重要接口,它使得程序能够安全、有效地执行需要操作系统干预的操作,如文件处理、进程管理等。尽管系统调用引入了一定的性能开销,但它的安全性和易用性使得它成为操作系统设计中不可或缺的一部分。
INT 80h:指令 INT 80h 触发软件中断。执行时,CPU 需要在处理中断之前保存程序的当前状态,包括各种寄存器。此过程是一个上下文切换,它涉及大量开销,因为 CPU 实质上是在暂停一个任务以启动另一个任务。
syscall: 该 syscall 指令旨在最大限度地减少需要保存和恢复的状态量,从而减少上下文切换开销。这是从用户模式到内核模式的更直接的过渡,需要更少的 CPU 时间和资源使用。
INT 80h: 使用 INT 80h ,中断处理机制更复杂。CPU 必须跳转到中断向量,然后中断向量指向相应的内核例程。这会增加额外的步骤并增加指令的路径长度。
syscall: syscall 提供更直接的路径。它使用特殊的 CPU 寄存器直接跳转到内核中的系统调用处理程序。这种较短的路径意味着仅向内核发送系统调用请求所花费的 CPU 周期更少。
虽然 syscall 效率更高,但只能用于特定的CPU体系架构。INT 80h具有向后兼容性,对于不可以使用syscall的机器或旧软件上尤为重要。
SYSCALL减少了上下文切换和简化了指令执行流程,性能更高,但对旧机器的兼容性不如int 80h.
当执行系统调用的时候,具体的执行流程如下。
在用户空间中,在执行 syscall
指令之前,应用程序会设置系统调用号及其参数。这通常是通过将系统调用号放在寄存器中(对于 x86-64 体系结构)并将参数放在其他 RAX 寄存器(如 RDI, RSI, RDX, R10, R8 和 R9 )中来完成的。
应用程序执行syscall
CPU 指令。和add
,mov
一样,这是专为转换到内核模式以执行系统调用而设计的 CPU 指令。读取到该指令后,CPU就会依次完成下面工作。
CPU从用户态(ring 3)切换到内核模式(ring 0),以获得更高的执行权限。
这一步步骤的具体流程:
1. 最小的上下文保存
a). CPU 自动将指令指针RIP
保存到寄存器中 RCX
。 RIP
指向应用程序中应在系统调用完成后执行的指令。
b). 将包含 CPU 当前状态的 RFLAGS
寄存器保存到寄存器中R11
。这包括中断启用/禁用标志等标志。
这种最小的上下文保存是提升INT 80h 效率的关键改进之一。
2. 直接跳转到系统调用处理程序
CPU载入模型特定寄存器(MSR)IA32_LSTAR
,其中包含内核中系统调用入口点的地址,直接跳转到系统调用处理程序。这种直接跳转是相对于 INT 80h 方法的另一个效率改进,INT 80h
方法需要遍历中断描述符表。
至此,完成了内核态的切换,下面开始在内核模式下执行系统调用的程序。
1. 执行调度程序
CPU 现在处于内核模式,在IA32_LSTAR
指向的地址处执行代码。这是Linux 内核中的系统调用调度程序。
调度程序从 RAX
寄存器中读取系统调用号。根据该调用号,从内核系统调用表中查找到相应的内核函数。
调度程序调用系统调用号对应的函数。该函数的参数取自寄存器(RDI、RSI、RDX、R10、R8、R9)。
2. 执行内核函数
内核函数执行。这可能涉及各种操作,例如访问文件、创建进程或修改系统设置,具体取决于调用的系统调用。
完成后,内核函数通常会返回一个结果,该结果将放置在寄存器中 RAX
。
处理完系统调用后,内核需要将控制权交还给用户空间中的应用程序。
CPU执行sysret
指令。此指令针对从系统调用返回进行了优化。
sysret 恢复 CPU 的用户模式操作状态。它从 RCX
寄存器加载 RIP
,从 R11
寄存器加载 RFLAGS
,有效地返回到执行syscall
之前的状态。
CPU 切换回用户模式,并在原始系统调用指令之后立即在应用程序中继续执行。