Linux内核源码阅读记录一之分析存储在不同段中的函数调用过程

时间:2022-01-10 17:18:35

在写驱动的过程中,对于入口函数与出口函数我们会用一句话来修饰他们:module_init与module_exit,那会什么经过修饰后,内核就能狗调用我们编写的入口函数与出口函数呢?下面就来分析内核调用module_init的过程(这里暂时分析编译进内核的模块,不涉及动态加载的模块),以这个过程为例子来了解内核对于不同段的函数的调用过程。

下面从内核的start_kernel函数开始分析,下面是调用过程:

start_kernel
rest_init
kernel_init
do_basic_setup
do_initcalls()

直接看到do_initcalls函数,看到第6行的for循环,它从__initcall_start开始到__initcall_end结束,调用在这区间内的函数,那么这些函数在哪里定义的呢?

 static void __init do_initcalls(void)
{
initcall_t *call;
int count = preempt_count(); for (call = __initcall_start; call < __initcall_end; call++) {/* 调用__initcall_start到__initcall_end内的函数*/
ktime_t t0, t1, delta;
char *msg = NULL;
char msgbuf[];
int result; if (initcall_debug) {
printk("Calling initcall 0x%p", *call);
print_fn_descriptor_symbol(": %s()",
(unsigned long) *call);
printk("\n");
t0 = ktime_get();
} result = (*call)(); if (initcall_debug) {
t1 = ktime_get();
delta = ktime_sub(t1, t0); printk("initcall 0x%p", *call);
print_fn_descriptor_symbol(": %s()",
(unsigned long) *call);
printk(" returned %d.\n", result); printk("initcall 0x%p ran for %Ld msecs: ",
*call, (unsigned long long)delta.tv64 >> );
print_fn_descriptor_symbol("%s()\n",
(unsigned long) *call);
} if (result && result != -ENODEV && initcall_debug) {
sprintf(msgbuf, "error code %d", result);
msg = msgbuf;
}
if (preempt_count() != count) {
msg = "preemption imbalance";
preempt_count() = count;
}
if (irqs_disabled()) {
msg = "disabled interrupts";
local_irq_enable();
}
if (msg) {
printk(KERN_WARNING "initcall at 0x%p", *call);
print_fn_descriptor_symbol(": %s()",
(unsigned long) *call);
printk(": returned with %s\n", msg);
}
} /* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}

继续往下看,我们搜索整个内核源码,发现找不到__initcall_start与__initcall_end的定义,其实这两个变量被定义在arch/arm/kernel/vmlinux.lds中,它是在内核编译的时候生成的,是整个内核源码的链接脚本,我们把关键部分代码抽出来。可以看到在__initcall_start与__initcall_end之间又被分成了许多段:从*(.initcall0.init) ~*(.initcall7s.init)。

  .init : { /* Init code and data        */
*(.init.text)
_einittext = .;
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist.init)
__tagtable_end = .;
. = ALIGN();
__setup_start = .;
*(.init.setup)
__setup_end = .;
__early_begin = .;
*(.early_param.init)
__early_end = .;
__initcall_start = .;
*(.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)
__initcall_end = .;
__con_initcall_start = .;
*(.con_initcall.init)
__con_initcall_end = .;
__security_initcall_start = .;
*(.security_initcall.init)
__security_initcall_end = .; . = ALIGN();
__initramfs_start = .;
usr/built-in.o(.init.ramfs)
__initramfs_end = .; . = ALIGN();
__per_cpu_start = .;
*(.data.percpu)
__per_cpu_end = .; __init_begin = _stext;
*(.init.data)
. = ALIGN();
__init_end = .; }

分析到这里,我们回过头再继续看module_init的定义,它被定义在include\linux\init.h中:

 #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 __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn
typedef int (*initcall_t)(void);

根据以上定义,最终把module_init展开可以得到:这句话的意思就是只要调用module_init(x),就把x定义为initcall_t类型的函数,并且这个函数函数名为__initcall_x6,它被存放在.initcall6.init中,而这个段正好位于__initcall_start与__initcall_end之间。所以它被内核调用的时机就是在do_initcalls函数的for循环中。

#define module_init(x)     static initcall_t __initcall_x6 __attribute_used__  __attribute__((__section__(".initcall" 6 ".init"))) = x

对于其他的段,也是类似的,内核会在某个地方利用for循环而调用到在其他地方所定义的段。