最近,我在看linux内核源码,读到了模块tty_io.c文件,其中有一个函数console_init()用于遍历访问加解引用访问了module_init()含参宏定义的函数指针变量。具体过程在这里捋一下。
首先,module_init()含参宏
module_init(x)-->__initcall(x)--->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定义了一个静态全局变量,并且与fn函数名表示的函数地址进行了绑定。
其次,再看看initcall_t的类型定义:
typedef int (*initcall_t)(void);定义了一个函数指针类型,指向的函数返回值为int,参数列表为void.
因此模块源文件中使用module_init(x),最终定义了static修饰的全局变量,而且是一个占4字节的函数指针类型的变量,并且进行了初始化,绑定了函数x。
值得注意的事这个函数指针变量分配了字段属性,如果再定义一个链接脚本用来指导链接器将具有相同字段属性的代码链接放在一起,也就形成了好多个函数指针变量是“连续排布的”,这就为使用函数指针变量而非函数名的方式间接实现函数调用创造了条件。
我在这里有一个错误理解:这里只是给变量分配了一个属性,至于链接器将相关属性的变量摆放在一起是由链接脚本指导完成的,而非连接器自动完成的。
另外就是,static修饰的静态全局函数,如果追踪查看发现没有内部函数通过使用变量名的方式实现“直接调用”,当然源文件之外的函数更加无法直接调用了(static的特性),表面上看是只完成了定义工作,没有访问动作。后面深层看,采用了一种高超不常用方式实现间接访问。(也说明了c语言不是绝对的而是相对的,c语言是灵活的,底层的,有时甚至是从内存角度直接考虑问题)。
再次,看看函数console_init():
void __init console_init(void) { initcall_t *call; /**函数指针变量 */ /* Setup the default TTY line discipline. */ tty_ldisc_begin(); /* * set up the console device so that later boot sequences can * inform about problems etc.. */ call = __con_initcall_start; /**这个就是initcall属性函数调用,而且是使用函数只恨间接调用的 */ while (call < __con_initcall_end) { (*call)(); call++; } }这个函数体内部,定义了一个函数指针类型变量,并且使用了while循环对从__con_initcall_start~__con_initcall_end的每一个函数指针变量进行解引用后访问(*call()),最终实现了函数指针变量绑定的函数的调用。
最后,追踪看看__con_initcall_start、__con_initcall_end这两个函数指针变量。
使用,vscode的搜索功能(clangd可以非常快速找到),找到了如下内容:
源文件int.h中的:
extern initcall_t __con_initcall_start[], __con_initcall_end[];首先c语言是一种强类型语言,无论是变量或者常量都要指定一个类型进行限制约束才能使用,也就说变量或者常量必须要有类型进行修饰/限制/约束才有意义,才能使用。这其实是函数指针常量声明(类型既可以修饰变量也可以修饰常量),声明了两个函数指针常量__con_initcall_start、__con_initcall_end,之所以使用数组方式盛行函数指针常量,是因为在这里数组这个概念比指针这个概念更贴切,暗含了对函数指针常量调用实际上是要进行数组相关操作。
链接脚本:vmlinux.lds中:
其中"."表示的就是代码段当前地址常量,这就话的含义就是链接脚本vmlinux.lds指导链接器将具有段属性为.con_initcall.init的变量放在一起。
看到这里基本上就捋清楚了整个过程,其实从宏观上也是面向对象的编写方法:各个各的,各完成各自的工作,然后进行打通链接(函数指针变量定义并初始化),这样整个过程就是通的了。
可以再看看我的另外一篇文章:
面向对象:linux内核中函数转数据的用法-CSDN博客文章浏览阅读199次,点赞4次,收藏5次。函数转数据”是我自己起的名称,可能不太准确,具体含义是:在一个源文件(模块)内部定义了好多static修饰的静态函数,一般用法源文件中另外一个函数去直接调用这个函数,但是“函数转数据”的用法是这样的:用到了函数指针变量,例如定义了一个结构体类型,结构体成员包含各种函数指针类型的变量,然后另一一个结构体类型的全局变量,全局变量初始化的时候被赋绑定的函数名,最后通过访问这个全局变量里面的成员,来间接实现对函数的访问。整个过程的好处就是各个各自的,互不干涉,另外就是实现了“隔离”,互补干涉。https://blog.csdn.net/duanjianbo3330/article/details/161655402?spm=1011.2124.3001.6209