《Linux操作系统分析》之分析系统调用system_call的处理过程

本篇文章通过将上篇文章中使用库函数API和C代码中嵌入汇编代码两种方式设计的系统调用添加到系统menu中,来说明在Linux系统中,系统调用的实现的时机以及具体运行,以及一般的中断处理过程。

相关知识

首先关于这篇文章会介绍一些用到的知识。

一、将系统调用号与相应的服务例程关联起来,内核利用了一个系统调用分派表(dispatch table)。这个表存放在sys_call_table数组中,有NR_syscalls个表项:第n个表项包含系统调用号为n的服务例程的地址。

二、因为这篇文章和上篇文章是上下文关系,所以其他相关知识请参考上篇文章:《Linux操作系统分析》之使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用。


分析过程

首先我们将自己写的系统调用fetpid和getpid-asm放进Menu系统的中。需要进行的操作有:

0)更新menuOS到最新版本

1)在test.c中的main函数中添加MenuConfig

2)添加对应的getpid函数和getpid-asm函数

3)make rootfs

如果在上面的操作中出现了什么问题,可以去https://github.com/mengning查询代码等。

第零步

rm menu -rf 强制删除原menu文件
git clone http://github.com/mengning/menu.git 从github中克隆

第一二步结果如下图:

《Linux操作系统分析》之分析系统调用system_call的处理过程_第1张图片

对其进行编译运行,即第三步

《Linux操作系统分析》之分析系统调用system_call的处理过程_第2张图片

运行结果如上图。

设置断点进行跟踪:

《Linux操作系统分析》之分析系统调用system_call的处理过程_第3张图片

我们发现断点进入到entry_32.s中。但是在这里没有停止,而是直接运行结束了。

在进入entry_32.s之前,运行的过程如下:

1)main.c中start_kernel函数:trap_init()
2)set_system_trap_gate(SYSCALL_VECTOR,&system_call)
SYSCALL_VECTOR:系统调用的中断向量
&system_call:汇编代码入口
3)一执行int 0x80,系统直接跳转到system_call。
然后执行到了entry_32.s,
我们将entry_32.s文件打开,看一下里面的内容。

# system call handler stub
ENTRY(system_call)            #系统调用处理入口(内核态)
    RING0_INT_FRAME         # can't unwind into user space anyway
    ASM_CLAC
    pushl_cfi %eax          # save orig_eax #保存eax,也就是调用号 
    SAVE_ALL              # 保存所有会被覆盖的寄存器信息 
    GET_THREAD_INFO(%ebp) # 当前进程的thread_info结构的地址,获取当前进程的信息。</span> 
                    # 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_badsys,小于则跳转到相应系统调用号所对应的服务例程当中。
syscall_call:
    call *sys_call_table(,%eax,4) # 在系统调用表中的调用相应的服务例程,eax为调用号。sys_call_table表的表项占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<span style="white-space:pre">	</span>#如果有其他的任务,则跳转到syscall_exit_work。没有就执行restore_all等恢复现场的动作。
syscall_exit_work:
    testl $_TIF_WORK_SYSCALL_EXIT, %ecx
    jz work_pending     # 测试是否退出前还有工作要处理,如果有的话跳转到work_pending  
    TRACE_IRQS_ON       # 开启系统中断跟踪 
    ENABLE_INTERRUPTS(CLBR_ANY)  # could let syscall_trace_leave() call
    # 允许中断   # schedule() instead
    movl %esp, %eax
    call syscall_trace_leave
    jmp resume_userspace   # 恢复用户空间 
END(syscall_exit_work)

work_pending:
    testb $_TIF_NEED_RESCHED, %cl  # 是否有需要继续调度的相关信号 
    jz work_notifysig     # 跳转到处理信号相关的代码处 
work_resched:
    call schedule     # 时间调度, 进程调度的时机在这里处理 
    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<span style="white-space:pre">	</span>#系统中断跟踪关闭
    movl TI_flags(%ebp), %ecx
    andl $_TIF_WORK_MASK, %ecx    # is there any work to be done other  是否还有其他工作要处理
                    # than syscall tracing?
    jz restore_all      #如果没有的话就恢复中断上下文,也就是恢复进入之前保存的寄存器相关内容
    testb $_TIF_NEED_RESCHED, %cl
    jnz work_resched

work_notifysig:        # deal with pending signals and 
                       # notify-resume requests                    
#ifdef CONFIG_VM86
    testl $X86_EFLAGS_VM, PT_EFLAGS(%esp)
    movl %esp, %eax
    jne work_notifysig_v86        # returning to kernel-space or
                                  # vm86-space

restore_all:
    TRACE_IRQS_IRET          # 恢复中断跟踪 
restore_all_notrace:
#ifdef CONFIG_X86_ESPFIX32
    movl PT_EFLAGS(%esp), %eax  # mix EFLAGS, SS and CS
    # Warning: PT_OLDSS(%esp) contains the wrong/random values if we
    # are returning to the kernel.
    # See comments in process.c:copy_thread() for details.
    movb PT_OLDSS(%esp), %ah
    movb PT_CS(%esp), %al
    andl $(X86_EFLAGS_VM | (SEGMENT_TI_MASK << 8) | SEGMENT_RPL_MASK), %eax
    cmpl $((SEGMENT_LDT << 8) | USER_RPL), %eax
    CFI_REMEMBER_STATE
    je ldt_ss           # returning to user-space with LDT SS
#endif
restore_nocheck:
    RESTORE_REGS 4          # skip orig_eax/error_code
irq_return:
    INTERRUPT_RETURN
在上面代码中我们知道无论是中断返回(ret_from_intr) ,还是系统调用返回,都使用了 work_pending 和resume_userspace。

对于宏SAVE_ALL来说,会把将寄存器的值压入堆栈当中,压入顺序对应struct pt_regs ,出栈时亦然。struct pt_regs可以在ptrace.h中查看。
系统调用的流程图如下:

《Linux操作系统分析》之分析系统调用system_call的处理过程_第4张图片

总结:

1)系统调用的初始化的顺序是:start_kernel()->trap_init()->set_system_trap_gates(SYSCALL_VECTOR,&system_call);

2)用户态到内核态通过0x80进行中断,在内核初始化期间调用trap_init(),用函数set_system_trap_gates(),建立了对应于向量128的中断描述符表表项,从而进入相应的中断服务。

3)system_call()函数首先将系统调用号或中断处理程序需要用到的所有的CPU寄存器保存到相应的栈中。然后进行服务的处理。当系统调用服务例程结束时,system_call()函数从eax获得它的返回值。然后进行一系列的检查,最后恢复用户态进程的执行。

如果想看详细的讲解,请大家参考:深入理解linux内核的第十章系统调用。

备注:

杨峻鹏 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 

你可能感兴趣的:(linux)