别急着改源码!Ubuntu 22.04编译Linux内核时‘yylloc重复定义’的隐藏陷阱与根治方案
Ubuntu 22.04编译Linux内核时'yylloc重复定义'问题的深度解析与系统级解决方案
当你在Ubuntu 22.04上尝试编译较老版本的Linux内核时,可能会遇到一个令人困惑的错误:
/usr/bin/ld: scripts/dtc/dtc-parser.tab.o:(.bss+0x50): multiple definition of 'yylloc'
。这个错误看似简单,实则揭示了Linux内核构建系统中一个深层次的设计机制。本文将带你深入理解这个问题的本质,并提供一套系统级的解决方案,而非简单的临时修补。
1. 问题本质:构建系统中的变量冲突
错误信息明确指出
yylloc
变量在
dtc-parser.tab.c
和
dtc-lexer.lex.c
两个文件中被重复定义。表面上看,这似乎只是一个简单的变量定义冲突,但实际上它反映了以下几个深层次问题:
- Bison/Flex工具链的版本兼容性 :Ubuntu 22.04自带的Bison和Flex版本与较老内核使用的版本存在行为差异
- 构建系统的自动化机制 :内核构建系统会自动重新生成某些中间文件
- 变量作用域管理 :词法分析器和语法分析器之间的变量共享机制
yylloc
是一个特殊变量,用于在Bison(语法分析器生成器)和Flex(词法分析器生成器)之间传递位置信息。在现代工具链中,这个变量的声明方式已经发生了变化,但老内核的构建系统可能没有相应更新。
2. 为什么简单的修改无效?
许多开发者第一反应是直接修改
dtc-parser.tab.c
文件,将
yylloc
的定义改为
extern
声明。这种方法看似合理,但往往会遇到以下问题:
# 典型的问题解决尝试(但通常无效)
sed -i 's/YYLTYPE yylloc;/extern YYLTYPE yylloc;/' scripts/dtc/dtc-parser.tab.c
这种修改无效的原因在于内核构建系统的特殊工作机制:
-
自动重新生成
:构建系统检测到
.y(Bison语法文件)或.l(Flex词法文件)有更新时,会自动重新生成对应的.tab.c和.lex.c文件 - 备份机制 :内核源代码树中通常包含这些生成文件的"干净"副本,用于在构建时恢复原始状态
- 构建顺序 :某些情况下,生成文件的重新生成发生在链接阶段之后,导致修改被覆盖
3. 系统级解决方案:修改构建规则而非生成文件
要彻底解决这个问题,我们需要从构建系统层面入手,而不是简单地修改生成文件。以下是几种系统级的解决方案:
3.1 方案一:修改Flex输入文件
正确的做法是修改
dtc-lexer.l
文件,确保它正确地声明
yylloc
变量:
/* 在dtc-lexer.l文件顶部添加 */
%{
#include "dtc-parser.tab.h"
extern YYLTYPE yylloc;
%}
然后重新构建内核。这样生成的
dtc-lexer.lex.c
会自动包含正确的
extern
声明。
3.2 方案二:更新构建规则
如果方案一不可行(例如因为
.l
文件本身也需要保持原始状态),可以修改
scripts/dtc/Makefile
,添加特定的编译标志:
# 在scripts/dtc/Makefile中添加
dtc-parser.tab.o: CFLAGS += -DYYLOC_IS_DECLARED=1
这个标志会告诉Bison生成的代码
yylloc
已经在其他地方声明。
3.3 方案三:使用补丁系统
对于需要长期维护的内核树,最佳实践是创建一个补丁文件:
# 创建补丁
diff -u original/dtc-lexer.l modified/dtc-lexer.l > yylloc_fix.patch
# 应用补丁
patch -p1 < yylloc_fix.patch
4. 深入理解构建系统机制
要真正掌握这类问题的解决方法,需要理解Linux内核构建系统的几个关键机制:
- Kbuild系统 :Linux内核的自定义构建系统,负责处理复杂的依赖关系和构建规则
-
中间文件生成
:
.y→.tab.c和.l→.lex.c的转换过程 -
文件备份机制
:内核源代码树中
.*.cmd和.tmp_*文件的作用
下表对比了直接修改生成文件和系统级解决方案的区别:
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 直接修改生成文件 | 快速简单 | 容易被覆盖,不可靠 | 一次性测试 |
| 修改Flex/Bison输入文件 | 持久有效 | 需要理解语法文件 | 长期维护 |
| 修改构建规则 | 不改变源代码 | 需要Makefile知识 | 系统集成 |
| 创建补丁 | 可版本控制 | 需要额外维护 | 团队协作 |
5. 高级技巧:调试构建系统
当遇到类似问题时,以下技巧可以帮助你更快地定位问题根源:
-
查看详细构建输出 :
make V=1这会显示完整的构建命令,帮助你理解文件是如何被处理的
-
检查文件时间戳 :
find scripts/dtc -name "*.c" -exec ls -l {} \;确认哪些文件在构建过程中被修改
-
保留临时文件 :
make KEEP_FILES=1这会保留所有中间文件,方便检查
6. 预防措施与最佳实践
为了避免类似问题,建议遵循以下内核编译最佳实践:
- 使用与内核版本匹配的工具链 :老内核最好在老系统上编译,或者使用Docker容器
- 理解构建系统 :花时间学习Kbuild系统的基本原理
- 使用版本控制 :所有修改都应该通过补丁或git提交管理
-
增量构建
:在修改后使用
make -jN而不是make clean来节省时间
对于Ubuntu 22.04用户,如果必须编译老内核,可以考虑以下工具链配置:
# 安装较旧版本的Bison和Flex
sudo apt install bison=2.7.12-1build1 flex=2.6.4-8
7. 扩展思考:构建系统的哲学
这个问题背后反映了软件工程中一个重要的设计哲学: 构建系统的确定性和可重复性 。内核开发者有意设计这种"还原"机制,目的是确保:
- 构建结果不依赖于本地修改的中间文件
- 任何人都能从原始源文件重建整个系统
- 避免因临时修改导致的不可预测行为
理解这一点,我们就能明白为什么直接修改生成文件不是最佳实践,以及为什么构建系统会"固执"地恢复这些文件。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)