编程语言中的反射机制

今天在开发的过程中需要把两个类似产品的代码进行合并,减少代码的维护量,同时可以减少物料的维护成本。
两个产品只是一个通信用结构体内的元素有些区别,结构体相同元素的便宜地址发生了变化,同时增加了一些元素。
在设计之初应该考虑到升级的问题,应该做到通信地址兼容,不改变原来的通信地址,但是由于前人已设计好了,且工程庞大,不易再修改。
在此基础上想到了,让程序动态的去动态的确定使用哪个结构体去使用,正好,这个结构体一个固定的便宜量中有一个产品ID能够用来区分是哪一种产品。这个恰好是反射机制,在此记录学习一下反射机制

反射机制

计算机中的反射,是在运行的时候来自我检查,并对内部成员进行操作。就是说这个变量的类型可以动态的改变,在运行的时候确定它的作用,很多高级编程语言中有这些机制:Python,lua,c#,java都自带有这个机制。下面以lua作为例子

local AllTypes = {
  Type1 = 1,
  Type2 = 2,
  Type3 = 3,
}
local typeClsHash = {}
typeClsHash[AllTypes.Type1] = Cls1
typeClsHash[AllTypes.Type2] = Cls2
typeClsHash[AllTypes.Type3] = Cls3

local theType = AllTypes.Type2

local cls = typeClsHash[theType]
local instance = cls:new()
instance:doSth()

因为没有指定变量的类型,所以可以把任意类型赋值给他,在运行的时候检测当前的类型。

c实现反射机制

高级语言的反射机制,简单来说就是可以通过字符串型获取对应的类或者函数。但是c语言没有这个机制,需要自己动手来实现。

基础形式,c语言结构化编程基础实现

1)声明

typedef void (*callback)(void);

typedef struct {
const char *name;
callback fn;
}callback_t;
void f0();
void f1();
callback_t callbacks[] = {
{ "cmd0", f0},
{"cmd1", f1},
}
void f0()
{
}
void f1()
{
}

2)调用

void do_callback(const char *name) 
{
  size_t i;
  for (i = 0; i < sizeof(callbacks) / sizeof(callbacks[0]); i++) {
    if (!strcmp(callbacks[i].name, name)) {
      return callbacks[i].fn();
    }
  }
}

这种方式的不便之处在于,当需要映射的函数因分散在不同文件时,每增加一个新的映射都需要修改这个数组,以及头文件。

利用自定义段

gcc支持通过使用__attribute__((section())),将函数、变量放到指定的数据段中。
也就是说,可以让编译器帮我们完成1中向数组添加成员的动作。
借助此机制,回调函数可以在任意文件申明,不需要修改其他文件。

内核驱动初始化函数的遍历执行就采用了这种方法。
下面是init/main.c对于初始化函数的遍历调用

extern initcall_t __initcall_start[];
extern initcall_t __initcall0_start[];
extern initcall_t __initcall1_start[];
extern initcall_t __initcall2_start[];
extern initcall_t __initcall3_start[];
extern initcall_t __initcall4_start[];
extern initcall_t __initcall5_start[];
extern initcall_t __initcall6_start[];
extern initcall_t __initcall7_start[];
extern initcall_t __initcall_end[];

static initcall_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
}; //这些值都是在lds链接脚本中得到的数据

/* Keep these in sync with initcalls in include/linux/init.h */
static char *initcall_level_names[] __initdata = {
"early",
"core",
"postcore",
"arch",
"subsys",
"fs",
"device",
"late",
};

static void __init do_initcall_level(int level)
{
extern const struct kernel_param __start___param[], __stop___param[];
initcall_t *fn;

strcpy(static_command_line, saved_command_line);
parse_args(initcall_level_names[level],
  static_command_line, __start___param,
  __stop___param - __start___param,
  level, level,
  &repair_env_string);

for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
}

static void __init do_initcalls(void)
{
int level;

for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}

配合arch/xxx/kernel/vmlinux.lds

 . = ALIGN(4096); /* Init code and data */
 __init_begin = .;
 . = ALIGN(4096); 
 .init.text : AT(ADDR(.init.text) - 0) 
 { 
   _sinittext = .; 
   *(.init.text) *(.cpuinit.text) *(.meminit.text) _einittext = .; 
 }
 .init.data : AT(ADDR(.init.data) - 0) 
 { 
   *(.init.data) *(.cpuinit.data) *(.meminit.data) *(.init.rodata) *(.cpuinit.rodata) *(.meminit.rodata) . = ALIGN(32); 
   __dtb_start = .; 
   *(.dtb.init.rodata) __dtb_end = .; 
   . = ALIGN(16); 
   __setup_start = .; 
   *(.init.setup) __setup_end = .; 
   __initcall_start = .; 
   *(.initcallearly.init) 
   __initcall0_start = .; 
   *(.initcall0.init) *(.initcall0s.init) 
   __initcall1_start = .; 
   *(.initcall1.init) *(.initcall1s.init) 
   __initcall2_start = .; 
   *(.initcall2.init) *(.initcall2s.init) 
   __initcall3_start = .; 
   *(.initcall3.init) *(.initcall3s.init) 
   __initcall4_start = .; 
   *(.initcall4.init) *(.initcall4s.init) 
   __initcall5_start = .; 
   *(.initcall5.init) *(.initcall5s.init) 
   __initcallrootfs_start = .; 
   *(.initcallrootfs.init) *(.initcallrootfss.init) 
   __initcall6_start = .; 
   *(.initcall6.init) *(.initcall6s.init) 
   __initcall7_start = .; 
   *(.initcall7.init) *(.initcall7s.init) 
   __initcall_end = .; 
   __con_initcall_start = .; 
   *(.con_initcall.init) 
   __con_initcall_end = .; 
   __security_initcall_start = .; 
   *(.security_initcall.init)
   __security_initcall_end = .; }

利用链接脚本可以得到每段的起始地址,然后在一段中遍历执行每一个初始化函数