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

发布时间:2024-08-19  

在写驱动的过程中,对于入口函数与出口函数我们会用一句话来修饰他们: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 %sn', 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的定义,它被定义在includelinuxinit.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循环而调用到在其他地方所定义的段。


文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>