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

news2024/11/18 15:32:47

文章目录

    • 一、导读
    • 二、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 = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
		do_initcall_level(level);
}

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

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 = {
	__initcall0_start,
	__initcall1_start,
	__initcall2_start,
	__initcall3_start,
	__initcall4_start,
	__initcall5_start,
	__initcall6_start,
	__initcall7_start,
	__initcall_end,
};

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

do_initcalls()函数中,会根据initcall_levels初始化调用级别的数量调用do_initcall_level(),该函数实现如下:

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 = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
		do_one_initcall(*fn);
}

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

三、构造section并添加函数

(3-1)构造初始化调用section

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

本文以ARM32架构为例

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

INIT_CALLS定义在/include/asm-generic/vmlinux.lds.h文件中:

而在内核的makefile中有以下语句:

LDFLAGS_vmlinux += -T arch/$(ARCH)/kernel/vmlinux.lds.s

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

(3-2)向section中添加函数

向section中添加函数的本质操作则是__define_initcall(),定义如下:

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

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

static void __init show_info(void)
{
  printk("I'm iriczhao \n")
}

core_initcall(show_info);

经过层层宏替换后,本质上则变成:

static initcall_t __initcall_core_initcall1 __used \
__attribute__((__section__(".initcall1.init"))) = show_info;

四、总结

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/163758.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c++开源协程库libgo介绍及使用

协程这个概念&#xff0c;最近这几年可是相当地流行了。尤其 go 语言问世之后&#xff0c;内置的协程特性&#xff0c;完全屏蔽了操作系统线程的复杂细节。甚至使 go 开发者“只知有协程&#xff0c;不知有线程”了。当然 C也有高性能的协程库&#xff0c;比如我了解到的微信的…

基于微信小程序的企业职工薪资查询系统小程序

文末联系获取源码 开发语言&#xff1a;Java 框架&#xff1a;ssm JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏览器…

卷积神经网络(CNN)详细介绍及其原理详解

文章目录前言一、什么是卷积神经网络二、输入层三、卷积层四、池化层五、全连接层六、输出层七、回顾整个过程总结前言 本文总结了关于卷积神经网络&#xff08;CNN&#xff09;的一些基础的概念&#xff0c;并且对于其中的细节进行了详细的原理讲解&#xff0c;通过此文可以十…

自动(智能)驾驶 | 4D雷达的数据集

上篇文章分享了关于Oculii 4D雷达的两篇报告。数据集是一个非常重要的研究工具&#xff0c;对于4D雷达领域来说&#xff0c;处于一个研究前沿的位置&#xff0c;鲜有公开的数据集&#xff0c;目前能找到的数据集有&#xff1a; 这些文章中的数据集有不少博主也写过&#xff0c…

S1000D规范导读

S1000D最初是由欧洲航空工业联盟开发的技术出版物规范&#xff0c;它主要为具有较长生命的复杂产品运行和维修而设计。这些年不断发展&#xff0c;已经扩展到这些行业的产品&#xff1a;国防系统 - 包括海、陆、空的产品&#xff0c;民用航空产品&#xff0c;基建行业产品和船舶…

15/365 java static final

1.static属性,方法 类内属性或方法用static修饰&#xff0c;表示该属性或方法属于类&#xff0c;不依赖于实例对象&#xff0c;所以不需要用对象调用&#xff0c;而是直接用类名调用。 static方法只能调用其他static方法&#xff0c;而普通方法可以调用其他的普通方法和stati…

Vue3商店后台管理系统设计文稿篇(二)

记录使用vscode构建Vue3商店后台管理系统&#xff0c;这是第二篇&#xff0c;主要记录Vue3中生命周期钩子&#xff0c;模板语法&#xff0c;以及相关的代码 文章目录一、Vue3生命周期二、Vue3模板语法三、代码展示正文内容&#xff1a; 一、Vue3生命周期 每个 Vue 实例在被创建…

拆机详解2:比Macintosh还早?苹果Lisa拆解

hello大家好&#xff0c;我是每天&#xff08;实际并不是每天&#xff0c;你们点的赞太少了&#xff0c;每人点一个赞我就日更&#xff09;给你们讲解的Eric_Bells.这里感谢博主半身风雪的支持&#xff0c;我会更新的&#xff01;看到的麻烦点个关注谢谢拉 今天唠唠一台比Maci…

【蓝桥杯基础题】2017年省赛—九宫幻方

&#x1f451;专栏内容&#xff1a;&#x1f449;蓝桥杯刷题&#x1f448;⛪个人主页&#xff1a;&#x1f449;子夜的星的主页&#x1f448;&#x1f495;座右铭&#xff1a;前路未远&#xff0c;步履不停 目录一、题目背景二、题目描述1.问题描述2.输入格式3.输出格式4.一个例…

