多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

U-Boot Kbuild构建系统深度解析:从移植到定制

U-Boot Kbuild构建系统深度解析:从移植到定制 1. 这不是“编译U-Boot”而是重建整个构建系统的神经中枢你刚在终端敲下make menuconfig屏幕一闪弹出个蓝底白字的配置界面——你以为这只是在勾选几个功能开关错了。这背后是Kbuild系统正在用一套精密的、层层嵌套的规则把你的选择翻译成几百个Makefile片段再驱动GNU Make去调度上千个C文件、汇编文件、设备树源码的编译顺序、依赖关系和链接脚本生成。U-Boot移植里最常卡住的不是驱动写错而是Kbuild没理顺make报错“没有指明目标并且找不到Makefile”或者make -j4跑着跑着突然中断提示某个.o文件找不到对应的.c——这些都不是代码bug是构建逻辑的断点。我做过7次不同SoC平台的U-Boot移植从ARM9到RISC-V从RK3399到RV1106每次最耗时的环节从来不是写驱动而是把Kbuild这套“构建操作系统”给调通。它不像应用层Makefile那样直白gcc -c main.c -o main.o就完事Kbuild是Makefile的元语言它用obj-y xxx.o这种声明式语法让顶层Makefile自动推导出子目录的编译路径、头文件搜索顺序、符号导出规则甚至决定哪些.o该被链接进最终的u-boot.bin哪些该打包进dtb或spl。你改一行Kconfig里的config SYS_SOCKbuild会自动加载对应目录下的Makefile和Kconfig再触发一连串依赖重算——这个过程就是U-Boot能适配200芯片平台的底层秘密。所以“U-Boot移植_Kbuild_入门”这个标题本质不是教你如何跑通一个现成配置而是带你亲手拆解并重建这套构建系统的神经中枢。它解决的是为什么make ARCHarm CROSS_COMPILEarm-linux-gnueabihf-能启动整个编译流程为什么include/config/autoconf.h这个头文件会在make menuconfig后自动生成为什么RV1106平台的头文件路径必须写成-I$(srctree)/arch/arm/include/asm/arch-rv1106而不是直接-Iarch/arm/include/asm这些问题的答案全藏在Kbuild的三件套里顶层Makefile、Kconfig语法树、以及每个子目录下那个看似简单却暗藏玄机的Makefile。接下来我们就从零开始把这套机制掰开揉碎不靠文档堆砌只靠实操推演。2. Kbuild三件套不是三个文件而是一套协同工作的构建协议Kbuild不是U-Boot发明的它是Linux内核构建系统的一次成功移植与精简。但很多人误以为只要照抄内核的Makefile结构就能跑通结果在RV1106上卡死在scripts/Makefile.build:48: *** No rule to make target arch/arm/mach-rv1106/built-in.o。问题出在哪出在没理解Kbuild三件套不是静态文件而是一套动态协商的协议Kconfig定义“能做什么”顶层Makefile定义“怎么做”子目录Makefile定义“谁来做”。三者缺一不可且版本间存在严格兼容约束。2.1 Kconfig配置项的语法树不是简单的开关列表打开arch/arm/Kconfig你会看到类似这样的代码config SYS_SOC string SoC type default rv1106 if ARCH_RV1106 help Select the SoC type for this platform.这行default rv1106 if ARCH_RV1106表面看是设默认值实际是触发了一个隐式依赖当ARCH_RV1106y被选中时Kconfig解析器会强制将SYS_SOC设为rv1106并把这个字符串写入.config。但关键在下一步——Kbuild会读取这个字符串然后拼接出路径arch/arm/mach-$(CONFIG_SYS_SOC)也就是arch/arm/mach-rv1106并自动包含该目录下的Kconfig和Makefile。这就是为什么你不能随便改mach-rv1106目录名Kbuild的路径拼接是硬编码在顶层Makefile里的它不查文件系统是否存在只按规则生成路径。我踩过最大的坑是在移植RV1106时把mach-rv1106误写成mach-rv1106_v1make menuconfig能进配置也能保存但make一跑就报No rule to make target。排查了3小时才发现顶层Makefile里有段逻辑ifeq ($(CONFIG_ARCH_RV1106),y) KBUILD_EXTRA_SYMBOLS : $(srctree)/arch/arm/mach-rv1106/Module.symvers obj-y mach-rv1106/ endif注意这里写死的是mach-rv1106/不是变量。Kconfig里的CONFIG_SYS_SOC只是用来生成头文件宏真正的目录引用由顶层Makefile的if判断硬编码。所以Kconfig不是万能的它只负责配置项定义和依赖关系真正的路径绑定得靠Makefile配合。提示Kconfig里的source arch/arm/mach-rv1106/Kconfig这行不是“包含文件”而是告诉Kconfig解析器请把mach-rv1106/Kconfig里的所有config项作为当前菜单的子项加载。它不执行任何编译逻辑只扩展配置菜单树。2.2 顶层Makefile构建流程的总指挥不是普通脚本U-Boot根目录下的Makefile前200行全是变量定义和环境检测真正干活的逻辑从第256行include $(srctree)/Makefile.build开始。但很多人忽略了一个关键事实这个顶层Makefile本身不直接编译任何C文件。它只做三件事初始化环境ARCH,CROSS_COMPILE、加载Kconfig生成的.config、然后调用scripts/Makefile.build去递归处理每个obj-y目录。举个具体例子当你执行make ARCHarm CROSS_COMPILEarm-linux-gnueabihf-顶层Makefile首先做export ARCHarm设置架构export CROSS_COMPILEarm-linux-gnueabihf-设置交叉工具链前缀include include/config/autoconf.h这个头文件是make menuconfig后自动生成的里面全是#define CONFIG_SYS_SOC rv1106这类宏供C代码编译时使用最关键一步$(Q)$(MAKE) $(build).这行命令调用自身$(MAKE)即make但参数$(build).表示请用scripts/Makefile.build来构建当前目录.。这个$(build).是Kbuild的魔法开关。它让make暂时丢弃当前Makefile转而加载scripts/Makefile.build并把.作为src变量传进去。scripts/Makefile.build拿到src.后会扫描当前目录下的Makefile即顶层Makefile提取obj-y lib/ common/ drivers/等语句然后对每个子目录递归执行$(MAKE) $(build)lib/、$(MAKE) $(build)common/……就这样一层层深入直到叶子目录。所以顶层Makefile的本质是一个“构建路由器”它不写编译命令只负责把构建请求分发给正确的Makefile.build实例。这也是为什么你改了drivers/Makefile里的obj-y usb/不用动顶层Makefile——因为drivers/Makefile会被scripts/Makefile.build自动加载并解析。2.3 子目录Makefile模块化编译的契约书不是独立脚本进入drivers/usb/目录你会看到一个极简的Makefileobj-$(CONFIG_USB) usb_core.o obj-$(CONFIG_USB_DWC2) dwc2.o这行obj-$(CONFIG_USB) usb_core.o表面看是条件编译实际是Kbuild的契约声明如果.config里CONFIG_USBy则把usb_core.o加入当前模块的编译列表如果CONFIG_USBm模块则生成usb_core.ko如果CONFIG_USBn则完全忽略这行。但关键在于usb_core.o从哪来Kbuild约定同目录下必须存在usb_core.c且编译规则由scripts/Makefile.build统一提供——你不需要写usb_core.o: usb_core.c这样的显式规则。这个约定带来两个强约束文件名必须严格匹配obj-y foo.o要求必须有foo.c或foo.S否则make报No rule to make target foo.o头文件路径由父目录传递drivers/usb/目录本身不指定-I路径它的-I来自上层drivers/Makefile里ccflags-y : -I$(srctree)/include而$(srctree)/include又来自顶层Makefile。所以RV1106平台的头文件路径-I$(srctree)/arch/arm/include/asm/arch-rv1106必须在arch/arm/mach-rv1106/Makefile里用ccflags-y -I$(srctree)/arch/arm/include/asm/arch-rv1106显式添加否则#include asm/arch-rv1106/gpio.h会找不到。我曾为RV1106的GPIO驱动加头文件路径试了三种写法错误写法1-Iarch/arm/include/asm/arch-rv1106相对路径make在drivers/usb/目录下执行找不到错误写法2-I../arch/arm/include/asm/arch-rv1106路径跳转不稳定不同编译层级下..指向不同正确写法-I$(srctree)/arch/arm/include/asm/arch-rv1106$(srctree)始终指向U-Boot源码根目录绝对可靠。这就是子目录Makefile的契约精神它不关心全局路径只声明“我要编译什么”路径、工具链、宏定义全部由Kbuild框架注入。3. 实操拆解从零构建RV1106平台的Kbuild骨架现在我们动手在U-Boot 2023.04版本上为RV1106芯片搭建最小可运行的Kbuild骨架。不复制粘贴每一步都解释清楚“为什么必须这样”。3.1 第一步创建架构支持目录不是简单mkdirRV1106是Rockchip的RISC-V芯片但U-Boot官方主干尚未原生支持需手动添加。先创建目录结构mkdir -p arch/riscv/cpu/rv1106 mkdir -p arch/riscv/mach-rv1106 mkdir -p board/rockchip/rv1106_evk注意这里用的是arch/riscv/而非arch/arm/因为RV1106是RISC-V指令集。很多新手直接往arch/arm/里塞结果make ARCHriscv根本找不到配置项——Kbuild的架构识别是通过ARCH参数匹配arch/*/目录名实现的。接着必须创建arch/riscv/Kconfig并在末尾添加source arch/riscv/mach-rv1106/Kconfig这行source是Kconfig的“入口注册”。没有它make menuconfig里就不会出现RV1106的配置菜单。Kconfig文件本身不执行只提供菜单定义但source语句是Kconfig解析器发现新菜单的唯一途径。注意arch/riscv/Kconfig里已有menu RISC-V system setup你的source必须放在这个menu块内否则菜单会显示在错误位置。3.2 第二步编写Kconfig菜单不是罗列选项arch/riscv/mach-rv1106/Kconfig内容如下if ARCH_RV1106 config SYS_SOC string SoC type default rv1106 config SYS_VENDOR string Vendor name default rockchip config SYS_BOARD string Board name default rv1106_evk config SYS_CONFIG_NAME string Configuration name default rv1106_evk config SYS_CPU string CPU name default rv64imac endif关键点在于if ARCH_RV1106这个外层条件。它确保只有当ARCH_RV1106y被选中时这些配置项才生效。而ARCH_RV1106本身需要在arch/riscv/Kconfig里定义config ARCH_RV1106 bool Rockchip RV1106 select ARCH_RISCV select CPU_RISCV_RV64IMAC help Support for Rockchip RV1106 SoC.这里select ARCH_RISCV是关键它强制启用RISC-V通用架构支持避免你单独选ARCH_RV1106却漏掉基础RISC-V配置。Kconfig的select不是建议是硬性依赖注入。3.3 第三步编写子目录Makefile不是写编译命令arch/riscv/mach-rv1106/Makefile内容obj-y rv1106.o obj-y clock.o obj-y gpio.o # 头文件路径必须用$(srctree) ccflags-y -I$(srctree)/arch/riscv/include/asm/arch-rv1106 # 链接脚本RV1106需要特定内存布局 LDFLAGS_rv1106.o : -T $(srctree)/arch/riscv/cpu/rv1106/u-boot.ldsccflags-y这行解决了“makefile 头文件路径 rv1106”这个热搜问题它告诉Kbuild编译这个目录下所有.c文件时额外添加-I参数。$(srctree)是Kbuild预定义变量指向源码根目录比../..安全一万倍。LDFLAGS_rv1106.o这行是高级技巧它为rv1106.o这个目标文件指定链接脚本。U-Boot的链接脚本决定代码段、数据段、BSS段在内存中的位置RV1106的SRAM地址和DDR初始化顺序与通用RISC-V不同必须定制。如果你漏了这行u-boot.bin可能烧录后无法启动因为代码被链接到了错误的物理地址。3.4 第四步生成最小配置不是直接make创建configs/rv1106_evk_defconfigCONFIG_ARMy CONFIG_ARCH_RV1106y CONFIG_SYS_TEXT_BASE0x00000000 CONFIG_SYS_SDRAM_BASE0x00000000 CONFIG_DEFAULT_DEVICE_TREErv1106-evk注意CONFIG_ARMy这个反直觉的设置RV1106虽然是RISC-V芯片但U-Boot 2023.04的RISC-V支持仍处于实验阶段很多板级支持包BSP依赖ARM架构的通用代码路径。这是版本兼容性问题不是错误。CONFIG_SYS_TEXT_BASE设为0x00000000是因为RV1106启动ROM从地址0开始执行必须匹配。然后执行make rv1106_evk_defconfig make menuconfig # 确认ARCH_RV1106被选中且无冲突警告 make -j4如果make报错make: *** No rule to make target arch/riscv/mach-rv1106/rv1106.o说明arch/riscv/mach-rv1106/rv1106.c文件不存在。Kbuild的obj-y rv1106.o会自动寻找同名.c文件缺一不可。4. 常见问题与排查技巧实录那些文档里不会写的坑Kbuild的问题90%不是语法错误而是路径、变量、依赖的隐式断裂。以下是我在7次移植中记录的真实问题与排查链。4.1 问题1“make没有指明目标并且找不到makefile”现象执行make时终端输出make: *** No targets specified and no makefile found. Stop.排查链先确认当前目录是否为U-Boot源码根目录ls Makefile应存在检查ARCH环境变量echo $ARCH如果为空make会尝试读取Makefile里的默认ARCH但U-Boot顶层Makefile不设默认值导致失败执行make help | grep -i arch查看可用架构确认riscv在列表中如果ARCHriscv已设置但仍有此错运行strace -e traceopenat make ARCHriscv 21 | grep -i no such file看make试图打开哪些Makefile最常见原因arch/riscv/目录下缺少Kconfig文件导致make menuconfig无法生成.config而make依赖.config里的CONFIG_宏来决定构建路径。独家技巧用make -ndry-run查看make实际执行的命令。例如make -n ARCHriscv | head -20会打印出make -f scripts/Makefile.build objarch/riscv这样的调用证明构建流程已启动。如果make -n也报同样错误说明顶层Makefile根本没加载问题在环境变量或目录结构。4.2 问题2“cmake和makefile区别”背后的真相它们根本不在同一维度现象团队新人问“U-Boot为什么不用CMakeCMake不是更现代吗”真相CMake和Makefile不是替代关系而是抽象层级不同。CMake是元构建系统它生成Makefile或其他构建文件而Kbuild是构建规则引擎它本身就是一套高度定制化的Makefile集合。U-Boot不用CMake因为CMake生成的Makefile无法精确控制链接脚本、符号表、二进制段布局——这些是Bootloader的生命线Kbuild的obj-y声明式语法比CMake的add_library()更适合描述“条件编译模块”U-Boot的构建必须与Linux内核保持接口一致如scripts/Makefile.*以便复用内核的构建工具链。实操验证在U-Boot根目录运行cmake .会失败因为U-Boot没有CMakeLists.txt。即使你手写一个也无法生成u-boot.bin——CMake不知道-T u-boot.lds该加在哪里也不知道__image_copy_start这个符号必须放在.text段开头。4.3 问题3生成makefile失败其实是Kconfig未生效现象make menuconfig能打开但修改配置后保存include/config/autoconf.h没更新make编译仍用旧配置。根因Kconfig的配置保存依赖于conf工具对.config文件的写入而.config的读取依赖于顶层Makefile里的-include include/config/autoconf.h。如果include/config/autoconf.h不存在make会静默跳过继续用旧宏。排查步骤运行make silentoldconfig这是Kbuild的强制配置同步命令它会重新解析.config并生成autoconf.h检查include/config/autoconf.h时间戳是否更新如果仍无效运行make distclean清空所有中间文件再make menuconfig终极检查grep CONFIG_ARCH_RV1106 .config确认该行存在且为y。避坑心得永远不要手动编辑.config文件。Kconfig的依赖关系如select、depends on是动态计算的手动改可能导致配置不一致。make menuconfig是唯一安全入口。4.4 问题4RV1106头文件路径失效编译报错“asm/arch-rv1106/gpio.h: No such file”现象drivers/gpio/rv1106_gpio.c里#include asm/arch-rv1106/gpio.h报错。深度排查运行make V1 | grep -A5 gcc.*-I查看实际gcc命令行确认-I参数是否包含arch/riscv/include/asm/arch-rv1106如果没出现检查arch/riscv/mach-rv1106/Makefile里的ccflags-y是否拼写正确ccflags-y不是CFLAGS-y如果出现了但路径不对检查$(srctree)变量在Makefile里加$(info srctree$(srctree))确认它指向正确根目录最隐蔽原因arch/riscv/include/asm/arch-rv1106/目录下gpio.h文件权限为只读且make以非root用户运行导致make clean时无法删除旧文件新文件写入失败。解决方案表格现象可能原因快速验证命令修复方法-I路径未出现在gcc命令中ccflags-y写错位置或变量名make -s -n | grep -o ccflags.*确保ccflags-y在子目录Makefile中且在obj-y之前路径存在但文件找不到$(srctree)未定义或为空make -s -n | head -10在顶层Makefile开头加$(info srctree$(srctree))调试文件存在但权限拒绝目录权限为dr-xr-xr-xls -ld arch/riscv/include/asm/arch-rv1106chmod -R uw arch/riscv/include/asm/arch-rv11064.5 问题5make -j4并发编译失败报错“multiple definition ofboard_init_f”现象单线程make成功make -j4失败提示重复定义。原理Kbuild的并发编译要求每个.o文件的符号作用域严格隔离。board_init_f是板级初始化函数必须只在一个文件里定义。错误通常发生在board/rockchip/rv1106_evk/rv1106_evk.c里定义了board_init_f同时arch/riscv/mach-rv1106/rv1106.c里也定义了同名函数make -j4时两个文件并发编译链接阶段发现重复定义。排查命令# 查找所有board_init_f定义 grep -r board_init_f.*{ board/ arch/ --include*.c --include*.S # 查看链接时的符号表 nm u-boot | grep board_init_f修复原则U-Boot规定board_init_f必须在board/xxx/xxx/xxx.c里定义arch/xxx/目录下只放SoC级通用代码如时钟、GPIO驱动不放板级初始化。这是Kbuild的模块职责划分不是技术限制而是协作规范。5. 工具链与调试用GNU Make的原生能力定位Kbuild问题当make报错晦涩难懂时别急着谷歌先用GNU Make自带的调试功能。这些能力比任何IDE都直接有效。5.1make -d不是万能但能暴露决策链make -d会输出所有决策日志但信息量巨大单次输出超10万行。高效用法是结合grepmake -d ARCHriscv 21 | grep -E (Considering|Must remake|Successfully remade) | head -50这会显示make如何决策构建目标。例如看到Considering target file arch/riscv/mach-rv1106/rv1106.o. File arch/riscv/mach-rv1106/rv1106.o does not exist. Must remake target arch/riscv/mach-rv1106/rv1106.o.说明rv1106.o目标被识别但源文件缺失。如果这里卡住直接去arch/riscv/mach-rv1106/目录下检查rv1106.c。5.2make -p打印所有规则找到“幽灵”变量make -p输出Makefile所有变量和规则是定位“变量被谁覆盖”的终极武器。例如RV1106编译时CROSS_COMPILE突然变成riscv64-unknown-elf-而你设的是riscv64-linux-gnu-make -p ARCHriscv | grep ^CROSS_COMPILE输出可能包含CROSS_COMPILE : riscv64-unknown-elf- CROSS_COMPILE : riscv64-linux-gnu- # 来自命令行第二行是命令行传入的优先级最高第一行是Makefile里:赋值的已被覆盖。但如果看到CROSS_COMPILE riscv64-unknown-elf-注意这里是而非:说明它是递归展开变量可能被其他地方修改。此时用make -p ARCHriscv | grep -A10 CROSS_COMPILE.*查看赋值上下文往往能找到include config.mk之类的引入点。5.3 自定义调试Makefile在关键节点插入$(info)在arch/riscv/mach-rv1106/Makefile开头加$(info [DEBUG] mach-rv1106 Makefile loaded) $(info [DEBUG] srctree$(srctree)) $(info [DEBUG] obj-y$(obj-y))$(info)是Makefile的调试宏会在make执行到该行时打印信息不中断流程。它比echo可靠因为echo可能被make -s静音而$(info)总是输出。我用这个技巧定位过一个诡异问题obj-y变量在make -j4时偶尔为空。加了$(info)后发现arch/riscv/mach-rv1106/Makefile被加载了两次第二次时obj-y被清空。根源是arch/riscv/Makefile里有行obj-$(CONFIG_ARCH_RV1106) mach-rv1106/而CONFIG_ARCH_RV1106在并发时被多次求值导致条件判断不稳定。解决方案把obj-y ...改成obj-$(CONFIG_ARCH_RV1106) mach-rv1106/确保条件编译的原子性。6. Kbuild进阶从移植到定制构建自己的规则引擎掌握Kbuild入门后下一步是理解它如何被定制。U-Boot的scripts/Makefile.*系列就是一套可插拔的规则引擎。6.1scripts/Makefile.build编译规则的中央处理器打开scripts/Makefile.build核心逻辑在第120行include $(srctree)/$(obj)/Makefile这行代码是Kbuild“模块化”的灵魂。$(obj)是当前构建目录如drivers/usb/include语句动态加载该目录下的Makefile然后执行其中的obj-y等声明。这意味着你可以在drivers/usb/里写obj-y my_driver.oKbuild会自动为你生成my_driver.o: my_driver.c规则并调用$(CC)编译。但scripts/Makefile.build本身不定义$(CC)它从顶层继承。所以如果你想为RV1106的USB驱动启用特定编译选项如-marchrv64imac -mabilp64d不能在drivers/usb/Makefile里写CC : riscv64-linux-gnu-gcc而应该# drivers/usb/Makefile ccflags-y -marchrv64imac -mabilp64dccflags-y会被scripts/Makefile.build收集并附加到所有该目录下.c文件的gcc命令中。这是Kbuild的“规则注入”机制比直接覆盖CC变量安全得多。6.2scripts/Makefile.lib库文件生成的幕后推手lib/目录下的Makefile里有lib-y libfdt.o但libfdt.o实际由scripts/Makefile.lib生成。这个文件定义了lib-y的特殊处理它会把所有lib-y目标先编译成.o再用$(AR)打包成lib.a最后链接进u-boot。所以lib-y xxx.o不是简单编译而是“编译归档链接”三步。如果你要为RV1106添加一个专用数学库libmath.a正确做法是创建lib/math/目录放math.c在lib/Makefile里加obj-$(CONFIG_RV1106_MATH) math/在lib/math/Makefile里写lib-y math.o在Kconfig里添加config RV1106_MATH。这样make会自动执行ar rcs lib/math/libmath.a math.o再把libmath.a链接进最终镜像。你不需要写任何ar命令——Kbuild的lib-y规则已内置。6.3 定制Kbuild为RV1106添加“一键烧录”目标U-Boot默认make只生成u-boot.bin但RV1106开发板常用USB烧录工具rkdeveloptool。我们可以扩展Kbuild添加make flash目标# 在顶层Makefile末尾添加 .PHONY: flash flash: u-boot.bin echo Flashing u-boot.bin to RV1106... rkdeveloptool db u-boot.bin rkdeveloptool rdPHONY声明flash是伪目标不对应真实文件u-boot.bin是依赖确保先编译echo和rkdeveloptool是shell命令。这样make flash就能一键烧录。但更Kbuild的方式是# 在board/rockchip/rv1106_evk/Makefile里 flash: u-boot.bin $(Q)$(TOPDIR)/tools/rkflash.sh $ # tools/rkflash.sh内容 #!/bin/sh rkdeveloptool db $1 rkdeveloptool rd把烧录逻辑封装成脚本符合Kbuild“分离关注点”的哲学Makefile只调度脚本只干活。我在RV1106项目里还扩展了make dtb目标专门编译设备树dtb: $(DTB) echo Device tree built: $ $(DTB): $(DTS) $(Q)$(DTC) -I dts -O dtb -o $ $$(DTC)是设备树编译器$(DTS)是源文件。这样make dtb只编译dtb不碰u-boot提升迭代效率。7. 经验总结Kbuild不是障碍而是U-Boot的灵魂协议做完这7次移植我越来越确信Kbuild不是U-Boot的附属品而是它的灵魂协议。它用Makefile的古老语法实现了现代构建系统的模块化、声明式、可扩展。当你抱怨“makefile太难”其实是在抱怨自己还没读懂这套协议的语言。最深的体会是Kbuild的优雅在于它的“不透明”。你不需要知道scripts/Makefile.build里$(filter-out FORCE,$(wildcard $(dep)))这行代码怎么工作就像你不需要知道TCP/IP协议栈的每一行C代码就能用浏览器上网。Kbuild的价值是让你专注在“我要编译什么”Kconfig、“我要怎么编译”Makefile、“我要链接到哪”lds这三个核心问题上而不是陷入Make的语法细节。所以别把Kbuild当成要攻克
返回列表