Skip to main content
 首页 » 操作系统

Linux 驱动模块(2)——模块信息与调试

2022年07月19日26落叶无声

一、查看内核模块信息

相关命令:modprobe、insmod、rmmod、modinfo、lsmod

1.查看内核所有内置模块
# cat /lib/modules/$(uname -r)/modules.builtin

kernel/arch/arm64/crypto/sha1-ce.ko 
kernel/arch/arm64/crypto/sha2-ce.ko 
kernel/arch/arm64/crypto/ghash-ce.ko 
kernel/arch/arm64/crypto/aes-ce-cipher.ko 
......

2.查看有哪些内置模块
# grep "=y" /boot/config-$(uname -r) | more

3.查看内核模块参数和值
# ls /sys/module/sd8xxx/parameters
在/sys/module目录下,可以找到内核模块(包含内置和可加载的)命名的子目录。进入每个模块目录,这里有个“parameters”目录,列出了这个模块所有的参数。
上面示例是要找出sd8xxx模块的参数。
# cat /sys/module/sd8xxx/parameters/mfg_mode 查看参数mfg_mode的值。

4.显示模块参数信息
# modinfo -p sd8xxx 只显示参数

5.显示模块全部信息
# modinfo sd8xxx

filename:       /lib/modules/4.14.35/extra/sd8xxx.ko 模块存放的位置 
license:        GPL 
version:        C546 模块版本 
author:         Marvell International Ltd. 
description:    M-WLAN Driver 
srcversion:     5F78B0AEFAD2117163CB186 
alias:          sdio:c*v02DFd9135* 
depends:        cfg80211,mlan 模块依赖 
name:           sd8xxx 
vermagic:       4.14.35 SMP preempt mod_unload aarch64  匹配内核版本信息 
parm:           cfg80211_drcs:1: Enable DRCS support; 0: Disable DRCS support (int) 
parm:           reg_alpha2:Regulatory alpha2 (charp) 模块参数 
...

二、模块声明信息

MODULE_LICENSE ("GPL"); //许可证申明(最好有) 
MODULE_DESCRIPTION("Hello world Module"); //模块描述(可选)  
MODULE_VERSION("V1.0"); //模块版本(可选) 
MODULE_ALIAS("a simple module"); //模块别名(可选) 
/* 
 * Name是模块参数的名称,type是这个参数的类型,Perm是模块参数的访问权限 
 * type常见值: Bool,int,charp:字符串型 
 * perm常见值:S_IRUGO:任何用户都对/sys/module中出现的该参数具有读权限, S_IWUSR:允许root用户修改/sys/module中出现的该参数 
 */ 
module_param(name,type,perm) 
EXPORT_SYMBOL(符号名) //导出符号给驱动模块使用 
EXPORT_SYMBOL_GPL(符号名) //导出只能用于包含GPL许可证的模块,/proc/kallsyms 记录了内核中所有导出的符号的名字与地址(记录输出到系统当中可以给其他模块使用的函数的名字) 
MODULE_AUTHOR("Jeff"); //声明谁编写了模块  
MODULE_DEVICE_TABLE(pci, ahci_pci_tbl); //来告知用户空间, 驱动模块支持那些设备

导出符号有 EXPORT_SYMBOL(符号名) 和 EXPORT_SYMBOL_GPL(符号名),其中 EXPORT_SYMBOL_GPL 只能用于包含GPL许可证的模块,也就是调用模块中有声明 MODULE_LICENSE("GPL")

三、内核模块的动态加载

request_module("sound-slot-%i", unit>>4);表示让linux系统的用户空间调用/sbin/modprobe函数加载名为sound-slot-0.ko模块

TODO:https://blog.csdn.net/liukun321/article/details/7057442

四、模块加载次序

1. 相关宏定义

