分析 system_call 中断处理过程
张晓攀+原创作品转载请注明出处+《Linux内核分析》MOOC课程https://mooc.study.163.com/course/1000029000
实验五——分析 system_call 中断处理过程
在上一次实验中我选择的4号系统调用write
一、打开shell并使用命令启动内核进入menu程序
cd ~/LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
进入menu程序后,可以看到该系统中仅有3个命令:help,version,quit

二、在该系统中添加两条命令:write和write-asm
(write和write-asm这两个函数是系统调用sys_write的C语言版本和汇编版本)
打开test.c文件可以看到main函数如下:

-
MenuConfig("version", "XXX V1.0(Menu program v1.0 inside)", NULL);- 配置菜单项。这个函数的第一个参数是命令名称"version",第二个参数是命令的描述或帮助信息,"XXX V1.0(Menu program v1.0 inside)",第三个参数是执行这个命令时的函数指针,这里是NULL,表示这个命令不会执行任何操作,只显示信息。 -
MenuConfig("quit", "Quit from XXX", Quit);
- 另一项菜单配置,设置了一个退出命令"quit",描述信息是"Quit from XXX",对应的函数指针为Quit`,用于退出菜单系统。 -
ExecuteMenu();` - 执行菜单,进入到菜单系统中并等待用户的输入指令。
在main函数中添加write和和write-asm:

并给出Write和Write_asm函数:

-
write(0, argv[i], strlen(argv[i]));:调用write函数,将argv[i]的内容写到文件描述符0(标准输入/输出可能被重定向),字符串长度由strlen(argv[i])决定。 -
write(0, "\n", 1);:写入一个换行符\n。

-
mov $4, %%eax
:将系统调用号4(sys_write)放入eax`,表示要执行写操作。 -
mov $1, %%ebx:将文件描述符1(标准输出)放入ebx。 -
mov %2, %%ecx:将str的地址放入ecx,指定要写入的字符串。 -
mov %3, %%edx:将字符串长度len放入edx。 -
int $0x80:触发中断0x80,执行系统调用。
再次输入help可以看到以上两条命令已经添加:

三、分析系统调用的过程,从 system_call 开始到 iret 结束之间的整个过程
在 Linux 系统中,系统调用的过程通常是通过软件中断来完成的。以 x86 架构的 Linux 为例,系统调用的核心过程是通过 int 0x80 中断来触发内核中的 system_call 函数。以下是从 system_call 开始到 iret 返回的整个过程详细分析:
1. 触发系统调用:int 0x80
用户态程序执行 int 0x80 指令,触发系统调用中断 0x80,这会导致处理器进入内核态并跳转到系统调用入口,即 system_call 函数。以下是一些重要的准备工作:
- 系统调用号:用户程序在
eax寄存器中设置系统调用号,用于指定执行哪个系统调用(例如,sys_write的系统调用号为 4)。 - 参数:用户程序在
ebx、ecx、edx、esi、edi等寄存器中设置参数,这些寄存器将被system_call读取。
2. system_call 函数入口
系统调用的入口通常会执行以下操作:
- 保存上下文:
system_call首先将用户态的寄存器上下文(例如eax、ebx、ecx、edx等寄存器)保存到内核栈,以便系统调用完成后恢复用户态的状态。 - 检查系统调用号:
system_call会验证eax中的系统调用号是否合法。如果无效,则可能返回错误(例如-ENOSYS表示系统调用不存在)。
3. 调用相应的系统调用处理函数
根据系统调用号 eax,system_call 会从系统调用表中找到对应的系统调用处理函数并跳转执行。例如,如果系统调用号是 4(即 sys_write),则会执行 sys_write 函数。
- 系统调用表(sys_call_table):这是一个函数指针数组,系统调用号用于索引该数组,从而找到相应的系统调用处理函数。例如
sys_write、sys_read等系统调用都在sys_call_table中有对应的函数指针。 - 执行系统调用逻辑:系统调用处理函数(如
sys_write)执行相应的内核逻辑,通常涉及与硬件交互、文件操作、内存管理等。
4. 返回系统调用结果
系统调用处理函数执行完毕后,将返回一个结果值。通常情况下,返回值存放在 eax 寄存器中。
- 错误处理:如果系统调用失败,内核会在
eax中设置一个负数的错误码,例如-EINVAL表示无效参数。
5. 恢复用户态上下文
在 system_call 函数返回用户态之前,内核需要恢复用户态的寄存器上下文,以确保系统调用返回时用户态的执行环境不受影响。
- 恢复寄存器:从内核栈中取出保存的寄存器值,恢复到各个寄存器。
- 清理内核栈:清理在进入内核态时保存的上下文信息。
6. 返回用户态:iret
系统调用完成后,处理器会执行 iret 指令返回到用户态。iret 指令会从栈中弹出用户态的 CS:EIP、EFLAGS、SS:ESP 等寄存器,恢复用户态的指令指针、标志位和栈指针。
- 切换回用户态:
iret会将处理器的状态切换回用户态,并开始执行用户程序中系统调用之后的指令。 - 返回结果:在用户程序中,系统调用的返回值将保存在
eax寄存器中,这样用户程序可以通过检查eax的值来知道系统调用是否成功,以及获取到的结果。
四、总结
整个系统调用过程包括以下关键步骤:
- 用户态程序通过
int 0x80触发系统调用中断。 system_call入口函数保存上下文,并根据系统调用号跳转到对应的系统调用处理函数。- 系统调用处理函数执行内核逻辑并返回结果。
system_call恢复用户态上下文。iret返回用户态,并让用户程序继续执行系统调用后的指令。
这个过程确保了系统调用的安全性和稳定性,使用户程序可以请求内核提供的服务而不会直接操作系统资源。