首页 /  技术专区  /  C 宽屏模式 >

宏深入用法——对类型进行自定义命名

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源码时,你还会看到很多古里古怪的宏用法,我们这里不可能都把这些例子举出来,总之通过这几个例子的介绍后,希望大家能够慢慢适应源码中宏的各种古怪的、复杂的用法,遇到了后希望大家能够按照我们介绍的方法去自行分析理解。


头像
0/200
图片验证码