分析系统调用的处理过程(systemcall->iret)
2015-04-05 16:22
381 查看
罗晓波 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
本片内容接上一篇,是系统调用的续篇,也就是简要分析一下系统调用的处理过程。同样,以一个实验开始。
本实验同样在实验楼环境下完成。下面先介绍一下实验:
1.实验:
将上一个系统调用函数和asm版本的实现整合进入menu的内核中:
在添加了getpid和getpid-asm这两个命令之后,make一下,再重新编译一下内核,make rootfs。再启动一下qemu:
接下来,我们开始调试这个系统调用函数,对应着getpid这个系统调用,在sys_getpid这个内核函数这里打一个断点。continue之后,输入getpid-asm或者getpid这个命令会停在sys_getpid();
可以看到,在kernel/sys.c中有这么一个sys_getpid()内核函数,这个也就是系统调用的服务例程。跟进这个task_tgid_vnr(current)函数,之后finish之后的返回值是1,也就是init进程的进程号。
当然打印出来的current pid 也是1 。
有点遗憾的是我没有找到让gdb在systemcall这个汇编代码处停止的办法,希望小伙伴可以友情提示。下面来进一步分析从systemcall到系统调用结束之后,iret之间的过程。
2.从systemcall到iret过程:
Systemcall内核源码(汇编代码)进行简要分析:
$(NR_syscalls),%eax 这个指令主要是为了防止eax传递参数越界了,也就是超过了系统调用符号表中的值,如果出错了就跳到syscall_badsys标签处。
如果上面的都没问题的话,我们来到系统调用函数执行的地方了,调用服务例程,系统调用号乘以4加上sys_call_table的基址,内核系统调用的服务例程,也就是getpid的函数地址就是它了,调用call指令,开始执行,又是顺理成章的将eax的值,也就是系统调用的返回值放到esp的某一个偏移量(PT_EAX)处。
在系统调用退出之前,在restoreall之前,会将threadinfo中的许多标志位进行比较,看是否需要在返回用户态之前做点什么事,比如我们看着一段:
在恢复用户态之前的一段代码:
本片内容接上一篇,是系统调用的续篇,也就是简要分析一下系统调用的处理过程。同样,以一个实验开始。
本实验同样在实验楼环境下完成。下面先介绍一下实验:
1.实验:
将上一个系统调用函数和asm版本的实现整合进入menu的内核中:
int GetPid() { int pid = getpid(); printf("The Current Progress pid is : %d\n",pid); return 0; } int GetPidAsm() { int pid; asm volatile( "mov $0,%%ebx\n\t" "mov $0x14,%%eax\n\t" "int $0x80\n\t" "mov %%eax,%0\n\t" : "=m" (pid) ); printf("The Current Progress pid asm is : %d\n",pid); 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); MenuConfig("getpid","Show Current Progress id",GetPid); MenuConfig("getpid-asm","Show Current Progress asm id",GetPidAsm); ExecuteMenu(); }简单介绍一下上面的一段代码,MenuConfig这个函数是菜单(也就是制作出来的内核)的初始化配置函数,第一个参数是命令,第二个参数是该命令的描述,第三个参数是这个命令相对应的handler,也就是回调函数,是通过一个函数指针进行实现的。ExecuteMenu这个函数是为了启动这个menu引擎,其实是一个循环等待用户输入命令的过程。
在添加了getpid和getpid-asm这两个命令之后,make一下,再重新编译一下内核,make rootfs。再启动一下qemu:
接下来,我们开始调试这个系统调用函数,对应着getpid这个系统调用,在sys_getpid这个内核函数这里打一个断点。continue之后,输入getpid-asm或者getpid这个命令会停在sys_getpid();
可以看到,在kernel/sys.c中有这么一个sys_getpid()内核函数,这个也就是系统调用的服务例程。跟进这个task_tgid_vnr(current)函数,之后finish之后的返回值是1,也就是init进程的进程号。
当然打印出来的current pid 也是1 。
有点遗憾的是我没有找到让gdb在systemcall这个汇编代码处停止的办法,希望小伙伴可以友情提示。下面来进一步分析从systemcall到系统调用结束之后,iret之间的过程。
2.从systemcall到iret过程:
Systemcall内核源码(汇编代码)进行简要分析:
490ENTRY(system_call) 491 RING0_INT_FRAME # can't unwind into user space anyway 492 ASM_CLAC 493 pushl_cfi %eax # save orig_eax 494 SAVE_ALL 495 GET_THREAD_INFO(%ebp) 496 # system call tracing in operation / emulation 497 testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp) 498 jnz syscall_trace_entry 499 cmpl $(NR_syscalls), %eax 500 jae syscall_badsys首先在内核栈中,压栈eax,也就是用户态传递给内核态的系统调用号,之后SaveAll这个指令就开始保存现场了,这里GET_THREAD_INFO(%ebp)是将这个进程的threadinfo结构体的地址指针放在了ebp寄存器里,下面对于这个结构体的访问也就可以顺理成章的用ebp的地址偏移进行访问就行了。比如下一句的TI_flags这个宏代表的其实就是一个offset,这个testl 指令主要是为了检查是否存在调试程序对这个系统调用正在调试,如果是,则跳转到syscall_trace_entry标签处,这个标签我一会来分析。接着往下看,cmpl
$(NR_syscalls),%eax 这个指令主要是为了防止eax传递参数越界了,也就是超过了系统调用符号表中的值,如果出错了就跳到syscall_badsys标签处。
501syscall_call: 502 call *sys_call_table(,%eax,4) 503syscall_after_call: 504 movl %eax,PT_EAX(%esp) # store the return value 505syscall_exit: 506 LOCKDEP_SYS_EXIT 507 DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt 508 # setting need_resched or sigpending 509 # between sampling and the iret
如果上面的都没问题的话,我们来到系统调用函数执行的地方了,调用服务例程,系统调用号乘以4加上sys_call_table的基址,内核系统调用的服务例程,也就是getpid的函数地址就是它了,调用call指令,开始执行,又是顺理成章的将eax的值,也就是系统调用的返回值放到esp的某一个偏移量(PT_EAX)处。
在系统调用退出之前,在restoreall之前,会将threadinfo中的许多标志位进行比较,看是否需要在返回用户态之前做点什么事,比如我们看着一段:
在恢复用户态之前的一段代码:
350 TRACE_IRQS_OFF 351 movl TI_flags(%ebp), %ecx 352 andl $_TIF_WORK_MASK, %ecx # is there any work to be done on 353 # int/exception return? 354 jne work_pending 355 jmp restore_all这里检查了TIF_WORK_MASK,有其他的int值或者异常要返回?这个时候我们跳到workpending,这个workpending我们贴一下代码看看:
593work_pending: 594 testb $_TIF_NEED_RESCHED, %cl 595 jz work_notifysig
596work_resched: 597 call schedule 598 LOCKDEP_SYS_EXIT 599 DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt 600 # setting need_resched or sigpending 601 # between sampling and the iret 602 TRACE_IRQS_OFF 603 movl TI_flags(%ebp), %ecx 604 andl $_TIF_WORK_MASK, %ecx # is there any work to be done other 605 # than syscall tracing? 606 jz restore_allworkpending主要是为了检测重调度标志,下面的work_resched标签是如果进程切换的请求被挂起了,辣么,在内核态开始一次进程调度,选择一个进程运行,当前面的进程要恢复的时候在跳转回到resume_userpace处,也即回到用户态的标签处。[/code]
在restoreall之后,就将中断的程序被恢复了。 下面是一张简要流程图:
相关文章推荐
- 通过分析system_call中断处理过程来深入理解系统调用
- 《Linux操作系统分析》之分析系统调用system_call的处理过程
- Linux系统调用的system_call处理过程分析
- 系统调用system_call的处理过程
- 《Linux内核分析》-- 扒开系统调用的三层皮(下)之system_call中断处理过程 20135311傅冬菁
- 系统调用system_call的处理过程
- Linux系统内核分析实验——system_call中断处理过程
- linux系统下,c++程序,调用system命令失败,分析过程
- 分析system_call中断处理过程
- 通过实验分析system_call中断处理过程
- 20135202闫佳歆--week5 分析system_call中断处理过程--实验及总结
- 《Linux内核分析》第五周:分析system_call中断处理过程
- 实验五:分析system_call中断处理过程
- 分析system_call中断处理过程
- Linux内核分析课程5_system_call中断处理过程
- 分析system_call中断处理过程
- Linux内核分析课程7_execve()函数对应的系统调用处理过程
- 通过分析exevc系统调用处理过程来理解Linux内核如何装载和启动一个可执行程序
- 分析system_call中断处理过程
- 记一次系统稳定性问题的分析处理过程(因CallContext使用不当而造成bug)