// include/linux/init.h 
#define pure_initcall(fn)            __define_initcall(fn, 0) 
#define core_initcall(fn)            __define_initcall(fn, 1) 
#define core_initcall_sync(fn)        __define_initcall(fn, 1s) 
#define postcore_initcall(fn)        __define_initcall(fn, 2) 
#define postcore_initcall_sync(fn)    __define_initcall(fn, 2s) 
#define arch_initcall(fn)            __define_initcall(fn, 3)    /*gpio注册*/ 
#define arch_initcall_sync(fn)        __define_initcall(fn, 3s) 
#define subsys_initcall(fn)            __define_initcall(fn, 4) 
#define subsys_initcall_sync(fn)    __define_initcall(fn, 4s) 
#define fs_initcall(fn)                __define_initcall(fn, 5) 
#define fs_initcall_sync(fn)        __define_initcall(fn, 5s) 
#define rootfs_initcall(fn)            __define_initcall(fn, rootfs) 
#define device_initcall(fn)            __define_initcall(fn, 6)     /*module_init()*/ 
#define device_initcall_sync(fn)    __define_initcall(fn, 6s) 
#define late_initcall(fn)            __define_initcall(fn, 7) 
#define late_initcall_sync(fn)        __define_initcall(fn, 7s) 
 
#define __initcall(fn) device_initcall(fn) 
 
#define __exitcall(fn)     static exitcall_t __exitcall_##fn __exit_call = fn /*exit call是没有等级的*/ 
 
#define console_initcall(fn)    ___define_initcall(fn,, .con_initcall) 
#define security_initcall(fn)    ___define_initcall(fn,, .security_initcall) 
 
 
// include/linux/module.h 
#define module_init(x)    __initcall(x); 
#define module_exit(x)    __exitcall(x);

2. do_initcall_level() 函数只在 do_initcalls() 中被调用一次,也就是说这七个等级的 initcall 没有任何一个等级做了特殊处理,同等对待,只是调用的先后次序不同而已。七个等级的 initcall 都被存放在 initcall_levels 这个数组中了,在 do_initcalls() 中一次性全部调用完。

static initcall_t *initcall_levels[] __initdata = { //init/main.c 
    __initcall0_start, 
    __initcall1_start, 
    __initcall2_start, 
    __initcall3_start, 
    __initcall4_start, 
    __initcall5_start, 
    __initcall6_start, 
    __initcall7_start, 
    __initcall_end, 
}; 
 
static void __init do_initcall_level(int level) 
{ 
    initcall_t *fn; 
 
    strcpy(initcall_command_line, saved_command_line); 
 
    /*先解析命令行参数后再执行*/ 
    parse_args(initcall_level_names[level], 
           initcall_command_line, __start___param, 
           __stop___param - __start___param, 
           level, level, 
           NULL, &repair_env_string); 
 
    for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++) 
        do_one_initcall(*fn); 
} 
 
static void __init do_initcalls(void) 
{ 
    int level; 
    /*这决定了pure/arch/late_initcall()等的先后顺序,越小越先被调用*/ 
    for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) 
        do_initcall_level(level); 
}

3. 一个.c文件中可以有多个module_init()和多个module_exit()。

4.module_init 的 initcall level 为6,设备树的解析在init call 3 时段。

五、MODULE_DEVICE_TABLE

1. 定义

//include/module.h 
#ifdef MODULE 
/* Creates an alias so file2alias.c can find device table. */ 
#define MODULE_DEVICE_TABLE(type, name)                    \ 
extern typeof(name) __mod_##type##__##name##_device_table        \ 
  __attribute__ ((unused, alias(__stringify(name)))) 
#else  /* !MODULE */ 
#define MODULE_DEVICE_TABLE(type, name) 
#endif

2. 使用举例

static const struct i2c_device_id sy7749_id_table[] = { 
    {"sy7749", 0}, 
    {} 
}; 
MODULE_DEVICE_TABLE(i2c, sy7749_id_table); 
 
static const struct of_device_id sy7749_of_table[] = { 
    { .compatible = "mtk,sy7749" }, 
    { }, 
}; 
MODULE_DEVICE_TABLE(of, sy7749_of_table); 
 
//vendor/lib/modules/modules.alias 中生成: 
alias i2c:sy7749 sy7749 
alias of:N*T*Cmtk,sy7749 sy7749 
alias of:N*T*Cmtk,sy7749C* sy7749

3. 说明

用法是:MODULE_DEVICE_TABLE(设备类型, 设备列表) 其中,设备类型,包括 i2c、of、platform、pci、usb 等,也可以自己起名字。设备表也是自己定义的,它的最后一项必须是空,用来标识结束。
用于模块加载系统在加载模块时,告知了什么模块对应什么硬件设备。


本文参考链接:https://www.cnblogs.com/hellokitty2/p/9431601.html