Skip to content

Commit

Permalink
changed at Thu May 9 07:49:34 UTC 2024
Browse files Browse the repository at this point in the history
  • Loading branch information
actionBot committed May 9, 2024
1 parent 05aedeb commit a6ff67e
Showing 1 changed file with 23 additions and 23 deletions.
46 changes: 23 additions & 23 deletions Documentation/teaching/labs/kernel_modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
内核模块概述
===========

虽然单体内核比微内核更快,但缺乏模块化和可扩展性。在现代单体内核中,这个问题已经通过使用内核模块来解决。内核模块(或可加载内核模式)是一个包含代码的目标文件(object file),它可以在运行时扩展内核的功能(根据需要加载);当不需要内核模块时,可以卸载它。大多数设备驱动程序以内核模块的形式使用
虽然单体内核的运行速度比微内核更快,但它在模块化和可扩展性方面存在不足。在现代的单体内核设计中,这个问题已经通过使用内核模块得到了解决。内核模块(或称为可加载内核模块)是一种包含代码的目标文件(object file),它可以在运行时根据需要加载,以扩展内核的功能;而当这些模块不再需要时,也可以将其卸载。大部分设备驱动程序都是以内核模块的形式实现的

为了开发 Linux 设备驱动程序,建议下载内核源代码,配置和编译它们,然后将编译后的版本安装在测试/开发工具机上。
要想开发 Linux 设备驱动程序,建议下载内核源代码,配置和编译它们,然后将编译后的版本安装在测试/开发工具机上。

..
_[SECTION-OVERVIEW-END]
Expand All @@ -32,7 +32,7 @@
内核模块示例
============

以下是一个非常简单的内核模块示例。当加载到内核中时,它会生成消息 :code:`"Hi"`。当卸载内核模块时,将生成消息 :code:`"Bye"`。
以下是一个非常简单的内核模块示例。当加载到内核中时,它会生成消息 :code:`"Hi"`。当卸载这个内核模块时,将生成消息 :code:`"Bye"`。

.. code-block:: c
Expand All @@ -59,7 +59,7 @@
module_exit(dummy_exit);
生成的消息不会显示在控制台上,而是保存在专门预留的内存区域中,由日志守护程序(syslog)负责将其提取出来。要显示内核消息,你可以使用 `dmesg` 命令或检查日志文件。
生成的消息不会显示在控制台上,而是保存在专门预留的内存区域中,由日志守护程序(syslog)负责将其提取出来。要显示内核消息,你可以使用 :command:`dmesg` 命令或检查日志文件。

.. code-block:: bash
Expand All @@ -80,7 +80,7 @@
编译内核模块
============

编译内核模块与编译用户程序不同。首先,需要使用另外的头文件。此外,模块不应链接到库。最重要的是,模块必须使用与加载模块的内核相同的选项进行编译。出于这些原因,有一种标准的编译方法(kbuild)。该方法需要使用两个文件: :file:`Makefile` 文件和 :file:`Kbuild` 文件。
编译内核模块与编译用户程序不同。首先,需要使用不同的头文件。此外,模块不应链接到库。最后,模块必须使用与加载模块的内核相同的选项进行编译。出于这些原因,有一种标准的编译方法(kbuild)。该方法需要使用两个文件: :file:`Makefile` 文件和 :file:`Kbuild` 文件。

以下是 :file:`Makefile` 文件的示例:

Expand All @@ -103,16 +103,16 @@
obj-m = modul.o
正如你所见,在示例中调用 :command:`make` 命令对 :file:`Makefile` 文件进行编译将导致在内核源代码目录 (``/lib/modules/`uname -r`/build``) 中调用 :command:`make` 并引用当前目录 (``M = `pwd``)。该过程最终会读取当前目录中的 :file:`Kbuild` 文件,并按照该文件中的指示编译模块
正如你所见,在示例中调用 :command:`make` 命令对 :file:`Makefile` 文件进行编译时,会在内核源代码目录 (``/lib/modules/`uname -r`/build``) 中执行 :command:`make` 命令,并引用当前目录 (``M = `pwd``)。这个过程最终会读取当前目录中的 :file:`Kbuild` 文件,并按照其中的指示编译模块

.. note:: 对于实验,我们将根据虚拟机的规格配置不同的 :command:`KDIR`:
.. note:: 对于实验,我们将根据虚拟机的规格,配置不同的 :command:`KDIR`:

.. code-block:: bash
KDIR = /home/student/src/linux
[...]
:file:`Kbuild` 文件包含一个或多个指令,用于编译内核模块。其中一个最简单的指令示例是 ``obj-m = module.o``。根据该指令,将从 ``module.o`` 文件开始创建一个内核模块( :code:`ko` ,即 :code:`kernel object`,也就是内核对象)。``module.o`` 将基于 ``module.c`` 或 ``module.S`` 创建。所有这些文件都可以在 :file:`Kbuild` 所在的目录中找到
:file:`Kbuild` 文件包含了一系列指令,这些指令用于编译内核模块。其中一个最基本的指令例子是 ``obj-m = module.o``。遵循这个指令,系统会基于 ``module.o`` 文件开始构建一个内核模块(也称为 :code:`ko`,即内核对象)。``module.o`` 文件是基于 ``module.c`` 或 ``module.S`` 生成的。所有这些文件都应该存放在包含 :file:`Kbuild` 的同一目录下

下面是一个使用多个子模块的 :file:`Kbuild` 文件示例:

Expand All @@ -132,14 +132,14 @@

:file:`Kbuild` 中目标(target)的后缀决定了它们的用途,如下所示:

* M(modules) 标示目标为可加载内核模块
* M(modules)标示目标为可加载内核模块

