linux内核使用链接脚本模仿module_init机制实战

嵌入式技术

1368人已加入

描述

编写过设备驱动就会经常碰到module_init这个宏来定义驱动入口函数。这个宏定义了一个函数指针指向我们的驱动入口函数,等到上电的时候就将这些一个个的函数指针拿出来调用,那么各个驱动得到加载。特别的是:这些函数指针是存放在linux kernel本体的某个段里。这是通过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镜像

  1. 了解自定义数据结构通过链接脚本如何放入kernel镜像
  2. 了解如果在合适时候使用这些数据结构

在这我将定义一个数据结构,放入自定义的段里面,然后在驱动加载的时候,取出这个数据结构里面的数据,打印出来。

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.结果

Linux

可以看到,在上电log里面用到了这个数据结构,数据结构大小是8,段的大小也是8,验证了数据结构存入段里面的空间分布。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分