杨明辉 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
一、实验过程
1. 首先进入实验楼,打开实验楼终端;然后输入命令cd LinuxKernel进入到LinuxKernel目录下,本次实验要给menu加入新的功能,所以输入命令rm menu -rf删除原来的menu,然后输入命令git clone https://github.com/mengning/menu.git重新克隆一个新版本的menu。实验结果如图1所示。
图12.输入命令cd menu 进入到menu中,然后输入make rootfs对menu进行重新编译,并启动qume。我们可以在图3中看到QUME中新增了两个命令time和time-asm,实验结果如图2、图3所示。
图2
图33. 我们可以再MENUOS中增加自己的系统调用和命令,具体过程如下:
1. 进入menu文件下,并打开menu文件夹下的文件test.c。2. 将上次实验中所写的两个系统调用的程序粘贴到test.c文件中。3. 在main函数中添加新的命令,利用MenuConfig函数添加新的命令,代码如下所示;其中第一个参数为MENUOS系统中新加的命令,第二个参数为输入该命令时输出的说明,第三个参数为执行命令时所调用的函数。MenuConfig("open","Show FD",Open); MenuConfig("open-asm","Show FD(asm)",Open_asm);
4. 根据步骤3添加完新的命令后,输入命令make rootfs对程序进行重新编译,然后我们会看到新添加的命令,输入命令会进行相应的系统调用,实验结果如图4所示:
图4
5. 输入命令cd ..返回到menu目录下,然后输入命令qume -kernel linux-3.18.6/arch/x86/boot/bzImage-initrd rootfs.img -s -S s 启动MENUOS,然后进入gdb 输入命令file linux-3.18.6/vmlinux,在gdb界面中加载符号表;然后输入命令target remote:1234建立gdb和gdbserver之间的连接;然后输入命令break start_kernel设置断点,输入c让qemu上的Linux继续运行,输入list可以查看断点附近的源码,实验结果如图5所示。
图56. 在gdb中输入命令b sys_open在open系统调用处打断点,然后开始执行,进行调试,实验结果如图6、图7所示。
图6
图7
二、系统调用在内核代码中的工作机制和初始化
1. 系统调用的过程如图2-1所示,系统从xyz()函数中调用system_call,然后system_call调用相对应的系统调用sys_xyz()。
图2-1
2. 系统调用的初始化过程如下:
1. 系统首先进入到/init/main.c文件中的start_kernel函数中,2. 在start_kernel函数中调用函数trap_init()函数,trap_init()函数在\arch\x86\kernel\traps.c文件中实现。
3. 最后在trap_init()函数中执行代码set_system_trap_gate(SYSCALL_VECTOR,&system_call)和set_bit(SYSCALL_VECTOP,used_vectors)绑定system_call函数,使的执行0x80中断时执行system_call开始的汇编代码。3. 代码执行完int ox80后就会进入到代码ENINRY(system_call)开始执行。执行代码如下所示:
<pre name="code" class="cpp">ENTRY(system_call) RING0_INT_FRAME # can't unwind into user space anyway ASM_CLAC pushl_cfi %eax # save orig_eax SAVE_ALL GET_THREAD_INFO(%ebp) # system call tracing in operation / emulation testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp) jnz syscall_trace_entry cmpl $(NR_syscalls), %eax jae syscall_badsys syscall_call: call *sys_call_table(,%eax,4) syscall_after_call: movl %eax,PT_EAX(%esp) # store the return value syscall_exit: LOCKDEP_SYS_EXIT DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt # setting need_resched or sigpending # between sampling and the iret TRACE_IRQS_OFF movl TI_flags(%ebp), %ecx testl $_TIF_ALLWORK_MASK, %ecx # current->work jne syscall_exit_work restore_all: TRACE_IRQS_IRET
4. 从system_call开始到iret结束之间的整个过程的流程图如图2-2所示。
图2-2
三、总结
1. 系统调用”是操作系统提供给用户程序进行调用的一些服务。这些服务是系统预先提供的函数,在这一点上系统调用与普通的用户程序是没有区别的。而区别则在于“系统调用”是由操作系统提供给用户的,这些服务更接近底层或者要求的安全性更高,因此由操作系统来统一实现和管理。2. 处理器在eax寄存器中拿到系统调用号之后,会到系统调用表中找到该系统调用所对应的入口函数地址,然后执行该函数。3. 函数的入口地址在在syscall.c中。4. 中断处理中读取中断号及参数,然后找到中断服务例程并执行,退出中断后进行堆栈切换,返回用户态,继续执行用户程序。