在写驱动的过程中,对于入口函数与出口函数我们会用一句话来修饰他们: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结束,调用在这区间内的函数,那么这些函数在哪里定义的呢?
1 static void __init do_initcalls(void) 2 { 3 initcall_t *call; 4 int count = preempt_count(); 5 6 for (call = __initcall_start; call < __initcall_end; call++) {/* 调用__initcall_start到__initcall_end内的函数*/ 7 ktime_t t0, t1, delta; 8 char *msg = NULL; 9 char msgbuf[40]; 10 int result; 11 12 if (initcall_debug) { 13 printk("Calling initcall 0x%p", *call); 14 print_fn_descriptor_symbol(": %s()", 15 (unsigned long) *call); 16 printk("\n"); 17 t0 = ktime_get(); 18 } 19 20 result = (*call)(); 21 22 if (initcall_debug) { 23 t1 = ktime_get(); 24 delta = ktime_sub(t1, t0); 25 26 printk("initcall 0x%p", *call); 27 print_fn_descriptor_symbol(": %s()", 28 (unsigned long) *call); 29 printk(" returned %d.\n", result); 30 31 printk("initcall 0x%p ran for %Ld msecs: ", 32 *call, (unsigned long long)delta.tv64 >> 20); 33 print_fn_descriptor_symbol("%s()\n", 34 (unsigned long) *call); 35 } 36 37 if (result && result != -ENODEV && initcall_debug) { 38 sprintf(msgbuf, "error code %d", result); 39 msg = msgbuf; 40 } 41 if (preempt_count() != count) { 42 msg = "preemption imbalance"; 43 preempt_count() = count; 44 } 45 if (irqs_disabled()) { 46 msg = "disabled interrupts"; 47 local_irq_enable(); 48 } 49 if (msg) { 50 printk(KERN_WARNING "initcall at 0x%p", *call); 51 print_fn_descriptor_symbol(": %s()", 52 (unsigned long) *call); 53 printk(": returned with %s\n", msg); 54 } 55 } 56 57 /* Make sure there is no pending stuff from the initcall sequence */ 58 flush_scheduled_work(); 59 }
继续往下看,我们搜索整个内核源码,发现找不到__initcall_start与__initcall_end的定义,其实这两个变量被定义在arch/arm/kernel/vmlinux.lds中,它是在内核编译的时候生成的,是整个内核源码的链接脚本,我们把关键部分代码抽出来。可以看到在__initcall_start与__initcall_end之间又被分成了许多段:从*(.initcall0.init) ~*(.initcall7s.init)。
1 .init : { /* Init code and data */ 2 *(.init.text) 3 _einittext = .; 4 __proc_info_begin = .; 5 *(.proc.info.init) 6 __proc_info_end = .; 7 __arch_info_begin = .; 8 *(.arch.info.init) 9 __arch_info_end = .; 10 __tagtable_begin = .; 11 *(.taglist.init) 12 __tagtable_end = .; 13 . = ALIGN(16); 14 __setup_start = .; 15 *(.init.setup) 16 __setup_end = .; 17 __early_begin = .; 18 *(.early_param.init) 19 __early_end = .; 20 __initcall_start = .; 21 *(.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) 22 __initcall_end = .; 23 __con_initcall_start = .; 24 *(.con_initcall.init) 25 __con_initcall_end = .; 26 __security_initcall_start = .; 27 *(.security_initcall.init) 28 __security_initcall_end = .; 29 30 . = ALIGN(32); 31 __initramfs_start = .; 32 usr/built-in.o(.init.ramfs) 33 __initramfs_end = .; 34 35 . = ALIGN(4096); 36 __per_cpu_start = .; 37 *(.data.percpu) 38 __per_cpu_end = .; 39 40 __init_begin = _stext; 41 *(.init.data) 42 . = ALIGN(4096); 43 __init_end = .; 44 45 }
分析到这里,我们回过头再继续看module_init的定义,它被定义在include\linux\init.h中:
194 #define module_init(x) __initcall(x); 135 #define __initcall(fn) device_initcall(fn) 130 #define device_initcall(fn) __define_initcall("6",fn,6) 107 #define __define_initcall(level,fn,id) 108 static initcall_t __initcall_##fn##id __attribute_used__ 109 __attribute__((__section__(".initcall" level ".init"))) = fn 76 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循环而调用到在其他地方所定义的段。
Linux内核源码阅读记录一之分析存储在不同段中的函数调用过程
原文:https://www.cnblogs.com/andyfly/p/11332997.html