linux子系统的初始化_subsys_initcall():那些入口函数【转】

内核选项的解析完成之后,各个子系统的初始化即进入第二部分—入口函数的调用。通常USB、PCI这样的子系统都会有一个名为subsys_initcall的入口,如果你选择它们作为研究内核的切入点,那么就请首先找到它。

朱德庸在《关于上班这件事》里说,要花前半生找入口,花后半生找出口。可见寻找入口对于咱们这一生,对于看内核代码这件事儿都是无比重要的。

但是很多时候,入口并不仅仅只有subsys_initcall一个,比如PCI。

以下代码来自 linux内核源码中 include/linux/init.h 文件

  1. 117 #define pure_initcall(fn) __define_initcall("0",fn,1)
  2. 118
  3. 119 #define core_initcall(fn) __define_initcall("1",fn,1)
  4. 120 #define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
  5. 121 #define postcore_initcall(fn) __define_initcall("2",fn,2)
  6. 122 #define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
  7. 123 #define arch_initcall(fn) __define_initcall("3",fn,3)
  8. 124 #define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
  9. 125 #define subsys_initcall(fn) __define_initcall("4",fn,4)
  10. 126 #define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
  11. 127 #define fs_initcall(fn) __define_initcall("5",fn,5)
  12. 128 #define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
  13. 129 #define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
  14. 130 #define device_initcall(fn) __define_initcall("6",fn,6)
  15. 131 #define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
  16. 132 #define late_initcall(fn) __define_initcall("7",fn,7)
  17. 133 #define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
  18. 134
  19. 135 #define __initcall(fn) device_initcall(fn)

这些入口有个共同的特征,它们都是使用__define_initcall宏定义的。它们的调用也不是随便的,而是按照一定顺序的,这个顺序就取决于__define_initcall宏。__define_initcall宏用来将指定的函数指针放到.initcall.init节里。

.initcall.init节

内核可执行文件由许多链接在一起的对象文件组成。对象文件有许多节,如文本、数据、init数据、bass等等。这些对象文件都是由一个称为链接器脚本的文件链接并装入的。这个链接器脚本的功能是将输入对象文件的各节映射到输出文件中;换句话说,它将所有输入对象文件都链接到单一的可执行文件中,将该可执行文件的各节装入到指定地址处。 vmlinux.lds是存在于arch/<target>/目录中的内核链接器脚本,它负责链接内核的各个节并将它们装入内存中特定偏移量处。在vmlinux.lds文件里查找initcall.init就可以看到下面的内容

  1. __inicall_start = .;
  2. .initcall.init : AT(ADDR(.initcall.init) – 0xC0000000) {
  3. *(.initcall1.init)
  4. *(.initcall2.init)
  5. *(.initcall3.init)
  6. *(.initcall4.init)
  7. *(.initcall5.init)
  8. *(.initcall6.init)
  9. *(.initcall7.init)
  10. }
  11. __initcall_end = .;

这就告诉我们.initcall.init节又分成了7个子节,而xxx_initcall入口函数指针具体放在哪一个子节里边儿是由xxx_initcall的定义中,__define_initcall宏的参数决定的,比如core_initcall将函数指针放在.initcall1.init子节,device_initcall将函数指针放在了.initcall6.init子节等等。各个子节的顺序是确定的,即先调用.initcall1.init中的函数指针再调用.initcall2.init中的函数指针,等等。不同的入口函数被放在不同的子节中,因此也就决定了它们的调用顺序。

注意:设备驱动程序中常见的module_init(x)函数,查看init.h文件发现,

#define module_init(x)__initcall(x);

#define __initcall(fn) device_initcall(fn)

#define device_initcall(fn) __define_initcall("6",fn,6)

这样推断 module_init 调用优先级为6低于subsys_initcall调用优先级4.

do_initcalls()函数

那些入口函数的调用由do_initcalls函数来完成。

do_initcall函数通过for循环,由__initcall_start开始,直到__initcall_end结束,依次调用识别到的初始化函数。而位于__initcall_start和__initcall_end之间的区域组成了.initcall.init节,其中保存了由xxx_initcall形式的宏标记的函数地址,do_initcall函数可以很轻松的取得函数地址并执行其指向的函数。

.initcall.init节所保存的函数地址有一定的优先级,越前面的函数优先级越高,也会比位于后面的函数先被调用。

