• linux内核模块


    • 一个简单的驱动

      模块的使用能使linux内核便于裁剪,根据不同的应用需求得到一个最小的内核,同时调试内核驱动也更为方便,比如如果调试i2c驱动,如果不采用模块的方式,那么每次修改i2c驱动就得编译整个内核,对于编译调试极其耗时,使用模块,一个简单的insmod就将模块加载进了内核,如果觉得不合适,需要调试,只需要rmmod就可以将模块卸载。

      一个简单的驱动模块:

     1 #include <linux/init.h>
     2 #include <linux/module.h>
     3 MODULE_LICENSE("Dual BSD/GPL");
     4 
     5 static int __init test_init(void)
     6 {
     7     printk(KERN_ALERT"test modules init
    ");
     8     return 0;
     9 }
    10 
    11 static void __exit test_exit(void)
    12 {
    13     printk(KERN_ALERT"test modules exit
    ");
    14 }
    15 
    16 
    17 module_init(test_init);
    18 module_exit(test_exit);

       这个驱动什么都没干,就打印了几句话,但是具备了驱动程序最基本的几个要素:

    1.  驱动初始化函数 XXX_init(void),使用宏module_init(test_init);告诉内核这是模块的初始化函数入口。
    2.  驱动退出函数XXX_exot(void),使用宏module_exit(test_exit);告诉内核这是模块的退出函数入口。
    3.    驱动模块遵循的协议 MODULE_LICENSE("Dual BSD/GPL")

      注意到初始化函数和退出函数分别被__init,__exit修限定,其实不用它们来限定2个函数也是可以的,那么用不用这2个东西限定有什么区别呢?首先看它们到底是2个什么东西:

    1 #define __init        __section(.init.text) __cold notrace
    2 #define __exit          __section(.exit.text) __exitused __cold notrace

      是2个宏,其中__section也是个宏

    1 # define __section(S) __attribute__ ((__section__(#S)))

      __attribute__是针对gcc的特有关键字,__attribute__ 可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute ),我见的比较多的是在设置数据结构的对齐属性上:

    1 struct S {
    2 
    3 short b[3];
    4 
    5 } __attribute__ ((aligned (8)));

      在这里设置为.init.text是指把这个函数放到特殊的.init.text段,看过汇编代码就可能有映象.text段,也就是代码段,函数都是放这个段的。.init.text段的特性是等模块加载完了,这个段将被释放,因为就好比我们写一个单片机程序一样,一般都会有个init函数,它完了就是个死循环,驱动实际在运行过程中XXX_init函数就没有什么作用了,因此就可以把XXX_init函数加载到内存的那段释放掉,腾出空间来给别人用。

      __exit就是在卸载模块完成后,将它标识的函数所占用的内存释放。

      除了__init,__exit,还有__initdata,__exitdata。作用基本类似,我原本猜想带data的应该是修饰仅在初始化函数和退出函数里面用到的全局变量,比如一个数组之类的东西,但是我试了用__initdata修饰函数好像编译并不报错,暂时不深究。

      MODULE_LICENSE("Dual BSD/GPL"),少了它不影响编译,但是加载模块的时候会警告提示这个模块污染了内核:

      [22321.826339] test: module license 'unspecified' taints kernel.
      [22321.826349] Disabling lock debugging due to kernel taint
      [22321.826759] test modules init

    • 编译并加载,卸载这个驱动

      编译模块的makefile

     1 ifneq ($(KERNELRELEASE), )
     2     obj-m := test.o book.o
     3 else
     4      KERNELDIR ?= /lib/modules/$(shell uname -r)/build
     5     PWD := $(shell pwd)
     6 defualt:    
     7     $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
     8 endif
     9 
    10 clean:
    11         rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order  Module.symvers
    12 
    13 .PHONY:clean

      加载模块:  

    1 sudo insmod test.ko

      为了图方便,使用一个简单的shell脚本监视dmesg的输出,脚本如下:

    1 #!/bin/bash
    2 
    3 set -o nounset                              # Treat unset variables as an error
    4 
    5 while true
    6 do
    7     dmesg -c 
    8     sleep 1
    9 done

       卸载模块:

     1 sudo rmmod test 

      卸载模块不需要后面的.ko。

    • 指定和导出模块的参数

      参数在驱动里面必须是一个全局变量,一般加static限定。

      声明参数,使用宏:

    1 module_param(<参数名>, <参数类型>, <参数读写权限>); 

      参数类型支持:byte、short、ushort、int、uint、long、ulong、charp(字符指针)、bool 或 invbool(布尔的反)

       例如:

    1 static int num = 5;
    2 
    3 module_param(num, int, S_IRUGO);
    4 
    5 static int __init test_init(void)
    6 {
    7     printk(KERN_ALERT"num:%d
    ",num);
    8     return 0;
    9 }

       如果在insmod的时候,指定num的值:

     1 insmod test.ko num=10

      那么输出:num:10

      需要注意的是,参数在声明时要给一个默认值,因为如果insmod指定参数值,那么它就使用这个默认值。

      一个参数(在驱动里面的一个全局变量)怎么会有读写权限?加载这个模块后,进入/sys/module目录,会看到一个test的目录,这个目录记录了这个test模块的一些信息,与未加模块参数的时候相比,它多出了一个parameters的目录,进如这个目录,发现会有个与模块参数同名的num文件,cat它里面的内容就是它的默认值5,再查看这个num文件的权限:

      -r--r--r-- 1 root root 4096 5月   3 21:30 num
      和我在代码中指定的一样,原来权限就是指这个文件的权限,参数与之对应的一个文件,也就是说参数的默认值是可以被更改的,只要权限拿到了。

      使用 cat /proc/kallsyms | grep "[test]"查看与test.ko这个模块相关的符号:

    1   00000000 t test_exit    [test]
    2   00000000 r __param_num    [test]
    3   00000000 r __param_str_num    [test]
    4   00000000 d num    [test]
    5   00000000 d __this_module    [test]
    6   00000000 t cleanup_module    [test]


      可以看到2个我们预料中的符号,num和test_exit,如果我们不使用__init限定test_init,那么可以看到test_init也在里面,也就证明了__init的作用是在调用初始化函数后释放了与之对应的内存:

    1 00000000 t test_init    [test]
    2 00000000 t test_exit    [test]
    3 00000000 r __param_num    [test]
    4 00000000 r __param_str_num    [test]
    5 00000000 d num    [test]
    6 00000000 d __this_module    [test]
    7 00000000 t cleanup_module    [test]
    8 00000000 t init_module    [test]

      使用module_param是指外面可以传一个值进来,如果要这个值对外可见,我们明显不能采用去掉static限定的办法,而是使用EXPORT_SYMBOL(num);这个宏是指将模块内的全局变量num对外可见,它不仅仅可以用于变量也可以对函数起作用。

    模块A:

     1 #include <linux/init.h>
     2 #include <linux/module.h>
     3 MODULE_LICENSE("Dual BSD/GPL");
     4 
     5 static int num = 5;
     6 module_param(num, int, S_IRUGO);
     7 
     8 static int __init test_init(void)
     9 {
    10     printk(KERN_ALERT"test modules init
    ");
    11     return 0;
    12 }
    13 
    14 static void test_saymyname(void)
    15 {
    16     printk(KERN_ALERT"you call me
    ");
    17 }
    18 static void __exit test_exit(void)
    19 {
    20     printk(KERN_ALERT"test modules exit
    ");
    21 }
    22 
    23 
    24 module_init(test_init);
    25 module_exit(test_exit);
    26 EXPORT_SYMBOL(test_saymyname);

    模块B调用模块A的 test_saymyname

     1 #include <linux/init.h>
     2 #include <linux/module.h>
     3 MODULE_LICENSE("Dual BSD/GPL");
     4 
     5 extern void test_saymyname(void);
     6 
     7 static int __init book_init(void)
     8 {
     9     test_saymyname();
    10     return 0;
    11 }
    12 
    13 static void __exit book_exit(void)
    14 {
    15     printk(KERN_ALERT"book modules exit
    ");
    16 }
    17 
    18 module_init(book_init);
    19 module_exit(book_exit);

    分别insmod test.ko和insmod book.ko,再卸载模块时,如果先卸载test.ko会发现提示test.ko里面有函数被book.ko调用,无法卸载,只有先卸载book.ko才行。

  • 相关阅读:
    【原理】【重点】异步回调的一些实现策略
    上下文传递
    洋码头全异步服务框架
    秒杀系统架构优化思路
    从urllib2的内存泄露看python的GC python引用计数 对象的引用数 循环引用
    jvisualvm All-in-One Java Troubleshooting Tool
    小心踩雷,一次Java内存泄漏排查实战
    django 请求处理流程 链路追踪
    存储过程
    Dijkstra's algorithm
  • 原文地址:https://www.cnblogs.com/thammer/p/4474531.html
Copyright © 2020-2023  润新知