* Y(yes) 表示目标是用于编译并链接到模块(``$(模块名称)-y``)或内核(``obj-y``)的对象文件
* Y(yes)标示目标是编译对象文件然后将其链接到模块(``$(模块名称)-y``)或内核(``obj-y``)

* 其他任何目标后缀都将被 :file:`Kbuild` 忽略,不会被编译


.. 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 支持。
.. 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 支持。

要了解更多详细信息,请参阅内核源代码中的 :file:`Documentation/kbuild/makefiles.txt` 和 :file:`Documentation/kbuild/modules.txt` 文件。

Expand All @@ -159,7 +159,7 @@
$ insmod module.ko
$ rmmod module.ko
加载内核模块时,将执行 ``module_init`` 宏(macro)参数指定的函数。同样,当卸载模块时,将执行 ``module_exit`` 宏参数指定的函数
加载内核模块时,将执行 ``module_init`` 宏参数指定的例程。同样,当卸载模块时,将执行 ``module_exit`` 宏参数指定的例程

下面是一个完整的编译、加载和卸载内核模块的示例:

Expand Down Expand Up @@ -206,7 +206,7 @@
内核模块调试
===========
与调试常规程序相比,调试内核模块要复杂得多。首先,内核模块中的错误可能导致整个系统阻塞。因此,故障排除的速度会大大降低。为了避免重新启动,建议使用虚拟机(qemu、virtualbox 或者 vmware)。
与调试常规程序相比,调试内核模块要复杂得多。首先,内核模块中的错误可能导致整个系统阻塞。因此,故障排查的速度会大大降低。为了避免重新启动,建议使用虚拟机(qemu、virtualbox 或者 vmware)。
当插入包含错误的模块到内核中时,它最终会生成一个 `内核 oops <https://zh.wikipedia.org/wiki/Oops_(Linux内核)>`_ 。内核 oops 是内核检测到的无效操作,只可能由内核生成。对于稳定的内核版本,这几乎可以肯定意味着模块含有错误。在 oops 出现后,内核仍将继续工作。
Expand Down Expand Up @@ -307,19 +307,19 @@
BUG: unable to handle kernel paging request at 00001234
EIP: [<c89d4005>] my_oops_init + 0x5 / 0x20 [oops]
告诉我们错误的原因和生成错误的指令的地址。在我们的例子中,这是对内存的无效访问。
告诉我们错误的原因和造成错误的指令的地址。在我们的例子中,这是对内存的无效访问。
接下来的一行是:
``Oops: 0002 [# 1] PREEMPT DEBUG_PAGEALLOC``
告诉我们这是第一个 oops(#1)。在这个上下文中,这很重要,因为一个 oops 可能会导致其他 oops。通常只有第一个 oops 是相关的。此外,oops 代码( ``0002`` )提供了有关错误类型的信息(参见 :file:`arch/x86/include/asm/trap_pf.h` ):
告诉我们这是第一个 oops(#1)。在这个上下文中,这很重要,因为一个 oops 可能会导致其他 oops。通常只有第一个 oops 是相关的。此外,oops 代码(``0002``)提供了有关错误类型的信息(参见 :file:`arch/x86/include/asm/trap_pf.h` ):
* Bit 0 == 0 表示找不到页面,1 表示保护故障
* Bit 1 == 0 表示读取,1 表示写入
* Bit 2 == 0 表示内核模式,1 表示用户模式
* 第 0 位 == 0 表示找不到页面,1 表示保护故障
* 第 1 位 == 0 表示读取,1 表示写入
* 第 2 位 == 0 表示内核模式,1 表示用户模式
在这种情况下,我们有一个写入访问导致了 oops(bit 1 1)。
在这种情况下,我们有一个写入访问导致了 oops( 1 位为 1)。
下面是寄存器的转储(dump)。它解码了指令指针 (``EIP``) 的值,并指出错误出现在 :code:`my_oops_init` 函数中,偏移为 5 个字节(``EIP: [<c89d4005>] my_oops_init+0x5``)。该消息还显示了堆栈内容和到目前为止的调用回溯。
Expand Down Expand Up @@ -451,11 +451,11 @@ objdump
c89d4026: 90 nop
c89d4027: 90 nop
请注意,生成 oops 的指令(先前确定为 ``c89d4005`` )是:
请注意,生成 oops 的指令(先前确定为 ``c89d4005``)是:
```C89d4005: c7 05 34 12 00 00 03 movl $ 0x3,0x1234``
``C89d4005: c7 05 34 12 00 00 03 movl $ 0x3,0x1234``
这正是预期的结果 - 将值 3 存储在地址 0x0001234 上。
这正是预期的结果——将值 3 存储在地址 0x0001234 上。
:file:`/proc/modules` 用于查找加载的内核模块的地址。:command:`--adjust-vma` 选项允许你相对于 ``0xc89d4000`` 位置显示指令。:command:`-l` 选项将显示源代码中每行的编号,源代码与汇编语言代码交错显示。
Expand Down Expand Up @@ -798,7 +798,7 @@ KDB 具有各种命令来控制和定义被调试系统的上下文:
这些命令将构建当前实验骨架中的所有模块。
.. warning::
在解决练习 3 之前,编译 ``3-error-mod`` 时会出现编译错误。为了避免此问题,删除 :file:`skels/kernel_modules/3-error-mod/` 目录,并从 ``skels/Kbuild`` 中删除相应的行。
在解决练习 3 之前,编译 ``3-error-mod`` 时会出现编译错误。为了避免此问题,请删除 :file:`skels/kernel_modules/3-error-mod/` 目录,并从 ``skels/Kbuild`` 中删除相应的行。
使用 :command:`make console` 启动虚拟机,并执行以下任务:
Expand Down

0 comments on commit a6ff67e

Please sign in to comment.