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

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

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

117 #define pure_initcall(fn)               __define_initcall("0",fn,1)
118
119 #define core_initcall(fn)               __define_initcall("1",fn,1)
120 #define core_initcall_sync(fn)          __define_initcall("1s",fn,1s)
121 #define postcore_initcall(fn)           __define_initcall("2",fn,2)
122 #define postcore_initcall_sync(fn)      __define_initcall("2s",fn,2s)
123 #define arch_initcall(fn)               __define_initcall("3",fn,3)
124 #define arch_initcall_sync(fn)          __define_initcall("3s",fn,3s)
125 #define subsys_initcall(fn)             __define_initcall("4",fn,4)
126 #define subsys_initcall_sync(fn)        __define_initcall("4s",fn,4s)
127 #define fs_initcall(fn)                 __define_initcall("5",fn,5)
128 #define fs_initcall_sync(fn)            __define_initcall("5s",fn,5s)
129 #define rootfs_initcall(fn)     __define_initcall("rootfs",fn,rootfs)
130 #define device_initcall(fn)             __define_initcall("6",fn,6)
131 #define device_initcall_sync(fn)        __define_initcall("6s",fn,6s)
132 #define late_initcall(fn)               __define_initcall("7",fn,7)
133 #define late_initcall_sync(fn)          __define_initcall("7s",fn,7s)
134
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就可以看到下面的内容

__inicall_start = .;
.initcall.init : AT(ADDR(.initcall.init) – 0xC0000000) {
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
}
__initcall_end = .;

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

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函数在返回前等待这些异步任务结束。

666 static void __init do_initcalls(void)
667 {
668   initcall_t *call;
669   int count = preempt_count();
670
671   for (call = __initcall_start; call < __initcall_end; call++) {
672    ktime_t t0, t1, delta;
673    char *msg = NULL;
674    char msgbuf[40];
675    int result;
676
677    if (initcall_debug) {
678     printk("Calling initcall 0x%p", *call);
679     print_fn_descriptor_symbol(": %s()",
680       (unsigned long) *call);
681     printk("/n");
682     t0 = ktime_get();
683    }
684
685    result = (*call)();
686
687    if (initcall_debug) {
688     t1 = ktime_get();
689     delta = ktime_sub(t1, t0);
690
691     printk("initcall 0x%p", *call);
692     print_fn_descriptor_symbol(": %s()",
693       (unsigned long) *call);
694     printk(" returned %d./n", result);
695
696     printk("initcall 0x%p ran for %Ld msecs: ",
697      *call, (unsigned long long)delta.tv64 >> 20);
698     print_fn_descriptor_symbol("%s()/n",
699      (unsigned long) *call);
700    }
701
702    if (result && result != -ENODEV && initcall_debug) {
703     sprintf(msgbuf, "error code %d", result);
704     msg = msgbuf;
705    }
706    if (preempt_count() != count) {
707     msg = "preemption imbalance";
708     preempt_count() = count;
709    }
710    if (irqs_disabled()) {
711     msg = "disabled interrupts";
712     local_irq_enable();
713    }
714    if (msg) {
715     printk(KERN_WARNING "initcall at 0x%p", *call);
716     print_fn_descriptor_symbol(": %s()",
717       (unsigned long) *call);
718     printk(": returned with %s/n", msg);
719    }
720   }
721
722   /* Make sure there is no pending stuff from the initcall sequence */
723   flush_scheduled_work();
724 }

