Linux 内核开发:从编译到调试全流程指南
在 Linux 内核开发中,从编译新内核、安装并引导它,到为其添加调试支持,这一系列过程对于深入理解和优化内核至关重要。以下将详细介绍每个步骤的操作方法和注意事项。
编译内核与模块
首先,我们需要编译内核并生成压缩内核镜像 bzImage ,具体命令如下:
# make bzImage
...
objcopy -O binary -R .note -R .comment -S compressed/bvmlinux compressed/bvmlinux.out
tools/build -b bbootsect bsetup compressed/bvmlinux.out CURRENT > bzImage
编译完成后, bzImage 会存放在 arch/i386/boot 目录下。接着,将其复制到 /boot 目录并赋予一个唯一的名称:
# cp arch/i386/boot/bzImage /boot/linux.spate
在命名时,要确保名称易于记忆,且不会覆盖 /boot 中现有的内核。但如果您频繁构建内核且清楚哪些可以安全覆盖,则可灵活处理。
由于内核的很多组件可能被选为内核模块,因此还需编译并安装这些模块:
# make modules
# make modules_install
这些模块会被编译并安装在 /lib/modules 目录下,每个内核版本都有对应的子目录,例如 /lib/modules/2.4.18 。务必记住在配置过程中编译并安装所选的模块,否则内核可能无法正常启动。
安装并引导新内核
接下来,需要配置引导加载程序以识别新内核。大多数 Linux 发行版使用 LILO 或 GRUB 作为引导加载程序,下面先介绍如何使用最常用的 LILO。
假设我们有一个 Red Hat 7.3 安装时生成的 /etc/lilo.conf 文件,其中的部分内容如下:
image=/boot/vxlinuz-2.4.18-3
label=linux
initrd=/boot/initrd-2.4.18-3.img
read-only
root=/dev/hda2
为了引导新内核,我们需要在文件末尾添加新的条目,并修改 image 和 label 行:
image=/boot/linux.spate
label=linux.spate
initrd=/boot/initrd-2.4.18-3.img
read-only
root=/dev/hda2
这样就为新内核创建了一个条目,同时保留了默认内核的现有条目。注意,不要修改 Linux 安装时安装的内核的任何配置信息,因为在构建新内核时可能会意外遗漏设备驱动,导致新内核无法启动。
最后,运行 lilo 命令将新内核的信息安装到主引导记录中:
# lilo
如果 lilo 运行成功,不会显示任何信息。重启机器后,您将看到新内核的条目。
除了 LILO,许多 Linux 发行版现在也使用 GRUB 作为引导加载程序。GRUB 功能丰富,操作方式与 LILO 不同,但添加新内核并不困难。只需在 /etc/grub.conf 文件中添加相应条目即可,无需运行额外的命令来安装信息到主引导记录。如需了解更多关于 GRUB 的信息,请参阅 grub 手册页。
完成上述配置后,重启机器。机器启动时,LILO 会显示可引导的内核列表,您可以使用箭头键选择新安装的内核,然后按 Enter 键加载。如果一切顺利,新内核将按预期启动。为了验证所请求的内核是否正在运行,可以使用 uname 命令显示内核版本:
# uname -a
Linux x.y.com 2.4.18 #2 SMP Tue Jul 30 18:55:27 PDT 2002 i686 unknown
通常建议将默认内核设置为 Linux 操作系统安装时安装的内核,这样在不确定当前运行的内核时会更加方便。
安装调试支持
分析文件系统源代码是了解文件系统工作原理的一种方法,但要真正理解内核和文件系统对特定操作的响应流程,安装并使用内核调试器是更好的选择。以下介绍三种主要的调试方法:
1. 使用 printk() 进行调试
printk() 是 Linux 为内核/模块开发者提供的函数,与 printf() 类似。通过在代码中插入 printk() 语句,可以显示程序运行时的信息,有助于开发或跟踪程序流程。例如,在编写 uxfs 时,可以在文件系统的每个入口点放置 printk() 语句,这样在用户输入各种命令时,就能轻松看到文件系统中哪些函数被调用。
由于 Linux 支持可加载模块,重新编译和加载模块的时间只需几秒钟,因此这是观察文件系统实际工作情况的最简单方法,适合刚开始进行内核开发的人员。为了更好地了解与文件系统相关的内核函数的工作原理,可以在整个内核中插入 printk() 调用,并显示各种结构。
2. 使用 SGI kdb 调试器
kdb 是一个内置调试器,需要与内核一起编译才能使用。它可以设置断点、显示内存、反汇编指令以及显示机器配置(如寄存器集)等。调试器围绕内核符号表运行,因此可以通过名称访问函数和结构。
kdb 的源代码可以从 SGI 网站下载,其主页为: http://oss.sgi.com/projects/kdb/ 。需要注意的是,下载目录中显示的是 kdb 的版本,而不是 Linux 内核的版本。对于开发 uxfs 所使用的内核(2.4.18),必须使用 kdb 版本 2.1。
下载前,应查看下载目录中的 README 文件,其中包含了要下载的文件说明。需要下载两个补丁文件,一个是所有不同机器架构通用的,另一个是特定于您正在运行的机器架构的。下载补丁后,可以按以下方式应用:
# cd /usr/src/linux-2.4.18
# patch -p1 < ../kdb-v2.1-2.4.18-common-3
patching file kernel/sysctl.c
patching file kernel/ksyms.c
patching file kernel/Makefile
patching file init/main.c
...
patching file Documentation/kdb/kdb_env.man
patching file Documentation/kdb/kdb.mm
patching file Documentation/kdb/kdb_bp.man
patching file Documentation/kdb/slides
# patch -p2 < ../kdb-v2.1-2.4.18-i386-1
patching file include/asm-i386/hw_irq.h
patching file include/asm-i386/keyboard.h
patching file include/asm-i386/ptrace.h
patching file arch/i386/vmlinux.lds
...
patching file arch/i386/kdb/kdbasupport.c
patching file arch/i386/kdb/ansidecl.h
patching file arch/i386/kdb/bfd.h
patching file arch/i386/kdb/ChangeLog
成功应用补丁后,需要更改内核配置以包含 kdb。在 Kernel hacking 部分,选择 Built-in Kernel Debugger support 选项并选择 KDB 模块。然后按照之前介绍的方法构建并重新安装内核。
kdb 补丁中包含了调试器的工作原理和可用命令的文档。按下 BREAK 键即可进入调试器,此时会显示 kdb 提示符:
Entering kdb (current=0xc03b0000,pid 0)on processor 0 due to Keyboard Entry
[0]kdb>
使用 ? 命令可以显示可用的命令,以下是一些常用命令的总结:
| 命令 | 功能 |
| ---- | ---- |
| bp | 设置或显示断点 |
| bph | 设置硬件断点 |
| bc | 清除断点 |
| bl | 列出当前断点 |
| bt | 显示当前进程的堆栈回溯 |
| go | 退出调试器并重启内核执行 |
| id | 反汇编指令 |
| md | 显示指定地址的内容 |
| mds | 以符号形式显示内存 |
| mm | 修改内存 |
| reboot | 立即重启机器 |
| rd | 显示寄存器内容 |
| ss | 单步执行(一次执行一条指令) |
| ssb | 单步执行 CPU 直到遇到分支 |
更多命令可参考 kdb(8) 手册页。
以下是使用 kdb 调试的流程 mermaid 图:
graph TD;
A[下载 kdb 源代码] --> B[应用通用补丁];
B --> C[应用架构特定补丁];
C --> D[更改内核配置包含 kdb];
D --> E[构建并重新安装内核];
E --> F[按下 BREAK 键进入调试器];
F --> G[使用命令进行调试];
3. 使用 gdb 进行源代码级调试
GNU 调试器 gdb 通常用于调试用户级程序,但通过将两台机器通过串口线连接成主机/目标机配置,也可以用于调试 Linux 内核。这需要对内核打补丁以包含 kgdb 驱动,以便主机上的 gdb 能够与内核通信。虽然这需要额外的机器和一些设置工作,但在源代码级别调试内核的便利性是值得的,因为不仅可以添加断点来显示内核的执行流程,还可以显示函数参数和相应的源代码。
kgdb 的主页为: http://kgdb.sourceforge.net/ ,该页面引用了所有的补丁并包含了 gdb 设置的详细说明。以下是主要步骤:
连接主机和目标机
首先,使用标准的 null 调制解调器将两台机器的串口连接起来,并验证数据可以通过该链接传输。串口支持 110 波特到 115,200 波特的传输速率,默认波特率为 9,600。对于简单调试,这个波特率通常足够,但如果要传输大量信息,建议使用更高的波特率。
连接成功后,需要确保两台机器的串口速度相同。可以在每台机器上使用以下命令验证:
# stty < /dev/ttyS0
speed 9600 baud; line = 0;
min = 0; time = 10;
-brkint -icrnl -imaxbel
-opost -onlcr
-isig -icanon -iexten -echo -echoe -echok -echoctl -echoke
如果波特率不同,可以使用以下命令设置:
# stty ispeed 9600 ospeed 9600 < /dev/ttyS0
假设两台机器的波特率相同且电缆连接正常,可以通过在一端发送字符串并在另一端读取来测试链接:
Host
Target
# cat /dev/ttyS0
# echo hello > /dev/ttyS0
hello
如果遇到问题,可以参考 kgdb 内核网站上的故障排除指南。
下载 kgdb 补丁
kgdb 内核网站的下载部分包含了特定 Linux 内核的补丁。每个补丁是一个 ASCII 文件,包含一组差异。下载后,可以按以下方式将构建 kgdb 到内核的补丁应用到内核:
# cd /usr/src/linux
# patch -p1 < ../linux-2.4.18-kgdb-1.5.patch
patching file Documentation/Configure.help
patching file Documentation/i386/gdb-serial.txt
patching file Makefile
patching file arch/i386/Makefile
patching file arch/i386/config.in
patching file arch/i386/kernel/Makefile
...
patching file kernel/ksyms.c
patching file kernel/sched.c
应用补丁后,需要更新内核配置以包含 kgdb 选项。在 Kernel Debugging 部分,选择 KGDB: Remote (serial) kernel debugging with gdb (NEW) 行,然后选择每个 kgdb 子选项,但不要选择 Verbose BUG() reporting 选项。
保存内核配置后,运行以下命令构建新内核:
# make dep
# make clean
# make bzImage
新内核将位于 arch/i386/boot 目录下。
安装 kgdb 修改后的内核
为了安装新内核,需要更改 lilo.conf 中的条目,以指示内核在引导时等待来自主机上 gdb 的连接。以下是 lilo.conf 中新内核的条目示例:
image=/boot/linux.gdb
label=linux.gdb
initrd=/boot/initrd-2.4.18-3.img
read-only
root=/dev/hda2
append="gdb gdbttyS=0 gdbbaud=9600"
这指示 kgdb 存根使用的串口( /dev/ttyS0 )和之前在 gdb 配置期间确定的波特率。
新内核引导时,会显示以下消息:
Waiting for connection from remote gdb...
要连接到目标机,需要在主机上运行 gdb 并输入以下命令:
# gdb
GNU gdb Red Hat Linux (5.1.90CVS-5)
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you
are welcome to change it and/or distribute copies of it under certain
conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for
details.
This GDB was configured as "i386-redhat-linux".
(gdb) target remote /dev/ttyS0
Remote debugging using /dev/ttyS0
0xc011323d in ?? ()
(gdb) c
Continuing.
PCI: PCI BIOS revision 2.10 entry at 0xfbfee, last bus=1
PCI: Using configuration type 1
...
target remote 命令指定要连接的串口以与内核通信, c 命令继续执行。
要进入调试器并指示其访问符号调试信息的位置,可以按下 Control-C :
Program received signal SIGTRAP, Trace/breakpoint trap.
0xc011323d in ?? ()
(gdb) symbol-file /usr/src/linux/vmlinux
Reading symbols from /usr/src/linux/vmlinux...done.
此时,调试器就有足够的信息来调试内核了。
gdb 与模块交互
由于 uxfs 是一个可加载模块,gdb 不知道该模块在内存中的位置或其符号信息的位置。因此,需要使用位于 kgdb 网站上的 loadmodule 脚本加载模块。假设模块源文件和二进制文件位于主机上,并且可以从主机复制到目标机。
在运行 loadmodule 之前,需要修改脚本顶部的 GDBSCRIPTS 变量,使其指向一个可以安装用于 gdb 的脚本的目录。例如:
GDBSCRIPTS=/home/spate/uxfs/tools/gdbscripts
然后可以运行脚本:
# loadmodule target-machine ../kern/uxfs
Copying ../kern/uxfs to linux
Loading module ../kern/uxfs
Generating script /home/spate/uxfs/tools/gdbscripts/loadlinuxuxfs
完成后,模块应该已加载到目标机上,并显示生成的脚本。可以在 gdb 中按下 Control-C 进入 gdb,然后执行该脚本:
Program received signal SIGTRAP, Trace/breakpoint trap.
breakpoint () at gdbstub.c:1177
1177
}
(gdb) so /home/spate/uxfs/tools/gdbscripts/loadlinuxuxfs
add symbol table from file "/home/spate/uxfs/kern/uxfs" at
.text_addr = 0xd0854060
.rodata_addr = 0xd0855c60
__ksymtab_addr = 0xd085618c
__archdata_addr = 0xd08562b0
__kallsyms_addr = 0xd08562b0
.data_addr = 0xd08568c0
.bss_addr = 0xd0856a60
至此,gdb 的设置完成。在任何时候都可以按下 Control-C 进入调试状态。
通过以上步骤,您可以完成 Linux 内核的编译、安装、引导和调试,深入了解内核和文件系统的工作原理,为进一步的开发和优化打下坚实的基础。
Linux 内核开发:从编译到调试全流程指南
调试方法总结与选择建议
上述介绍的三种调试方法各有优劣,适用于不同的场景和需求。以下是对它们的总结和选择建议:
| 调试方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
printk() | 简单易用,无需额外配置;能快速查看程序运行信息 | 调试信息输出较简单,缺乏交互性;可能影响程序性能 | 初步了解程序流程,快速定位问题;适合初学者 |
| SGI kdb 调试器 | 功能丰富,可设置断点、查看内存等;围绕内核符号表,方便访问函数和结构 | 需要与内核一起编译,配置较复杂;依赖特定的 kdb 版本 | 需要深入调试内核,进行详细的内存分析和指令跟踪 |
| gdb 源代码级调试 | 可在源代码级别调试,能显示函数参数和源代码;支持多线程调试 | 需要额外的机器和串口连接,设置复杂;对硬件环境要求较高 | 调试复杂的内核问题,需要精确控制程序执行流程 |
如果您是刚开始进行内核开发,建议先使用 printk() 方法,熟悉程序的基本流程。当需要更深入的调试时,可以尝试使用 SGI kdb 调试器。如果遇到复杂的内核问题,且具备相应的硬件条件,那么 gdb 源代码级调试将是更好的选择。
常见问题及解决方法
在 Linux 内核开发和调试过程中,可能会遇到一些常见问题,以下是一些问题及解决方法:
1. 内核编译失败
- 问题描述 :执行
make bzImage或make modules时出现错误。 - 可能原因 :缺少必要的依赖库、内核配置错误、补丁应用失败等。
- 解决方法 :
- 检查系统是否安装了所有必要的开发工具和依赖库,如
gcc、make等。 - 重新检查内核配置,确保所有选项正确设置。
- 检查补丁是否正确应用,如有必要,重新应用补丁。
- 检查系统是否安装了所有必要的开发工具和依赖库,如
2. 新内核无法启动
- 问题描述 :重启机器后,选择新内核启动时出现错误或无法进入系统。
- 可能原因 :内核配置错误、模块未正确安装、引导加载程序配置错误等。
- 解决方法 :
- 检查内核配置,确保所有必要的驱动和模块都已正确配置。
- 重新编译并安装内核模块,确保模块与内核版本匹配。
- 检查引导加载程序(如 LILO 或 GRUB)的配置文件,确保新内核的条目正确。
3. 调试器无法正常工作
- 问题描述 :使用
printk()时看不到调试信息,或使用 kdb、gdb 时无法进入调试状态。 - 可能原因 :调试器配置错误、内核未正确编译包含调试支持、硬件连接问题等。
- 解决方法 :
- 对于
printk(),检查内核日志级别设置,确保调试信息能够显示。 - 对于 kdb 和 gdb,检查内核配置是否正确包含了调试支持,重新编译并安装内核。
- 检查硬件连接,确保串口线连接正常,波特率设置一致。
- 对于
调试技巧与最佳实践
1. 合理使用断点
在使用 kdb 或 gdb 调试时,合理设置断点可以帮助我们快速定位问题。可以在关键函数入口、循环开始处或可能出现问题的代码行设置断点,逐步跟踪程序执行流程。
2. 查看堆栈回溯信息
当程序出现错误或崩溃时,查看堆栈回溯信息可以帮助我们了解程序的调用链,找到错误发生的位置。在 kdb 中使用 bt 命令,在 gdb 中按下 Control-C 后也可以查看堆栈信息。
3. 记录调试信息
在调试过程中,记录重要的调试信息和操作步骤是很有必要的。可以使用文本文件或日志工具记录调试过程中的输出信息、断点设置、变量值等,方便后续分析和总结。
4. 多使用辅助工具
除了上述介绍的调试方法,还可以使用一些辅助工具来帮助调试,如 strace 可以跟踪系统调用, ltrace 可以跟踪库函数调用等。
未来发展趋势
随着 Linux 内核的不断发展,内核开发和调试技术也在不断进步。以下是一些未来可能的发展趋势:
1. 更强大的调试工具
未来可能会出现功能更强大、使用更方便的调试工具,能够提供更详细的调试信息和更高效的调试方式。
2. 可视化调试界面
可视化调试界面可以让开发者更直观地查看程序的执行流程和变量状态,提高调试效率。未来可能会有更多支持 Linux 内核调试的可视化工具出现。
3. 自动化调试技术
自动化调试技术可以通过分析程序的运行数据和错误信息,自动定位和修复问题。随着人工智能和机器学习技术的发展,自动化调试技术在 Linux 内核开发中的应用可能会越来越广泛。
总结
本文详细介绍了 Linux 内核开发从编译到调试的全流程,包括内核和模块的编译、引导加载程序的配置、新内核的安装和引导,以及三种主要的调试方法: printk() 、SGI kdb 调试器和 gdb 源代码级调试。同时,还总结了调试方法的选择建议、常见问题及解决方法、调试技巧与最佳实践,并展望了未来的发展趋势。
通过掌握这些知识和技能,您可以更深入地了解 Linux 内核和文件系统的工作原理,提高内核开发和调试的效率,为 Linux 系统的优化和创新做出更大的贡献。
以下是整个 Linux 内核开发与调试流程的 mermaid 图:
graph LR;
A[编译内核与模块] --> B[安装并引导新内核];
B --> C[安装调试支持];
C --> D{选择调试方法};
D --> E[printk()调试];
D --> F[kdb 调试];
D --> G[gdb 调试];
E --> H[分析调试信息];
F --> H;
G --> H;
H --> I{问题解决?};
I -->|是| J[完成开发];
I -->|否| K[排查问题];
K --> C;
希望本文对您在 Linux 内核开发和调试方面有所帮助,祝您在这个领域取得更多的成果!
超级会员免费看
2071

被折叠的 条评论
为什么被折叠?



