• Linux内核模块


    1、什么是内核模块?

    内核模块是Linux提供的一种机制,允许在内核运行时动态加载进内核中,具有两个特点:

             1)内核模块本身不编译入内核映像,有效控制缩减内核镜像大小

             2)内核模块一旦被加载,他就和内核中的其他部分完全一样

    2、为什么需要内核模块?

    如果在内核编译时把所有的功能都编译进去,就会导致内核很大,而且要往内核中添加或删除功能时必须重新编译内核

    比如在Ubuntu在通用PC平台上,预先无法知道需要什么设备,就不知道预先编译什么驱动。

    3、内核模块和应用程序的区别

     

    工作模式

    工作性质

    层次

    权限

    影响

    竞态

    运行方式

    应用程序

    USR模式

    策略性

    用户层

    局部

    局部

    主动

    内核模块

    SVC模式

    功能性

    内核层

    全局

    全局

    被挡

              

    4、内核模块的基本构成

    |——两个函数(一般需要)
    |        |——模块初始化(加载)函数:当内核模块加载进内核的时候,做一些准备工作
    |        |——模块卸载函数:回收、清理资源
    |
    |——授权(许可证声明)(必须):Linux内核受GPL(General Public License)授权约束
    |——模块参数(可选):模块被加载时可以被传递给它的值,本身对应模块内的全局变量
    |——模块导出符号(可选)
    |——模块信息说明(可选)

     5、模块加载(初始化)函数

    一般以 __init标识声明

    函数命名规则 xxx_init           xxx设备名       init功能名(初始化)

    函数形式:

    static ini __init  xxx_init(void)
    {
             /* 初始化代码
                * 返回值:           成功:0                    失败:负数,绝对值是错误码
    * 应用层得到的返回值是-1,错误码保存到errno(每个进程有一个);      标准化errno.h已经明确定义linux/errno.h
        */
    }

    注册方式: module_init(x);  x为模块初始化函数的首地址

     6、模块卸载函数

    一般以 __exit标识声明

    函数命名规则 xxx_exit          xxx设备名       exit功能名(卸载)

    static ini __exit  xxx_exit(void)
    {
             /* 释放代码 */
    }

    注册方式: module_exit(x); x为模块卸载函数的首地址

    7、模块许可证声明

    MODULE_LICENSE(_license)   //_license就是授权名称的字符串
    //"GPL"                            [GNU Public License v2 or later]
    //"GPL v2"                         [GNU Public License v2]
    //"GPL and additional rights"     [GNU Public License v2 rights and more]
    //"Dual BSD/GPL"                     [GNU Public License v2 or BSD license choice]
    //"Dual MIT/GPL"                     [GNU Public License v2 or MIT license choice]
    //"Dual MPL/GPL"                     [GNU Public License v2 or Mozilla license choice]

     8、模块声明与描述

    在Linux内核模块中,我们可以用MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_VERSION、MODULE_DEVICE_TABLE、MODULE_ALIAS分别来声明模块的作者、描述、版本、设备表和别名,例如:

    MODULE_AUTHOR(author);
    MODULE_DESCRIPTION(description);
    MODULE_VERSION(version_string);
    MODULE_DEVICE_TABLE(table_info);
    MODULE_ALIAS(alternate_name);

    对于USB、PCI等设备驱动,通常会创建一个MODULE_DEVICE_TABLE,表明该驱动模块支持的设备,如:

    /* 对应此驱动的设备列表 */

    static struct usb_device_id skel_table [ ] = {
             {
                       USB_DEVICE(USB_SKEL_VENDOR_ID,
          USB_SKEL_PRODUCT_ID) },
           { } /* 表结束 */
             }
    };
    MODULE_DEVICE_TABLE (usb, skel_table);

    9、模块参数:在加载模块时,可以给模块传参

    头文件 linux/moduleparam.h 

    A、传递普通变量

    module_param(name, type, perm);
    声明内核模块参数
    /*
    name - 接收参数的变量名
    type - 变量类型
      Standard types are:
      byte, short, ushort, int, uint, long, ulong
      charp: a character pointer
      bool: a bool, values 0/1, y/n, Y/N.
      invbool: the above, only sense-reversed (N = true)
    perm - 权限
      头文件 linux/stat.h
      #define S_IRWXUGO        (S_IRWXU|S_IRWXG|S_IRWXO)
      #define S_IALLUGO (S_ISUID|S_ISGID|S_ISVTX|S_IRWXUGO)
      #define S_IRUGO              (S_IRUSR|S_IRGRP|S_IROTH)
      #define S_IWUGO             (S_IWUSR|S_IWGRP|S_IWOTH)
      #define S_IXUGO              (S_IXUSR|S_IXGRP|S_IXOTH)
    */

     范例:

    int i = 0;
    module_param(i, int, 0644);

    运行:

    # insmod xxx.ko i=10

     B、传递数组参数

    module_param_array(name, type, nump, perm)
    /*
    声明内核模块数组参数
    name - 数组名
    type - 数组成员类型
    nump – 一个指向保存数组长度的整型变量的指针
    perm - 权限
    */

    范例:

    int arr[] = {1,2,3,4,5,6};
    int len=0;
    module_param(arr, int, &len, 0644);

    运行:

    # insmod xxx.ko arr=1,2,3,4,5

     C、传递字符串参数

    module_param_string(name, string, len, perm)
    /*
    声明内核模块字符串参数
    name - 字符串缓存的外部名(传入变量名)
    string - 字符串缓存的内部名
    nump - 数组的数量
    perm - 权限
    */

    范例:

    char insidestr[] = "hello world";
    module_param(extstr, insidestr, szieof(insidestr), 0644);

    运行:

    # insmod xxx.ko extstr="hello"

     10、编译内核模块

    如果一个内核模块要加载到某个内核中运行,则这个模块必须使用编译该内核镜像的源码进行编译,否则运行时会出错

    A、头文件(语法问题)

    B、编译结果(最主要影响)

    • 编译时符号表(只在编译时使用)
    • 运行时内核符号表
    • # cat /proc/kallsyms 运行时内核符号表

    C、编译系统

    示例Makefile:

    # 内核模块的Makefile(模块源码在内核源码外,且内核先编译)
    # 1、找内核的Makefile
    # 2、内核的Makefile找内核模块的Makeifle
    内核模块的Makeifle定义要编译对象
    ifneq ($(KERNELRELEASE),)
    #要编译对象表示把demo.c编译成demo.ko
      obj-m = demo.o
    else
    #内核源码目录
    KERNELDIR :=  /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
    modules:
      $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    endif
    clean:
      rm -rf .tmp_versions Module.symvers modules.order .tmp_versions .*.cmd *.o *.ko *.mod.c

    KERNELRELEASE 是在内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE没有被定义,所以make将读取执行else之后的内容。

    当make的目标为all时,-C $(KERNELDIR) 指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile。

    当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容。else之前的内容为kbuild语法的语句, 指明模块源码中各文件的依赖关系,以及要生成的目标模块名。

    每个内核的名字都包含了它的版本号,这也是 uname -r 命令显示的值。

    11、操作内核模块

    A、加载模块

    1. # insmode 模块文件名 
    2. # modprobe 模块名   # depmod
    3. # modinfo  可以产看模块信息

    B、查看模块

    # lsmod

    模块名       模块大小     使用计数         使用者(by没有内容的是用户模块,有没用的是内核模块)

    Module           size          Used     by

    demo            2333               0                                 (Used是当前有多少在用,为0时才允许被卸载)

    mptscsih      39530               1   mptspi

    C、卸载模块

    # rmmod 模块名(Used为0时才允许被卸载)

    D、查看内核输出信息

    # dmesg | tail –n 10                  /* 查看内后最后十行信息 */

    F、导出符号表

    #define EXPORT_SYMBOL(导出符号表),符号可以是全局变量,也可以是函数

  • 相关阅读:
    开淘店记录
    广告轮播效果
    loading事件加载效果
    正则表达式摘要
    cookie存取数据分析
    js 空格与回车处理
    数据对象型转换为数组型
    变换闪烁效果
    eclipse配置新环境
    五小时轻松入门Python
  • 原文地址:https://www.cnblogs.com/chen-farsight/p/6128561.html
Copyright © 2020-2023  润新知