实验五——分析 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
返回用户态,并让用户程序继续执行系统调用后的指令。
这个过程确保了系统调用的安全性和稳定性,使用户程序可以请求内核提供的服务而不会直接操作系统资源。