iis服务器助手广告广告
返回顶部
首页 > 资讯 > 操作系统 >Linux内核是怎么初始化各个模块的
  • 915
分享到

Linux内核是怎么初始化各个模块的

2023-06-15 16:06:17 915人浏览 八月长安
摘要

小编给大家分享一下linux内核是怎么初始化各个模块的,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!相信很多在研究linux内核源码的同学,经常会发现一些模块的初

小编给大家分享一下linux内核是怎么初始化各个模块的,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

相信很多在研究linux内核源码的同学,经常会发现一些模块的初始化函数找不到调用者,比如下面的网络模块的初始化函数:

// net/ipv4/af_inet.c  static int __init inet_init(void)  {          ...                    ip_init();                    tcp_init();                    udp_init();          ...  }  fs_initcall(inet_init);

即使你在整个内核代码中搜索,也找不到任何地方调用这个函数,那这个函数到底是怎么调用的呢?

秘密就在这个函数之后的一行代码里:

fs_initcall( inet_init);

在该行代码中,fs_initcall是一个宏,具体定义如下:

// include/linux/init.h  #define ___define_initcall(fn, id, __sec) \          static initcall_t __initcall_##fn##id __used \                  __attribute__((__section__(#__sec ".init"))) = fn;  ...  #define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)  ...  #define fs_initcall(fn)                 __define_initcall(fn, 5)

在该宏展开后,上面宏调用的结果,大致像下面这个样子:

static initcall_t __initcall_inet_init5 __attribute__((__section__(".initcall5.init"))) = inet_init;

由上可见,fs_initcall宏最终是定义了一个静态变量,该变量的类型是initcall_t,值是宏参数表示的函数地址。

initcall_t类型的定义如下:

typedef int (*initcall_t)(void);

由上可见,initcall_t是一个函数指针类型,它定义的变量会指向一个函数,该函数的参数要为空,返回类型要为int。

我们可以再看下上面的 inet_init 方法,该方法确实符合这些要求。

综上可知,fs_initcall宏定义了一个变量 __initcall_inet_init5,它的类型为initcall_t,它的值为inet_init函数的地址。

到这里我相信很多同学会想,linux内核一定是通过这个变量来调用inet_init函数的,对吗?

对,也不对。

对是因为内核确实是通过该变量指向的内存来获取inet_init方法的地址并调用该方法的。

不对是因为内核并不是通过上面的__initcall_inet_init5变量来访问这个内存的。

那不用这个变量,还能通过其他方式访问这个内存吗?

当然可以,这正是linux内核设计的巧妙之处。

我们再来看下上面的宏展开之后,静态变量__initcall_inet_init5的定义,在该定义中有如下的一些代码:

__attribute__((__section__(".initcall5.init")))

该部分代码并不属于C语言标准,而是GCc对c语言的扩展,它的作用是声明该变量属于 .initcall5.init这个section。

所谓section,我们可以简单的理解为对程序所占内存区域的一种布局和规划,比如我们常见的 section有 .text用来存放我们的代码,.data或.bss用来存放我们的变量。

通过这些section的定义,我们可以把程序中的相关功能放到同一块内存区域中,这样来方便内存管理。

除了这些默认的section之外,我们还可以通过gcc的attribute来自定义section,这样我们就可以把相关的函数或变量放到相同的section中了。

比如上面的__initcall_inet_init5变量就属于.initcall5.init这个自定义section。

在定义了这些section之后,我们可以在链接脚本中告诉linker,这些section在内存中的位置及布局是什么样子的。

对于x86平台来说,内核的链接脚本是:

arch/x86/kernel/vmlinux.lds.S

在该脚本中,对.initcall5.init等这些section做了相关定义,具体逻辑如下:

// include/asm-generic/vmlinux.lds.h  #define INIT_CALLS_LEVEL(level)                                         \                  __initcall##level##_start = .;                          \                  KEEP(*(.initcall##level##.init))                        \                  KEEP(*(.initcall##level##s.init))                       \  #define INIT_CALLS                                                      \                  __initcall_start = .;                                   \                  KEEP(*(.initcallearly.init))                            \                  INIT_CALLS_LEVEL(0)                                     \                  INIT_CALLS_LEVEL(1)                                     \                  INIT_CALLS_LEVEL(2)                                     \                  INIT_CALLS_LEVEL(3)                                     \                  INIT_CALLS_LEVEL(4)                                     \                  INIT_CALLS_LEVEL(5)                                     \                  INIT_CALLS_LEVEL(rootfs)                                \                  INIT_CALLS_LEVEL(6)                                     \                  INIT_CALLS_LEVEL(7)                                     \                  __initcall_end = .;

