系统调用小识
:xujianguo
原创作品转载请注明出处,《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
——————————————————————————————————————————————————————-————
实验目的:
通过gdb调试和汇编嵌入menuOS的文件来进一步熟悉linux系统调用。
实验环境:
实验楼:www.shiyanlou.com。
实验步骤:
1.配置环境,登录实验楼网站。
按照上次实验的基本步骤,结合老师视频所讲,完成相关实验。
cd LinuxKernel
删除menu
然后从github上克隆相应的mengning/menu.git
2.编译menu,生成rootfs。
3.调整main.c,添加上次实验所用的系统调用段。
系统调用:
MenuConfig(“open”,"open or create file", Open);
汇编嵌入也类似。
4.编译和配置调试条件。
按照学过的方式配置环境:
5.启动gdb调试器,分别在sys_open、start_kernel、system_call处设置断点调试。
实验分析:
实验参考资料:
http://codelab.shiyanlou.com/xref/linux-3.18.6/arch/x86/syscalls/syscall_32.tbl
http://codelab.shiyanlou.com/xref/linux-3.18.6/arch/x86/kernel/entry_32.S
重要源码分析:
# system call handler stub
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
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
510 TRACE_IRQS_OFF
511 movl TI_flags(%ebp), %ecx
512 testl $_TIF_ALLWORK_MASK, %ecx # current->work
513 jne syscall_exit_work
514 515restore_all:
516 TRACE_IRQS_IRET
517restore_all_notrace:
..........
530restore_nocheck:
531 RESTORE_REGS 4 # skip orig_eax/error_code
532irq_return://系统调用返回
533 INTERRUPT_RETURN
534.section .fixup,"ax"
535ENTRY(iret_exc)
536 pushl $0 # no error code
537 pushl $do_iret_error
538 jmp error_code
...............
588ENDPROC(system_call)
589
590 # perform work that needs to be done immediately before resumption
591 ALIGN
592 RING0_PTREGS_FRAME # can't unwind into user space anyway
593work_pending:
594 testb $_TIF_NEED_RESCHED, %cl
595 jz work_notifysig
596work_resched:
655 ALIGN
656syscall_exit_work://系统调用结束处理环节
657 testl $_TIF_WORK_SYSCALL_EXIT, %ecx
658 jz work_pending
659 TRACE_IRQS_ON
660 ENABLE_INTERRUPTS(CLBR_ANY) # could let syscall_trace_leave() call
661 # schedule() instead
662 movl %esp, %eax
663 call syscall_trace_leave
664 jmp resume_userspace
665END(syscall_exit_work)
666 CFI_ENDPROC
668 RING0_INT_FRAME # can't unwind into user space anyway
669syscall_fault://默认处理
670 ASM_CLAC
671 GET_THREAD_INFO(%ebp)
672 movl $-EFAULT,PT_EAX(%esp)
673 jmp resume_userspace
674END(syscall_fault)
675
676syscall_badsys://坏调用处理
677 movl $-ENOSYS,%eax
678 jmp syscall_after_call
679END(syscall_badsys)
680
....................
本次实验是基于上次实验的基础进一步分析和发现,通过编译系统调试的方式来分析system_call。实验过程中,发现所使用open函数如果在启动过程中设置断点的话,调试的步骤比较多,只好跳过;在用高蛋白调试的过程中,n(Next)和s(Step)两种命令的区别和联系,值得注意;其中主要是在sys_open 、sys_time和system_call等处设置测试点。由于个人知识储备不足,对系统调用的部分细节没有好的意见,敬请谅解。
下面继续对system_call对应的汇编代码的工作过程进行分析:
系统在用户态和核心态之间的系统调用主要是通过int指令来完成的。start_kernel中trap_init()完成系统调用的初始化。
根据entry_32.S(简略细节如下图)文件的system_call环节可知,
1)ENTRY(system_call) //入口,标志着系统已经由用户态转向Kernel;
2)SAVE_ALL//保存相关寄存器的值,起着保护现场的作用,方便中断返回;
3)GET_THREAD_INFO(%ebp) // 通常ebp寄存器被高级语言编译器用以建造‘堆栈帧’来保存函数或过程的局部变量,此时可以知道将来返回到那个函数的那个部位
4)syscall_call: //真正的系统调用!sys_call_table就是
call *sys_call_table(,%eax,4)
movl %eax,PT_EAX(%esp) //保存系统调用的返回值
5)syscall_exit:
LOCKDEP_SYS_EXIT
DISABLE_INTERRUPTS(CLBR_ANY) //屏蔽其他系统调用
..............
6)restore_all_notrace:
........
je ldt_ss //返回到用户空间,系统调用返回
7)RESTORE_ALL//恢复现场;
8)irq_return//返回调用
总结:
系统调用主要是用户态向核心态进行调用请求,主要以中断的形式完成调度。在核心态下,系统调用通过系统调用号来完成调用匹配,system_call进行调度,sys_xxx来完成相应的系统调用,ret_from_sys_call获取返回结果,进行相关处理和调用后,通过iret返回相关结果。
系统调用实际上是利用了中断的处理过程。而中断的一般处理过程是:当CPU收到中断或者异常的信号时,它会暂停执行当前的程序或任务,通过一定的机制跳转到负责处理这个信号的相关处理程序中,在完成对这个信号的处理后再跳回到刚才被打断的程序或任务中。
在LINUX中,发生中断时,内核就在被中断进程的上下文中,在内核态下执行中断服务例程。但同时会保留所有需要用到的资源,以便中继服务结束时能恢复被中断进程的执行。
参考资料:
1. 老师的讲解视频;
2.http://www.tuicool.com/articles/NNFzuu
3.http://blog.csdn.net/wangzhen199009/article/details/38677075