linux驱动开发LDD(三)——系统调用和内核、为内核增加一个系统调用

(一)系统调用

  1. 定义
    系统调用由操作系统实现提供的应用编程接口(Application Programming Interface,API),是应用程序同系统之间的接口,实则,系统调用是软件上由用户态进入内核态的唯一方式,保证用户用户空间以安全的访问内核。

  2. 内核为什么要提供系统调用呢?
    在这里插入图片描述首先说系统调用的调用流程(后文有更详细的系统调用实现流程),以open(2)和fopen(3)举例, 当用户调用fopen(3)时,C库中的实现,实际上是加上一些的封装的,简化用户的调用的open(2)函数,即fopen(3)调用了open(2)函数,open(2)即时内核提供的系统调用。
    系统调用作为内核对外提供的接口,可以间接地使用驱动程序来操纵硬件,如图中红色部分,通过系统调用open(2),最终使用磁盘驱动来操控磁盘(如果是对磁盘写的话,系统并不是立即对磁盘产生写动作,而是由磁盘驱动调度,在一个合适的时间将数据写入,与具体的磁盘驱动算法有关)
     
    从以上的调用层次分析可得:
    a) 系统调用可以避免用户直接操控内核中的函数、驱动程序,使得调用者可以安全地调用内核中的驱动。
    b) 系统调用实现了类似“多态”的机制。例如,open(2)可以open一个文件,也可以open一个外设,open的对象不同,实现的效果是不一样的。简化了调用者的编程,而不用打开每个设备都单独的调用该设备的open函数。
    c) 系统调用是一组定好的编程接口,一般不会再变动,对上层应用来看,只要系统调用接口不变,程序就是兼容的、可移植的。而内核中的函数,随着内核的更新是会变化的,即时不考虑内核安全,出于这个方面的考虑,系统调用也是有存在的必要的,不应该在用户空间直接调用内核函数。所以也就有了内核态的用户态之分。

  3. 系统调用的实现
    实现方式概述:软中断
    用户程序首先将系统调用号填充到某个寄存器(ARM v7 r7寄存器可),可查看内核源码—>执行软中断指令—>软中断异常—>跳转到异常向量表中执行—>从异常向量表跳转到真正的软中断异常处理代码执行—>在软中断异常处理代码中找到系统调用号,根据系统调用号调用内核中对应的函数,并把该函数的执行结果返回给用户空间
     
    open(2)函数的实现中(libc.so中),会将系统调用号(open的系统调用号是5)存到对应的寄存器(arm r7),然后调用软中断指令触发软中断(mips中有syscall指令,作用类似),软中断通过系统调用号5,调用内核中sys_open函数,sys_open函数,根据打开文件或者设备的类型不同,去匹配对应的open函数(类似多态机制),如果没有找到该种类型对应的open,会执行一段默认的open代码。
     
    为什么非要走软中断呢?系统调用去调用内核函数不就好了?原因是在于,内核函数的执行需要在内核态,而由用户态切换到内核态,实际上就是由软中断所实现的
     
    内核源码路径如下:
    系统调用号:arch/arm/include/asm/unistd.h
    系统调用表 位于calls.S
    软中断处理代码:entry-common.S

    vector_swi: 
        获取系统调用号--->r7
        ...
        adr	tbl, sys_call_table //系统调用表  位于calls.S  
       
        r7 < 系统调用号的最大值
        
        sys_call_table[r7]
    

(二)为内核增加一个系统调用

  1. 打开内核源码路径下的arch/arm/kernel/sys_arm.c文件,这个文件中实现了一下linux/arm平台下的一些非标准调用顺序的系统调用,这个文件一定是参加内核编译的(可查看Makefile文件确认,已确认),在这个文件中增加一个函数用来作为系统调用。
  2. 仿照内核源码系统调用的格式,编写以下代码
    asmlinkage int sys_add(int x, int y)
    {
    	printk("<1>" "enter %s \n", __func__);
    	return x+y;
    }
    
  3. 更新系统调用表vi arch/arm/kernel/calls.S
    在这里插入图片描述可以看到,在我没有更新系统调用表时,系统调用号最大为377,对应函数为 sys_process_vm_writev,我在后边加了一个sys_add,所以我添加的这个系统调用的调用号为378
    系统调用表的作用是,当有系统调用到来时,来的是375号,就调用sys_setns,如果是378号,就调用sys_add函数,以此类推
  4. 增加新的系统调用号 vi arch/arm/include/asm/unistd.h
    在这里插入图片描述找到最后一个系统调用377号,在其后类比添加一行
    #define __NR_add (__NR_SYSCALL_BASE+378)
    这个不改也可以通过syscall(378, 1, 2)来调用sys_add(1,2),推测这个文件中的基址+偏移应该是针对共享库的,通过基址+偏移得到各个系统调用在库中的地址,再通过这个地址去访问具体的系统调用代码实现(包括存储中断号,执行软中断指令等)
  5. 重新编译内核 让开发板使用新内核
  6. 编写测试程序
    由于我添加的函数在内核中,正常的系统调用如open在调用过程中,open是在libc.so库中的,然后通过软中断去调用内核函数sys_open,而我只实现了sys_add在内核,libc.so中并没有对应的add函数,所以在我的UC程序中不能直接调用add函数,无法通过用户态调用add进而去调用内核函数sys_add,所以我通过另一个方式来调用内核函数sys_add-------syscall(2)
    #include <stdio.h>
    
    int main(void){
    	int res = syscall(378, 10, 20);//sys_add(10, 20)
    	printf("res=%d\n", res);//30
    	return 0;
    }
    
    交叉编译,到下位机执行(注意下位机一定要使用新内核),若是报错缺少库文件,则去上位机交叉编译工具目录下拷贝到下位机/lib即可
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值