陈民禾——原创作品转载请注明出处—— 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
一.上周内容回顾
计算机科学中有一句话,任何计算机相关问题都可以通过加一个中间层来解决。操作系统的系统调用是这样,system_call将api和系统函数连接起来,这样可以保证内核的安全,不会因为用户的失误操作而造成问题。操作系统为了安全,把一些重要的调用放在内核部分,这样只能通过触发系统调用来完成相应功能,这样可以保证内核的安全。
二.系统调用在内核代码中的机制和初始化
这部分代码对我们来说理解整个操作系统的运行机制是非常重要的。因为杂过程中涉及到了进程调度的时机,系统调用的时机对我们理解操作系统的运行机制非常的重要,所以我们需要仔细的来分析一下system_call这个系统调用的过程。
在xyz系统调用里面我们使用了int 0x80,在int 0x80中断向量对应着system_call,它是什么时候绑定起来的呢,是初始化的时候,初始化这个中断向量的时间,然后呢system_call的处理过程非常重要,这段汇编代码我们需要仔细的分析一下,在这个system_call调用的时候,哪里用到了sys_xyz,通过系统调用,通过中断向量来匹配起来的。那么在返回到用户态之前有一个返回时机,在我们调试的时候,你一直按n的话,从systime退出之后,下面会跟踪到schedule,我们先分析一下初始化,它从start_kernel里面有一个trap_init,trap_init里面有一个set_system_trap_gate(SYACALL_VECTOR(也就是系统调用的中断向量和system_call汇编代码的入口,这样一旦执行int 0x80,CPU 就自动跳转到system_call,这个位置来执行。
三.了解系统调用的基本代码
这个代码实际上就是中断系统调用的处理过程,系统调用其实就是一个特殊一点的中断,存在保护现场和恢复现场的问题(比如save_all)同时我们要是仔细读这个代码的话,这个代码还相对比较复杂一点,这里有一个syscall_table(%eax,4)它是由系统调用的定义系统调用表,eax传递系统调用号,实际上,我们在这一条的时候在例子里面就是调用systime。syscall_after_all这个时候它就需要保存它的返回值,它在退出之前有一个syscall_exit_work,会检查当前进程是否需要执行 syscall_exit_work
,如果需要处理,就会查看是否有进程信号或进程调度,若有进程信号或进程调度就再进行相应操作。
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 #恢复现场 irq_return: INTERRUPT_RETURN #中断返回
四.中断执行过程的流程图
五.我的实验过程及截图
本次实验选用 Linux
操作系统 2 号系统调用,fork
函数,通过对比嵌入式汇编编程和系统函数编程,深入理解和分析系统调用的过程。
命令行如下:
cd LinuxKernel
rm menu -rf
git clone https://github.com/mengning/menu.git
cd menu
make rootfs
先打开实验楼里面的LinuxKernel,然后我们输入删除的操作,rm menu -rf,然后我们重新克隆一个,因为新版本的Menu已经把上次的两个系统调用,然后我们可以重新克隆一个,因为新版本的Menu已经提交到github里面。
int menufork(int argc, char *argv[]) { pid_t fpid; int count = 0; fpid = fork(); if (fpid < 0) printf("error in fork!"); else if (fpid == 0) { printf("i am the child process, my process id is %d\n",getpid()); count++; } else { printf("i am the parent process, my process id is %d\n",getpid()); count++; } printf("count: %d\n",count); return 0; } int menuforkAsm(int argc, char *argv[]) { pid_t fpid; int count = 0; asm volatile ( "mov $0, %%ebx\n\t" "mov $0x2, %%eax\n\t" "int $0x80\n\t" "mov %%eax, %0\n\t" : "=m" (fpid) ); if (fpid < 0) printf("error in fork!"); else if (fpid == 0) { printf("i am the child process, my process id is %d\n",getpid()); count++; } else { printf("i am the parent process, my process id is %d\n",getpid()); count++; } printf("count: %d\n",count); return 0; } int main() { PrintMenuOS(); SetPrompt("MenuOS>>"); MenuConfig("version","MenuOS V1.0(Based on Linux 3.18.6)",NULL); MenuConfig("quit","Quit from MenuOS",Quit); MenuConfig("time","Show System Time",Time); MenuConfig("time-asm","Show System Time(asm)",TimeAsm); /* add for menufork */ MenuConfig("menufork","menufork --fork a proc", menufork); MenuConfig("menufork-asm","menufork-asm --fork a proc(asm)",menuforkAsm); ExecuteMenu(); }
gdb file linux-3.18.6/vmlinux 加载调试用的符号表 target remote:1234 b start_kernel 设置断点 c b sys_fork
我们使用命令vi test.c,test.c这里面从main 函数开始读,发现我们这个里面其实只增加了两行代码:这两行对应的有两个函数,一个menuforkAsm还有menufork函数:只是我们需要增加这些东西就可以完成对MenuOS的修改,只是我们需要增加新的命令的话,需要按照下面的方式,MenuConfig增加,然后对应的函数增加。
int 0x80
中断跳转到系统调用处理程序 system_call
函数处,执行相应的例程。但是,由于是代表的是用户进程,所以这个执行过程并不属于中断上下文,而是进程上下文。因此,系统调用执行过程中,可以访问用户进程的许多信息,也可以被更高优先级的进程抢占。