内核选项的解析完成之后,各个子系统的初始化即进入第二部分—入口函数的调用。通常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. SQL基础(五):SQL函数

    一.SQL Aggregate 函数: SQL Aggregate 函数计算从列中取得的值,返回一个单一的值. 1.AVG() 函数 AVG() 函数返回数值列的平均值. 语法:SELECT AVG( ...

  2. [mysql] C++操作mysql方法总结(1)

    From: http://www.cnblogs.com/magicsoar/p/3817518.html C++通过mysql的c api和通过mysql的Connector C++ 1.1.3操作 ...

  3. 向第一个 p 元素添加一个类

    This is a heading This is a paragraph. This is another paragraph. 向第一个 p 元素添加一个类 111 <html> &l ...

  4. IDA Pro基本简介

    IDA Pro基本简介 IDA加载完程序后,3个立即可见的窗口分别为IDA-View,Named,和消息输出窗口(output Window). IDA图形视图会有执行流,Yes箭头默认为绿色,No箭 ...

  5. 开源 免费 java CMS - FreeCMS1.5-数据对象-guestbook

    下载地址:http://code.google.com/p/freecms/ guestbook 从FreeCMS 1.5 开始支持 在使用留言相关标签时,标签会封装guestbook供页面调用. 属 ...

  6. 算法笔记_109:第四届蓝桥杯软件类省赛真题(JAVA软件开发本科B组部分习题)试题解答

    目录 1 马虎的算式 2 黄金连分数 3 有理数类 4 幸运数 5 连号区间数   前言:以下试题解答代码部分仅供参考,若有不当之处,还请路过的同学提醒一下~ 1 马虎的算式 标题: 马虎的算式 小明 ...

  7. Java获取登录用户IP地址

    /** * 获取登录用户IP地址 * * @param request * @return */ public static String getIpAddr(HttpServletRequest r ...

  8. jquery 圆形进度条

    最近手里面的项目需要完成这个对设备性能的检测显示功能,需要使用到圆形进度条这样的效果,网上找了一圈,有很多相当的插件,找到:circliful 插件,看了他的使用说明比较的方便,于是就下载了它并将自己 ...

  9. 使用Python创建MySQL数据库实现字段动态添加以及动态的插入数据

    应用场景: 我们须要设计一个数据库来保存多个文档中每一个文档的keyword. 假如我们每一个文档字符都超过了1000,取当中出现频率最大的为我们的keyword. 如果每个文档的keyword都超过 ...

  10. Java中的软(弱)引用

    一.Java中的强.软.弱.虚引用 在JDK中我们能够看到有一个java.lang.ref的包.这个包中就是Java中实现强.软.弱.虚引用的包,例如以下: PhantomReference 虚引用: ...