Linux syscall

syscall是应用程序和Linux内核之间的基本接口,syscall是Linux内核系统调用接口的高级概述,用于处理各个组件与用户空间之间的通信。

 

Linux_kernel_interfaces - syscall

除了一些嵌入式系统之外,大多数现代处理器的架构都涉及安全模型。程序通常仅限于其自己的地址空间,因此它无法访问或修改其他正在运行的程序或操作系统本身,并且通常无法直接操作硬件设备(例如帧缓冲器或网络设备)。

通常,系统提供位于正常程序和操作系统之间的库或API。在类Unix系统上,该API通常是C库(glibc为GNU系统和GNU/Linux系统以及使用Linux作为内核的许多其他系统提供的核心库)实现的一部分,例如glibc,它为系统调用提供包装函数,通常与它们调用的系统调用相同。

在Windows NT上,该API是ntdll.dll库中Native API的一部分 ; 这是常规Windows API实现使用的未记录的API并直接由Windows上的某些系统程序使用。库的包装函数公开了一个普通的函数调用约定(在程序集级别上的子程序调用)来使用系统调用,以及使系统调用更加模块化。

这里,包装器的主要功能是将所有参数传递给适当的处理器寄存器(也可能在调用堆栈中)的系统调用,并为内核设置一个唯一的系统调用号来调用。通过这种方式,存在于OS和应用程序之间的库增加了可移植性。

对库函数本身的调用不会导致切换到内核模式(如果执行尚未处于内核模式)并且通常是正常的子例程调用(例如,在某些指令集体系结构中使用“CALL”汇编指令)(ISA)中)。

实际的系统调用确实将控制转移到内核(并且比抽象它的库调用更依赖于实现和平台)。例如,在类似Unix的系统中,fork并且execve是C库函数,反过来执行该调用指令fork和exec系统调用。

直接在应用程序代码中进行系统调用更复杂,可能需要使用嵌入式汇编代码(在C和C ++中)以及系统调用操作的低级二进制接口的知识,这可能会随着时间的推移而发生变化,因此不会成为应用二进制接口 ; 库函数旨在将其抽象出来。

在基于exokernel的系统上,该库作为中介尤其重要。在exokernel上,库将用户应用程序与极低级别的内核API隔离开来,并提供抽象和资源管理。

来自OS / 360和DOS / 360的 IBM操作系统,包括z / OS和z / VSE,通过汇编语言宏库实现系统调用。这反映了它们在汇编语言编程比高级语言使用更常见时的起源。因此,IBM系统调用不能由高级语言程序直接执行,但需要可调用的汇编语言包装器子例程。

但是,许多普通应用程序显然需要访问这些组件,因此操作系统可以使用系统调用来为这些操作提供定义明确,安全的实现。操作系统以最高级别的权限执行,并允许应用程序通过系统调用请求服务,系统调用通常通过中断启动。

中断会自动将CPU置于某个提升的权限级别,然后将控制权传递给内核,从而确定是否应该授予调用程序所请求的服务。如果授予服务,则内核执行一组特定的指令,调用程序无法直接控制该指令,将权限级别返回到调用程序的权限级别,然后将控制权返回给调用程序。

syscall通常不是直接调用,而是通过glibc(glibc为GNU系统和GNU/Linux系统以及使用Linux作为内核的许多其他系统提供的核心库)中的包装器函数调用。但并非总是如此,包装函数的名称与它调用的syscall的名称相同。例如,glibc包含一个函数truncate(),它调用底层的“truncate”syscall。

通常glibc包装器函数非常薄,除了在调用系统调用之前将参数复制到正确的寄存器,然后在系统调用返回后适当地设置errno之外,做的工作很少。(这些是由syscall执行的相同步骤,可用于调用未提供包装函数的系统调用。)

注意:syscall通过向调用者返回负错误号来表示失败。当发生这种情况时,包装函数否定返回的错误号(使其成为正数),将其复制到errno,并将-1返回给包装器的调用者。

但是,有时候包装函数在调用syscall之前会做一些额外的工作。包装函数检查内核提供哪些系统调用并确定哪个可以使用。

syscall为操作系统的安全性和稳定性提供保障,应用程序进程无法直接执行内核代码和访问内核空间。

粗略地说,可以Linux内核源代码/usr/include/asm/unistd.h中找到属于sys_xxx()的系统调用号。i386的调度表可以在/usr/src/linux/arch/i386/kernel/entry.S中找到。然而,有很多例外,主要是因为较旧的系统调用被新的系统调用取代了。