前言
前面有学习过模块化编程,及其字符设备的编写,现在我们深入学习一下字符设备驱动编程,了解字符设备编程所用函数之间的调用关系。
静态加载与动态加载概念
静态加载:将驱动代码直接编译进内核,内核在启动过程中就会自动加载内核;
动态加载:将驱动代码单独编译成.ko格式的文件,再用insmod命令在需要的时候加载内核,在不需要驱动的时候用rmmod命令卸载驱动。静态加载一般用于基础功能的驱动,反正都是迟早是要加载的,编译进内核效率更高;动态加载一般用于扩展功能的驱动,这个设备可能
在加载驱动的时候我们使用:
module_init();
在卸载驱动的时候我们使用:
module_exit();
在我们使用insmod加载驱动的时候就是间接调用module_init(),使用rmmod卸载驱动的时候间接调用module_exit()。为什么是间接调用呢?insmod和rmmod其实并不能识别module_init和module_exit,它们只能识别init_module和cleanup_module。
静态加载
在内核中如果是静态编译,函数调用关系如下:
根据上面的调用关系,可以知道module_init(x)和module_exit(x)的展开结果:
展开前:
module_init(x);
展开后:
static initcall_t __initcall_x6 __used \ __attribute__((__section__(".initcall6.init"))) = x;
展开前:
module_exit(x);
展开后:
static exitcall_t __exitcall_x __used __attribute__ ((__section__(#.exitcall.exit))) = x
动态加载
在我们使用insmod加载驱动的时候就是间接调用module_init(),使用rmmod卸载驱动的时候间接调用module_exit()。为什么是间接调用呢?insmod和rmmod其实并不能识别module_init和module_exit,它们只能识别init_module和cleanup_module。
/* Each module must use one module_init(). */ #define module_init(initfn) \ static inline initcall_t __inittest(void) \ { return initfn; } \ int init_module(void) __attribute__((alias(#initfn))); /* This is only required if you want to be unloadable. */ #define module_exit(exitfn) \ static inline exitcall_t __exittest(void) \ { return exitfn; } \ void cleanup_module(void) __attribute__((alias(#exitfn)));
驱动导出
linux内核采用的是模块化的形式管理内核代码。内核中每个模块之间是相互独立的,也就是说A模块的全局变量和函数,B模块是无法访问的。若B模块想要使用A模块中的已有符号,那么必须将A模块中的符号做符号导出,导出到模块符号表中,然后B模块可以使用A模块导出的符号。我们常常使用下面宏来实现驱动导出。
EXPORT_SYMBOL(函数名);
EXPORT_SYMBOL标签内定义的函数或者符号对全部内核代码公开,不用修改内核代码就可以在您的内核模块中直接调用,即使用EXPORT_SYMBOL可以将一个函数以符号的方式导出给其他模块使用。内核中的定义如下:
/* For every exported symbol, place a struct in the __ksymtab section */ #define __EXPORT_SYMBOL(sym, sec) \ extern typeof(sym) sym; \ __CRC_SYMBOL(sym, sec) \ static const char __kstrtab_##sym \ __attribute__((section("__ksymtab_strings"), aligned(1))) \ = VMLINUX_SYMBOL_STR(sym); \ extern const struct kernel_symbol __ksymtab_##sym; \ __visible const struct kernel_symbol __ksymtab_##sym \ __used \ __attribute__((section("___ksymtab" sec "+" #sym), unused)) \ = { (unsigned long)&sym, __kstrtab_##sym } #define EXPORT_SYMBOL(sym) \ __EXPORT_SYMBOL(sym, "")
我们只需要会使用即可(使用EXPORT_SYMBOL()导出的符号,都将在ksymtab节中放置一个与之关联的结构体)。