由上可见,initcall相关的section有很多,我们上面例子中的.initcall5.init只是其中一个,除此之外还有 .initcall0.init,.initcall1.init等等这些section。

这些section都是通过宏INIT_CALLS_LEVEL来定义其处理规则的,相同level的section被放到同一块内存区域,不同level的section的内存区域按level大小依次连接在一起。

对于上面的__initcall_inet_init5变量来说,它的section是.initcall5.init,它的level是5。

假设我们还有其他方法调用了宏fs_initcall,那该宏为该方法定义的静态变量所属的section也是.initcall5.init,level也是5。

由于该变量和__initcall_inet_init5变量所属的initcall的level都相同,所以它们被连续放在同一块内存区域里。

也就是说,这些level为5的静态变量所占的内存区域是连续的,又因为这些变量的类型都为initcall_t,所以它们正好构成了一个类型为initcall_t的数组,而数组的起始地址也在INIT_CALLS_LEVEL宏中定义了,就是__initcall5_start。

如果我们想要调用这些level为5的initcall,只要先拿到__initcall5_start地址,把其当成元素类型为initcall_t的数组的起始地址,然后遍历数组中的元素,获取该元素对应的函数指针,就可以通过该指针调用对应的函数了。

来看下具体代码:

// init/main.c  extern initcall_entry_t __initcall_start[];  extern initcall_entry_t __initcall0_start[];  extern initcall_entry_t __initcall1_start[];  extern initcall_entry_t __initcall2_start[];  extern initcall_entry_t __initcall3_start[];  extern initcall_entry_t __initcall4_start[];  extern initcall_entry_t __initcall5_start[];  extern initcall_entry_t __initcall6_start[];  extern initcall_entry_t __initcall7_start[];  extern initcall_entry_t __initcall_end[];  static initcall_entry_t *initcall_levels[] __initdata = {          __initcall0_start,          __initcall1_start,          __initcall2_start,          __initcall3_start,          __initcall4_start,          __initcall5_start,          __initcall6_start,          __initcall7_start,          __initcall_end,  };  static void __init do_initcall_level(int level)  {          initcall_entry_t *fn;          ...          for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)                  do_one_initcall(initcall_from_entry(fn));  }  static void __init do_initcalls(void)  {          int level;          for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)                  do_initcall_level(level);  }

在上面的代码中,do_initcalls方法遍历了所有的合法level,对于每个level,do_initcall_level方法又调用了该level里所有函数指针指向的函数。

我们上面示例中的inet_init方法就属于level 5,也是在这里被调用到的。

linux内核就是通过这种方式来调用各个模块的初始化方法的,很巧妙吧。

最后我们再来总结下:

在各模块的初始化方法之后,一般都会调用一个类似于fs_initcall(inet_init)的宏,该宏的参数是该模块的初始化方法的方法名。

该宏展开后的结果是定义一个静态变量,该变量通过gcc的attribute来声明其所属的initcall level的section,比如inet_init方法对应的静态变量就属于.initcall5.init这个section。

在linux的链接脚本里,通过INIT_CALLS_LEVEL宏告知linker,将属于同一level的所有静态变量放到连续的一块内存中,组成一个元素类型为initcall_t的数组,该数组的起始地址放在类似__initcall5_start的变量中。

在内核的初始化过程中,会通过调用 do_initcalls方法,遍历各个level里的各个函数指针,然后调用该指针指向的方法,即各模块的初始化方法。

以上是“Linux内核是怎么初始化各个模块的”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注编程网操作系统频道!

--结束END--

本文标题: Linux内核是怎么初始化各个模块的

