Linux驱动学习:Linux内核模块


以下基于宋宝华老师的《Linux设备驱动开发详解》

示例代码

#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("Barry Song <21cnbao@gmail.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("A simple Hello World Module");
MODULE_ALIAS("a simplest module");

模块相关命令

  1. 加载模块:insmod xxx.ko
  2. 卸载模块:rmmod xxx.ko
  3. 查看所有已加载的模块以及模块间的依赖关系:lsmod
    1. 实质上是读取并分析文件:/proc/modules
    2. 已加载模块的信息也存在于目录下:/sys/module
  4. 更强大的命令:modprobe
    1. 该命令在加载某一个模块时,会同时加载该模块所依赖的其他模块,卸载同理。
      1. 加载:modprobe xxx
      2. 卸载:modprobe -r xxx
      3. 注意,这个命令似乎是从配置的指定路径下面去寻找的,不能手动的指定路径,所以没有insmod灵活
    2. 模块之家的依赖关系存放在文件:/lib/modules/<kernel-version>/modules.dep
      1. depmod工具在整体编译内核时生成
    3. 获得模块的信息:modinfo xxx.ko

Linux内核模块程序结构

  1. 模块加载函数
  2. 模块卸载函数
  3. 模块许可证声明
    1. 可接受的许可证(LICENSE)包括:GPLGPL V2GPL and additional rightsDial BSD/GPLDual MPL/GPLProprietary,是否可以采用非GPL是有争议的,大多数情况下声明为GPL v2
  4. 模块参数(可选)
  5. 模块导出符号(可选)
  6. 模块作者等信息声明(可选)

模块加载函数

通用示例如下

static int __init initialization_function(void)
{
}
module_init(initialization_function);
  1. 一般以__init标识声明
  2. module_init(函数名)的形式被指定。
    1. 返回整型值,0代表成功,其他代表错误编码,错误编码应该为接近于0的负值,在<linux/errno.h>中定义
  3. 在内核中,可以通过request_module(const char* fmt,...)函数加载其他内核模块
  4. 所有标识为__init的函数如果直接被编译进入内核,那么在连接时,就会放在.init.text这个区段内
    1. #define __init __attribute__((section__(".init.text")))
    2. 相应的函数指针保存在:.initcal.init
    3. 初始化完成后,会释放init区段(包括了上面这两个)的内存
  5. 数据也可以被定义为__initdata:主要针对于只是初始化阶段需要的数据。
    1. 定义为__initdata后,内核在初始化完毕后,也会释放它们所占用的内存

模块卸载函数

通用示例如下

static void __exit cleanup_function(void)
{
    
}
module_exit(cleanup_function);
  1. 模块卸载的时候执行,不返回任何值
  2. 必须以module_exit(函数名)指定
  3. 如果被__exit修饰的函数,在相关模块被编译进入了内核,那么被修饰的函数会被直接省略。
  4. 只有退出阶段采用的数据也可以用__exitdata来修饰

模块参数

  1. 定义一个模块参数:module_param(参数名,参数类型,参数读/写权限)
  2. 在装载内核模块时,可以向模块传递参数:insmod/modprobe 模块名 参数名=参数值
    1. 不传递就使用默认值
    2. 如果模块是被编译进入了内核,也就是被内置,那么可以通过bootloaderbootargs里面设置模块名.参数名=值
  3. 模块参数数组
    1. 定义一个模块参数数组:module_param_array(数组名,数组类型,数组长,参数读/写权限)
  4. 模块加载后,/sys/module下将出现对应的目录
    1. 如果模块存在参数读/写权限不为0的模块参数,那么会出现parameters目录,且该目录下包含参数读/写权限不为0的模块参数文件节点
      1. 文件内容为参数的值
  5. 逗号分隔输入的数组元素

导出符号(导出函数)

  1. 内核符号表(函数表)文件:/proc/kallsyms
    1. 该文件记录了符号以及符号所在的内存地址
  2. 导出函数到内核函数表中:
    1. EXPORT_SYMBOL(符号名/函数名)
    2. EXPORT_SYMBOL_GPL(符号名/函数名):仅仅适用于包含GPL许可权的模块

模块声明与描述

  1. 声明模块的作者:MODULE_AUTHOR()
  2. 声明模块的描述:MODULE_DESCRIPTION()
  3. 声明模块的版本:MODULE_VERSION()
  4. 声明模块的设备表:MODULE_DEVICE_TABLE()
  5. 声明模块的别名:MODULE_ALIAS()

声明USB/PCI等设备驱动模块所支持的设备

static struct usb_device_id skel_table[] = {
    USB_DEVICE(USB_SKEL_VENDOR_ID,
    USB_SKEL_PRODUCT_ID)},
	{}
};
MODULE_DEVICE_TABLE(usb,skel_table)

模块使用计数

  1. Linux 2.6以前,使用MOD_INC_USE_COUNTMOD_DEC_USE_COUNT宏管理自己被使用的计数
  2. Linux2.6以后,使用
    1. 2.6以后,为不同类型的设备定义了struct module * owner
    2. try_module_get(dev->owner):增加模块使用计数
    3. module_put(dev->owner):减少模块使用计数
  3. 使用这个主要是为了让设备在使用时,不被卸载。

模块的编译:简单的Makefile

KVERS = $(shell uname -r) #当前内核的版本

# Kernel modules
#哪一个模块
obj-m += book.o 

# Specify flags for the module compilation.
#EXTRA_CFLAGS=-g -O0 #开启这里得到包含调试信息的模块

build: kernel_modules

kernel_modules:
    	# 将模块扔到/lib/modules/$(KVERS)/build目录下去编译
    	# M=$(CURDIR) : 模块在哪个目录,CURDIR为当前目录
        make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules

clean:
        make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean

注意,该Makefile得到的xxx.ko是只能在PC上运行,如果需要让xxx.ko能装载到ARM上,需要交叉编译内核源码,以及在Makefile中指定make所使用的交叉编译工具

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值