• Linux 系统调用过程详细分析


    内核版本:Linux-4.19

    操作系统通过系统调用为运行于其上的进程提供服务。

    那么,在应用程序内,调用一个系统调用的流程是怎样的呢?

    我们以一个假设的系统调用 xyz() 为例,介绍一次系统调用的所有环节。

    如上图所示,系统调用执行的流程如下:

    1. 应用程序代码调用 xyz(),该函数是一个包装系统调用的库函数;
    2. 库函数 xyz() 负责准备向内核传递的参数,并触发软中断以切换到内核;
    3. CPU 被软中断打断后,执行中断处理函数,即系统调用处理函数(system_call);
    4. 系统调用处理函数调用系统调用服务例程(sys_xyz ),真正开始处理该系统调用。
    

    系统调用的实现来自于Glibc,几乎所有 C 程序都要调用 Glibc 的动态链接库 libc.so 中的库函数。这些库函数的源码是不可见的,可通过 objdump 或 gdb 等工具对代码进行汇编反编译,摸清大体的过程。

    我们可不必太过纠结,知道原理就好。

    下面继续分析在内核中的实现过程。

    Pure EABI user space always put syscall number into scno (r7).

    当从用户态转为内核态时,系统会将 syscall number 存储在寄存器 R7 中,利用 R7 来传参。

    在 entry-header.S 文件中,有如下代码:

    scno	.req	r7		@ syscall number
    tbl	.req	r8		@ syscall table pointer
    why	.req	r8		@ Linux syscall (!= 0)
    tsk	.req	r9		@ current thread_info
    

    类似于给寄存器起了个“别名”。

    最后通过

    invoke_syscall tbl, scno, r10, __ret_fast_syscall

    代码成功调用 syscall table 中的服务程序。

    invoke_syscall 定义如下:

    	.macro	invoke_syscall, table, nr, tmp, ret, reload=0
    #ifdef CONFIG_CPU_SPECTRE
    	mov		mp, 
    r
    	cmp		mp, #NR_syscalls		@ check upper syscall limit
    	movcs		mp, #0
    	csdb
    	badr	lr, 
    et			@ return address
    	.if	
    eload
    	add	r1, sp, #S_R0 + S_OFF		@ pointer to regs
    	ldmccia	r1, {r0 - r6}			@ reload r0-r6
    	stmccia	sp, {r4, r5}			@ update stack arguments
    	.endif
    	ldrcc	pc, [	able, 	mp, lsl #2]	@ call sys_* routine
    #else
    	cmp	
    r, #NR_syscalls		@ check upper syscall limit
    	badr	lr, 
    et			@ return address
    	.if	
    eload
    	add	r1, sp, #S_R0 + S_OFF		@ pointer to regs
    	ldmccia	r1, {r0 - r6}			@ reload r0-r6
    	stmccia	sp, {r4, r5}			@ update stack arguments
    	.endif
    	ldrcc	pc, [	able, 
    r, lsl #2]	@ call sys_* routine
    #endif
    	.endm
    

    回看

    invoke_syscall tbl, scno, r10, __ret_fast_syscall

    这段代码。tbl 是指向的何处呢?

    接下来,就简单的介绍一下 syscall table 这个表是怎样形成的。

    查看代码我们发现,tbl 表示 sys_call_table 的地址:

    adr tbl, sys_call_table @ load syscall table pointer

    entry-common.S 中有这样一段代码:

    	syscall_table_start sys_call_table
    	
    #define COMPAT(nr, native, compat) syscall nr, native
    #ifdef CONFIG_AEABI
    #include <calls-eabi.S>
    #else
    #include <calls-oabi.S>
    #endif
    #undef COMPAT
    
    	syscall_table_end sys_call_table
    

    calls-eabi.S 文件内容如下:

    NATIVE(0, sys_restart_syscall)
    NATIVE(1, sys_exit)
    NATIVE(2, sys_fork)
    NATIVE(3, sys_read)
    NATIVE(4, sys_write)
    NATIVE(5, sys_open)
    NATIVE(6, sys_close)
    NATIVE(8, sys_creat)
    NATIVE(9, sys_link)
    NATIVE(10, sys_unlink)
    NATIVE(11, sys_execve)
    NATIVE(12, sys_chdir)
    NATIVE(14, sys_mknod)
    NATIVE(15, sys_chmod)
    NATIVE(16, sys_lchown16)
    NATIVE(19, sys_lseek)
    NATIVE(20, sys_getpid)
        ...
    

    以上代码中宏的定义如下:

        /* 定义 sys_call_table,并将 __sys_nr 清 0 */
    	.macro	syscall_table_start, sym
    	.equ	__sys_nr, 0
    	.type	sym, #object
    ENTRY(sym)
    	.endm
    
        /* 检查序号错误,并利用 sys_ni_syscall 填充缺少的序号 */
    	.macro	syscall, nr, func
    	.ifgt	__sys_nr - 
    r
    	.error	"Duplicated/unorded system call entry"
    	.endif
    	.rept	
    r - __sys_nr
    	.long	sys_ni_syscall
    	.endr
    	.long	func
    	.equ	__sys_nr, 
    r + 1
    	.endm
    
        /* 检查序号是否超过了 __NR_syscalls,如果不足的话,用 sys_ni_syscall 来填充 */
    	.macro	syscall_table_end, sym
    	.ifgt	__sys_nr - __NR_syscalls
    	.error	"System call table too big"
    	.endif
    	.rept	__NR_syscalls - __sys_nr
    	.long	sys_ni_syscall
    	.endr
    	.size	sym, . - sym
    	.endm
    
        /* NATIVE 宏定义 */
    #define NATIVE(nr, func) syscall nr, func
    

    最后会通过SWI中断号调用到相关系统函数,如 sys_open,而 sys_open 的声明形式则如下所示, 1、2、3 由函数的形参所定,如在 source insight 中搜索到 sys_open 的函数定义,可搜索关键词 “SYSCALL_DEFINE3(open”。

    #define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
    #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
    #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
    #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
    #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
    #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
    

    到这里应该分析完了系统调用的大概过程,感谢大家花费宝贵的时间浏览,如果有什么问题欢迎探讨,后期会进行修改和补充!

    部分参考于:www.cnblogs.com/fasionchan/p/9431784.html

  • 相关阅读:
    最小生成数kruskal算法和prim算法
    图的表示及遍历
    mysql忘记root用户密码重置密码的方式
    dwr2.0版本的demo
    web.xml中不同版本的servlet头以及版本控制
    初学jboss
    Filter学习总结,顺便提及点servlet3.0异步filter和异步监听
    监听器
    问题发现和解决
    linux学习
  • 原文地址:https://www.cnblogs.com/GyForever1004/p/10414013.html
Copyright © 2020-2023  润新知