
第8课 很好这一课可以作为你 Yocto 学习路线 的第 8 课。摘要本课是 Yocto 学习路线的第 8 课专注于如何在 Yocto 项目中集成、编译和安装 Linux 内核模块Kernel Module。课程从理解内核模块的基本概念开始详细介绍了模块与内置驱动的区别、最小驱动示例、Yocto 中编译模块的专用 classmodule.bbclass、目录结构、Recipe 编写、Makefile 配置、模块安装到镜像的流程以及在 Raspberry Pi 上加载和验证模块的完整步骤。最后课程厘清了 Yocto 中 Recipe、Package、Image、Driver 等核心概念的关系帮助读者掌握从源码到镜像集成的完整链路。前面你已经掌握了✔ 从零写 recipe✔ do_install✔ system/service✔ Layer✔ Image✔ Package / RDEPENDS✔ menuconfigKernel Configuration下一步就是第8课Kernel Module —— 掌握驱动模块目标不是成为 Linux Driver 开发者而是理解 Yocto 如何编译、安装、加载 Kernel Module。本课最终效果最终 Raspberry Pi 上应该能够做到rootraspberrypi:~# lsmodhello_driver163840rootraspberrypi:~# modinfo hello_driverrootraspberrypi:~# insmod hello_driver.koHello Driver Loaded rootraspberrypi:~# rmmod hello_driverHello Driver Removed也就是说.bb ↓ bitbake ↓ hello_driver.ko ↓ 放入 rootfs ↓ Pi启动 ↓ insmod ↓ printk()这一条链路全部打通。第一部分理解 Kernel ModuleLinux 驱动一般有两种① Built-in编译进 kernelvmlinuz ├── ext4 ├── usb ├── ethernet └── spi不能卸载。② Module本课编译成hello_driver.ko需要时insmod hello_driver.ko不用rmmod hello_driver所以驱动开发几乎都先写 module。一个最小驱动#include linux/module.h#include linux/init.hstatic int __init hello_init(void){printk(Hello Driver Loaded\n);return0;}static void __exit hello_exit(void){printk(Hello Driver Removed\n);}module_init(hello_init);module_exit(hello_exit);MODULE_LICENSE(GPL);MODULE_AUTHOR(Me);MODULE_DESCRIPTION(Hello Driver);这就是 Linux 最小驱动。第二部分模块如何编译普通 Linux 下Makefile hello_driver.cmake得到hello_driver.ko但是 Yocto 不直接用这个流程。Yoctohello-driver.bb ▼ module.bbclass ▼ kernel build system ▼ hello_driver.ko所以重点是module.bbclass这是所有 Kernel Module Recipe 的核心。第三部分Yocto目录建议建立meta-myproduct └── recipes-kernel └── hello-driver ├── hello-driver.bb ├── files │ ├── hello_driver.c │ └── Makefile以后几乎所有驱动都放recipes-kernel/第四部分Recipe最简单SUMMARYHello Kernel ModuleLICENSEMITLIC_FILES_CHKSUMfile://LICENSE;md5f4fa7fd56aac72279ad21a112915ca87inherit module S${WORKDIR}SRC_URI\ file://hello_driver.c \ file://Makefile \ file://LICENSE \ do_install(){install-d${D}${nonarch_base_libdir}/modules/${KERNEL_VERSION}/extrainstall-m0644 hello_driver.ko\${D}${nonarch_base_libdir}/modules/${KERNEL_VERSION}/extra/}你会发现整个 recipe 非常短。因为inherit module已经替你完成- 编译 - 安装 - strip - package - depmod这就是 module.bbclass 的价值。第五部分MakefileKernel Module 的 Makefile 非常固定 obj-m :hello_driver.o all:$(MAKE)-C$(KERNEL_PATH)M$(PWD)modules modules_install:$(MAKE)-C$(KERNEL_PATH)M$(PWD)modules_install clean:$(MAKE)-C$(KERNEL_PATH)M$(PWD)cleanYocto 会自动调用 Kernel 构建系统来完成实际编译不需要在这里写复杂规则。第六部分安装到 Image例如在你的 myproduct-image.bb 中IMAGE_INSTALL “hello-driver”重新构建bitbake myproduct-image然后检查 tmp/work/raspberrypi2-poky-linux-gnueabi/hello-driver/1.0/hello_driver.kobuild-rpi2$ find tmp/work -path “/myproduct-image//rootfs/” -name “.ko”tmp/work/raspberrypi2-poky-linux-gnueabi/myproduct-image/1.0/rootfs/usr/lib/modules/6.6.63-mytest/extra/hello_driver.ko推荐做法Scarthgap对于只有一个简单外部模块的学习例子我建议不要自己实现 modules_install而是在 recipe 中覆盖 do_install。例如inherit module S${WORKDIR}do_install(){install-d${D}${nonarch_base_libdir}/modules/${KERNEL_VERSION}/extrainstall-m0644 hello_driver.ko\${D}${nonarch_base_libdir}/modules/${KERNEL_VERSION}/extra/}这样- do_compile仍由 module.bbclass 完成。 - do_install直接把已经生成的 hello_driver.ko 安装到最终镜像目录。 - 避开了 modules_install 的复杂逻辑非常适合学习。第七部分Pi 上验证查看模块find /lib/modules -name “*.ko”加载insmod hello_driver.ko查看内核日志dmesg应看到Hello Driver Loaded卸载rmmod hello_driver再次查看dmesg输出Hello Driver Removed第八部分理解整个流程hello_driver.c ▼ Makefile ▼ hello-driver.bb │ inherit module ▼ bitbake ▼ hello_driver.ko ▼ package ▼ image ▼ Raspberry Pi ▼ insmod ▼ Kernel第九部分代码移植和重用从14-product, 就没有写sdcard了 ssh systemd 功能没有加进去。以前是core-image-minimal, 现在是myproduct-image, 在core-image-minimal 调通的recipe直接copy过来。但receipes-core/image中的bb, bbappend名字要改一下# myproduct-image.bbSUMMARYMy Product ImageLICENSEMITinherit core-image IMAGE_FEATURESssh-server-opensshIMAGE_INSTALL \ product-test \ hello-script \ hello-driver \ ------------------------------------# myproduct-image.bbappendIMAGE_INSTALL:append network-config mypackage mypackage-config hello-systemdBitBake 读取 myproduct-image.bb 和 myproduct-image.bbappend根据IMAGE_INSTALL 安装指定的 packages 到 image 中。每个 package 由相应的 recipe 构建生成package 可以包含程序、脚本、配置文件、systemd service、内核驱动等文件。bitbake -e myproduct-image | grep ‘^IMAGE_INSTALL’一句话总结- Recipe(.bb)定义如何构建软件包。 - Package构建完成后安装到 image 的软件包。 一个 package 本质上就是一组将要安装到系统中的文件。注意在 Yocto 构建过程中package 并不一定意味着你会看到一个 .deb 或 .rpm 文件。它首先是 BitBake内部管理的软件包概念最终会被安装到 rootfs。- Image(.bb)通过 IMAGE_INSTALL 指定需要安装哪些 package最终生成完整的系统镜像。这三个层次Recipe → Package → Image是 Yocto 最核心的概念。cxiccyocto:~/yocto/build-rpi2$ bitbake -e myproduct-image | grep ‘^IMAGE_INSTALL’IMAGE_INSTALL packagegroup-core-boot packagegroup-base-extended product-test hello-script hello-driver hello-yocto execute-c network-config mypackage mypackage-config hello-systemd第十部分 烧写最新生成的 .wic 镜像到 SD 卡启动 Raspberry Pi 后检查查找驱动文件 find /usr/lib/modules -name “*.ko”应该能看到/usr/lib/modules/6.6.63-mytest/extra/hello_driver.ko然后加载insmod /usr/lib/modules/6.6.63-mytest/extra/hello_driver.ko查看日志dmesg | tail为什么lsmod看不到hello_driver.ko这是 Yocto 新手最容易混淆的地方之一。答案很简单find 能找到 .ko 文件只说明它已经安装到 rootfs。 lsmod 只能看到已经被 Linux Kernel加载load到内核中的模块。也就是说find/usr/lib/modules-name*.ko看到的是hello_driver.ko说明RootFS └── /usr/lib/modules/6.6.63-mytest/extra/ hello_driver.ko里面有这个文件。但是lsmod显示的是Kernel Memory里面已经运行的 module。这两个完全不是一回事。举个例子假设你的 PC 上C:\Program Files\notepad.exe存在。并不代表Task Manager恭喜 从 Yocto 的角度来看这意味着你的第 8 课已经完成了你已经成功将自己编写的 Linux 内核模块集成到了 Yocto镜像中。本课真正要掌握的知识点完成这一课后你应该能够回答这些问题- 什么是 Kernel Module为什么使用 .ko 而不是直接编进内核 - module.bbclass 在 Yocto 中承担什么作用 - 一个内核模块 Recipe 的最小组成是什么 - 模块最终会安装到 /lib/modules/kernel-version/ 的哪个位置 - 如何用 insmod、rmmod、lsmod 和dmesg验证模块是否正常工作建议下一步的实践我们可以一起从零创建 hello-driver.bb、hello_driver.c 和 Makefile然后一步步编译、打包到 myproduct-image最后在 Raspberry Pi 2 上成功加载第一个内核模块。这会把 Yocto、Kernel 和驱动开发三者真正串联起来。总结 recipepackage, poduct, image, driver我觉得你的感觉是对的因为 Yocto 有两类概念混在一起了- 对象Whatrecipe、package、image、driver、product…… - 机制Howinherit、IMAGE_INSTALL、RDEPENDS……如果把它们混在一起就容易迷糊。我建议你按下面这张表理解。① Recipe例如hello-script.bb它是 BitBake 能识别的构建文件。里面通常会写inherit allarch或者 inherit module或者 inherit cmake为什么因为 Recipe 要告诉 BitBake“请用哪种方式来构建我。”② Package例如hello-script它只是构建结果。不用写inherit …因为它已经不是 .bb 文件了。③ Image例如myproduct-image.bb一定会看到inherit core-image为什么因为Image 本身也是一个 Recipe。它只是特殊的 Recipe。④ DriverDriver 不是一种 Recipe。Driver 是软件内容。例如hello_driver.c最终生成hello_driver.ko为了编译它你会写hello-driver.bb里面inherit module注意不是 Driver 需要 inherit module。而是编译 Driver 的 Recipe需要 inherit module。⑤ ProductProduct 不是 Yocto 的对象。例如你的meta-myproduct以及 myproduct-image.bb只是“这是我的产品”Yocto 并没有 inherit product。一个完整例子你的工程其实就是meta-myproduct │ ├── hello-script.bb │ inherit allarch │ ├──hello-driver.bb │ inherit module │ ├── hello-systemd.bb │inherit systemd │ └── myproduct-image.bbinherit core-image这些 Recipe 会生成hello-script(package)hello-driver(package)hello-systemd(package)然后IMAGE_INSTALL hello-script hello-driver hello-systemd BitBake 把这些 package 安装到 RootFS最后生成myproduct-image.wicallarch 的意思是这个软件包与 CPU 架构无关Architecture Independent。我建议你以后记住一句口诀只有 Recipe.bb需要考虑 inherit。- Recipe → 根据功能选择 inherit如 core-image、module、systemd、cmake 等。 - Package → 不需要 inherit它是 Recipe 的输出。 - Image → 本质上也是一种 Recipe所以需要 inherit core-image。 - Driver、脚本、配置文件、Service → 它们都是被 Recipe 构建和打包的内容本身不是 inherit 的对象。这个思路能把 Yocto 的几个核心概念清楚地区分开。