文章目录
一、分析 system_call 中断处理过程
1、使用 gdb 跟踪分析一个系统调用内核函数(上周选择的那一个系统调用,即getpid和getppid)。
2、根据本周所学知识分析系统调用的过程,从 system_call 开始到 iret 结束之间的整个过程,并画出简要准确的流程图,撰写一篇署名博客。
二、实验过程
1.重新定义系统调用getpid和getppid。
首先,重新进入实验楼,与上周操作相同,重新编译内核后,进入menu界面下,进行test.c的编辑。
cd ~/LinuxKernel
rm -rf menu
git clone https://github.com/mengning/menu.git
cd menu
vim test.c
在test.c中加入上一实验的两个代码,即getpid和getppid的代码,并在test.c中的main函数中加入两个MenuConfig函数进行这两个系统调用函数的配置:
int _20232831_main1(){
pid_t pid=getpid();
pid_t ppid=getppid();
printf("pid=%d ppid=%d\n",pid,ppid);
}
int _20232831_main2() {
pid_t pid, ppid;
// 获取当前进程的PID(使用getpid)
asm volatile("mov $0x14, %%eax\n\t" // getpid 的ID为20
"int $0x80\n\t"
"mov %%eax, %0\n\t"
: "=r" (pid) :: "%eax");
// 获取当前进程的父进程的PID(使用getppid)
asm volatile("mov $0x40, %%eax\n\t" // getppid 的ID为64
"int $0x80\n\t"
"mov %%eax, %0\n\t"
: "=r" (ppid) :: "%eax");
printf("pid=%d ppid=%d\n", pid, ppid);
return 0;
}
两个配置系统调用函数的MenuConfig函数如下:
MenuConfig("getpid1","system_call_1",_20232831_main1);
MenuConfig("getpid_asm_2","system_call_asm_2",_20232831_main2);
输入help和getpid1和getpid_asm_2,即可发现自己定义的两个系统调用函数,并能成功使用。
2.使用 gdb 跟踪分析getpid和getppid系统调用内核函数
这一模块与上周的实验类似,也是先启动冻结的内核,再另打开一个shell,进行GDB调试,建立GDB和gdbserver之间的连接。此后,在getpid函数处设置断点,进行分析。
# 打开 GDB 调试器
$ gdb
# 在 GDB 中输入以下命令:
# 在gdb界面中targe remote之前加载符号表
(gdb)file linux-3.18.6/vmlinux
# 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
(gdb)target remote:1234
# 在getpid处设置断点
(gdb)b sys_getpid
设置断点后可以发现,sys_getpid是位于kernel/sys.c中的817行,当输入c继续运行内核后,冻结的内核开始运行,且getpid1能够正常运行,但是运行getpid_asm_2时会发现,gdb中将显示设置的断点处sys_getpid,无法继续进行下一步,且MenuOS中也将卡住无法进行getpid_asm_2的运行,这说明此时进入了中断。
经过分析及查阅资料得知,原因如下:
问题出现在使用 int $0x80 汇编指令来触发系统调用。int $0x80 是一种触发系统调用的传统方法,但在该情况下,GDB 不会正常跟踪系统调用。这是因为 int $0x80 触发的系统调用进入了内核态,而 GDB 默认情况下只跟踪用户态的代码。因此,跟踪asm处的系统调用时进入了内核态,便无法追踪了。
3.分析 system_call 开始到 iret 结束之间的整个过程,并画出简要准确的流程图
首先,根据ChatGPT的知识要点,理解系统调用的机制以及system_call 开始到 iret 结束之间的整个过程。
以下是system_call简化后的代码以及代码分析
ENTRY(system_call)
RINGO_INT_FRAME
ASM_CLAC
push1_cfi %eax /*保存系统调用号*/
SAVE_ALL /*保存现场,将用到的所有CPU寄存器保存到栈中*/
GET_THREAD_INFO(%ebp) /*ebp用于存放当前进程thread_info结构的地址*/
test1 $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
jnz syscall_trace_entry
cmp1 $(nr_syscalls),%eax /*检查系统调用号(系统调用号应小于NR_syscalls)*/
jae syscall_badsys /*不合法,跳入异常处理*/
syscall_call:
call *sys_call_table(,%eax,4) /*合法,对照系统调用号在系统调用表中寻找相应的系统调用的内核处理函数*/
movl %eax,PT_EAX(%esp) /*保存返回值到栈中*/
syscall_exit:
testl $_TIF_ALLWORK_MASK, %ecx /*检查是否需要处理信号*/
jne syscall_exit_work /*需要,进入 syscall_exit_work*/
restore_all:
TRACE_IRQS_IRET /*不需要,执行restore_all恢复,返回用户态*/
irq_return:
INTERRUPT_RETURN /*相当于iret*/
画出简要准确的流程图,如下图所示。system_call在start_kernel里初始化,在执行int 0x80后,cpu会从system_call开始执行。紧接着,系统调用的整个过程如下:先保存所有信息,再开始从system_call执行。经过一系列的系统调用处理和参数传递后,最终恢复用户空间。
三、Chatgpt帮助
总结
本次实验中,我使用了gdb工具跟踪分析了Linux内核中的两个系统调用函数:getpid 和 getppid。这帮助我更深入地理解了系统调用的执行过程,包括用户空间和内核空间之间的切换、参数传递、系统调用号的解析等。通过调试和观察寄存器状态的变化,我成功捕获了系统调用的执行步骤,并进一步绘制了流程图,以更清晰地展示系统调用的执行过程。通过本次实验,我深入了解了操作系统内核的基本运行原理。