CAN通信----(创芯科技)CAN分析仪使用----CANTest安装和驱动安装

前言 我在调试CAN通信时&#xff0c;使用的是在淘宝买的CAN分析仪。 CAN分析仪的实物如下&#xff1a; 使用CAN分析仪&#xff0c;调试CAN通信&#xff0c;PC电脑端需要使用CANTest测试软件&#xff0c;还需要安装驱动。 一、创芯科技 CAN分析仪资料包下载 步骤1&#xff1…

测开-基础篇

一、软件测试的生命周期 先来回顾软件的生命周期 &#x1f351;软件的生命周期 需求分析--》计划--》设计--》编码--》测试--》运营维护 需求分析&#xff1a;进行市场分析&#xff0c;这个需求量大不大&#xff1f;投入与盈利的占比&#xff1f;技术上 能否实现或者说实现的…

深度学习 10 神经网络简介

1. 深度学习和机器学习的主要区别在于对数据的处理, 机器学习主要通过算法直接进行推断, 而深度学习主要通过神经网络对各种算法进行加权, 然后汇总得出结论. 2. 常用的激活函数: tanh函数relu函数leaky relu函数1.1 深度学习介绍 1.1.1 区别 机器学习的特征工程步骤是要靠手…

Effective Objective-C 2.0学习记录(五)

23.通过委托和数据源协议进行对象间通信 使用委托模式&#xff1a;获取网络数据的类含有一个“委托对象”&#xff0c;在获取完数据后&#xff0c;它会回调这个委托对象。 利用协议机制&#xff0c;很容易就 能以OC代码实现此模式&#xff0c;在图中演示的情况下。可以这样定义…

【Java AWT 图形界面编程】Container 容器总结

文章目录一、AWT 简介二、AWT 核心类继承体系三、Container 容器类子类四、Container 容器常用 API五、Frame 窗口示例六、Panel 示例七、窗口中文乱码处理八、ScrollPane 可滚动容器示例一、AWT 简介 Java 中 使用 AWT 和 Swing 进行 图形界面开发 , AWT 是 抽象窗口工具集 , …

线程安全问题(3)

线程不安全:在多线程的调度情况下&#xff0c;导致出现了一些随机性&#xff0c;随机性是代码中出现了一些BUG&#xff0c;导致我们的线程是不安全的 造成线程不安全的原因: 1)操作系统抢占式执行&#xff0c;线程调度随机&#xff0c;这是万恶之源&#xff0c;我们无能为力 2)…

Web进阶:Day7 响应式、BootStrap、实战演练

Web进阶&#xff1a;Day7 Date: January 10, 2023 Summary: 响应式、BootStrap、实战演练 响应式 媒体查询 目标&#xff1a;能够根据设备宽度的变化&#xff0c;设置差异化样式 媒体特性常用写法 媒体特性常用写法&#xff1a; max-width&#xff08;从小到大&#xff0…

transformers包介绍——nlp界最顶级的包——可以不用 但不能不知道——python包推荐系列

背景1 现在在AI行业&#xff0c;什么最火&#xff1f;计算机视觉还是自然语言处理&#xff1f;其实不得不说&#xff0c;现在nlp很火。还有人记得上个月很多科技爱好者都在玩的chatgpt么&#xff1f;那个就是nlp技术的一大应用。现在都在觉得AI赚钱&#xff0c;工资高&#xf…

深度学习 12 正则化

1. 对于高方差(过拟合)&#xff0c;有以下几种方式&#xff1a; 获取更多的数据&#xff0c;使得训练能够包含所有可能出现的情况 正则化&#xff08;Regularization&#xff09; 寻找更合适的网络结构 2. 对于高偏差(欠拟合)&#xff0c;有以下几种方式&#xff1a; 扩大网…

【C语言进阶】只看此篇,让你学会动态内存管理

目录 前言 一、为什么存在动态内存分配 二、动态内存函数的介绍 1 、malloc和free 2、 calloc 3 、realloc 三、常见的动态内存错误 四、动态内存管理笔试题 1 题目1&#xff1a; 2 题目2&#xff1a; 3 题目3&#xff1a; 4 题目4&#xff1a; 五、C/C程序的…

5.10回溯法--圆排列问题--排列树

圆排列问题描述 给定n个大小不相等的圆&#xff0c;要将这n个大小不相等的圆排进一个矩形框中&#xff0c;且要求个个圆都与矩形框的最底边相切。要找出最小长度的圆排列。 问题分析 排列排列&#xff0c;解空间是一个排列树。 设开始时&#xff0c;a[n]储存n个圆的半径&…