驱动加载的本质

admin 2022年2月10日04:18:50评论53 views字数 3308阅读11分1秒阅读模式
来自公众号:人人都是极客

内核启动的过程中会通过函数 do_initcalls,将按顺序从 __initcall_start 开始,到 __initcall_end 结束的 section 中以函数指针的形式取出这些编译到内核的驱动模块中初始化函数起始地址,来依次完成相应的初始化。这些初始化函数由 __define_initcall(level,fn) 指示编译器在编译的时候,将这些初始化函数的起始地址值按照一定的顺序放在这个section中。

__initcall_start 到 __initcall_end 之间的 section,通过 vmlinux.lds 可以看到:

驱动加载的本质

宏 INIT_CALLS 中定义的这些 section 中放了一系列的函数,这些函数是用 pure_initcall,core_initcall 等宏定义的。如下所示:

#define INIT_CALLS_LEVEL(level)      
  VMLINUX_SYMBOL(__initcall##level##_start) = .;  
  KEEP(*(.initcall##level##.init))   
  KEEP(*(.initcall##level##s.init))   

#define INIT_CALLS       
  VMLINUX_SYMBOL(__initcall_start) = .;   
  KEEP(*(.initcallearly.init))    
  INIT_CALLS_LEVEL(0)     
  INIT_CALLS_LEVEL(1)     
  INIT_CALLS_LEVEL(2)     
  INIT_CALLS_LEVEL(3)     
  INIT_CALLS_LEVEL(4)     
  INIT_CALLS_LEVEL(5)     
  INIT_CALLS_LEVEL(rootfs)    
  INIT_CALLS_LEVEL(6)     
  INIT_CALLS_LEVEL(7)     
  VMLINUX_SYMBOL(__initcall_end) = .;

#define __define_initcall(fn, id) 
 static initcall_t __initcall_name(fn, id) __used 
 __attribute__((__section__(".initcall" #id ".init"))) = fn;

  
#define pure_initcall(fn)  __define_initcall(fn, 0)
#define core_initcall(fn)  __define_initcall(fn, 1)
#define core_initcall_sync(fn)  __define_initcall(fn, 1s)
#define postcore_initcall(fn)  __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
#define arch_initcall(fn)  __define_initcall(fn, 3)
#define arch_initcall_sync(fn)  __define_initcall(fn, 3s)
#define subsys_initcall(fn)  __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
#define fs_initcall(fn)   __define_initcall(fn, 5)
#define fs_initcall_sync(fn)  __define_initcall(fn, 5s)
#define rootfs_initcall(fn)  __define_initcall(fn, rootfs)
#define device_initcall(fn)  __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn)  __define_initcall(fn, 7)
#define late_initcall_sync(fn)  __define_initcall(fn, 7s)

所以,pure_initcall 定义的 initcall 函数放在 .initcall.0.init,core_initcall 定义的 initcall 函数放在 .initcall.1.init,以此类推。

do_initcalls

现在我们看下内核启动过程中,实现驱动加载的函数。

static void __init do_initcalls(void)
{
 int level;

 for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
  //依次调用不同等级的初始化函数
  do_initcall_level(level);
}
static void __init do_initcall_level(int level)
{
 initcall_t *fn;

 strcpy(initcall_command_line, saved_command_line);
 //initcall_level_names 是个数组,里面存放了各个级别的初始化函数级数名
 parse_args(initcall_level_names[level],
     initcall_command_line, __start___param,
     __stop___param - __start___param,
     level, level,
     NULL, &repair_env_string);

 //执行各个初始化级别的函数
 for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
  //初始化同一级别中的函数
  do_one_initcall(*fn);
}

initcall_level_names 是个数组,里面存放了各个级别的初始化函数级数名。如下所示:

static char *initcall_level_names[] __initdata = {
 "early",
 "core",
 "postcore",
 "arch",
 "subsys",
 "fs",
 "device",
 "late",
};

即轮询执行各个级别的函数,然后通过 do_one_initcall 初始化同一级别中的函数。

module_init

以 module_init 为例子,因为:

#define module_init(x) __initcall(x);
#define __initcall(fn) device_initcall(fn);
#define device_initcall(fn)  __define_initcall(fn, 6);

所以:

module_init(gpu_init);

变为:

#define __define_initcall(gpu_init, 6) 
static initcall_t __initcall_gpu_init6 __used 
__attribute__((__section__(".initcall.6.init"))) = gpu_init;

通过查找内核映射表 System.map,可以看到 __initcall_gpu_init6 函数指针:

驱动加载的本质


--- EOF ---


推荐↓↓↓

原文始发于微信公众号(Linux学习):驱动加载的本质

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年2月10日04:18:50
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   驱动加载的本质http://cn-sec.com/archives/771974.html

发表评论

匿名网友 填写信息