注:以下讨论建立在内核 3.2.9基础之上。
linux中驱动模块的加载由module_init() 宏功能出发,其用法例:
int __init fimc_md_init(void)
{
int ret;
request_module("s5p-csis");
ret = fimc_register_driver();
if (ret)
return ret;
return platform_driver_register(&fimc_md_driver);
}
module_init(fimc_md_init);
从上面的例子可以看出,该宏函数的参数是以宏__init 修饰的函数的函数名,故而可知其传入的是函数指针。那__init这个又是什么呢,我们把它展开来看一下,
#define __init __attribute__ ((__section__ (".text.init")))
好的,这里遇到了__attribute__ ,为了更进一步的理解,这里不得不提及到 GNU C编译器扩展的一些知识作为辅助,其具体内容可见转载的《GNU C 扩展之__attribute__ 机制简介》这篇文章,这里只对__attribute__ 做简单的描述,__attribute__是GNU C编译器的扩展功能,其作用是修改函数、变量和类型的编译属性,括号内的__section__表示段属性,整句的意思是将该宏修饰的函数或变量放入指定的段中,这里是.text.init 段,该例子中 __init 的作用就是 将函数fimc_md_init(void)编译的时候放入段.text.init 中,由于一些初始化操作只会运行一次,为了节约内存,在这些段内的代码运行完之后,linux会将其代码占用的内存释放掉。现在转过来继续聊module_init(),先把它逐层展开:
#define module_init(x) __initcall(x);
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
好的,基本展开完成,这里出现了initcall_t,查看一下定义,
typedef int (*initcall_t)(void);很清晰,就是typedef了个函数指针类型,连贯起来理解以上宏内容就是,定义了一个函数指针变量__initcall_##fn##id,然后将其初始化为fn,并通过__attribute__的编译选项,将其放入段".initcall" level ".init" 中,这里的##是预编译时的字串连接符,该例子中把字串替换进去后的结果是:
static initcall_t __initcall_fimc_md_init6 __used
__attribute__((__section__(".initcall6.init"))) = fn
到现在我们明白了,module_init(x)的作用就是在.initcall6.init段中定义一个函数指针变量,然后将其初始化成 x。那这样做的具体作用是啥呢,这个时候我们有必要先看看.initcall6.init了。
上面说到的#define device_initcall(fn) __define_initcall("6",fn,6) 出现在 init.h文件中,跟它并列的几个宏如下:
#define pure_initcall(fn) __define_initcall("0",fn,0)
#define core_initcall(fn) __define_initcall("1",fn,1)
#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
#define postcore_initcall(fn) __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn)__define_initcall("2s",fn,2s)
#define arch_initcall(fn) __define_initcall("3",fn,3)
#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#define subsys_initcall(fn) __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn) __define_initcall("5",fn,5)
#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn) __define_initcall("7",fn,7)
#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
按照上面分析,以上宏最终会将定义的相应的函数指针变量放到以下段中:
.initcall0.init
.initcall0s.init
.initcall1.init
.initcall1s.init
.initcall2.init
.initcall2s.init
…..
.initcall7s.init
我们看看以上段标记引用的地方,在vmlinux.lds.h中代码片段:
#defineINITCALLS \
*(.initcallearly.init) \
VMLINUX_SYMBOL(__early_initcall_end) = .; \
*(.initcall0.init) \
*(.initcall0s.init) \
*(.initcall1.init) \
*(.initcall1s.init) \
*(.initcall2.init) \
*(.initcall2s.init) \
*(.initcall3.init) \
*(.initcall3s.init) \
*(.initcall4.init) \
*(.initcall4s.init) \
*(.initcall5.init) \
*(.initcall5s.init) \
*(.initcallrootfs.init) \
*(.initcall6.init) \
*(.initcall6s.init) \
*(.initcall7.init) \
*(.initcall7s.init)
以上各段按顺序排列,初始化运行的时候会按找上面的顺序挨个运行,具体调用位置往下看。
#defineINIT_CALLS \
VMLINUX_SYMBOL(__initcall_start) = .; \ // 导出标记__initcall_start
INITCALLS \
VMLINUX_SYMBOL(__initcall_end) = .;
#define INIT_DATA_SECTION(initsetup_align) \
.init.data : AT(ADDR(.init.data) - LOAD_OFFSET) { \
INIT_DATA \
INIT_SETUP(initsetup_align) \
INIT_CALLS \
CON_INITCALL \
SECURITY_INITCALL \
INIT_RAM_FS \
}
在 vmlinux.lds.S中,
SECTIONS
{
……
/* Will be freed after init */
__init_begin = ALIGN(PAGE_SIZE);
INIT_TEXT_SECTION(PAGE_SIZE)
INIT_DATA_SECTION(16) /*编译器指定的初始化对齐方式*/
PERCPU_SECTION(L1_CACHE_BYTES)
/* Align to THREAD_SIZE rather than PAGE_SIZE here so any padding page
needed for the THREAD_SIZE aligned init_task gets freed after init */
. = ALIGN(THREAD_SIZE);
__init_end = .;
/* Freed after init ends here */
……….
}
Main.c中
extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[]; // 眼熟吧,就是上面提到的导出的符号
static void __initdo_pre_smp_initcalls(void)
{
initcall_t *fn;
for (fn = __initcall_start; fn < __early_initcall_end; fn++)// 按照上面说的段顺序,依次调用标记之间的所有函数指针,这种不同段的调用顺序,也就是分布在各段函数指针所指向的初始化代码的先后顺序,但同一段内的不同模块的函数指针调用顺序不确定。
do_one_initcall(*fn);
}
往上寻找一下该函数的调用关系,
static int __init kernel_init(void * unused)
{
/*
* Wait until kthreadd is all set-up.
*/
wait_for_completion(&kthreadd_done);
/*
* init can allocate pages on any node
*/
set_mems_allowed(node_states[N_HIGH_MEMORY]);
/*
* init can run on any cpu.
*/
set_cpus_allowed_ptr(current, cpu_all_mask);
cad_pid = task_pid(current);
smp_prepare_cpus(setup_max_cpus);
do_pre_smp_initcalls();
lockup_detector_init();
smp_init();
sched_init_smp();
do_basic_setup();
/* Open the /dev/console on the rootfs, this should never fail */
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
printk(KERN_WARNING "Warning: unable to open an initial console.\n");
………..
init_post();
return 0;
}
static noinline void __init_refokrest_init(void)
{
int pid;
rcu_scheduler_starting();
/*
* We need to spawn init first so that it obtains pid 1, however
* the init task will end up wanting to create kthreads, which, if
* we schedule it before we create kthreadd, will OOPS.
*/
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); //启动 kernel_init
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
preempt_enable_no_resched();
schedule();
/* Call into cpu_idle with preempt disabled */
preempt_disable();
cpu_idle();
}
asmlinkage void __initstart_kernel(void) // 很明显了,这是启动 kernel的函数
{
char * command_line;
extern const struct kernel_param __start___param[], __stop___param[];
smp_setup_processor_id();
/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();
debug_objects_early_init();
/*
* Set up the the initial canary ASAP:
*/
boot_init_stack_canary();
cgroup_init_early();
…..
acpi_early_init(); /* before LAPIC and SMP init */
sfi_init_late();
ftrace_init();
/* Do the rest non-__init'ed, we're now alive */
rest_init();
}
以上调用过程已经很明晰了,module_init()的功能介绍也就到此结束了。
本文详细介绍了Linux驱动模块加载过程中module_init()宏的作用和工作原理。通过示例代码展示了module_init()如何将函数放入特定段,并通过内核初始化调用顺序完成驱动初始化。内容包括宏展开、函数指针、编译器扩展以及内核初始化调用流程。
2970

被折叠的 条评论
为什么被折叠?



