linux initcall机制

2023-11-17


Linux系统启动过程很复杂,因为它既需要支持模块静态加载机制也要支持动态加载机制。模块动态加载机制给系统提供了极大的灵活性,驱动程序既可支持静态编译进内核,也可以支持动态加载机制。Linux系统中对设备和子系统的初始化在最后进行,主要过程可以用下图表示。



图1

进入子系统初始化时,在内核init进程中进行设备初始化,最为复杂、诡异的机制莫过于do_initcalls()函数调用,该函数完成了所有需要静态加载模块的初始化,需要进行静态加载的内核模块,需要使用一些特定的宏进行处理,下面详细来说明一些linux内核中initcalls机制。

先来看看do_initcalls()函数原型:


图2

核心部分是639~671之间,该部分是一个函数指针调用,遍历_initcall_start~_initcall_end范围,逐个调用函数指针。

那_initcall_start~_initcall_end之间存放的是什么呢,可以以下面一幅示意图来说明。


图3

图左边是地址指针,右边是相关宏,使用相关宏处理函数指针,可以将被处理的函数指针放在特定的指针范围内。例如,网络设备层初始化函数是net_dev_init(),定义在net/core/dev.c中,在该函数下方有条宏处理subsys_initcall(net_dev_init),该宏完成将net_dev_init函数指针放在上图中.initcall4.init段中,在do_initcalls()函数调用时,其处于_initcall_start~_initcall_end直接,所以net_dev_init()就这样被调用了。

这种机制真是比较巧妙,也比较难以理解,设计初衷就是为了实现一个通用的启动流程,使移植或扩展时,只需要对需要启动加载的模块进行宏处理即可。

下面来详细了解这种机制的实现方法。

先说一说gcc对手动定位代码段位置的支持,_attribute_是gcc的关键字,指示编译器给符号设置特定属性。编译完成后输入到链接器的是各个带有符号表的文件,链接器对各个文件中符号进行重定位,_attribute_在该阶段进行处理,将指定符号放在链接生成文件段中特定位置,不单只指代码段,也包括数据段,如系统初始化中经常见到的_initdata,即将指定符号放到数据段特定位置。

当然,具体这些段是如何生成的,也是有文件进行配置,即在链接配置文件arch/xxx/vmlinux.ds.S.中,如下



inux中初始化程序是分等级的,在include/linux/init.h中我们可以找到

#define early_initcall(fn) __define_initcall("early",fn,early)

#define pure_initcall(fn) __define_initcall("0",fn,0)

#define core_initcall(fn) __define_initcall("1",fn,1)

#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)

#define postcore_initcall(fn) __define_initcall("2",fn,2)

#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)

#define arch_initcall(fn) __define_initcall("3",fn,3)

#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)

#define subsys_initcall(fn) __define_initcall("4",fn,4)

#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)

#define fs_initcall(fn) __define_initcall("5",fn,5)

#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)

#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)

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

#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)

#define late_initcall(fn) __define_initcall("7",fn,7)

#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)

一共17个等级(实际上后接_sync的7个等级是几乎没有用到的)。优先级从上往下越来越低,early > 0 > 1....> 7s(从下面的INITCALLS的链接顺序可以看出)

start_kernel->rest_init

系统在启动后在rest_init中会创建init内核线程

init->do_basic_setup->do_initcalls

do_initcalls中会把.initcall.init.中的函数依次执行一遍:

 

for (call = __initcall_start; call < __initcall_end; call++) {

.    .....

result = (*call)();

.    ........

}

 

这个__initcall_start是在文件<arch/xxx/kernel/vmlinux.lds.S>定义的:

 .initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {

   __initcall_start = .;

   INITCALLS

   __initcall_end = .;

  }

而在Vmlinux.lds.h中我们可以找到

#define INITCALLS \

*(.initcallearly.init) \

VMLINUX_SYMBOL(__early_initcall_end) = .; \

   *(.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)

可以看到优先级越来越低。

好吧,这么多的等级,怎么让driver早些出现呢,可不可以随便指定一个呢,比如直接用pure_initcall。大哥,虽然没人说你这样做伤天害理,但大家都是文明人,买车票上厕所都是要排队的。不过孕妇是可以不排队的,好吧,我们就让指定的driver享享孕妇的待遇吧。

但是查队插到什么地方好呢,是不是越靠前越好呢?当然不是,我们先来理理通常的初始化函数的顺序设定。通常设备的加载是arch_initcall,从init/main.c看起:

-->Start_kernel (kernel启动的第一个函数)

...

-->setup_arch (arch/arm/kernel/setup.c)

...

-->init_machine = mdesc->init_machine;//一般的设备都是在                                       //mdesc->init_machine中定义加载

另外我们可以在arch/arm/kernel/setup.c中看到:

static int __init customize_machine(void)

{

/* customizes platform devices, or adds new ones */

if (init_machine)

init_machine();

return 0;

}

arch_initcall(customize_machine);

这里对customize_machine定义的初始化级别为arch_initcall,也就是说一般device加载的优先级是arch_initcall,那么我们就没必要将driver提到arch_initcall之前吧,不然driver妹妹也是再那空等,咱们device还是要讲点绅士风度的,约会不迟到。

好吧我们就可以给开后门的这个驱动优先级设定为arch_initcall、arch_initcall_sync、subsys_initcall、ubsys_initcall_sync、fs_initcall、fs_initcall_sync、rootfs_initcall等几个等级了(有的人要问了,不还有device_initcall、device_initcall_sync、late_initcall、late_initcall_sync几个等级呢?额,正常我们设备驱动都是用device_initcall这个等级的,咱们没必要帮倒忙吧)。至于具体给他那个优先级呢,这个就要考虑些其他因素了,比如这个驱动的初始化是否依赖其他设备初始化的完成,如果要依赖于某个设备的初始化,那么就优先级就必须比另外那个的低。

好吧,下面我们给出一般设备和驱动加载的优先级列表:

初始化等级

             对应的初始化设备或驱动的函数

     备注

early_initcall

migration_init和spawn_ksoftirqd等

主要是register_cpu_notifier

pure_initcall

init_cpufreq_transition_notifier_list


core_initcall

netlink_proto_init、cpuidle_init、xxx_gpio_init、filelock_init、pm_init、sock_init、wakelocks_init等

主要是一些关键部分的初始化,像gpio、通信、电源管理等部分

core_initcall_sync



postcore_initcall

backlight_class_init、dma_sysclass_init、i2c_init、kobject_uevent_init、pci_driver_init、spi_init、tty_class_init等

主要是一些总线的节点的创建和链表初始化等(总线驱动的加载在后面)

postcore_initcall_sync



arch_initcall

xxx_init_device、xxx_devices_setup、customize_machine、platform_init等

主要是板级设备的加载(i2c、spi、usb、串口等以及一些外设的加载)

arch_initcall_sync



subsys_initcall

blk_ioc_ini、xxx_dma_init、xxx_i2c_init_driver、usb_init、xxx_spi_init等

块设备驱动、以及主要的bus总线驱动

subsys_initcall_sync



fs_initcall

inet_init、alignment_init、chr_dev_init、tracer_alloc_buffers等


fs_initcall_sync



rootfs_initcall

populate_rootfs、default_rootfs

Rootfs先关初始化

device_initcall

一般的外设驱动的加载函数

module_init = device_initcall,外设驱动

device_initcall_sync



late_initcall

late_resume_init 等


late_initcall_sync



 

另外一般的外设驱动加载函数都是device_initcall级别的,如果外设很多,必然导致这一等级的加载函数很多。如何在这同一等级中调整driver加载的顺序呢?

不同的芯片厂家给的BSP包中具体的初始化顺序可能会有些差异,但是大概都是和这差不多。


对于同一等级(device_initcall)中的driverA和driverB,driverA说我比driverB大点,我要排到前面点去,driverB也没有意见,那好吧,我们可以来改改Makefile试试。

下面贴我自己写的几个驱动看看,这几个驱动都是放在/drivers/i2c/chip目录下的,先看我最初的Makefile:


好,接着可以从System.map中看看各个驱动的链接的顺序:


看到了吧,链接中的顺序和Makefile中的先后顺序是一模一样的。接下来我们调整一下Makefile中编译的顺序,看看链接的顺序是否也会跟着变过来。


这是修改后的顺序,把bma023放到中间了,重新编译来看看System.map里面的顺序

看到了吧,真的也跑到中间去了,这样是可以修改设备驱动的加载顺序的,说明了linux kernel下链接的顺序和编译的顺序是一样的(顶层的Makefile中规则指定的吧,具体我没弄明白)。看链接的符号表我想大家应该想起了前面定义在INITCALLS里面的*(.initcall6.init)了吧,是的上面这个函数就是按顺序放在*(.initcall6.init)这个段中的。

对于不同目录的driver如果需要调整顺序,当然也可以调整/drivers目录下Makefile下各种驱动类型的编译顺序(不过这个必须谨慎,不能随便乱改)。

总结:linux设备驱动有bus、device、driver三个重要的组成部分,先有bus,然后在bus上挂device和driver,有匹配的才调驱动的probe函数真正的初始化设备并注册API;

修改设备加载的顺序可以有两种方法,一种是修改设备加载的初始化等级,另外就是修改Makefile中的顺序。

备注:在Linux中,usb、sd、i2c、spi等总线上可以挂设备,但是它们自己相对于系统来说也是设备,也要有自己的驱动,在linux中,所有的这些总线都挂在platform这个虚拟总线上,在customize_machine(arch_initcall级)的时候,我们先会加载这些总线的platform_device结构,而这些总线的驱动(platform _driver)一般是定义为subsys_initcall级。


转自:http://blog.chinaunix.net/uid-26772535-id-3262471.html

http://blog.csdn.net/ericghw/article/details/8302689

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

linux initcall机制 的相关文章

  • golang-bufio 缓冲写

    1 缓冲写 在阅读这篇博客之前 请先阅读上一篇 golang bufio 缓冲读 buffered output Writer implements buffering for an io Writer object If an error
  • Go语言的中“...”省略号总结

    1 数组中的 省略号 在数组的定义中 如果在数组长度的位置出现 省略号 则表示数组的长度是根据初始化值的个数来计算 因此 q int 1 2 3 等同于var q 3 int 3 int 1 2 3 按我目前获得的信息来看 q int 1
  • 静态链表系列操作

    静态链表 include
  • Qt调用外部程序

    一 调用系统默认应用打开文件 使用QDesktopServices的openUrl 成员 这个函数是跨平台的 Qt会根据不同的系统平台调用默认的程序打开指定文件 QUrl存放制定的路径 使用非常简便 示例代码如下 QString fileN
  • Linux系统查看磁盘空间占用情况的几个常用命令

    Linux系统下 需要使用命令查看磁盘空间占用情况 下面将这些常用命令进行整理 以作备忘 一 查看磁盘物理分区信息 使用如下命令查看磁盘分区信息 cat proc partitions 二 查看磁盘分区占用情况 使用如下命令查看磁盘分区占用
  • 代码检查与代码走查

    代码检查与代码走查是基于人工测试的白盒测试方法 目的 找出错误 而不是改正错误 是测试而不是调试 优点 能够精确地定位的错误 降低调试成本 可以成批的发现错误 而计算机测试往往是逐个发现错误并改正 注意 代码检查 代码走查 基于计算机的测试
  • 算法工程师福利:超实用技术路线图

    对于不同级别的算法工程师技能要求 我们大致可以分成以下几个层级 初级 可以在一些指导和协助下独立完成开发任务 具体到算法方面 需要你对于工具框架 建模技术 业务特性等方面有一定的了解 可以独立实现一些算法项目上的需求 中级 可以基本独立完成
  • USB+HOST+FATFS

    转载https blog csdn net zcshoucsdn article details 78944536
  • Mysql 5.6 双主配置 自动同步脚本

    最近有项目应用到了 mysql 双主结构 现在贴出来共享 mysql 版本 5 6 11 操作系统版本 rhel 6 2 Master 的 my cnf 配置 只贴M M 结构部分 log bin fabian server id 1 bi
  • 【Deep Learning】Hinton. Reducing the Dimensionality of Data with Neural Networks Reading Note

    2006年 机器学习泰斗 多伦多大学计算机系教授Geoffery Hinton在Science发表文章 提出基于深度信念网络 Deep Belief Networks DBN 可使用非监督的逐层贪心训练算法 为训练深度神经网络带来了希望 如
  • Houdini中全景摄像机shader立体左右眼成像方法

    熟悉Houdini Shader部分的同学应该多多少少也了解camera自身也可以设定自己的shader 其中polar panoramic shader 能够非常方便的为艺术家渲染360全景视角的cg画面 但是这样渲染出来的画面只是单眼所
  • md5加密

    crypto模块 crypto模块 封装了一系列密码学相关的功能 下载该模块 npm install save crypto 使用 crypto createHash md5 update 加密数据 digest hex 示例
  • 线程的状态与切换

    Java中的线程的生命周期大体可分为5种状态 1 新建 初始化 NEW 新创建了一个线程对象 2 可运行 RUNNABLE 线程对象创建后 其他线程 比如main线程 调用了该对象的start 方法 该状态的线程位于可运行线程池中 等待被线
  • react入门,适合新手小白!

    1 1官方网站 中文官网 React 官方中文文档 用于构建用户界面的 JavaScript 库 2 react简介 1 2 1 React是什么 主要是帮助咱们操作界面 也就是操作视图呈现页面 1 2 2 为什么要学React 易于学习
  • Hive(完整版)

    Hive 1 基本概念 Hive本质上是基于 Hadoop 的一个数据仓库工具 可以将结构化的数据文件映射为一张表 并 提供类 SQL 查询功能 通俗一点就是Hive相当于一个hadoop的客户端 利用hdfs存储数据 利用mapreduc

随机推荐

  • 【CV】第 10 章:使用 R-CNN、SSD 和 R-FCN 进行目标检测

    大家好 我是Sonhhxg 柒 希望你看完之后 能对你有所帮助 不足请指正 共同学习交流 个人主页 Sonhhxg 柒的博客 CSDN博客 欢迎各位 点赞 收藏 留言 系列专栏 机器学习 ML 自然语言处理 NLP 深度学习 DL fore
  • JAVA计算摘要,例如MD5和SHA-256

    摘要有什么用 1 保证数据的完整性 例如你发送一个100M的文件给你的B 但是你不知道B收到的是否是完整的文件 此时你首先使用摘要算法 如MD5 计算了一个固定长度的摘要 将这个摘要和文件一起发送给B B接收完文件之后 同样使用MD5计算摘
  • Android 使用SwipeRefreshLayout实现RecyclerVeiw的下拉刷新和上拉加载

    博主前些天发现了一个巨牛的人工智能学习网站 通俗易懂 风趣幽默 忍不住也分享一下给大家 点击跳转到网站 实现下拉刷新和上拉加载的完整代码如下 一 布局文件代码如下 主界面main xml代码
  • PLSQL性能优化方法

    转载自http www itfarmer com cn 1 选择最有效率的表名顺序 只在基于规则的优化器中有效 ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名 FROM子句中写在最后的表 基础表driving table 将
  • stm32f407+cjson的避坑

    1 添加cjson库文件后 编译工程文件 报错 提示 CJSON test c 461 error 268 declaration may not appear after executable statement in block 如下图
  • python 使用socket建立小型聊天室

    一个聊天室 由两个部分组成 服务端和客户端 服务端接收客户端发来的消息 并将接收到的消息发送给其他客户端 客户端负责发送消息到服务端 并接收来自服务端发送的来自其他客户端的消息 示例图 服务端和客户端 这是属于一个群聊的聊天室 服务端会把每
  • 淦、我的服务器又被攻击了

    作者简介 CSDN top100 阿里云博客专家 华为云享专家 网络安全领域优质创作者 推荐专栏 对网络安全感兴趣的小伙伴可以关注专栏 网络安全入门到精通 最近老是有粉丝问我 被黑客攻击了 一定要拔网线吗 还有没有别的方法 按理说 如果条件
  • Python使用os.path.join只保留最后一个变量的原因

    在使用Python的os path join a path bbb ccc 来合并路径时 合并的结果如果只保留了最后的 ccc 是因为最后的一个变量名包含了斜杠 函数会将其识别成绝对路径 因此就会忽略前面所以的其他路径
  • File类的知识

    File 文章目录 File 概述 构造方法 抽象路径 成员方法 创建 删除 判断和获取和遍历 判断 获取 遍历 概述 java编写的一个专门用于描述计算机中的文件和文件夹的类 1 是文件和目录路径名的抽象表示 2 文件和目录是可以通过Fi
  • System has not been booted with systemd as init system (PID 1). Can‘t operate

    system has not been booted with systemd as init system pid 1 can t operate Error 报错 System has not been booted with syst
  • SMTP协议解读以及如何使用SMTP协议发送电子邮件

    电子邮件协议中POP3协议用于接收邮件 SMTP协议用于发送邮件 SMTP的全称为Simple Mail Transfer Protocol 也就是简单邮件传输协议 字如其名 相较于POP3而言 SMTP确实比较简单 这里的简单并不是指SM
  • 李承鹏小说

    1 李可乐抗拆记 城镇化进程 拆迁是个大事情 房子是普通老百姓生活中最重要的东西 一个被拆迁的社区就是一个矛盾激化的社会 有人想终于可捞一笔了 一辈子也就这么一次暴富的机会 有人喜欢老地方 不在乎钱 就不要搬 大部分都是乐意搬 只要补偿合理
  • SprongBoot集成MinIo

    SprongBoot集成MinIo 1 集成MinIo 1 1 添加依赖
  • 第八篇 VGGNet——网络实战

    文章目录 摘要 1 项目结构 2 划分训练集和测试集 3 计算mean和Standard 3 1 标准化的作用 3 2 归一化的作用 4 训练 4 1 导入项目使用的库 4 2 设置随机因子
  • 利用ANSYS随机振动分析功能实现随机疲劳分析

    ANSYS随机振动分析功能可以获得结构随机振动响应过程的各种统计参数 如 均值 均方根和平均频率等 根据各种随机疲劳寿命预测理论就可以成功地预测结构的随机疲劳寿命 本文介绍了ANSYS随机振动分析功能 以及利用该功能 按照Steinberg
  • Android SDK Android NDK 官方下载地址

    Android SDK Android NDK 官方下载地址 Android NDK r6b Windows http dl google com android ndk android ndk r6b windows zip Mac OS
  • VisualStudio(2022)- 打包项目文件为.exe安装包

    目录 前言 一 安装扩展 二 制作安装包 setup文件 2 1 添加setup项目 2 2 配置setup项目 2 3 添加项目文件到setup项目中 扩展知识 三个文件夹说明 2 4 设置项目主输出 2 5 设置快捷方式 2 6 生成安
  • 数据结构--单链表的c语言实现(超详细注释/实验报告)

    数据结构 单链表的c语言实现 超详细注释 实验报告 知识小回顾 在顺序表中 用一组地址连续的存储单元来一次存放线性表的结点 因此结点的逻辑顺序和物理顺序是一致的 而链表则不然 链表是用一组任意的存储单元来存放线性表的结点 这组储存单元可以是
  • 面向对象之魔法方法

    目录 概念 魔法方法分类 构造与初始化 new new 的使用场景 init del 类的表示 str repr bool 访问控制 比较操作 eq ne lt gt 容器类操作 重要 可调用对象 序列化 getstate setstate
  • linux initcall机制

    Linux系统启动过程很复杂 因为它既需要支持模块静态加载机制也要支持动态加载机制 模块动态加载机制给系统提供了极大的灵活性 驱动程序既可支持静态编译进内核 也可以支持动态加载机制 Linux系统中对设备和子系统的初始化在最后进行 主要过程