|
19 | 19 | 内核模块概述
|
20 | 20 | ===========
|
21 | 21 |
|
22 |
| -虽然单体内核比微内核更快,但缺乏模块化和可扩展性。在现代单体内核中,这个问题已经通过使用内核模块来解决。内核模块(或可加载内核模式)是一个包含代码的目标文件(object file),它可以在运行时扩展内核的功能(根据需要加载);当不需要内核模块时,可以卸载它。大多数设备驱动程序以内核模块的形式使用。 |
| 22 | +虽然单体内核的运行速度比微内核更快,但它在模块化和可扩展性方面存在不足。在现代的单体内核设计中,这个问题已经通过使用内核模块得到了解决。内核模块(或称为可加载内核模块)是一种包含代码的目标文件(object file),它可以在运行时根据需要加载,以扩展内核的功能;而当这些模块不再需要时,也可以将其卸载。大部分设备驱动程序都是以内核模块的形式实现的。 |
23 | 23 |
|
24 |
| -为了开发 Linux 设备驱动程序,建议下载内核源代码,配置和编译它们,然后将编译后的版本安装在测试/开发工具机上。 |
| 24 | +要想开发 Linux 设备驱动程序,建议下载内核源代码,配置和编译它们,然后将编译后的版本安装在测试/开发工具机上。 |
25 | 25 |
|
26 | 26 | ..
|
27 | 27 | _[SECTION-OVERVIEW-END]
|
|
32 | 32 | 内核模块示例
|
33 | 33 | ============
|
34 | 34 |
|
35 |
| -以下是一个非常简单的内核模块示例。当加载到内核中时,它会生成消息 :code:`"Hi"`。当卸载内核模块时,将生成消息 :code:`"Bye"`。 |
| 35 | +以下是一个非常简单的内核模块示例。当加载到内核中时,它会生成消息 :code:`"Hi"`。当卸载这个内核模块时,将生成消息 :code:`"Bye"`。 |
36 | 36 |
|
37 | 37 | .. code-block:: c
|
38 | 38 |
|
|
59 | 59 | module_exit(dummy_exit);
|
60 | 60 |
|
61 | 61 |
|
62 |
| -生成的消息不会显示在控制台上,而是保存在专门预留的内存区域中,由日志守护程序(syslog)负责将其提取出来。要显示内核消息,你可以使用 `dmesg` 命令或检查日志文件。 |
| 62 | +生成的消息不会显示在控制台上,而是保存在专门预留的内存区域中,由日志守护程序(syslog)负责将其提取出来。要显示内核消息,你可以使用 :command:`dmesg` 命令或检查日志文件。 |
63 | 63 |
|
64 | 64 | .. code-block:: bash
|
65 | 65 |
|
|
80 | 80 | 编译内核模块
|
81 | 81 | ============
|
82 | 82 |
|
83 |
| -编译内核模块与编译用户程序不同。首先,需要使用另外的头文件。此外,模块不应链接到库。最重要的是,模块必须使用与加载模块的内核相同的选项进行编译。出于这些原因,有一种标准的编译方法(kbuild)。该方法需要使用两个文件: :file:`Makefile` 文件和 :file:`Kbuild` 文件。 |
| 83 | +编译内核模块与编译用户程序不同。首先,需要使用不同的头文件。此外,模块不应链接到库。最后,模块必须使用与加载模块的内核相同的选项进行编译。出于这些原因,有一种标准的编译方法(kbuild)。该方法需要使用两个文件: :file:`Makefile` 文件和 :file:`Kbuild` 文件。 |
84 | 84 |
|
85 | 85 | 以下是 :file:`Makefile` 文件的示例:
|
86 | 86 |
|
|
103 | 103 | obj-m = modul.o
|
104 | 104 |
|
105 | 105 |
|
106 |
| -正如你所见,在示例中调用 :command:`make` 命令对 :file:`Makefile` 文件进行编译将导致在内核源代码目录 (``/lib/modules/`uname -r`/build``) 中调用 :command:`make` 并引用当前目录 (``M = `pwd``)。该过程最终会读取当前目录中的 :file:`Kbuild` 文件,并按照该文件中的指示编译模块。 |
| 106 | +正如你所见,在示例中调用 :command:`make` 命令对 :file:`Makefile` 文件进行编译时,会在内核源代码目录 (``/lib/modules/`uname -r`/build``) 中执行 :command:`make` 命令,并引用当前目录 (``M = `pwd``)。这个过程最终会读取当前目录中的 :file:`Kbuild` 文件,并按照其中的指示编译模块。 |
107 | 107 |
|
108 |
| -.. note:: 对于实验,我们将根据虚拟机的规格配置不同的 :command:`KDIR`: |
| 108 | +.. note:: 对于实验,我们将根据虚拟机的规格,配置不同的 :command:`KDIR`: |
109 | 109 |
|
110 | 110 | .. code-block:: bash
|
111 | 111 |
|
112 | 112 | KDIR = /home/student/src/linux
|
113 | 113 | [...]
|
114 | 114 |
|
115 |
| -:file:`Kbuild` 文件包含一个或多个指令,用于编译内核模块。其中一个最简单的指令示例是 ``obj-m = module.o``。根据该指令,将从 ``module.o`` 文件开始创建一个内核模块( :code:`ko` ,即 :code:`kernel object`,也就是内核对象)。``module.o`` 将基于 ``module.c`` 或 ``module.S`` 创建。所有这些文件都可以在 :file:`Kbuild` 所在的目录中找到。 |
| 115 | +:file:`Kbuild` 文件包含了一系列指令,这些指令用于编译内核模块。其中一个最基本的指令例子是 ``obj-m = module.o``。遵循这个指令,系统会基于 ``module.o`` 文件开始构建一个内核模块(也称为 :code:`ko`,即内核对象)。``module.o`` 文件是基于 ``module.c`` 或 ``module.S`` 生成的。所有这些文件都应该存放在包含 :file:`Kbuild` 的同一目录下。 |
116 | 116 |
|
117 | 117 | 下面是一个使用多个子模块的 :file:`Kbuild` 文件示例:
|
118 | 118 |
|
|
132 | 132 |
|
133 | 133 | :file:`Kbuild` 中目标(target)的后缀决定了它们的用途,如下所示:
|
134 | 134 |
|
135 |
| - * M(modules) 标示目标为可加载内核模块 |
| 135 | + * M(modules)标示目标为可加载内核模块 |
136 | 136 |
|
137 |
| - * Y(yes) 表示目标是用于编译并链接到模块(``$(模块名称)-y``)或内核(``obj-y``)的对象文件 |
| 137 | + * Y(yes)标示目标是编译对象文件然后将其链接到模块(``$(模块名称)-y``)或内核(``obj-y``) |
138 | 138 |
|
139 | 139 | * 其他任何目标后缀都将被 :file:`Kbuild` 忽略,不会被编译
|
140 | 140 |
|
141 | 141 |
|
142 |
| -.. note:: 这些后缀使得可以通过运行 :command:`make menuconfig` 命令或直接编辑 :file:`.config` 文件来轻松配置内核。该文件设置了一系列变量,用于确定在构建时向内核添加哪些特性。例如,使用 :command:`make menuconfig` 命令添加 BTRFS 支持时,在 :file:`.config` 文件中添加行 :code:`CONFIG_BTRFS_FS = y`。BTRFS kbuild 包含了一行 ``obj-$(CONFIG_BTRFS_FS):= btrfs.o``,它会转变成 ``obj-y:= btrfs.o``。这将编译 :file:`btrfs.o` 对象,并将其链接到内核。如果没有设置变量,该行会转变成 ``obj:=btrfs.o``,然后被忽略,进而内核构建时不会包含 BTRFS 支持。 |
| 142 | +.. note:: 借助这些后缀,开发者可以通过运行 :command:`make menuconfig` 命令或直接编辑 :file:`.config` 文件来轻松配置内核。该文件设置了一系列变量,这些变量决定了在构建过程中哪些特性会被添加到内核中。例如,当使用 :command:`make menuconfig` 命令添加 BTRFS 支持时,:file:`.config` 文件中会增加 :code:`CONFIG_BTRFS_FS = y` 这一行。BTRFS 的 kbuild 包含了一行 ``obj-$(CONFIG_BTRFS_FS):= btrfs.o``,如果设置了相应的变量,这行代码会变成 ``obj-y:= btrfs.o``。这将导致系统编译 :file:`btrfs.o` 对象,并将其链接到内核中。如果没有设置该变量,这行代码则会变成 ``obj:= btrfs.o`` 并被忽略,结果是内核构建时不会包含 BTRFS 支持。 |
143 | 143 |
|
144 | 144 | 要了解更多详细信息,请参阅内核源代码中的 :file:`Documentation/kbuild/makefiles.txt` 和 :file:`Documentation/kbuild/modules.txt` 文件。
|
145 | 145 |
|
|
159 | 159 | $ insmod module.ko
|
160 | 160 | $ rmmod module.ko
|
161 | 161 |
|
162 |
| -加载内核模块时,将执行 ``module_init`` 宏(macro)参数指定的函数。同样,当卸载模块时,将执行 ``module_exit`` 宏参数指定的函数。 |
| 162 | +加载内核模块时,将执行 ``module_init`` 宏参数指定的例程。同样,当卸载模块时,将执行 ``module_exit`` 宏参数指定的例程。 |
163 | 163 |
|
164 | 164 | 下面是一个完整的编译、加载和卸载内核模块的示例:
|
165 | 165 |
|
|
206 | 206 | 内核模块调试
|
207 | 207 | ===========
|
208 | 208 |
|
209 |
| -与调试常规程序相比,调试内核模块要复杂得多。首先,内核模块中的错误可能导致整个系统阻塞。因此,故障排除的速度会大大降低。为了避免重新启动,建议使用虚拟机(qemu、virtualbox 或者 vmware)。 |
| 209 | +与调试常规程序相比,调试内核模块要复杂得多。首先,内核模块中的错误可能导致整个系统阻塞。因此,故障排查的速度会大大降低。为了避免重新启动,建议使用虚拟机(qemu、virtualbox 或者 vmware)。 |
210 | 210 |
|
211 | 211 | 当插入包含错误的模块到内核中时,它最终会生成一个 `内核 oops <https://zh.wikipedia.org/wiki/Oops_(Linux内核)>`_ 。内核 oops 是内核检测到的无效操作,只可能由内核生成。对于稳定的内核版本,这几乎可以肯定意味着模块含有错误。在 oops 出现后,内核仍将继续工作。
|
212 | 212 |
|
|
307 | 307 | BUG: unable to handle kernel paging request at 00001234
|
308 | 308 | EIP: [<c89d4005>] my_oops_init + 0x5 / 0x20 [oops]
|
309 | 309 |
|
310 |
| -告诉我们错误的原因和生成错误的指令的地址。在我们的例子中,这是对内存的无效访问。 |
| 310 | +告诉我们错误的原因和造成错误的指令的地址。在我们的例子中,这是对内存的无效访问。 |
311 | 311 |
|
312 | 312 | 接下来的一行是:
|
313 | 313 |
|
314 | 314 | ``Oops: 0002 [# 1] PREEMPT DEBUG_PAGEALLOC``
|
315 | 315 |
|
316 |
| -告诉我们这是第一个 oops(#1)。在这个上下文中,这很重要,因为一个 oops 可能会导致其他 oops。通常只有第一个 oops 是相关的。此外,oops 代码( ``0002`` )提供了有关错误类型的信息(参见 :file:`arch/x86/include/asm/trap_pf.h` ): |
| 316 | +告诉我们这是第一个 oops(#1)。在这个上下文中,这很重要,因为一个 oops 可能会导致其他 oops。通常只有第一个 oops 是相关的。此外,oops 代码(``0002``)提供了有关错误类型的信息(参见 :file:`arch/x86/include/asm/trap_pf.h` ): |
317 | 317 |
|
318 |
| - * Bit 0 == 0 表示找不到页面,1 表示保护故障 |
319 |
| - * Bit 1 == 0 表示读取,1 表示写入 |
320 |
| - * Bit 2 == 0 表示内核模式,1 表示用户模式 |
| 318 | + * 第 0 位 == 0 表示找不到页面,1 表示保护故障 |
| 319 | + * 第 1 位 == 0 表示读取,1 表示写入 |
| 320 | + * 第 2 位 == 0 表示内核模式,1 表示用户模式 |
321 | 321 |
|
322 |
| -在这种情况下,我们有一个写入访问导致了 oops(bit 1 为 1)。 |
| 322 | +在这种情况下,我们有一个写入访问导致了 oops(第 1 位为 1)。 |
323 | 323 |
|
324 | 324 | 下面是寄存器的转储(dump)。它解码了指令指针 (``EIP``) 的值,并指出错误出现在 :code:`my_oops_init` 函数中,偏移为 5 个字节(``EIP: [<c89d4005>] my_oops_init+0x5``)。该消息还显示了堆栈内容和到目前为止的调用回溯。
|
325 | 325 |
|
@@ -451,11 +451,11 @@ objdump
|
451 | 451 | c89d4026: 90 nop
|
452 | 452 | c89d4027: 90 nop
|
453 | 453 |
|
454 |
| -请注意,生成 oops 的指令(先前确定为 ``c89d4005`` )是: |
| 454 | +请注意,生成 oops 的指令(先前确定为 ``c89d4005``)是: |
455 | 455 |
|
456 |
| - ```C89d4005: c7 05 34 12 00 00 03 movl $ 0x3,0x1234`` |
| 456 | + ``C89d4005: c7 05 34 12 00 00 03 movl $ 0x3,0x1234`` |
457 | 457 |
|
458 |
| -这正是预期的结果 - 将值 3 存储在地址 0x0001234 上。 |
| 458 | +这正是预期的结果——将值 3 存储在地址 0x0001234 上。 |
459 | 459 |
|
460 | 460 | :file:`/proc/modules` 用于查找加载的内核模块的地址。:command:`--adjust-vma` 选项允许你相对于 ``0xc89d4000`` 位置显示指令。:command:`-l` 选项将显示源代码中每行的编号,源代码与汇编语言代码交错显示。
|
461 | 461 |
|
@@ -798,7 +798,7 @@ KDB 具有各种命令来控制和定义被调试系统的上下文:
|
798 | 798 | 这些命令将构建当前实验骨架中的所有模块。
|
799 | 799 |
|
800 | 800 | .. warning::
|
801 |
| - 在解决练习 3 之前,编译 ``3-error-mod`` 时会出现编译错误。为了避免此问题,删除 :file:`skels/kernel_modules/3-error-mod/` 目录,并从 ``skels/Kbuild`` 中删除相应的行。 |
| 801 | + 在解决练习 3 之前,编译 ``3-error-mod`` 时会出现编译错误。为了避免此问题,请删除 :file:`skels/kernel_modules/3-error-mod/` 目录,并从 ``skels/Kbuild`` 中删除相应的行。 |
802 | 802 |
|
803 | 803 | 使用 :command:`make console` 启动虚拟机,并执行以下任务:
|
804 | 804 |
|
|
0 commit comments