由do_initcalls函数调用的函数不应该改变其优先级状态和禁止中断。因此,每个函数执行后,do_initcalls会检查该函数是否做了任何变化,如果有必要,它会校正优先级和中断状态。

另外,这些被执行的函数有可以完成一些需要异步执行的任务,flush_scheduled_work函数则用于确保do_initcalls函数在返回前等待这些异步任务结束。

  1. 666 static void __init do_initcalls(void)
  2. 667 {
  3. 668 initcall_t *call;
  4. 669 int count = preempt_count();
  5. 670
  6. 671 for (call = __initcall_start; call < __initcall_end; call++) {
  7. 672 ktime_t t0, t1, delta;
  8. 673 char *msg = NULL;
  9. 674 char msgbuf[40];
  10. 675 int result;
  11. 676
  12. 677 if (initcall_debug) {
  13. 678 printk("Calling initcall 0x%p", *call);
  14. 679 print_fn_descriptor_symbol(": %s()",
  15. 680 (unsigned long) *call);
  16. 681 printk("/n");
  17. 682 t0 = ktime_get();
  18. 683 }
  19. 684
  20. 685 result = (*call)();
  21. 686
  22. 687 if (initcall_debug) {
  23. 688 t1 = ktime_get();
  24. 689 delta = ktime_sub(t1, t0);
  25. 690
  26. 691 printk("initcall 0x%p", *call);
  27. 692 print_fn_descriptor_symbol(": %s()",
  28. 693 (unsigned long) *call);
  29. 694 printk(" returned %d./n", result);
  30. 695
  31. 696 printk("initcall 0x%p ran for %Ld msecs: ",
  32. 697 *call, (unsigned long long)delta.tv64 >> 20);
  33. 698 print_fn_descriptor_symbol("%s()/n",
  34. 699 (unsigned long) *call);
  35. 700 }
  36. 701
  37. 702 if (result && result != -ENODEV && initcall_debug) {
  38. 703 sprintf(msgbuf, "error code %d", result);
  39. 704 msg = msgbuf;
  40. 705 }
  41. 706 if (preempt_count() != count) {
  42. 707 msg = "preemption imbalance";
  43. 708 preempt_count() = count;
  44. 709 }
  45. 710 if (irqs_disabled()) {
  46. 711 msg = "disabled interrupts";
  47. 712 local_irq_enable();
  48. 713 }
  49. 714 if (msg) {
  50. 715 printk(KERN_WARNING "initcall at 0x%p", *call);
  51. 716 print_fn_descriptor_symbol(": %s()",
  52. 717 (unsigned long) *call);
  53. 718 printk(": returned with %s/n", msg);
  54. 719 }
  55. 720 }
  56. 721
  57. 722 /* Make sure there is no pending stuff from the initcall sequence */
  58. 723 flush_scheduled_work();
  59. 724 }

目前研究Linux驱动程序的启动流程,这篇文章对Linux子系统调用顺序进行了详细的讲解,同时也说明了设备驱动程序的调用顺序,很值得收藏。

本文来自:http://blog.csdn.net/yimiyangguang1314/article/details/7312209

时间: 2024-10-10 06:03:25

linux子系统的初始化_subsys_initcall():那些入口函数【转】的相关文章

Linux内核(12) - 子系统的初始化之那些入口函数

内核选项的解析完成之后,各个子系统的初始化即进入第二部分-入口函数的调用.通常USB.PCI这样的子系统都会有一个名为subsys_initcall的入口,如果你选择它们作为研究内核的切入点,那么就请首先找到它. 朱德庸在<关于上班这件事>里说,要花前半生找入口,花后半生找出口.可见寻找入口对于咱们这一生,对于看内核代码这件事儿都是无比重要的. 但是很多时候,入口并不仅仅只有subsys_initcall一个,比如PCI. 117 #define pure_initcall(fn)      

linux子系统的初始化_subsys_initcall()【转】

转自:http://my.oschina.net/u/572632/blog/305492 目录[-] 概述 section的声明 注册 调用 IN BUILDING 概述 内核选项的解析完成之后,各个子系统的初始化即进入第二部分—入口函数的调用.通常USB.PCI这样的子系统都会有一个名为subsys_initcall的入口,如果你选择它们作为研究内核的切入点,那么就请首先找到它. section的声明 C 语言中attribute属性的section是在目标文件链接时可以用于主动定制代码的位