Linux内核(12) - 子系统的初始化之那些入口函数的更多相关文章

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

    首先感谢国家.其次感谢上大的钟莉颖,让我知道了大学不仅有校花,还有校鸡,而且很多时候这两者其实没什么差别.最后感谢清华女刘静,让我深刻体会到了素质教育的重要性,让我感到有责任写写子系统的初始化. 各个 ...

  2. Linux内核(13) - 子系统的初始化之以PCI子系统为例

    由Kconfig这张地图的分布来看,PCI这块儿的代码应该分布在两个地方,drivers/pci和arch/i386/pci,两岸三地都属于一个中国,不管是drivers/pci那儿的,还是arch/ ...

  3. 浅谈 Linux 内核无线子系统

    浅谈 Linux 内核无线子系统 本文目录 1. 全局概览 2. 模块间接口 3. 数据路径与管理路径 4. 数据包是如何被发送? 5. 谈谈管理路径 6. 数据包又是如何被接收? 7. 总结一下 L ...

  4. Linux 内核无线子系统

    Linux 内核无线子系统 浅谈 Linux 内核无线子系统 Table of Contents 1. 全局概览 2. 模块间接口 3. 数据路径与管理路径 4. 数据包是如何被发送? 5. 谈谈管理 ...

  5. (转)浅谈 Linux 内核无线子系统

    前言 Linux 内核是如何实现无线网络接口呢?数据包是通过怎样的方式被发送和接收呢? 刚开始工作接触 Linux 无线网络时,我曾迷失在浩瀚的基础代码中,寻找具有介绍性的材料来回答如上面提到的那些高 ...

  6. Linux内核--usb子系统的分析

    drivers/usb/core/usb.c subsys_init(usb_init); module_exit(usb_exit); 我们 看到一个subsys_initcall,它也是一个宏,我 ...

  7. 【原创】解BUG-xenomai内核与linux内核时间子系统之间存在漂移

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有问题,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 一.问题起源 何为漂移?举个例子两颗32.768kH ...

  8. 嵌入式Linux内核I2C子系统详解

    1.1 I2C总线知识 1.1.1  I2C总线物理拓扑结构     I2C总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成.通信原理是通过对SCL和SDA线高 ...

  9. Linux内核管理子系统和进程管理子系统

    内核管理子系统职能:1.管理虚拟地址与物理地址的映射 2.物理内存的分配 程序:存放在磁盘上的一系列代码和数据的可执行映像,是一个静止的实体. 进程:是一个执行中的程序,它是动态的实体 进程四要素: ...

随机推荐

  1. 超级简单的jquery操作表格(添加/删除行、添加/删除列)

    利用jquery给指定的table添加一行.删除一行 <script language="javascript" src="./jquery.js"> ...

  2. VB.NET与C# 语法区别展示

    在学习VB.NET后发现,VB.NET与C#的语法主要的不同在两个部分,这两部分搞通了,那就游刃有余,迎刃而解了.现将其对比总结如下: 一.实体部分 (与VB相比,在C#和VB.NET中,实体的使用很 ...

  3. 为什么空格拷贝到linux 会变成两个

    为什么空格拷贝到linux 会变成两个 学习了:https://zhidao.baidu.com/question/266438357.html 在vi界面内输入:set paste 然后进行拷贝: ...

  4. Mac下Sublime Text 总是以新窗口打开文件的解决办法

    Mac下的Sublime有个毛病,经常打开后,之前打开的窗口都没了,太难受了. Windows/Linux下的sublime总是默认的以标签页的形式打开关联的文件,但是在Mac下使用Sublime打开 ...

  5. [Javascirpt] Immediately-Invoked function!!! IMPORTANT

    var parkRides = [["Birch Bumpers", 40], ["Pines Plunge", 55], ["Cedar Coast ...

  6. 如何使用Ultraiso制作U盘启动盘

    准备好可启动的ISO文件和足够容量的U盘.点击工具-写入硬盘镜像. 各种U盘启动模式简介 1.USB-HDD:硬盘仿真模式,DOS启动后显示C:盘,HP U盘格式化工具制作的U盘即采用此启动模式.此模 ...

  7. uni-app 如何开启sass\less处理

    开启方式:工具->插件安装->安装完成,启用即可

  8. Java从零开始学六(运算符)

    运算符 一.赋值运算符号 No. 赋值运算符号 描述 1 = 赋值 int num=22; System.out.println("num= "+num); num=num-3; ...

  9. 当Activity继承AppCompatActivity如何实现隐藏标题栏与状态栏

    @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); g ...

  10. Chrome 对于 glyphicon 字体图标不显示的解决的方法

    在将Chome默认字体渲染为微软雅黑后,部分字体图标显示为方框,这里Chome扩展文档提供的解决的方法为: 找到  custom.css 文件,路径为: C:\Users\(username)\App ...