
我们下一步就真正开始解剖摘要本文通过解剖 BusyBox 的构建过程深入解析 Yocto/BitBake 的任务依赖机制。首先从bitbake -e输出入手揭示do_configure → do_compile → do_install → do_package依赖链的构建原理解释parents与depends的区别、addtask的画箭头本质以及base_do_compile()与do_compile()的关系。接着分析do_compile[dirs] ${B}等 task flag 的作用并展示如何将 BusyBox 解剖为 Recipe、继承/include、最终 task、DAG 四层结构。最后针对 Raspberry Pi 2 的 eth0 网络接口启动问题提供两种解决方案创建启动脚本或修复ifup -a自动执行机制。do_configure → do_compile → do_install → do_package到底是谁把这条 dependency chain 建出来的。对这一步就是 Yocto / BitBake 真正值得“解剖”的地方。先给你结论不是某一个地方直接写死了 do_configure → do_compile → do_install → do_package 而是BitBake 在解析 recipe、继承 class、构造 task flags 和自动依赖关系时逐层把这条链“推导”出来。对这组输出已经非常关键了。你现在其实已经把 BusyBox 的 task DAG任务依赖图和 task 实现来源 两件事情同时抓到了。我们继续沿着你上一节“解剖 busybox.bb”的路线不急着看所有 task而是只追一条最重要的链do_configure ↓ do_compile ↓ do_install ↓ do_package ↓ do_packagedata ↓ do_package_write_rpm你刚才的输出已经把这条链的骨架暴露得非常清楚。1. 先看最重要的一行你的parents:{...do_compile:[do_configure_ptest_base,do_configure],do_install:[do_compile_ptest_base,do_compile],...do_package:[do_install_ptest_base,do_install],do_packagedata:[do_package],do_package_write_rpm:[do_packagedata,do_package,do_deploy_source_date_epoch],...}把它翻译成人话do_configure │ ├──→ do_compile │ │ │ └──→ do_install │ │ │ └──→ do_package │ │ │ └──→ do_packagedata │ │ │ └──→ do_package_write_rpm所以你可以先记住Recipe 文件不是“执行程序”而是“描述任务和任务之间关系的输入”。BitBake 最终把这些描述变成一个 DAG。2. 你的 grep do_compile 到底看到了什么你看到19176:base_do_compile() {然后19415:do_compile() {这里非常容易产生误解。很多初学者会认为do_compile()就是 busybox.bb 里面定义的函数。不是。这正是我们现在要学会的一个核心bitbake -e busybox 输出的是 最终展开后的 datastore。它不是单纯把busybox.bb busybox.inc base.bbclass...某一个文件原样打印出来。BitBake 已经把各种 class、include、append、override 等全部合并了。3. base.bbclass 是关键来源你刚才又执行grep-ndo_configure\../poky/meta/classes-global/base.bbclass得到334:do_configure[dirs]${B}335:base_do_configure(){353:addtask compile after do_configure然后grep-ndo_compile\../poky/meta/classes-global/base.bbclass得到354:do_compile[dirs]${B}355:base_do_compile(){363:addtaskinstallafter do_compile726:EXPORT_FUNCTIONS do_fetch do_unpack do_configure do_compile do_install这几行非常值得你停下来理解。4. base_do_compile() 和 do_compile() 为什么有两个这是 BitBake class 机制的一个核心设计。base.bbclass 中大致是base_do_compile(){...}do_compile[dirs]${B}EXPORT_FUNCTIONS do_compile它实际上是在说我这个 class 提供一个基础版本的 do_compile。但是 BitBake 的 class 函数导出机制会让最终 recipe 获得do_compile这个 task。所以你看到base_do_compile()和do_compile()不要理解成两个不同的编译阶段而应该理解成base_do_compile ↓ class 提供的默认实现 do_compile ↓ recipe 最终使用的 task5. 更重要的是 addtask你看到353:addtask compile after do_configure以及363:addtask install after do_compile这是整个 BitBake 学习中必须吃透的语法。例如addtask compile after do_configure不是“现在执行 compile。”而是建立任务依赖关系。也就是do_configure↓do_compile同理addtask install after do_compile表示do_compile ↓ do_install6. 所以 addtask 可以理解成“画箭头”这是我建议你现在建立起来的思维方式。看到addtask compile after do_configure脑子里不要读成addtask 是一个命令。而应该直接翻译成do_configure ──────→ do_compile看到addtask install after do_compile立即翻译do_compile ────────→ do_install看到addtask package after do_install立即翻译do_install ─────────→ do_package这样学习 BitBake 会快很多。7. 现在回到你的 bitbake -e你得到‘do_compile’: [‘do_configure_ptest_base’, ‘do_configure’]注意do_compile有两个 parentdo_configure do_configure_ptest_base所以真实 DAG 不是简单的一条线。更准确是┌──→ do_configure ────────┐... ──────────────┤ ├──→ do_compile └──→ do_configure_ptest_base┘然后do_compile │ ├──→ do_install │ └──→ do_compile_ptest_base再往后do_install ├──→ do_package └──→ do_install_ptest_base因此你现在看到的不是A → B → C而是这就是为什么之前我们让你去看task-depends.dot因为它实际上就是把这种关系画出来。8. 还有一个特别重要的区别parents vs depends你输出中还有depends:{do_install: virtual/fakeroot-native:do_populate_sysroot,...}这个和parents:{do_install:[do_compile]}不是一回事。这是你现在学习 BitBake 最值得建立的概念之一第一种task-to-task 依赖例如do_compile → do_install来自addtask install after do_compile它是同一个 recipe 内部的 task 顺序。第二种recipe/task 对另外一个 recipe/task 的依赖你的do_install: virtual/fakeroot-native:do_populate_sysroot意思是busybox:do_install │ ↓ virtual/fakeroot-native:do_populate_sysroot也就是说BusyBox 的 do_install 需要先准备fakeroot-native的 sysroot。所以整个图实际上是二维的这就是 BitBake 和普通 Makefile 思维非常不同的地方。9. 为什么 do_compile 没有 fakeroot你这里特别值得注意depends:{do_install: virtual/fakeroot-native:do_populate_sysroot,...}但是没有do_compile: virtual/fakeroot-native:do_populate_sysroot原因非常直观编译通常不需要 fakeroot。编译阶段主要是sourcecode ↓ compiler ↓ object files ↓ binary而安装阶段开始涉及owner group permissions device nodes filesystem layout所以do_install需要 fakeroot 环境。你的输出还明确告诉你fakeroot:{do_install:1,do_package:1,do_package_qa:1,do_package_write_rpm:1,...}因此可以建立这个认知do_compile │ 普通编译环境 ↓ do_install │ fakeroot ↓ do_package │ fakeroot ↓ do_package_write_rpm10. do_compile[dirs] “${B}” 又是什么意思你刚刚看到do_compile[dirs] “${B}”这个也非常重要。它不是do_compile 在 B 目录执行这么简单。它是在给 task 添加一个 task flagdo_compile └──[dirs]${B}也就是说执行 do在执行 do_compile 任务之前BitBake 会确保${B}目录存在并将其设置为该任务的工作目录之一。对于 BusyBox你可以继续查bitbake -e busybox | grep ‘^B’以及bitbake -e busybox | grep ‘^S’你大概率会看到类似S...B...于是你就能把源码目录 S 编译目录 B和do_compile[dirs] “${B}”连接起来。11. 现在再看 do_install你找到19204:base_do_install() {以及19523:do_install() {同样不要迷惑。它仍然属于class 提供默认实现 ↓ recipe 最终 task你现在真正应该追的是do_install到底执行了什么命令。因此下一步不要再 grep 整个 bitbake -e。我们直接让 BitBake 告诉你bitbake-ebusybox|sed-n19520,19560p然后再看bitbake -e busybox | sed -n ‘19410,19440p’分别观察do_compile()和do_install()12. 但是这里有一个“学习陷阱”你现在已经看到base_do_compile()所以很容易产生一个错误认识“BusyBox 编译就是 base.bbclass 的 base_do_compile()。”不一定。这正是我们下一步必须确认的东西。因为 BitBake 中可能存在base.bbclass ↓ busybox.bb ↓ busybox.inc ↓ 其他 class ↓ 最终 do_compile某个 recipe 可以do_compile(){...}直接覆盖默认实现也可以do_compile:append(){...}在默认实现后追加也可以do_compile:prepend(){...}在默认实现前追加。所以bitbake -e的真正价值就是看“最终到底是什么”而不是猜“哪个文件可能定义了它”。13. 你这次输出已经揭示了整个学习路线现在我们可以把 BusyBox 解剖成四层。第一层Recipebusybox.bb告诉 BitBake我要构建 BusyBox。 ↓第二层继承 / includebusybox.bb ↓ busybox.inc ↓ base.bbclass ↓ 其他 class ↓第三层最终 taskBitBake 展开成do_fetch do_unpack do_patch do_configure do_compile do_install do_package do_packagedata do_package_write_rpm...↓第四层DAG最终变成do_fetch ↓ do_unpack ↓ do_patch ↓ do_configure ↓ do_compile ↓ do_install ↓ do_package ↓ do_packagedata ↓ do_package_write_rpm同时旁边还挂着fakeroot-native binutils rpm-native file-native dwarfsrcfiles-native update-rc.d-native...这就是你之前看到的task-depends.dot的本质。14. 下一步我们不要继续“看更多”现在最重要的是动手验证一个问题BusyBox 的 do_compile 到底最终执行了什么请你运行下面这组命令bitbake -e busybox | sed -n ‘19410,19440p’然后bitbake -e busybox | sed -n ‘19515,19545p’再加三个非常关键的变量bitbake-ebusybox|grep^Sbitbake-ebusybox|grep^Bbitbake-ebusybox|grep^D我们下一步就只解剖这三个目录 do_compile do_install。一旦这一步搞懂你就会真正理解${S}源码在哪里${B}编译在哪里${D}安装到哪里以及do_compile ↓ 生成什么 do_install ↓ 把什么复制到${D}do_package ↓ 从${D}里面切出 RPM/IPK/DEB 的文件内容这才是从“会用 BitBake”进入“真正看懂 BitBake”的关键一步。15. eth0 upip link set eth0 up 解决连接不上问题 怎么把它写到 image 里面可以。既然你现在已经确认ip link set eth0 up执行后网络就恢复那么问题是 eth0 没有在启动时自动 UP。你现在使用的是/etc/network/interfaces所以最适合你的方案是让 ifup 在启动时自动执行 ip link set eth0 up。不过有一个更简单的方法在 interfaces 里加入 auto eth0 后确保启动阶段真的执行 ifup -a。你当前已经有auto eth0 iface eth0 inet static address169.254.125.50 netmask255.255.0.0 gateway169.254.125.127如果系统没有执行 ifup -aauto eth0 本身不会自动产生效果。推荐做法在你的 network-config.bb 里加入一个启动脚本让系统启动时执行ip link set eth0 up例如network-config/ ├── network-config.bb └── files/ └── eth0-up.sheth0-up.sh#!/bin/shiplinkseteth0 uprecipeSUMMARYNetwork configurationLICENSECLOSEDSRC_URIfile://interfaces \ file://eth0-up.shS${WORKDIR}do_install(){install-d${D}${sysconfdir}/networkinstall-m0644${WORKDIR}/interfaces\${D}${sysconfdir}/network/interfacesinstall-d${D}${sysconfdir}/init.dinstall-m0755${WORKDIR}/eth0-up.sh\${D}${sysconfdir}/init.d/eth0-up}INITSCRIPT_NAMEeth0-upINITSCRIPT_PARAMSstart 20 2 3 4 5 .inherit update-rc.d然后bitbake-cclean network-config bitbake core-image-minimal重新烧录 WIC。但是我更建议你先确认一个东西你执行ip link set eth0 up以后网络马上恢复说明eth0 DOWN ↓iplinkseteth0 up ↓ eth0 UP ↓ 网络正常但如果你已经有auto eth0那么正常情况下 ifup eth0 应该也会把 interface 拉起来。所以先在 Pi 2 上测试ifup eth0然后ip link show eth0如果ifup eth0也能解决问题那么不要自己写 ip link set eth0 up 启动脚本。这时候应该解决的是为什么 boot 时没有执行 ifup -a。这其实更符合你现在的 Yocto 配置。我建议你现在在 Pi 2 上只测试这两条ifdowneth0然后ifup eth0再ip link show eth0如果 ifup eth0 能成功我们就沿着你现在的 /etc/network/interfaces sysvinit 方案把 ifup eth0 自动加入 boot 流程这样比硬编码 ip link set eth0 up 更标准