本文链接: https://www.lsjlt.com/news/280847.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Linux内核是怎么初始化各个模块的
    小编给大家分享一下Linux内核是怎么初始化各个模块的,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!相信很多在研究linux内核源码的同学,经常会发现一些模块的初...
    99+
    2023-06-15
  • Linux内核是怎么巧妙的初始化各个模块的
    这篇文章跟大家分析一下“Linux内核是怎么巧妙的初始化各个模块的”。内容详细易懂,对“Linux内核是怎么巧妙的初始化各个模块的”感兴趣的朋友可以跟着小编的思路慢慢深入来阅读一下,希望阅读后能够对大家有所帮助。下面跟着小编一起深入学习“L...
    99+
    2023-06-28
  • linux中内核模块指的是什么
    本文小编为大家详细介绍“linux中内核模块指的是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“linux中内核模块指的是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。在linux中,内核模块是linu...
    99+
    2023-06-29
  • 怎么编写Linux内核模块
    这篇文章主要讲解了“怎么编写Linux内核模块”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么编写Linux内核模块”吧!Linux内核和它的用户空间是大不相同的:抛开漫不经心,你必须小心...
    99+
    2023-06-16
  • linux怎么在2.6内核中编译内核模块
    这篇文章主要介绍linux怎么在2.6内核中编译内核模块,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!编译内核模块的方法与编译一般应用程序的方法略有不同. 我们会发现在内核源码树的层层目录中, 都存在有Makefil...
    99+
    2023-06-16
  • 怎么在Linux中编写内核模块
    怎么在Linux中编写内核模块?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。尽可能不要用root身份默认情况下,/dev/reverse只有root可以使用,因此你只能使用su...
    99+
    2023-06-12
  • 怎么深入学习Linux内核模块
    本篇文章给大家分享的是有关怎么深入学习Linux内核模块,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。lsmod 命令能够告诉你当前系统上加载了哪些内核模块,以及关于使用它们的...
    99+
    2023-06-16
  • linux初始化的方法是什么
    Linux的初始化方法可以通过执行以下步骤来完成:1. 启动计算机并进入BIOS设置,在启动选项中选择从可引导介质(如硬盘或USB驱...
    99+
    2023-08-30
    linux
  • OGG中各种数据泵的初始化脚本是什么
    OGG中各种数据泵的初始化脚本是什么,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。一、先导元数据## 导出元数据 (用户模式)userid=system/system_cm...
    99+
    2023-06-06
  • Linux中systemd的初始化流程是什么
    在Linux中,systemd是一个系统和服务管理器,它负责启动系统服务和管理系统进程。systemd的初始化流程如下: 系统引...
    99+
    2024-04-02
  • CSS和HTML的初始化模板怎么写
    本篇内容介绍了“CSS和HTML的初始化模板怎么写”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1、CSS...
    99+
    2024-04-02
  • Linux内核怎样访问另外一个模块的函数和变量
    今天就跟大家聊聊有关Linux内核怎样访问另外一个模块的函数和变量,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。一、问题整理内核中两个模块,一个A,一个B,A模块中有操作函数,B模块...
    99+
    2023-06-15
  • FlexApplication初始化顺序是怎么样的
    这篇文章将为大家详细讲解有关FlexApplication初始化顺序是怎么样的,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Flex组件在建立的时候都会经历四个事件:preinitialize, init...
    99+
    2023-06-17
  • Linux内核是怎么样的
    这篇文章将为大家详细讲解有关Linux内核是怎么样的,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Linux 内核,这个经常听见,却不不知道它具体是干嘛的东西,是不是觉得非常神秘Linux 内核看不见摸不...
    99+
    2023-06-16
  • Linux内核模块中的module_init和init_module有什么区别
    module_init和init_module都是Linux内核模块中用来初始化模块的函数,但是它们的用法和执行顺序有所不同。1. ...
    99+
    2023-09-27
    解决
  • Android中多个ContentProvider的初始化顺序是什么
    这篇“Android中多个ContentProvider的初始化顺序是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“An...
    99+
    2023-06-30
  • Linux系统怎么使用lsmod命令列出内核模块
    这篇文章主要介绍Linux系统怎么使用lsmod命令列出内核模块,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!lsmod是一个命令行实用程序,用于显示有关已加载的Linux内核模块的信息。内核模块内核是操作系统的核心...
    99+
    2023-06-28
  • Hugo游乐场内容初始化的方法是什么
    这篇“Hugo游乐场内容初始化的方法是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Hugo游乐场内容初始化的方法是什么...
    99+
    2023-07-05
  • Linux系统下怎么编译安装支持NTFS的内核模块
    这篇文章主要讲解了“Linux系统下怎么编译安装支持NTFS的内核模块”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Linux系统下怎么编译安装支持NTFS的内核模块”吧!当你安装完Linu...
    99+
    2023-06-10
  • c语言怎么使用未初始化的内存
    在C语言中,使用未初始化的内存是不安全和不推荐的,因为未初始化的内存中的值是不确定的,可能是任意的。这样可能导致程序出现未定义的行为...
    99+
    2023-09-26
    c语言
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作