阅读O'REILLY的《Linux设备驱动程序(第三版)》第二章感觉有很多之前学习驱动时忽略的内容,之前不求甚解只求先观其全貌,错失了很多细节,经过LDD3的阅读,简单记录其中自己觉得有意思的内容,以及自己的一些思考。
加粗标红为原书的内容,以及每个问题都会附带内容所在页面
1、为什么驱动模块能够调用库函数如printk——P23
“模块能够调用printk是因为在insmod函数装入模块后,模块就连接到了内核,因此可以访问内核的公用符号(包括函数和变量)”
根据《程序员的自我修养》书中介绍,程序应该在静态链接过程中,将需要用到的符号地址进行指定,如果是动态链接,则会在指定库中找到对应的函数地址,进行调用。
对于驱动模块而言,可以类比用户程序。
2、printk和printf的区别——p24
”printk函数就是有内核定义并导出给模块使用的的一个printf的内核版本,除了几个细小差别以外,他们功能类似,最大的不同是printk缺乏对浮点数的支持“
内核基本不需要使用浮点运算。
3、模块是否会和函数库链接,即使用普通函数库的头文件——p24
"没有任何函数库会和模块链接,因此,源文件(模块)中不能包含通用的头文件,像<stdarg.h>以及一些非常特殊的情况例外“
模块会使用include/linux和include/asm目录下,提供给内核相关的函数使用。
4、怎么获得当前进程——p27
”内核代码可以通过访问全局项current来获得当前进程“
current定义在<asm.current.h>,是一个指向struct task_struct 的指针。
5、内核的栈大小——p27
"内核具有非常小的栈,可能只和4096字节大小的页那样小”“如果需要大的结构,应该调用时动态分配该结构”
6、linux内核API中具有两个下划线前缀(__)的函数,通常是接口的底层组件,应该谨慎使用。
7、内核代码能够实现浮点运算——p28
"可以通过打开浮点支持,在某些架构上,需要在进入和退出内核空间时保存和恢复浮点处理器的状态。这种额外的开销没有任何价值,内核代码中也不需要浮点运算。”
8、内核如何支持insmod工作——p30
”insmod依赖于定义在kernel/module.c中的一个系统调用。函数sys_init_module给模块:
1、分配内核内存(vmalloc负责内存分配)以便装载模块。
2、将模块正文复制到内存区域
3、通过内核符号表解析模块中的内核应用
4、调用模块的初始化函数“
9、系统调用名字前带有sys_前缀
10、modprobe和insmod的区别——p31
"modprobe要考虑要装载的模块是否引用了一些当前内核不存在的符号。如果有这类引用,modprobe会在当前模块搜索路径中查找定义了这些符号的其他模块。如果modprobe找到了这些模块(即要装载的模块所依赖的模块),他会同时将这些模块装载到内核“
”如果在需要依赖其他模块的情况下使用insmod,则该命令会失败,并在系统日志文件中记录unresolved symbols(未解析的符号)消息。
11、模块装载失败的具体信息可以在系统日志文件/var/log/messages或者系统配置使用的文件查看。——p31
12、模块如何导出符号,即提供自身的变量和函数给其他模块使用——p34
EXPORT_SYMBOL(name); EXPORT_SYMBOL_GPL(name);
这两个宏均可将给定符号导出到模块外部。 _GPL版本使得要导出的模块只能在GPL许可证的模块下使用。
这两个宏会将变量在ELF中一个特殊部分存储,装载时,内核通过这个段来寻找模块导出的变量。
13、模块必须用到的头文件,专门用于模块——p35
#include <linux/module.h> #include <linux/init.h>
14、module_init指定模块的入口,如何实现——p36
"module_init的使用是强制性的,这个宏会在模块的目标代码中增加一个特殊的段,用于说明内核初始化函数所在的位置。没有这个定义,初始化函数永远不会被调用”
15、模块的清除函数是如何实现的——p37
"模块清除函数使用__exit修饰,__exit修饰词标记该代码仅用于模块卸载(编译器将把该函数放到特殊的ELF段中)。如果一个模块未定义清除函数,则内核不允许卸载该模块“
16、错误编码定义在哪里,表示的意思——p38
"错误编码是定义在<linux/errno.h>中的负整数,用用户程序可以通过perror函数或者类似的途径将他们转换为有意义的字符串“
17、模块加载时如何传入参数——p41
"在insmode改变模块参数之前,模块必须让这些参数对insmod命令可见,参数必须使用module_param宏来声明,该宏定义在moduleparam.h中。module_param需要三个参数:变量的名称、类型以及用于sysfs入口项的访问许可掩码,该宏需要放到任何函数之外,通常是源文件头部“
static char *whom = "world";
static int howmany = 1;
module_param(whom, charp, S_IRUGO);
module_param(howmany, int, S_IRUGO);
参数的类型:
bool,invbool(布尔值),charp(字符指针),int,long,short,uint,ulong,ushort,也可以用模块代码中的钩子来定义自定义类型。(参考moduleparam.h)
参数的访问许可掩码(定义在<linux/stat.h>):
S_IRUGO(任何人可读,不可修改) S_IRUGO|S_IWUSR(允许root用户修改) ...