linux子系统的初始化_subsys_initcall()

概述 内核选项的解析完成之后,各个子系统的初始化即进入第二部分-入口函数的调用.通常USB.PCI这样的子系统都会有一个名为subsys_initcall的入口,如果你选择它们作为研究内核的切入点,那么就请首先找到它. section的声明 C 语言中attribute属性的section是在目标文件链接时可以用于主动定制代码的位置,具体可以WIKI,下面看linux kernel中是如何定义的. 以下代码来自 linux内核源码中 include/linux/init.h 文件.下面使用相同语

Linux内核(11) - 子系统的初始化之内核选项解析

首先感谢国家.其次感谢上大的钟莉颖,让我知道了大学不仅有校花,还有校鸡,而且很多时候这两者其实没什么差别.最后感谢清华女刘静,让我深刻体会到了素质教育的重要性,让我感到有责任写写子系统的初始化. 各个子系统的初始化是内核整个初始化过程必然要完成的基本任务,这些任务按照固定的模式来处理,可以归纳为两个部分:内核选项的解析以及那些子系统入口(初始化)函数的调用. 内核选项 Linux允许用户传递内核配置选项给内核,内核在初始化过程中调用parse_args函数对这些选项进行解析,并调用相应的处理函数

c++程序真正的入口函数

今天终于有时间来研究一下一个很大很大的工程编译成一个exe和若干dll后,程序是如果执行它的第一条指令的?操作系统以什么规则来找到应该执行的第一条指令(或说如何找到第一个入口函数的)? 我们以前写windows控制台程序时,都是先写个main()函数,写windows窗口程序时,首先要写winmain()函数,然后再写自己的逻辑:然后编译,然后点击exe就能运行我们的程序了:并且认为main或winmain是程序中第一个运行的程序,也是必须存在的函数,但深入了解window的编程就会发现,mai

C++学习--入口函数

在学习第一个C++程序的时候发现控制台程序的入口函数是int _tmain而不是main,查了资料才发现_tmain()是为了支持unicode所使用的main一个别名,宏定义在<stdafx.h>,有这么两行 #include <stdio.h>#include <tchar.h> 可以在头文件<tchar.h>里找到_tmain的宏定义 #define _tmain      wmain 所以,经过预编译以后, _tmain就变成main了. #defi

&#8203;Linux下C如何调用PCI Lib函数

Linux下C如何调用PCI Lib函数 在Linux下,可以通过"setpci"和"setpci"命令来访问PCI设备的配置空间,那么能否用程序来访问PCI 配置空间呢?答案当然是肯定的,linux下提供了多个pci库以供应用程序访问.下面就以最常见的为例,从安装.使用和编译的角度分别进行说明. 安装在centos中,用超级用户权限,可用下面的命令查看到和pci访问相关的库包括:libpciaccess.i686 : PCI access librarylibpc

VS2010中wmain入口函数中使用wprintf输出中文乱码问题

生活中的单例 中国(China),位于东亚,是一个以华夏文明为主体.中华文化为基础,以汉族为主要民族的统一多民族国家,通用汉语.中国疆域内的各个民族统称为中华民族,龙是中华民族的象征.古老的中国凭借自身的发展依旧美丽的屹立于东方民族之林,闪耀着她动人的光彩,世界上只有一个中国,任何部分都是祖国不可分割的一部分,今天我们的设计模式就从伟大的祖国开始说起---单例模式. 详解单例模式 单例模式是什么?跟我们的祖国有着怎样的关系呢?首先我们来看一下单例,从"单例"字面意思上理解为-一个类只有

1.2. chromium源代码分析 - chromiumframe - 入口函数

ChromiumFrame的入口函数在main.cpp中,打开main.cpp.中包含3个类和_tWinMain函数._tWinMain就是我们要找的入口函数.我做了部分注释: 1 int APIENTRY _tWinMain(HINSTANCE hInstance, 2 HINSTANCE hPrevInstance, 3 LPTSTR lpCmdLine, 4 int nCmdShow) 5 { 6 // 1. 支持OLE,退出管理机制,GDIPlus 初始化,本地资源管理(new) 7 H