编程语言中的反射机制
今天在开发的过程中需要把两个类似产品的代码进行合并,减少代码的维护量,同时可以减少物料的维护成本。
两个产品只是一个通信用结构体内的元素有些区别,结构体相同元素的便宜地址发生了变化,同时增加了一些元素。
在设计之初应该考虑到升级的问题,应该做到通信地址兼容,不改变原来的通信地址,但是由于前人已设计好了,且工程庞大,不易再修改。
在此基础上想到了,让程序动态的去动态的确定使用哪个结构体去使用,正好,这个结构体一个固定的便宜量中有一个产品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 = .; }
利用链接脚本可以得到每段的起始地址,然后在一段中遍历执行每一个初始化函数