杨金龙 原创作品转载请注明出处
课程相关–《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
本文主要记录Linux内核分析课程第五周的学习内容和实验分析,详细内容如下。
在老师给出代码的基础上,添加自己实现的简单系统调用的代码,增加两个菜单命令,详细代码不再贴出,主要在main函数中增加命令配置,如下:
int main()
{
...
MenuConfig("makedir","Create a empty folder through C code!",Mkdir);
MenuConfig("makedir-asm","Create a empty folder through asm!",Mkdir_asm);
...
}
重新编译,试运行成功,结果如下:
打开另一个终端,开启gdb调试
$ gdb
(gdb) file linux-3.18.6/vmlinux
Reading symbols from linux-3.18.6/vmlinux...done.
(gdb) target remote:1234
Remote debugging using :1234
0x0000fff0 in ?? ()
(gdb) b sys_mkdir
其中,file命令用于加载linux内核代码的符号表,target用于将gdb调试工具连接到已经启动的程序上。后面设置了mkdir系统调用的断点,然后开始调试。
运行到断点位置,可以看到系统调用函数原型如下:
可以看到,在sys_mkdir系统调用中,实际上是通过调用sys_mkdirat函数进行创建文件夹。
由上次课程可以知道,在程序中执行系统调用,操作系统就会进入中断处理,根据中断向量表进行相应的中断服务处理,因此当程序执行到 int *0x80* 这个指令时就会调转到system_call处执行,这个对应关系是由中断向量设计的时候就固定的。
system_call具体代码如下
489 # system call handler stub
490 # 系统执行"int 0x80"指令时,会跳转到该处执行系统调用的代码
491 # 系统调用就是一个特殊的中断,所以有和中断类似的保存现场和恢复现场的操作
492 ENTRY(system_call)
493 RING0_INT_FRAME # can't unwind into user space anyway
494 ASM_CLAC
495 pushl_cfi %eax # save orig_eax
496
497 SAVE_ALL # 保存现场
498 GET_THREAD_INFO(%ebp)
499 # system call tracing in operation / emulation
500 testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
501 jnz syscall_trace_entry
502 cmpl $(NR_syscalls), %eax
503 jae syscall_badsys
504 syscall_call:
505 # 调用系统调用,%eax中存放的就是系统调用号,如:13号系统调用sys_time
506 call *sys_call_table(,%eax,4)
507 syscall_after_call:
508 movl %eax,PT_EAX(%esp) # store the return value
509 syscall_exit:
510 LOCKDEP_SYS_EXIT
511 DISABLE_INTERRUPTS(CLBR_ANY)# make sure we don't miss an interrupt
512 # setting need_resched or sigpending
513 # between sampling and the iret
514 TRACE_IRQS_OFF
515 movl TI_flags(%ebp), %ecx
516 # 检测当前任务是否需要调用syscall_exit_work
517 testl $_TIF_ALLWORK_MASK, %ecx # current->work
518 # 系统调用结束前可能需要的一些处理放在这里
519 # 如:当前进程接收到的一些信号需要处理;当前系统进程需要发生调度;等等...
520 jne syscall_exit_work
521
522 # 返回到用户态
523 restore_all:
524 TRACE_IRQS_IRET
525 restore_all_notrace:
526 #ifdef CONFIG_X86_ESPFIX32
527 movl PT_EFLAGS(%esp), %eax # mix EFLAGS, SS and CS
528 # Warning: PT_OLDSS(%esp) contains the wrong/random values if we
529 # are returning to the kernel.
530 # See comments in process.c:copy_thread() for details.
531 movb PT_OLDSS(%esp), %ah
532 movb PT_CS(%esp), %al
533 andl $(X86_EFLAGS_VM | (SEGMENT_TI_MASK << 8) | SEGMENT_RPL_MASK), %eax
534 cmpl $((SEGMENT_LDT << 8) | USER_RPL), %eax
535 CFI_REMEMBER_STATE
536 je ldt_ss # returning to user-space with LDT SS
537 #endif
538 restore_nocheck:
539 RESTORE_REGS 4 # skip orig_eax/error_code
540 # 系统调用(中断)结束返回
541 irq_return:
542 # 该宏定义其实就是iret
543 INTERRUPT_RETURN
信号处理与进程调度,syscall_exit_work函数调用work_pending函数:
669 syscall_exit_work:
670 testl $_TIF_WORK_SYSCALL_EXIT, %ecx
671 # 进行信号处理,以及进程调度
672 jz work_pending
work_pending函数调用信号处理函数work_notifysig或者进程调度函数schedule:
603 work_pending:
604 testb $_TIF_NEED_RESCHED, %cl
605 # 处理信号, 如:进程间通讯的信号
606 jz work_notifysig
607 work_resched:
608 # 重新进程调度,进程调度里面会发生进程上下文的切换
609 call schedule
610 LOCKDEP_SYS_EXIT
611 DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
612 # setting need_resched or sigpending
613 # between sampling and the iret
614 TRACE_IRQS_OFF
可以看到,在系统调用的流程中,程序主要做了保存现场,根据%eax中保存的系统调用号执行对应的系统调用内核函数,恢复现场并返回到用户态这几部操作。由其要注意的是,在恢复现场之前,会检查是否有信号需要处理以及是否有进程需要调度,如果有的话,会先做这些事情。关于进程调度的时机,实际上就体现在这里。
根据上面的分析,可以得出系统调用的大概流程如下: