热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【linuxkernel】linux内核重要函数|do_initcalls

文章目录一、导读二、do_initcalls三、构造section并添加函数(3-1)构造初始化调用section(3-2)


文章目录


    • 一、导读
    • 二、do_initcalls
    • 三、构造section并添加函数
      • (3-1)构造初始化调用section
      • (3-2)向section中添加函数

    • 四、总结




一、导读

在linux内核启动过程中,会向终端打印出很多的日志信息,从这些日志信息中可以得到许多内核的行为。当在内核启动过程中,如果在启动阶段出现了问题,那么很多的提示信息也会从终端打印出。这些信息的输出和具体模块功能的执行都归功于一个函数:do_initcalls,本文将主要分析这个函数的详细执行逻辑,且从这个函数开始延伸到linux各个子系统初始化背后的机制。



本文所有源码分析基于linux内核版本:4.1.15



二、do_initcalls

do_initcallsdo_basic_setup()调用:

do_basic_setup()kernel_init()代表的内核init线程函数间接调用(在kernel_init_freeable()被调用)。在调用do_basic_setup之前,处理器已经被初始化了,CPU子系统已经启动并且运行,内存和进程管理工作也工作正常,但是系统中的设备还没有被初始化,故而do_basic_setup正作用于此,本文主要描述do_initcalls,所以不再进而分析其他的函数。

do_initcalls在/init/main.c文件中实现:

static void __init do_initcalls(void)
{
int level;
for (level &#61; 0; level < ARRAY_SIZE(initcall_levels) - 1; level&#43;&#43;)
do_initcall_level(level);
}

函数中内容比较少&#xff0c;是一个for循环结构&#xff0c;循环的对象是initcall_levels数组&#xff0c;该数组用于描述初始化调用的级别&#xff0c;定义如下&#xff1a;

extern initcall_t __initcall_start[];
extern initcall_t __initcall0_start[];
extern initcall_t __initcall1_start[];
extern initcall_t __initcall2_start[];
extern initcall_t __initcall3_start[];
extern initcall_t __initcall4_start[];
extern initcall_t __initcall5_start[];
extern initcall_t __initcall6_start[];
extern initcall_t __initcall7_start[];
extern initcall_t __initcall_end[];
static initcall_t *initcall_levels[] __initdata &#61; {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};

从上述代码可见&#xff0c;initcall_levels数组中的元素为initcall_t类型的指针&#xff0c;回到do_initcalls()函数中&#xff0c;该函数的核心操作是&#xff1a;按顺序从__initcall0_start开始&#xff0c;到__initcall_end结束的节段中取出存在不同段之间的函数&#xff0c;并执行。存在这几个初始化调用段之间的函数都是各个模块的初始化函数&#xff0c;而这些函数是如何加入到初始化调用段中的呢&#xff1f;又是如何设置调用级别的&#xff0c;会在后文中描述到。

do_initcalls()函数中&#xff0c;会根据initcall_levels初始化调用级别的数量调用do_initcall_level()&#xff0c;该函数实现如下&#xff1a;

static void __init do_initcall_level(int level)
{
initcall_t *fn;
strcpy(initcall_command_line, saved_command_line);
parse_args(initcall_level_names[level],
initcall_command_line, __start___param,
__stop___param - __start___param,
level, level,
&repair_env_string);
for (fn &#61; initcall_levels[level]; fn < initcall_levels[level&#43;1]; fn&#43;&#43;)
do_one_initcall(*fn);
}

从上述代码可见&#xff0c;在函数的最后是一个for循环结构&#xff0c;该循环的操作对象为函数指针&#xff0c;且会将对应的函数指针传递到do_one_initcall中&#xff0c;在该函数则会执行函数指针所指向的函数&#xff1a;


三、构造section并添加函数


&#xff08;3-1&#xff09;构造初始化调用section

在linux内核中&#xff0c;不同架构&#xff08;ARCH&#xff09;下的kernel目录中&#xff0c;都会有一个名为vmlinux.lds.S的链接脚本&#xff0c;初始化调用section的构造则在该链接脚本中完成。



本文以ARM32架构为例


在/arch/arm/kernel/vmlinux.lds.S中的链接脚本中&#xff0c;在.init.data输出节段中则需要INIT_CALLS作为输入节段&#xff1a;

INIT_CALLS定义在/include/asm-generic/vmlinux.lds.h文件中&#xff1a;

而在内核的makefile中有以下语句&#xff1a;

LDFLAGS_vmlinux &#43;&#61; -T arch/$(ARCH)/kernel/vmlinux.lds.s

指定了构建linux内核镜像时所使用的链接脚本&#xff0c;基于此&#xff0c;则会构造好初始化调用section。当初始化调用section构造完成后&#xff0c;是如何向该section中添加函数的呢&#xff1f;继续往下看。


&#xff08;3-2&#xff09;向section中添加函数

向section中添加函数的本质操作则是__define_initcall()&#xff0c;定义如下&#xff1a;

然后linux内核会基于__define_initcall()封装出多个宏定义接口&#xff0c;供内核中各个模块使用&#xff0c;接口如下&#xff1a;

__define_initcall()宏定义的本质则是定义一个initcall_t函数指针类型的变量并命名为__initcall_##fn##id&#xff0c;其中fn为赋值给该变量的函数名称&#xff0c;id为初始化调用级别&#xff0c;然后并将fn赋值给该变量。接着就是最为重要的技术点&#xff1a;使用__attribute__将该变量加入到命名为"initcall##id.init"的section中&#xff0c;其中id为初始化调用级别&#xff0c;所以将fn添加到初始化调用section中则是通过这一点实现。例如&#xff1a;如果有以下类似的代码&#xff1a;

static void __init show_info(void)
{
printk("I&#39;m iriczhao \n")
}
core_initcall(show_info);

经过层层宏替换后&#xff0c;本质上则变成&#xff1a;

static initcall_t __initcall_core_initcall1 __used \
__attribute__((__section__(".initcall1.init"))) &#61; show_info;

四、总结

从上述内容可以知道&#xff0c;linux内核中使用基于__define_initcall封装出的多个接口API初始化内核的各个模块&#xff0c;使用这些API接口会将指定的函数放到名称为.initcall##id.init的section中&#xff0c;id为初始化调用级别&#xff0c;内核中定义了14种调用级别&#xff1a;分别为17和1s7s&#xff08;linux 3.0后增加的扩展&#xff09;。这些调用级别是按照先后顺序依次排列的。

&#xff08;4-1&#xff09;linux内核中&#xff0c;对于内核的各个模块的初始化&#xff0c;正是通过使用__define_initcall()的衍生宏定义接口API将初始化函数放置到__initcall##id.initsection中&#xff0c;不同模块的初始化函数按照调用级别顺序排列。在内核启动阶段&#xff0c;这些放置到这个section中的函数指针将被do_initcalls()按顺序依次调用&#xff0c;进而完成各个模块的初始化。

linux内核系统非常庞大&#xff0c;各个子系统也非常多&#xff0c;他们的初始化函数是不需要在内核启动过程中去主动调用的&#xff0c;从设计上这一点也不现实&#xff0c;随着内核功能的增加&#xff0c;越来越复杂的驱动程序&#xff0c;从而linux内核基于编译器section技术&#xff0c;设计了初始化调用机制&#xff0c;将各个模块的初始化与linux内核启动主线分离。

&#xff08;4-2&#xff09;当使用基于__define_initcall封装出的多个API接口时&#xff0c;函数指针放置到哪个子section由具体的宏定义API接口的level参数确定&#xff0c;较小的level参数则对应的函数指针则被放置在前面。而位于同一个子section内的函数指针顺序不定&#xff0c;由编译器按照编译的顺序随机指定。所以&#xff0c;如果一个模块的初始化函数想要越早被调用执行&#xff0c;则需要有较小的调用级别。







推荐阅读
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • Java值传递机制的说明及示例代码
    本文对Java值传递机制进行了详细说明,包括形参和实参的定义和传递方式,以及通过示例代码展示了交换值的方法。 ... [详细]
author-avatar
萍聚20121018
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有