• 20135337——Linux实践二:模块


    一、编译&生成&测试&删除

    1.编写模块代码,查看如下

    gedit 1.c(编写)
    cat 1.c(查看)
    

    MODULE_AUTHOR("Z")
    MODULE_DESCRIPTION(模块用途的简单描述);
    MODULE_VERSION(模块的版本字符串);
    MODULE_ALIAS(模块的别名);
    

    2.查看版本信息

    3.写Makefile并查看

    obj-m :这个变量是指定你要声称哪些模块模块的格式为 obj-m := <模块名>.o
    modules-objs :这个变量是说明声称模块modules需要的目标文件 格式要求   <模块名>-objs := <目标文件>
           切记:模块的名字不能取与目标文件相同的名字。如在这里模块名不能取成 mymod;
    KDIR   :这是我们正在运行的操作系统内核编译目录。也就是编译模块需要的环境
    M=     :指定我们源文件的位置
    PWD   :这是当前工作路径$(shell   )是make的一个内置函数。用来执行shell命令。
    

    4.make自动生成很多文件

    5.加载、测试、删除模块

    dmesg(查看内核信息)
    

    • 加载、测试模块

    • 卸载模块

    • 其他

    dmesg | tail -12  查看内核输出信息;tail -12 显示最后12条。
    
    modinfo查看模块信息:
        sudo modinfo 1.ko
    
    depmod分析可加载模块的依赖性,并生成modules.dep文件和映射文件
    modprobe Linux内核添加删除模块
    

    二、系统调用(增加223号)

    1.查看本机系统调用表地址

    查看系统调用号


    2.源码(详细代码略)、测试程序

    测试程序

    #include <stdio.h>
    #include <stdlib.h>
    int main()
    {
        unsigned long x = 0;
        x = syscall(223);      
        printf("Hello, %ld
    ", x);
        return 0;
    }
    

    分析

    代码

    static int clear_cr0(void)  //使cr0寄存器的第17位设置为0(即是内核空间可写)
    {
    unsigned int cr0 = 0;
    unsigned int ret;
    asm volatile ("movl %%cr0, %%eax":"=a"(cr0));  //将cr0寄存器的值移动到eax的寄存器中,同时输出到cr0变量中
    ret = cr0;
    cr0 &= 0xfffeffff;  //将cr0变量的值中的第17位清0,一会将修改后的值写入cr0寄存器
    asm volatile ("movl %%eax, %%cr0": :"a"(cr0));  //将cr0变量的值做为输入,输入到寄存器eax中,同时移动到寄存器cr0中
    return ret;
    }
    
    static int __init call_init(void)
    {
    sys_call_table_my = (unsigned long*)(SYS_CALL_TABLE_ADDRESS);
    printk("call_init.......
    ");
    anything_saved = (int (*)(void))(sys_call_table_my[NUM]);  //保存系统调用表中的NUM位置上的系统调用
    orig_cr0 = clear_cr0();  //使内核地址空间可写
    sys_call_table_my[NUM] =(unsigned long) &sys_mycall;  //用自己的系统调用替换NUM位置上的系统调用
    setback_cr0(orig_cr0);  //使内核地址空间不可写
    return 0;
    }
    

    asm volatile("movl %%eax,%%cr0"::"a"(val));
    举例说明:volatile int i=10; int j = i; ... int k = i; volatile 告诉编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i的地址读取数据放在k中。 volatile 影响编译器编译的结果,指出,volatile 变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错,(VC++ 在产生release版可执行码时会进行编译优化,加volatile关键字的变量有关的运算,将不进行编译优化。)。其中编译器编译优化是:由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在k中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问,不会出错。
    

    模块加载函数static int __init call_init(void):先保存系统调用表中223位置的系统调用,然后用static int clear_cr0(void)清零cr0的第十六        位,使内核空间可写,在内核空间可写的情况下用自己的系统调用asmlinkage long sys_mycall(void)替换223位置的系统调用,在完成替换后重新将cr0第十六位置位static void setback_cr0(int val)。
    模块卸载函数static void __exit call_exit(void):用static int clear_cr0(void)清零cr0的第十六位,使内核空间可写,将保存的系统调用anything_saved恢复,完成后重新将cr0第十六位置位static void setback_cr0(int val)。
    

    3.结果

    测试代码为52.c
    编译gcc 52.c -o b
    运行 ./b
    


    三、增加模块,功能:存在某指定文件,则输出“open success”,否则输出“open error”

        asmlinkage long sys_mycall(void)  //定义自己的系统调用
        {
        struct file *fp;
    	    fp = filp_open("/home/zhu/mm", O_RDWR, 0644);  //打开指定的文件
    	    if (IS_ERR(fp)) //如果失败,输出“error”
        {
    		printk("open error!!!!!!!!!!!!!!!!!!!!!!!!!
    ");
    		return 0;
    	    }
    	    else //成功,输出“success”
    	    {
    	printk("open success!!!!!!!!!!!!!!!!!!!!!!!
    ");
    	    }
    return 0;
        }
    

    make
    sudo insmod XXX.ko
    ./test.c
    

    四、asmlinkage

    0.函数定义前加宏asmlinkage ,表示这些函数通过堆栈而不是通过寄存器传递参数。

    1.gcc编译器在汇编过程中调用c语言函数时传递参数有两种方法:一种是通过堆栈,另一种是通过寄存器。缺省时采用寄存器,假如你要在你的汇编过程中调用c语言函数,并且想通过堆栈传递参数,你定义的c函数时要在函数前加上宏asmlinkage。

    1.采用asmlinkage,绝对不是因为不同架构可以通用户,而是因为用户态寄存器在系统调用进入内核态时,会把用户态的寄存器全部压栈,通过合理的构造。正好满足用户态通过寄存器传递参数,内核态通过栈取参数的标准要求。这是很巧妙的安排,是高效的体现!其实还可以发现,内核只有在系统调用时才用asmlinkage,其它函数都没有。这是有意而为之的。

    2.用户调用syscall的时候,参数都是通过寄存器传进来的。中间内核由把所有的参数压栈了, 所以这个asmlinkage可以通过到gcc,恰好可以用正确的调用方式取到参数。(系统调用做的工作除了函数调用,其本质就是现场保护、压栈之类的操作)

    3.内核前面的那些统一处理很重要,这样后端真正的的syscall 实现函数就可以得到统一的调用方式了,而不是之间面对不同的abi。确实比较方便了。不然每个syscall函数里面都要自己去处理不同abi,多很多重复代码。

    4.也可以考虑在这个统一的处理的时候,把参数全部按照一定的规范放到寄存器。 但这个方法不能在所有的cpu架构上面都做的到。

    5.“统一”要比这个“寄存器传参”要重要。

    6.从用户切换到内核,要做大量的处理。相比较其他部分,参数的开销不算什么。

  • 相关阅读:
    echarts圆套圆
    两个对象深度比较,借鉴,记录
    js异步加载的方式
    elementUI使用el-card高度自适应
    如何在页面上实现一个圆形的可点击区域
    清除浮动
    水平垂直居中的几种方式
    BFC原理
    正则表达式
    Vue项目中难点问题
  • 原文地址:https://www.cnblogs.com/zzzz5/p/5444273.html
Copyright © 2020-2023  润新知