美文网首页我爱编程
Linux 内核模块

Linux 内核模块

作者: 守拙圆 | 来源:发表于2018-04-11 15:37 被阅读126次

    1 Linux 内核模块简介

    Linux 内核是一个十分庞大的系统,如何能够为其瘦身,订制适合自己应用场景的 linux 系统,这就需要了解模块技术。

    linux 内核与各模块的关系分为两种:

    • 把所有需要的功能都直接编译进入内核,但这会带来两个问题,一是生成的内核很大,从而导致内核占用内存也很大;二是如果内核需要新增或者删除功能的话,必须要重新编译内核。
    • 编出的内核本身不需要包含所有功能,而这些功能在需要使用时动态加载到内核,如此动态加载的功能单元就叫做模块。

    Linux 提供的这种机制被称为模块,模块具有这样的特点:

    • 模块本身不被编译进入内核,从而控制了内核的大小。
    • 模块一旦被加载,它就和内核中的其它部分完全一样。

    2 模块样例

    以下是一个样例模块 hello.ko 样例:

    /*
     *a simple kernel module: hello
     *
     *Licensed under GPLv2 or later
     */
    
    #include <linux/init.h>
    #include <linux/module.h>
    
    static int __init hello_init(void)
    {
        printk(KERN_INFO "Hello World enter.\n");
        return 0;
    }
    
    module_init(hello_init);
    
    static void __exit hello_exit(void)
    {
        printk(KERN_INFO "Hello world exit.\n");
    }
    
    module_exit(hello_exit);
    
    MODULE_AUTHOR("penghuster <penghuster@163.com>");
    MODULE_LICENSE("GPL v2");
    MODULE_DESCRIPTION("A simple Hello World Module.");
    MODULE_ALIAS("a simplest module.");
    

    编译此模块的方法可以分为两种方式:

    2.1 与内核编译同步捆绑进行

    在内核的相关功能单元(如 drivers)下创建 hello 文件夹,然后根据内核Kconfig 和 Makefile的编写规则,编写 hello 模块的 Kconfig 和 Makefile 文件。
    drivers/hello/Kconfig:

    #
    # Hello driver configuration 
    #
    
    #menu "HELLO driver"
    comment "hello driver"
    
    config CONFIG_HELLO
        bool "support HELLO"
    
    #endmenu
    

    drivers/hello/Makefile:

    #drivers/test/test/Makefile
    #
    #Makefile for the TEST CPU
    #
    
    obj-$(CONFIG_HELLO) += hello.o
    

    另外在drivers/Kconfig文件中添加行 source "drivers/hello/Kconfig"
    在drivers/Makefile文件中添加 obj-y += hello/

    注意:hello/Makefile也可直接写为 obj-m += hello.oobj-y += hello.o 从而可以省略 Kconfig的相关工作。Kconfig是为了 make menuconfig 配置而添加。

    2.2 独立编译模块

    需要进行独立模块编译前,需要确认 /lib/modules/目录下是否已经安装你需要进行模块编译的内核版本。如果没有,请安装对应版本的 linux-image 和 Linux-image-extra 包,具体命令如下:

    sudo apt-get install --reinstall linux-image-3.13.0-34-generic
    sudo apt-get install --reinstall linux-image-extra-3.13.0-34-generic`
    

    然后编写如下Makefile:

    ##此处表示编写在本机上使用的,交叉编译器进行交叉编译,此处也需要改变。
    KVERS = $(shell uname -r) 
    
    obj-m += hello.o
    
    #Specify flags for the module compilation
    #EXTRA_CFLAGS = -g -o0
    
    build: kernel_modules
    
    kernel_modules:
        make -C /lib/modules/$(KVERS)/build M=$(PWD) modules
        
    clean:
        make -C /lib/modules/$(KVERS)/build M=$(PWD) clean
    

    2.3 内核模块的相关操作命令

    insmod ./hello.ko ---加载模块
    rmmod hello ---卸载模块
    lsmod / cat /proc/modules ---查看所有模块
    /sys/module/hello 下执行 tree -a ---查看模块的详细组成文件及其信息
    /lib/modules/<kernel-version>/modules.dep ---文件查看模块依赖
    modinfo hello.ko ---查看模块信息
    modprobe ./hello.ko ---加载该模块的同时,加载该模块的所有依赖模块
    modprobe -r hello ---卸载该模块,同时卸载其依赖模块

    3 Linux 内核模块的程序结构

    3.1 模块加载函数

    初始化函数原型

    statitc int __init initialization_function(void)
    {
        /* 初始化代码*/
    }
    module_init(initialization_function);
    

    内核中也可以主动调用以下函数来灵活加载模块
    request_module(module_name);
    在 Linux ,所有标识为 __init 的函数如果直接编译进入内核,成为内核镜像的一部分,在连接的时候都会放到 .init.text 段区。

    注意:很明显模块通过 insmod 加载,并没有编入到内核镜像中,两者是通过MODULE宏变量来实现是否编入内核。
    #ifdef MODULE
    #define module_init(initfn) static inline initcall_t __inittest(void)
    { return initfn; }
    #else
    #define module_init(x) __initcall(x);
    #endif

    #define __init __attribute__((__section__(".init.text")))
    所有的 __init 函数在区段 .initcall.init 中保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些 __init 函数,并在初始化完成后,释放 init区段(包括 .init.text / .initcall.init)的内存.
    除函数外数据也可以定义为 __initdata. 只有初始化阶段需要的函数.

    3.2 模块卸载函数

    函数原型

    static void __exit cleanup_function(void)
    {
        //释放代码
    }
    module_exit(cleanup_function);
    

    同样也存在 __exitdata 只在退出阶段可用的数据。

    3.3 模块参数

    我们可以使用 "module_param(param_name, param_type, r/w)" 为模块定义一个参数。例如:

    static char *book_name = "dissecting Linux device Driver";
    module_param(book_name, charp, S_IRUGO);
    
    static int book_num = 4000;
    module_param(book_num, int, S_IRUGO);
    

    其参数类型可以是 byte, short, ushort, int, uint, long, ulong, charp(char *), bool 或 invbool(布尔的反)。

    模块参数的传递,用户可以在加载模块时使用形式为 insmod(/modprbe) module_name param_name = praram_value,如果模块被内置,则使用 bootloader 通过在 bootargs 里设置 “模块名.参数名 = 值” 值得形式传递给内核。

    另外也可使用 “module_param_array(array_name, array_type, array_length, r/w)” 来传递数组参数。运行 insmod 或 modprobe 时,应该使用逗号分隔开数组元素。

    模块加载成功后,在 /sys/module/ 目录下会出现以此模块名命名的目录。当参数的读写属性为 0 时,标示在 sysfs 文件系统下,不存在对应的文件节点;当模块存在 “参数读写属性” 不为 0 的命令行参数时, 此模块目录下将出现 parameters 目录,此目录下将出现与参数同名的文件,其文件的读写属性与参数一致。

    3.4 导出符号

    Linux 的 “/proc/kallsyms” 文件对应着内核符号表,它记录了符号以及符号所在的内存地址。
    模块可以使用如下宏导出符号到内核符号中:

    EXPORT_SYMBOL(symbol_name);
    EXPORT_SYMBOL_GPL(symbol_name);
    

    导出的符号可以被其它模块使用,只需要使用前申明下即可。EXPORT_SYMBOL_GPL 只适用于包含 GPL 许可权的模块。

    3.5 模块声明与描述

    Linux 内核模块的一般信息声明

    MODULE_AUTHOR("penghuster <penghuster@163.com>");
    MODULE_LICENSE("GPL v2");
    MODULE_DESCRIPTION("A simple Hello World Module.");
    MODULE_ALIAS("a simplest module.");
    MODULE_DEVICE_TABLE(table_info);
    

    对于 USB和PCI等设备,通常会创建一个 MODULE_DEVICE_TABLE,已表明该驱动模块所支持的设备。

    3.6 模块的使用计数

    Linux 2.4 内核中,模块自身通过 MOD_INC_USE_COUNT、MOD_DEC_USE_COUNT 宏来管理自己被使用的计数。

    Linux 2.6 以后内核提供了模块计数管理接口 try_module_get(&module)则增加使用计数函数 和 module_put(&module) 减少使用计数函数,从而取代 linux 2.4 的方式。只有当模块使用计数为 0 时,方能够卸载模块。

    相关文章

      网友评论

        本文标题:Linux 内核模块

        本文链接:https://www.haomeiwen.com/subject/abpnhftx.html