Linux下驱动模块学习
Posted hellokitty2
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux下驱动模块学习相关的知识,希望对你有一定的参考价值。
1.modutils中提供了相关的insmod,rmmod,modinfo工具
2.modprobe在识别出目标模块所依赖模块后也是调用insmod.
3.从外部看模块只是普通可重定位的目标文件。可重定位文件的函数都不会引用绝对地址,而只是指向代码中的相对地址,因此可以在内存
中的任意偏移地址加载。
4.加载模块只需要一个系统调用init_module,即可在内核中完成所有的操作。
5.nm工具可用于产生模块(或任意目标文件)中所有的外部函数列表。
# nm atmel_mxt_ts.ko
# nm a.out
U代表未解决的引用;T表示位于代码段;D表示位于数据段
6.配置 CONFIG_KALLSYMS 选项为y即可在内核中启用 kallsyms 功能:
有的符号是大写的,有的是小写。大写的符号是全局的。
b 符号在BSS段中
c 普通符号,是未初始化区域
d 符号在数据段中
g 符号针对小object,在初始化数据区
i 非直接引用其他符号的符号
n 调试符号
r 符号在只读存储区
s 符号针对小object,在未初始化数据区
t 符号在代码段
u 符号引用还未解决
7.modutils标准工具中的depmod工具可用于计算系统各个模块之间的依赖关系,每次启动或安装模块时就会运行该程序,默认会将
依赖关系保存到/lib/modules/version/module.dep中,表示格式B:A表示模块B依赖于模块A
8./proc/kallsyms中列出了内核导出的所有符号(早些版本应该是/lib/modules/version/System.map),模块中未解决引用的符号会在这里面找。
9.内核中模块的信息存放在.modinfo段中。模块中的信息可以使用modinfo来看。
10.模块的自动加载与热插拔
1)在用户空间完成模块加载比在内核空间完成模块加载更方便,内核将该工作委托给kmod进程。注意kmod并不是一个永久守护进程,内核会
按需启用它。
2)eg:mount -t vfat /dev/fd0 /mnt/floppy 若之前内核中没有vfat.ko,这时会先插入这个驱动模块,在mount返回后,所需的模块已经载
入内核了。执行过程:内核发现其数据结构中没有vfat的信息 --> 内核请求 --> modprobe --> 模块查找 --> request_module --> 加载模块
request_module
├── 为modeprob准备环境
├── 同时调用request_module的次数过多(50个)?==> return
└── call_usermodehelper
modprobe_path通常是/sbin/modprobe,但是可以通过/proc/sys/kernel/modprobe或sysctl改变。
3).有时候内核需要加载模块的时候可能会出现无法唯一确定哪个模块能提供所需的功能,因此产生模块别名(module alias)
eg:U盘插入到系统中,被主机控制器识别为新设备,我们知道需要装载的模块时usb-storage,但是内核是如何知道的呢:
附加在每个模块上都有一个“小小的数据库”,里面描述了该模块所支持的设备。对于USB设备,数据库的信息包括所支持的接口类型列表,
厂商ID或能够标识设备的任意类型信息。
数据库信息通过模块别名(module alias)提供,它是模块的通用标识。modules.h中的MODULE_INFO、MODULE_ALIAS宏,eg raid5.c
udevd实现模块的热插拔也是使用了这个信息。
4)比直接别名更重要的是设备数据库,内核使用宏MODULE_DEVICE_TABLE来实现这样的数据库。
5)add_uevent_var() 位于kobject_uevent.c中,可以用于向uevent中的插拔消息提供一个新的键值对!
通过比较MODALIAS值和各个模块提供的别名,udevd可以找到需要插入的模块。
①模块代码中使用MODULE_ALIAS指定别名;
②设备插入时的udev事件中使用下面方法上报设备的别名
add_uevent_var(env, "MODALIAS=virtio:d%08Xv%08X", dev->id.device, dev->id.vendor);
这个函数这个使用方法在内核中还存在,应该没有被遗弃!!
11.模块的插入和删除
1)两个系统调用,init_module() 和 delete_module(), 可以使用man 2 来看
init_module
├── load_module
├── 将模块插入到内核链表
├── mod->init
└── 释放初始化数据/代码占用的区域
sys_delete_module
├── find_module
├── 确认模块未被使用
├── mod->exit
└── free_module
2)模块状态:
enum module_state {
MODULE_STATE_LIVE, /*模块正常运行时*/
MODULE_STATE_COMING, /*模块装载期间*/
MODULE_STATE_GOING, /*模块正在移除*/
MODULE_STATE_UNFORMED, /* Still setting it up. */
};
12.license_is_gpl_compatible()用来判断给定许可证是否GPL兼容
13.在模块自身和所依赖的所有其它模块都已经编译完成之前,模块中的有些段是无法生成的
14.模块的初始化和清理函数保存在.gnu.linkonce.module段中的module实例中,该实例位于每个模块自动生成的附加文件中。名为module.mod.c中
15.使用EXPORT_SYMBOL()导出符号,它会在__ksymtab段中产生一个结构。此宏包含的__CRC_SYMBOL()来实现版本控制,主语模块的版本控制是per-symbol
的。
16. 模块信息常用宏
MODULE_INFO 一般模块信息
MODULE_LICENSE 模块许可证
MODULE_AUTHOR 模块作者
MODULE_DESCRIPTION 模块描述
MODULE_ALIAS 指定模块别名(udevd使用它执行热插拔加载模块)
17.版本控制
1)基本版本控制
module.mod.c中MODULE_INFO(vermagic, VERMAGIC_STRING)
#define VERMAGIC_STRING
UTS_RELEASE " " /*字符串形式的内核版本*/
MODULE_VERMAGIC_SMP MODULE_VERMAGIC_PREEMPT
MODULE_VERMAGIC_MODULE_UNLOAD MODULE_VERMAGIC_MODVERSIONS
MODULE_ARCH_VERMAGIC
MODULE_RANDSTRUCT_PLUGIN
展开就是:"4.14.0 SMP preempt mod_unload aarch64"
内核和模块中都会存储VERMAGIC_STRING的一份副本。只有这两份副本匹配时,模块才能加载。这意味着模块和内核的下列方面必须一致才能加载:
①SMP配置(是否启用)
②抢占配置(是否启用)
③使用的编译器版本
④特定于体系结构的常数
注意:基本版本控制中内核的版本虽然会存储,但是在进行比较时会忽略。因此内核版本不同的模块,只要剩余版本字符串匹配,这个检查就不影
响模块的装载。
2)per-symbol的版本控制
在EXPORT_SYMBOL()中有个__CRC_SYMBOL()来产生此symbol的crc,加载的时候会比较
版本控制函数check_version()
18.内核不仅在设备插入与移除时向用户空间提供消息,实际上内核在很多一般事件发生时都会发送消息。模块的插入与移除也会.
设备模型的每一个部分都可以向用户层发送注册和撤销注册事件。
19.将module.info.c文件编译成目标文件,并使用ld将其与模块现存的.o目标文件链接起来,结构命名为module.ko,这就是最终的模块!
20.不仅设备驱动程序可以编译成模块,内核中除了最基础部分外都可以编译成模块。
以上是关于Linux下驱动模块学习的主要内容,如果未能解决你的问题,请参考以下文章