编写过设备驱动就会经常碰到module_init这个宏来定义驱动入口函数。这个宏定义了一个函数指针指向我们的驱动入口函数,等到上电的时候就将这些一个个的函数指针拿出来调用,那么各个驱动得到加载。特别的是:这些函数指针是存放在linuxkernel本体的某个段里。这是通过gnu 的__attribute__来修饰的。
实际上,kernel里面有非常多的段,这些段的起始地址和结束地址都能被源码里得到,因此学会看链接脚本和引用里面的段再或者是自定义段对于理解kernel的源码大有益处。
(资料图片)
链接脚本
任何一个可执行程序是被链接脚本将一个个的.o链接起来的。kernel也不例外,kernel的每个架构都有一个默认的链接脚本路径,如:arch/arm/kernel/vmlinux.lds
.init.arch.info : { __arch_info_begin = .; *(.arch.info.init)//机器信息段 __arch_info_end = .; }
部分段的形式如上,__arch_info_begin 和__arch_info_end 就能被源码引用,得到这个地址范围的数据。怎么把数据放入这些段:使用attribute修饰你想要放入这个段的数据结构 。
对于kernel的段组成有非常多,按需查看源码即可。
实战
目的:
了解自定义数据结构通过链接脚本如何放入kernel镜像
了解自定义数据结构通过链接脚本如何放入kernel镜像了解如果在合适时候使用这些数据结构在这我将定义一个数据结构,放入自定义的段里面,然后在驱动加载的时候,取出这个数据结构里面的数据,打印出来。
1.在vmlinux.lds增加自定义段
.my_section :{ my_section_begin = .; *(.my_section) my_section_end = .; }
在vmlinux.lds中间找个位置,定义一个.my_section段,并且使用my_section_begin 和my_section_end 记录这个段的起始地址和结束地址。
2.定义一个宏修饰数据结构放在.my_section
#define my_section __attribute__((__section__(".my_section")))
my_section 给这个修饰符取一个常用明显的名字,常见于kernel的用法. attribute (( section ("xxx")))的语法可以参考gnu相关文档。
意思是使用my_section 修饰的数据结构都会被放到.my_section这个段里面
3.声明段起始地址和结束地址
extern const struct person my_section_begin[],my_section_end[];
extern表示my_section_begin,my_section_end已经在链接脚本里面定义了,使用一个同名数组名表示这个地址,这个段里面存放的是一个个的自定义数据结构struct person。
4.修饰自定义数据结构
struct person{ int age; char *name;};struct person my_section lzy ={ 18, "liangzhengyi",};
经过my_section修饰,lzy 这个实例会被放到vmlinux.lds定义的段里面。
5.完整代码
#include < linux/kernel.h >#include < linux/module.h >#include < uapi/linux/sched.h >#include < linux/init_task.h >#include < linux/init.h >#include < linux/fdtable.h >#include < linux/fs_struct.h >#include < linux/mm_types.h >#include < linux/list.h >#include < linux/types.h >#define my_section __attribute__((__section__(".my_section")))struct person{ int age; char *name;};struct person my_section lzy ={ 18, "liangzhengyi",};extern const struct person my_section_begin[],my_section_end[];static int __init section_add_init(void){ struct person *addr_begin = my_section_begin; struct person *addr_end = my_section_end; printk("section_add_init\\n"); printk("find section %d %s",addr_begin- >age,addr_begin- >name); printk("my section lenth:%d\\n",(my_section_end-my_section_begin)*sizeof(struct person)); return 0;}//内核模块退出函数static void __exit section_add_exit(void){ printk("section_add_exit\\n");}module_init(section_add_init);//入口module_exit(section_add_exit);//出口MODULE_LICENSE("GPL");//许可证
在驱动里,我引用段的起始地址得到我的数据结构,并且我利用了这些数据(打印,最后我打印这个段的大小。
需要注意的是,这个驱动代码需要编译进kernel,因为里面要用到的变量是属于kernel的一部分,段就是kernel的一部分。
6.结果
可以看到,在上电log里面用到了这个数据结构,数据结构大小是8,段的大小也是8,验证了数据结构存入段里面的空间分布。