系统调用(System Call)是操作系统提供给用户程序访问内核功能的接口,是用户空间与内核空间交互的标准方式。下面我将结合Linux 0.12源码详细解析其实现机制。
特权级切换:用户程序运行在CPU的Ring3特权级,内核运行在Ring0。系统调用触发从用户态到内核态的切换。
中断机制:传统上通过软中断(如int 0x80)实现,现代CPU有专用指令(sysenter/sysexit)。
参数传递:通过寄存器(如eax存放系统调用号,ebx/ecx/edx等存放参数)。
在include/linux/sys.h
中定义系统调用表:
// 系统调用函数指针类型
typedef int (*fn_ptr)();
// 系统调用表
fn_ptr sys_call_table[] = {
sys_setup, sys_exit, sys_fork, sys_read, /*...*/
};
在kernel/system_call.s
中定义中断处理入口:
_system_call:
cmpl $NR_syscalls-1,%eax # 检查系统调用号是否合法
ja bad_sys_call
push %ds # 保存寄存器
push %es
push %fs
pushl %edx
pushl %ecx
pushl %ebx
movl $0x10,%edx # 设置内核数据段
mov %dx,%ds
mov %dx,%es
movl $0x17,%edx # 设置用户数据段
mov %dx,%fs
call *sys_call_table(,%eax,4) # 调用系统调用处理函数
pushl %eax # 保存返回值
# ... 恢复寄存器等后续处理
用户程序通过库函数触发系统调用,如lib/open.c
:
int open(const char * filename, int flag, ...)
{
register int res;
__asm__ volatile (
"int $0x80" # 触发0x80中断
: "=a" (res) # 返回值存入res
: "0" (__NR_open), "b" (filename), "c" (flag) # 参数传递
);
return res;
}
参数通过寄存器传递: - eax: 系统调用号(如__NR_open=5) - ebx: 第一个参数(文件名指针) - ecx: 第二个参数(标志) - edx: 第三个参数(模式)
用户空间程序 内核空间
| |
| 1. 执行int 0x80 |
|------------------->|
| | 2. 查找IDT表,跳转到system_call
| | 3. 保存寄存器,切换段
| | 4. 查sys_call_table[eax]
| | 5. 执行对应系统调用函数
| | 6. 恢复寄存器,iret返回
|<-------------------|
| 7. 继续执行 |
kernel/sched.c
中初始化:void sched_init(void)
{
set_intr_gate(0x80,&system_call); // 设置0x80中断入口
}
include/unistd.h
中:#define __NR_setup 0
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
// ...
快速系统调用指令:现代CPU使用sysenter/sysexit
替代int 0x80
,减少开销。
vsyscall/vDSO:将部分常用系统调用(如gettimeofday)映射到用户空间。
上下文切换优化:尽量减少需要保存/恢复的寄存器数量。
Linux 0.12虽然简单,但已经包含了现代系统调用的核心机制,后续版本主要是在此基础上的优化和扩展。