当前位置: 首页 > news >正文

第二章:模块的编译与运行-6 Compiling and Loading

In continuation of the previous text:第二章:模块的编译与运行-5 The Current process, let's GO ahead .

Compiling and Loading

The “hello world” example at the beginning of this chapter included a brief demon-stration of building a module and loading it into the system. There is, of course, a lot more to that whole process than we have seen so far. This section provides more detail on how a module author turns source code into an executing subsystem withinthe kernel.

本章开头的 “hello world” 示例简要演示了模块的构建和加载过程。当然,整个过程远比我们目前所看到的要复杂得多。本节将更详细地介绍模块开发者如何将源代码转换为内核中可执行的子系统。

Compiling Modules

As the first step, we need to look a bit at how modules must be built. The build process for modules differs significantly from that used for user-space applications; the kernel is a large, standalone program with detailed and explicit requirements on how its pieces are put together. The build process also differs from how things were done with previous versions of the kernel; the new build system is simpler to use and produces more correct results, but it looks very different from what came before. The kernel build system is a complex beast, and we just look at a tiny piece of it. The files found in the Documentation/kbuild directory in the kernel source are required reading for anybody wanting to understand all that is really going on beneath the surface.

首先,我们需要稍微了解一下模块的构建方式。模块的构建过程与用户空间应用程序的构建过程有显著差异:内核是一个庞大的独立程序,对其各个部分的组合方式有细致且明确的要求。

此外,这一构建过程与早期内核版本的做法也有所不同:新的构建系统使用更简单,且能产生更正确的结果,但其外观与以往的系统大相径庭。

内核构建系统是一个复杂的机制,我们在这里仅触及其中一小部分。对于任何想要深入理解其底层工作原理的人来说,内核源码中 Documentation/kbuild 目录下的文件是必读资料。

补充说明:

  1. 内核构建系统的核心特点内核构建系统(通常称为 kbuild)是基于 Makefile 的专用框架,专为处理内核及其模块的复杂依赖关系而设计。它不仅负责编译代码,还会处理配置选项(如 make menuconfig 生成的配置)、符号导出、跨架构编译等任务,确保内核各组件(包括模块)之间的二进制兼容性。

  2. 与旧版本构建系统的差异早期内核(如 2.4 版本及之前)的模块构建需要手动指定大量编译参数(如内核头文件路径、架构类型),容易出错;而 2.6 及之后的 kbuild 系统通过统一的规则简化了这一过程,模块开发者只需在 Makefile 中声明模块目标和内核源码路径,系统会自动处理编译选项、依赖关系和符号解析,大幅降低了配置复杂度。

  3. Documentation/kbuild 的重要性该目录包含 kbuild 系统的官方文档(如 modules.txt 介绍模块构建规则,makefiles.txt 详解 Makefile 语法),涵盖了从基础用法到高级特性(如条件编译、多文件模块构建)的所有内容,是解决模块编译问题的权威参考。例如,当需要将多个源文件打包为一个模块时,文档中会说明如何使用 obj-m += module_name.o 和 module_name-objs := file1.o file2.o 进行配置。

理解 kbuild 系统的基本原理,是确保模块能够正确编译、加载和运行的前提,尤其对于处理复杂驱动程序的多文件结构或跨版本兼容性问题至关重要。
There are some prerequisites that you must get out of the way before you can build kernel modules. The first is to ensure that you have sufficiently current versions of the compiler, module utilities, and other necessary tools. The file Documentation/Changes in the kernel documentation directory always lists the required tool versions; you should consult it before going any further. Trying to build a kernel (and its modules) with the wrong tool versions can lead to no end of subtle, difficult problems. Note that, occasionally, a version of the compiler that is too new can be just as problematic as one that is too old; the kernel source makes a great many assumptions about the compiler, and new releases can sometimes break things for a while.

在构建内核模块之前,你需要先把一些前提条件准备就绪。首先,要确保你拥有足够新的编译器、模块工具以及其他必要工具的版本。内核文档目录中的 Documentation/Changes 文件总会列出所需工具的版本要求;在继续操作之前,你应该查阅该文件。

使用错误的工具版本来构建内核(及其模块),可能会导致无数难以排查的隐性问题。需要注意的是,有时候编译器版本过新可能会和版本过旧一样带来麻烦;内核源码对编译器有很多特定假设,而新版本的编译器有时可能会在一段时间内导致编译出错。

