系统调用过程

杨金龙 原创作品转载请注明出处

课程相关–《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);
    ...
}

  重新编译,试运行成功,结果如下:

跟踪mkdir系统调用函数

  打开另一个终端,开启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中保存的系统调用号执行对应的系统调用内核函数,恢复现场并返回到用户态这几部操作。由其要注意的是,在恢复现场之前,会检查是否有信号需要处理以及是否有进程需要调度,如果有的话,会先做这些事情。关于进程调度的时机,实际上就体现在这里。

系统调用流程图

  根据上面的分析,可以得出系统调用的大概流程如下:

你可能感兴趣的:(Linux代码学习,操作系统,嵌入式汇编)