Linux2.6.32内核笔记(6)系统调用

本文深入解析Linux内核系统调用的工作原理,包括系统调用的概念、内核源码中的实现机制以及如何在内核中定义自己的系统调用。详细介绍了系统调用号的作用、软中断指令(swi)的使用、系统调用表的加载过程,以及在内核中实现自定义系统调用的步骤。通过实际代码示例,展示了如何在用户空间生成系统调用并执行内核空间的自定义函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    摘要:解释了什么是系统调用,分析了系统调用的具体流程,在内核中实现一个自己定义的系统调用。

 

    一、什么是系统调用

    Linux被分为内核空间和用户空间,我们在用户空间使用open,read,write这些操作的时候,会调用内核空间的系统调用函数,因为内核是受到保护的,所以我们的应用程序是以某种方式通知系统,告诉内核自己需要执行一个系统调用,希望系统切换到内核态,这样内核就可以代表应用程序来执行该系统调用了。这种通知内核的机制就叫做“软中断”。在arm当中,是通过“swi”软中断这个指令实现的。

    在这个机制当中,因为我们有很多系统调用,所以每个系统调用都被赋予一个系统调用号,通过这个独一无二的号就可以关联系统调用。当用户空间的进程执行一个系统调用的时候,通过swi这个指令传入一个参数,也就是系统调用号,这个系统调用号就被用来指明到底是要执行哪个系统调用,内核接收到这个调用号的时候,就会去执行。

    arm中,在产生“swi”软中断的时候,进程会通知内核,我们把调用号放在了r7寄存器里面,然后内核去读取r7当中的系统调用号,再根据一个调用table去找到这个号对应的系统调用函数执行,这样就完成了一次系统调用。


   二、分析内核源码中系统调用机制

    主要是以下两个文件实现:

   1.entry-common.S

    当用户空间进行系统调用,产生“swi”中断,这时候entry-common.S当中,在第187行:

    ENTRY(vector_swi)

    sub sp, sp, #S_FRAME_SIZE

    stmia  sp, {r0 - r12}           @Calling r0 - r12

    ARM(   add r8, sp, #S_PC     )

    ARM(   stmdb  r8, {sp, lr}^     )   @ Calling sp, lr

    THUMB( mov r8, sp        )

    THUMB( store_user_sp_lrr8, r10, S_SP  )   @ calling sp, lr

    mrs r8, spsr          @called from non-FIQ mode, so ok.

    str lr, [sp, #S_PC]          @Save calling PC

    str r8, [sp, #S_PSR]     @ Save CPSR

    str r0, [sp, #S_OLD_R0]      @ Save OLD_R0

    zero_fp   

    会对swi进行响应,因为响应之后,我们要切换到内核去执行系统调用,所以这里先对当前用户态执行的一些寄存器和状态进行堆栈操作,保存r0到r12的值,保存PC,CPSR,和OLD_R0。然后下面从第226行到第249行,会根据不同的情况进行具体的操作,我们这里是从r7当中获取系统调用号:

    /*

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

     */

   A710(    ldr ip, [lr, #-4]        @get SWI instruction    )

   A710(    and ip, ip, #0x0f000000      @ check for SWI      )

   A710(    teq ip, #0x0f000000                    )

   A710(    bne .Larm710bug                     )

 

    #elifdefined(CONFIG_ARM_THUMB)

 

    /* Legacy ABI only, possibly thumb mode. */

    tst r8,#PSR_T_BIT           @ this is SPSR fromsave_user_regs

    addne  scno, r7, #__NR_SYSCALL_BASE    @ put OS number in

    ldreq  scno,[lr, #-4]

 

#else

 

    /* Legacy ABI only. */

    ldr scno,[lr, #-4]          @ get SWI instruction

  A710(    and ip, scno, #0x0f000000       @ check for SWI      )

  A710(    teq ip, #0x0f000000                    )

  A710(    bne .Larm710bug                     )

 

#endif

 

    然后在259行,载入了我们的系统调用表:

    adr tbl, sys_call_table      @ load syscall table pointer

    这里sys_call_table在第470行:

    .type  sys_oabi_call_table, #object

    ENTRY(sys_oabi_call_table)

    #include "calls.S"

    这时候指向了我们的calls.S文件。

 

   2.calls.S

   是由各个系统调用函数函数指针组成的表格。

    /*0 */       CALL(sys_restart_syscall)

       CALL(sys_exit)

       CALL(sys_fork_wrapper)

       CALL(sys_read)

       CALL(sys_write)

    /* 5*/       CALL(sys_open)

       CALL(sys_close)

    .........

    /*355 */  CALL(sys_signalfd4)

       CALL(sys_eventfd2)

       CALL(sys_epoll_create1)

       CALL(sys_dup3)

       CALL(sys_pipe2)

    /* 360*/  CALL(sys_inotify_init1)

       CALL(sys_preadv)

       CALL(sys_pwritev)

       CALL(sys_rt_tgsigqueueinfo)

       CALL(sys_perf_event_open)

    这里面都是系统调用函数,我们有了调用号作为偏移地址,有了表格的基地址,那么就可以找到对应的系统调用。

   

    3.unist.h

    这里面也包含了各个系统调用的系统调用号,和calls.S是一致的。

    #define__NR_restart_syscall       (__NR_SYSCALL_BASE+  0)

    #define__NR_exit        (__NR_SYSCALL_BASE+  1)

    #define__NR_fork        (__NR_SYSCALL_BASE+  2)

    #define__NR_read        (__NR_SYSCALL_BASE+  3)

    .. . . . .

    #define__NR_preadv         (__NR_SYSCALL_BASE+361)

    #define__NR_pwritev        (__NR_SYSCALL_BASE+362)

    #define__NR_rt_tgsigqueueinfo     (__NR_SYSCALL_BASE+363)

    #define__NR_perf_event_open       (__NR_SYSCALL_BASE+364)

   

   三、在内核中实现自己的系统调用

    目的:实现系统调用以后,打印一行信息。

    第一步:修改/kernel/printk.c

    在中后空闲位置插入如下系统调用函数实现体:

    void sys_pk(void)

    {

    printk("this is a sys call!\n");  

    }

   

    第二步:修改/arch/arm/kernel/calls.S

    在接近末尾处:

    CALL(sys_perf_event_open)

    CALL(sys_pk)

   

    第三步:修改/arch/arm/include/asm/unistd.h

    也是在末尾处:

    #define__NR_perf_event_open       (__NR_SYSCALL_BASE+364)

    #define __NR_pk      (__NR_SYSCALL_BASE+365)

   

    第四步:编译内核

    #make clean

    #make zImage然后转化成uImage

  

    第五步:编写应用程序syscall.c调用系统调用

    因为实现系统调用要使用swi指令,这里涉及到硬件操作,所以需要内嵌汇编代码,代码如下:

    void pk()

    {

    __asm__ (

    "ldr r7,=365 \n"

    "swi \n"

    :

    :

    :"memory");

    }

 

    void main()

    {

         pk(); 

    }

    这里ldr r7 , =365是把我们刚才指定的系统调用号传进去,然后产生swi软中断,最后这三行,大神解释说是内嵌汇编代码需要真么写,表示输入,输出,和破坏描述。

    编写完了之后执行:

    #arm-linux-gccsyscall.c -o syscall

   

    第六步:下载测试

    将刚才编译好的内核下载到开发板,然后通过nfs起根文件系统,将生成的syscall拷贝到共享目录下,执行:

    # ./syscall

    我的开发板输出信息如下:

    / # ./syscall

    this is a sys call!

    / #

    表示实验成功,这篇帖子就写到这里吧,主要是对系统调用的理解和具体的实现流程,要想真正实现一个系统调用要做的工作其实不止这么简单,如有不正确的地方还请指出,大家共同进步。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值