全球快播:linux内核使用链接脚本模仿module_init机制实战

来源:justice与初中生小许   2023-06-16 11:43:47

编写过设备驱动就会经常碰到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,验证了数据结构存入段里面的空间分布。

精彩推送