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

宏深入用法——分析module_init()宏

分析module_init()宏

module_init是一个带参宏,只不过Linux内核把它写成了小写的,不过我们说过,在我们在自己的代码中应该尽量将宏写成大写。

为什么内核喜欢将好些带参宏写成小写?

就是想让你把它看成是一个函数,或者说就是希望你把它理解为一个函数,当成一个函数来用,不过它其实是一个宏,但是我们还是建议,在我们自己的程序里面,宏尽可能的大写。

(a)module_init()定义在了什么位置

        在/include/linux/init.h中,我们在ubuntu下是找不到这个头文件的,因为这个是Linux内核源码文件,ubunu的Linux内核早就被编译好了,所以ubuntu下没有Linux的源码,所以找不到这个头文件。

        假如我写了一个鼠标驱动程序,入口函数叫mouse_device,现在想引用module_init告诉内核,驱动的入口函数是mouse_device,那应该怎么做呢。

        很简单,引用这个带参宏来实现:

module_init(mouse_device)

(b)分析module_init(mouse_device)

· module_init宏定义的层层嵌套的逻辑

#define module_init(x)__initcall(x);
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn)__define_initcall("6",fn,6)
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn

· 替换的过程

module_init(mouse_device) ——> __initcall(mouse_device) ——> device_initcall(mouse_device)—————> __define_initcall("6", mouse_device, 6s) ————>———>static initcall_t __initcall_##mouse_device##6s __used __attribute__((__section__(".initcall" "6" ".init"))) = mouse_device

- ##:连字符,将前后连在一起,连在一起后##就消失了,最后就演变为如下形式。

static initcall_t __initcall_mouse_device6s __used __attribute__((__section__(".initcall" "6" ".init"))) = mouse_device

有关##的作用,后面还会在讲到。

· 分析最终原型

原型中的__used __attribute__((__section__(".initcall" "6" ".init")))作用是用于指定相关属性,为了便于分析,我们将属性相关的内容给它省略,化繁为简暴露出核心信息,最后就得到了如下格式。

static initcall_t __initcall_mouse_device6s = mouse_device



- initcall_t:为typedef int (*initcall_t)(void)

是一个函数指针类型,有关函数指针类型,我们后面在讲函数时还会讲到。

static initcall_t __initcall_mouse_device6s = mouse_device

定义一个函数指针变量,存放mouse_device的函数地址。

等价为:

static int (*__initcall_mouse_device6)(void) = mouse_device;

这个就是最终的原型,说白了就是定义了一个函数指针变量,用于保存驱动程序的入口函数mouse_device的函数地址,在c语法中函数名就是函数的第一条指令的地址。

内核到时候就是通过__initcall_mouse_device6这个函数指针变量,来调用驱动程序的入口函数mouse_device。

__initcall_mouse_device6(参数);


· 分析__used __attribute__((__section__(".initcall" "6" ".init")))

- __used宏:在/include/linux/.h中,__used为空,其它的.h中就不一定为空了

- __attribute__:这个是gcc编译器所支持的c关键字。

表示要设置属性,具体设置什么属性,由后面的内容((__section__(".initcall" "6" ".init")))来决定。

一般底层开发才会用到这个__attribute__关键字,如果你写的C程序只是一个应用程序的话,基本是看不到这个关键字的,正是由于不常见,所以很多同学刚开始看到这个关键字的时候就蒙圈了,因为大家大多学c的时候,写的都是应用程序,分本见不着这个关键字,所以不熟悉。

有关这个关键字,我们后面的课程还会讲到。

- ((__section__(".initcall" "6" ".init"))):代表具体要设置的属性。

+ __section__:也是gcc支持的c关键字,基本只有底层开发才会用到c关键字,这个关键字的作用是,用于说明你的“驱动入口函数的代码”放到.text中的什么位置。

+ ".initcall" "6" ".init" :说明具体放的位置

最简单的理解就是,表示放到内核二进制代码的.text中的.init中的.initcall中的第6个位置。


为什么要把驱动的代码放到Linux内核代码.text中?

我们前面说过,驱动代码最后是要加入内核,成为内核代码一部分的,所以肯定是要放到内核代码的.text中。

如果我们不设置属性,指定入口函数具体放在.text的什么位置,就会在.text中随机放,如果指定了,就会放到你指定的位置。

为什么向Linux内核告诉驱动程序的入口函数时,要指定入口函数的代码在.text存放的位置呢?

作用有很多,比如,放到指定的位置,这个指定的位置对代码的访问权限会做相应限制,有关这个问题,我们就到这里就不再深入,当后面Linux驱动课程涉及到后,我们再具体什么介绍。

我们在写c应用程序,编译器编译时所有函数的代码放到.text中的什么位置,这个是由编译器的链接脚本自行决定的,我们不需要指定位置。

当然我们也可以自己在代码中加以指定,编译器编译时可以放到我们自己指定的位置,但是在c应用程序中这么做意义不大,当然也正是由于这种用法很少见,所以大家在看内核源码、碰到这类东西的时候,才会蒙圈。


头像
0/200
图片验证码