系统调用:运行在用户空间的程序向操作系统内核请求需要更高权限运行的服务。系统调用提供用户程序与操作系统之间的接口。
系统调用需要了解的点:
系统调用将处理器从用户态切换到核心态,以便 CPU 访问受到保护的内核内存。
每个系统调用都由一个唯一的数字来标识。
系统调用可以有一套参数,用于用户空间与内核空间之间相互传递信息。(x86-64中最多使用寄存器传递6个参数,参考资料如下:
x86-32 [Free|Open|Net|DragonFly]BSD UNIX System Call convention:
Parameters are passed on the stack. Push the parameters (last parameter pushed first) on to the stack. Then push an additional 32-bit of dummy data (Its not actually dummy data. refer to following link for more info) and then give a system call instruction
int $0x80
x86-64 Linux System Call convention:
- User-level applications use as integer registers for passing the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9. The kernel interface uses %rdi, %rsi, %rdx, %r10, %r8 and %r9.
- A system-call is done via the
syscall
instruction. This clobbers %rcx and %r11 as well as the %rax return value, but other registers are preserved.- The number of the syscall has to be passed in register %rax.
- System-calls are limited to six arguments, no argument is passed directly on the stack.
- Returning from the syscall, register %rax contains the result of the system-call. A value in the range between -4095 and -1 indicates an error, it is
-errno
.- Only values of class INTEGER or class MEMORY are passed to the kernel.
以x86-32为例,说明系统调用过程:
通过调用外壳(wrapper)函数发起系统调用(glibc)。
参数通过堆栈传入外壳函数,外壳函数会将上述参数复制到寄存器(系统调用最多使用6个)。
外壳函数将系统调用编号复制到eax寄存器中。
外壳函数执行中断机器指令(int 0x80),引发处理器从用户态切换到核心态,并执行系统中断0x80(十进制数128)的中断向量所指向的代码。
为响应中断0x80,内核会调用system_call()例程(位于汇编文件arch/i386/entry.S中)来处理中断,具体如下:
5.1 在内核栈中保存寄存器值。
5.2 审核系统调用编号是否有效。
5.3 如果调用号有效,就去存放所有调用服务例程的列表中进行索引,发现并调用相应的系统调用服务例程。
5.4 从内核栈中恢复各寄存器值,并将系统调用返回值置于栈中。
5.5 返回至外壳函数,同时将处理器切换回用户态。
若系统调用服务例程的返回值表明调用有误,外壳函数会使用该值来设置全局变量 errno。然后,外壳函数会返回到调用程序,并同时返回一个整型值,以表明系统调用是否成功。
Linux可以在命令行中使用 strace追踪系统调用 ,ltrace追踪库函数的调用。
Mac OS可以使用dtruss追踪系统调用。
[1] UNIX 系统编程手册
[2] 现代操作系统