1 、回顾module_init()的作用
两个:
· 第一个:告诉内核,驱动程序的入口函数的地址
· 第二个:设置相应的属性
· 第一个:告诉内核,驱动程序的入口函数的地址
做法是定义一个函数指针变量,然后将入口函数地址保存到里面,内核即可通过这个指针变量来调用。指针变量的名字中有一部分就是入口函数名字。
static initcall_t __initcall_mouse_device6 = mouse_device
------------- ------------
思考下:为什么内核入口函数的名字可以随便自己定?
内核只需要拿到入口函数的地址即可,至于取什么名字,完全由驱动开发者自己来定。
· 第二个:设置相应的属性
定义函数指针变量时,通过__used __attribute__((__section__(".initcall" "6" ".init")))设置相关属性。
总结
通过这个例子,我们可以看出,想要理解好module_init这个宏,不仅仅只是宏的问题,还涉及到__attribute__等关键字的问题,不过如果你连最起码的宏这一关都过不了的话,你的分析讲无从下手。
分析这类比较复杂的宏的时候,千万不要只干眼看,一定要动手去层层替换找到本源,只要当你得到最本源的东西时就好办了,如果最本源的里面还有其它无法理解的东西,那就再想办法解决,但如果你连本源都得不到,那你一定会抓瞎。
大家以后在源码中再次碰到这样的复杂宏的时候,就按照我们这里介绍的这个方法来分析,一定就没有问题。
疑问:为什么内核搞得这么复杂
引用module_init时,把module_init(mouse_device)直接换成如下写法也是可以,
static initcall_t __initcall_mouse_device6 __used \ __attribute__((__section__(".initcall" "6" ".init"))) = mouse_device
不过这个写法显然不够性化,Linux内核必须给提供最简单的接口,比如module_init,以方便驱动开发者使用。
世界上没有完美的事,这种简单的接口,虽然给“引用”带来了方便,但是却也给源码分析带来了麻烦,不过只要大家按照我们给的方法去分析,其实也没有什么难的。
其实大家最终会发现,这个宏的例子,其实与我们前面讲的文件锁的那个例子非常相似
· 文件锁的例子
所有的宏最终都指向了一个东西,lock_set函数。
虽然都是同一个函数,但是通过参数就可以区分你想做的事情。
· mode_init的例子
所有的宏最终指向的也是同一个东西,那就是__define_initcall带参宏。
moduel_init —> __initcall —> device_initcall(使用参数来区分)
\
\
\
V
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
^
/
/
***_init —> __***call —> ***_initcall(使用参数来区分)
内核通过mode_init带参宏,很好的简化了引用,减少了参数。
2、例子:使用宏对类型进行自定义命名
#define INT32_t int INT32 a; #define U32_t usigned int U32_t a; #define STUDENT_t struct info_student; STUDENT stu;
不过对类型自定义命名,最好还是使用typedef来实现,因为宏只是简单的替换,如果使用不当的话,这种简单替换会导致bug,有关这个问题,我们后面讲typedef时再来对比介绍。
不过使用宏这种方式来实现类型自定义命名方式,冷不丁的在有些源码中还是会看见的,特别是在好些单片机的程序中,这种方式还是挺多的,所以这里要了解下。
疑问:为什么要对类型进行重新自定义命名?
有关这个问题,我们讲typedef的时候再来解答。
3、例子:
(1)offsetof宏
#define offsetof(type, member) (size_t)&(((type*)0)->member)
(2)container_of宏
#define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );})
不过这两个宏分析起来就没有那么容易了,我们将这两个例子举出来,仅仅只是想让你知道,宏这个东西在实际开发中的各种源码中经常看见,而且往往还比较古怪,所以希望大家能够学会适应宏的存在,学会阅读宏,理解宏。
由于这两个宏与结构体相关,所以有关这两个宏的分析,我们就留在讲结构体时再来分析,其实分析的方法还是一样的,只是稍微麻烦些。
总结
以后大家看复杂C源码时,你还会看到很多古里古怪的宏用法,我们这里不可能都把这些例子举出来,总之通过这几个例子的介绍后,希望大家能够慢慢适应源码中宏的各种古怪的、复杂的用法,遇到了后希望大家能够按照我们介绍的方法去自行分析理解。