补充说明:

  1. Documentation/Changes 的核心作用该文件是内核开发的 “工具版本指南”,详细列出了编译特定内核版本所需的最小工具版本,包括:

    • 编译器(如 gcc
    • 二进制工具集(如 binutils
    • 模块工具(如 insmodrmmod,通常包含在 kmod 包中)
    • 配置工具(如 bc,用于解析内核配置中的数学表达式)
    • 文档生成工具(如 xmlto,用于生成内核文档)

    例如,某内核版本可能要求 gcc 最低为 9.3.0,binutils 最低为 2.34,若使用低于此版本的工具,编译过程可能直接报错或生成不稳定的模块。

  2. 工具版本不匹配的风险

    • 版本过旧:可能不支持内核源码中使用的新语法或特性(如 gcc 的某些编译选项、C 标准的新特性),导致编译失败。
    • 版本过新:新编译器可能对代码规范性要求更严格,或修改了某些未定义行为的处理方式,而内核源码中可能存在依赖旧编译器行为的代码,从而引发编译错误(如警告被提升为错误)。
  3. 实际操作建议对于新手,推荐使用与目标内核版本 “配套” 的工具链 —— 例如,通过系统包管理器安装的 linux-headers-$(uname -r) 对应的工具版本,通常经过发行版验证,兼容性最佳。若需使用自定义工具链,务必先对照 Documentation/Changes 确认版本兼容性,避免浪费时间在工具导致的问题上。

If you still do not have a kernel tree handy, or have not yet configured and built that kernel, now is the time to go do it. You cannot build loadable modules for a 2.6 kernel without this tree on your filesystem. It is also helpful (though not required) to be actually running the kernel that you are building for.

Once you have everything set up, creating a makefile for your module is straightforward. In fact, for the “hello world” example shown earlier in this chapter, a single line will suffice:

obj-m := hello.o

Readers who are familiar with make, but not with the 2.6 kernel build system, are likely to be wondering how this makefile works. The above line is not how a traditional makefile looks, after all. The answer, of course, is that the kernel build system handles the rest. The assignment above (which takes advantage of the extended syntax provided by GNU make) states that there is one module to be built from the object file hello.o. The resulting module is named hello.ko after being built from the object file.

如果你手头还没有内核源码树,或者尚未配置并编译该内核,那么现在就该着手去做了。对于 2.6 版本的内核,若文件系统中没有这个源码树,你就无法构建可加载模块。实际上运行你所针对的内核版本(尽管这不是必需的)也会很有帮助。

一旦所有东西都设置妥当,为你的模块创建一个 Makefile 就很简单了。事实上,对于本章前面所示的 “hello world” 示例,只需一行代码就足够了:

obj-m := hello.o

熟悉 make 但不了解 2.6 内核构建系统的读者可能会好奇这个 Makefile 是如何工作的。毕竟,上面这行代码看起来并不像传统的 Makefile。答案当然是,内核构建系统会处理剩下的所有事情。上面的赋值语句(利用了 GNU make 提供的扩展语法)表明,有一个模块要从目标文件 hello.o 构建而来。构建完成后,生成的模块名为 hello.ko。

补充说明:

  • obj-m 的含义:这是内核构建系统(kbuild)的专用变量,用于声明 “要构建为可加载模块的目标文件”。obj-m := hello.o 表示将 hello.o 编译为独立的可加载模块(.ko 文件),而非直接链接到内核镜像中。

  • 模块命名规则:kbuild 会自动根据目标文件名称生成模块名,即 hello.o 对应生成 hello.ko。.ko 文件是内核模块的标准格式,包含了模块的二进制代码、符号信息、版本信息等元数据,供内核加载时解析。

  • 与传统 Makefile 的区别:传统应用程序的 Makefile 需要显式定义编译规则(如 gcc 命令、依赖关系),而内核模块的 Makefile 只需通过 kbuild 变量声明模块信息,具体的编译选项(如头文件路径、宏定义、架构相关参数)由内核源码树中的顶层 Makefile 自动处理,简化了模块开发者的配置工作。

这种极简的 Makefile 格式是 2.6 内核构建系统的一大特点,它将复杂的编译逻辑封装在内核自身的构建框架中,让开发者可以专注于模块功能实现,而非编译细节。

If, instead, you have a module called module.ko that is generated from two source files (called, say, file1.c and file2.c), the correct incantation would be:

如果你的模块名为 module.ko,且由两个源文件(例如 file1.c 和 file2.c)生成,正确的写法应为:

obj-m := module.o
module-objs := file1.o file2.o

For a makefile like those shown above to work, it must be invoked within the context of the larger kernel build system. If your kernel source tree is located in, say,  your ~/kernel-2.6 directory, the make command required to build your module (typed in the directory containing the module source and makefile) would be:

要让上述这类 Makefile 正常工作,必须在完整的内核构建系统环境中调用它。假设你的内核源码树位于 ~/kernel-2.6 目录,那么构建模块的 make 命令(在包含模块源码和 Makefile 的目录中执行)应为:

make -C ~/kernel-2.6 M=`pwd` modules

This command starts by changing its directory to the one provided with the -C option (that is, your kernel source directory). There it finds the kernel’s top-level makefile. The M= option causes that makefile to move back into your module source directory before trying to build the modules target. This target, in turn, refers to the list of modules found in the obj-m variable, which we’ve set to module.o in our examples.

该命令首先通过 -C 选项切换到指定目录(即内核源码目录),在那里找到内核的顶层 Makefile。M= 选项则让该 Makefile 在尝试构建 modules 目标前,先切换回你的模块源码目录。而 modules 目标会引用 obj-m 变量中列出的模块(在我们的示例中已将其设置为 module.o)。

Typing the previous make command can get tiresome after a while, so the kernel developers have developed a sort of makefile idiom, which makes life easier for those building modules outside of the kernel tree. The trick is to write your makefile as follows:

反复输入上述 make 命令可能会有些繁琐,因此内核开发者设计了一种 Makefile 惯用写法,让在核外构建模块的工作变得更轻松。技巧是将你的 Makefile 写成如下形式:

# If KERNELRELEASE is defined, we've been invoked from the
# kernel build system and can use its language.
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
# Otherwise we were called directly from the command
# line; invoke the kernel build system.
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif

补充说明:

  1. 多文件模块的构建逻辑当模块由多个源文件组成时,obj-m 需指定最终的模块名(如 module.o),而 module-objs 则列出该模块依赖的所有目标文件(如 file1.o file2.o)。内核构建系统会自动将这些目标文件链接为一个 .ko 模块,避免手动编写链接规则。

  2. KERNELRELEASE 变量的作用这是内核构建系统定义的内部变量,用于标识当前是否在核内构建环境中。当直接运行 make 时,KERNELRELEASE 未定义,Makefile 会执行 else 分支:设置内核源码路径(KERNELDIR,默认指向当前运行内核的头文件目录),并调用内核构建系统。而当内核构建系统递归处理模块时,KERNELRELEASE 被定义,此时会执行 ifneq 分支,声明模块目标(obj-m)。

  3. 简化命令的优势采用这种写法后,开发者只需在模块目录中直接运行 make,无需每次手动输入 -C 和 M= 参数,Makefile 会自动完成内核目录切换和模块构建,大幅提升开发效率。

  4. KERNELDIR 的默认路径/lib/modules/$(shell uname -r)/build 是一个符号链接,通常指向当前运行内核对应的源码或头文件目录(由发行版预配置),确保模块与当前系统内核版本匹配。若需针对其他内核版本构建,可手动指定 KERNELDIR(如 make KERNELDIR=~/custom-kernel)。

这种 Makefile 结构是内核模块开发的标准模板,既兼容内核构建系统的要求,又简化了开发者的操作流程。

Once again, we are seeing the extended GNU make syntax in action. This makefile is read twice on a typical build. When the makefile is invoked from the command line, it notices that the KERNELRELEASE variable has not been set. It locates the kernel source directory by taking advantage of the fact that the symbolic link build in the installed modules directory points back at the kernel build tree. If you are not actually running the kernel that you are building for, you can supply a KERNELDIR= option on the command line, set the KERNELDIR environment variable, or rewrite the line that sets KERNELDIR in the makefile. Once the kernel source tree has been found, the makefile invokes the default: target, which runs a second make command (parameterized in the makefile as $(MAKE)) to invoke the kernel build system as described previously.

On the second reading, the makefile sets obj-m, and the kernel makefiles take care of
actually building the module.

我们再次看到了扩展的 GNU make 语法在发挥作用。在典型的构建过程中,这个 Makefile 会被读取两次。当从命令行调用 Makefile 时,它会发现 KERNELRELEASE 变量尚未设置。它利用已安装模块目录中的符号链接 build 指向内核构建树这一特性来定位内核源码目录。

如果你实际运行的内核并非你要为之构建模块的内核,你可以在命令行中提供 KERNELDIR= 选项,设置 KERNELDIR 环境变量,或者重写 Makefile 中设置 KERNELDIR 的那一行。一旦找到内核源码树,Makefile 就会调用 default: 目标,该目标会运行第二个 make 命令(在 Makefile 中参数化为 $(MAKE)),如前所述调用内核构建系统。

在第二次读取时,Makefile 会设置 obj-m,然后内核的 Makefile 会负责实际构建模块。

This mechanism for building modules may strike you as a bit unwieldy and obscure. Once you get used to it, however, you will likely appreciate the capabilities that have been programmed into the kernel build system. Do note that the above is not a complete makefile; a real makefile includes the usual sort of targets for cleaning up unneeded files, installing modules, etc. See the makefiles in the example source directory for a complete example.

这种构建模块的机制可能会让你觉得有些繁琐和晦涩。然而,一旦习惯了它,你可能会欣赏内核构建系统中所设计的这些功能。需要注意的是,上面的内容并非一个完整的 Makefile;一个真正的 Makefile 还包含用于清理不需要的文件、安装模块等常见目标。可以查看示例源码目录中的 Makefile 以获取完整示例。

补充说明:

  1. Makefile 两次读取的细节

    • 第一次读取(命令行直接调用):KERNELRELEASE 未定义,执行 else 分支,主要任务是定位内核源码树并启动内核构建系统。

    • 第二次读取(内核构建系统调用):KERNELRELEASE 被内核 Makefile 定义(通常是内核版本号,如 5.4.0),执行 ifneq 分支,声明模块构建信息(obj-m 等),由内核构建系统完成编译链接。

  2. build 符号链接的作用系统中 /lib/modules/$(uname -r)/build 通常链接到内核源码或编译目录,这是发行版为简化模块构建而设置的。例如,当你安装 linux-headers 包时,该链接会指向对应的头文件和构建配置,使模块能正确引用内核接口。

  3. 完整 Makefile 的常见目标实际开发中,模块 Makefile 通常包含以下扩展目标:

    makefile

    clean:$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
    install:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
    
    • clean:清理编译生成的 .ko.o 等文件,避免旧文件干扰。

    • install:将模块安装到系统默认的模块目录(/lib/modules/$(uname -r)/extra/),便于 modprobe 加载。

这种设计虽初看复杂,但通过复用内核构建系统的逻辑,确保了模块与内核的兼容性,同时减少了开发者编写编译规则的工作量。

http://www.dtcms.com/a/419419.html

相关文章:

  • Coze源码分析-资源库-编辑插件-前端源码-核心API
  • 如何做导购网站电子商务网站软件建设的核心是
  • 新奇特:神经网络的集团作战思维,权重共享层的智慧
  • 从零开始学神经网络——CNN(卷积神经网络)
  • Fork/Join框架性能调优:工作窃取算法与伪共享问题的终极解决方案
  • 网站的风格有哪些网站建设一般都有什么项目
  • Vue2 插槽(Slot)核心总结
  • 二维数组前缀和
  • 代码随想录第23天第24天 | 回溯 (二)
  • 初始化VUE3项目
  • [C++项目框架库]redis的简单介绍和使用
  • redis特性和应用场景
  • 手机网站建设制作wordpress2019谷歌字体
  • 网站建设一个月多少钱网站图片设置教程
  • Linux零基础入门:权限与常用命令详解
  • 【Pyzmq】python 跨进程线程通信 跨平台跨服务器通信
  • 科技企业网站建设网站建设咨询什么
  • K8s部署与NodePort暴露全指南
  • 数据结构 02 线性表
  • 建设工商联网站的意义湟源县网站建设
  • 浙江网站建设技术公司淘宝客商品推广网站建设
  • 【HarmonyOS】鸿蒙应用实现微信分享-最新版
  • 房地产项目网站建设方案做外贸的网站简称为什么网站
  • Vue 3 开发的 HLS 视频流播放组件+异常处理
  • 前端核心框架vue之(路由核心案例篇3/5)
  • vue中不同的watch方法的坑
  • 网站首页排版设计广州网络公关公司
  • 批量重命名技巧:使用PowerShell一键整理图片文件命名规范
  • 手机版网站怎么做的企业解决方案架构师
  • 网站企业备案改个人备案专业微网站制作