简介:树莓派 Linux 4.19.y 内核源码包面向嵌入式 Linux 开发者、驱动工程师及树莓派进阶用户,提供与树莓派硬件平台匹配的内核源码,便于完成内核配置、交叉编译、驱动移植与性能裁剪。压缩包约 193.93MB,文件总数约 2000,内容以 C/H 源码、Makefile、Kconfig、DTS/DTSI 设备树文件为主,并配有 TXT/RST 说明文档、Shell/Python 脚本及 defconfig 配置,覆盖网络、字符编码、无线网卡、显示等驱动模块。已有 600 人学习或下载。读者可据此搭建完整的内核编译环境,查阅树莓派相关板级配置与驱动实现,修改设备树和外设支持,并对照文档排查编译与启动问题,适合用于驱动开发、内核定制和嵌入式系统学习。
1. 树莓派 Linux 源码 4.19.y:先想清楚你要改的是哪一层
树莓派 Linux 源码 4.19.y 这几个字,多数人是被 version magic 报错按在地上摩擦之后才去搜的:板子上装的 headers 和你自己编的模块对不上,insmod 直接甩一句 version magic mismatch。这时候纠结“要不要学内核”已经没意义了,你需要的就是一份和板子固件版本对得上的完整内核源码树。4.19 是 LTS 维护分支,树莓派在这条线上保留了板级补丁、defconfig 和设备树 overlay 目录,编出来的东西才能和官方启动流程咬合。它适合三种人:要写或改内核模块的、要动设备树和驱动的、要做定制镜像或量产固件的。只想跑 Python 脚本、装个容器的人,拿这份源码纯属给自己找罪受。下面按「认源码 → 编出第一版 → 动 overlay → 排坑 → 固化成基线」的顺序拆。
2. 解开源码包先认路:4.19.y 目录里哪些是树莓派专属
源码包解压出来通常是一个十来 GB 的目录树,几万个文件铺开,第一次看容易懵。它并不是一份“加了几行注释”的官方内核,而是在 4.19 主线基础上叠了板级补丁、板级配置和一套独立的设备树 overlay 体系。先把这三层的关系捋清楚,后面改任何东西都知道该往哪个目录里动手,而不是满树乱翻。
2.1 三层内容:主线代码、板级补丁、overlay
第一层是纯主线代码,比如kernel/sched/、mm/、net/ipv4/,这些和树莓派没关系,改动它们属于内核通用开发范畴,升级主线时也要跟着走。第二层是板级补丁,散落在各子系统里,特征是文件名带bcm、rpi、vc04这类前缀。第三层是设备树,树莓派把“硬件怎么接”这件事从内核代码里剥出来,放进arch/arm/boot/dts/,外设的开关则做成overlays/下的一堆.dts。
下面这张表是我接手新源码树时第一遍必看的路径,按“改动频率”排了序:
| 路径 | 内容 | 什么时候动 |
|---|---|---|
arch/arm/boot/dts/overlays/ | 几十个外设 overlay,I2C、SPI、PWM、串口都在这里 | 加外设、改引脚功能,优先在这里解决 |
arch/arm/configs/ | bcmrpi/bcm2709/bcm2711等 defconfig | 编译前决定板型和功能开关 |
arch/arm/boot/dts/ | bcm27xx-*.dts板级设备树 | 改内存布局、板载 LED、固定外设节点 |
drivers/staging/vc04_services/ | 和板载固件通信、摄像头/显示相关 | 做摄像头、显示、VCHIQ 时才碰 |
drivers/media/ | 图像传感器驱动 | 换摄像头模组 |
sound/soc/bcm/ | 板载音频通路 | 音频口异常且确认是内核侧问题 |
drivers/net/wireless/ | 无线网卡驱动 | 换无线模组或调试连接 |
vc04_services这个目录名是树莓派历史遗留,里面是 mailbox、VCHIQ 那套与板载固件通信的通道。很多人一看staging就以为是可以随便删的实验代码,实际上摄像头栈和部分显示通路跑在上面,删掉能编过,起来之后功能就是缺的。要动这一块,先在板子上确认dmesg里相关初始化日志正常,再动手改。
2.2 用 defconfig 定板型:别拿 2711 的配置烧到 2709 上
这是翻车率最高的一步。源码里一份 defconfig 对应一类板子,选错了照样能编出一个内核,烧进去表现是黑屏或者启动到一半卡住,因为编译器按错误的 CPU 架构和内存布局生成了代码。
先确认板子是哪一类,再对号入座。板子能起来的时候,直接读设备树里的 model 字段:
# 读板子型号,比看丝印靠谱 tr -d '\0' < /proc/device-tree/model # 顺带看内核版本和架构 uname -a拿到型号后按下表选配置,注意 32 位和 64 位是两套目录:
| 板型 | 架构 | defconfig 路径 |
|---|---|---|
| 第一代 / Zero 系列 | ARMv6 32 位 | arch/arm/configs/bcmrpi_defconfig |
| 2B / 3B 系列 | ARMv7 32 位 | arch/arm/configs/bcm2709_defconfig |
| 4B / 400 系列 32 位 | ARMv7 32 位 | arch/arm/configs/bcm2711_defconfig |
| 3B 系列 64 位 | ARMv8 64 位 | arch/arm64/configs/bcmrpi3_defconfig |
| 4B / 400 系列 64 位 | ARMv8 64 位 | arch/arm64/configs/bcm2711_defconfig |
常见做法是:先用官方镜像默认的那套位宽,别一上来就切 64 位。官方镜像长期是 32 位用户态,你在 64 位内核上加的模块、编的驱动,和用户态里的库不一定对得上。4.19.y 这条线尤其如此,64 位更适合“从底层重做一套系统”的场景。
2.3 交叉编译还是板载编译
决定在哪台机器上编,直接影响后面几个小时的体验。板载编译的意思是把源码拷到板子自己的 SD 卡上,直接make;交叉编译是在一台性能更好的开发机上,用交叉工具链产出 ARM 目标文件。
| 维度 | 交叉编译 | 板载编译 |
|---|---|---|
| 全量编译耗时 | 十几分钟到半小时 | 数小时起步,4B 也要很久 |
| 内存压力 | 开发机承担 | 板子内存小,-j开大容易 OOM |
| 调试便利性 | 主机上改代码、看日志方便 | 改一行等半天 |
| 环境一致性 | 需要自己装工具链 | 板子上自带编译器,省心 |
| 适合场景 | 反复迭代、做产物 | 一次性验证、没主机可用 |
我一般是交叉编译做主路径,板载编译只留作应急:当交叉编出来的东西行为诡异,先在板子上用同样源码编一版,用来排除工具链差异。这条经验救过我好几次,后面避坑那章会展开。
3. 交叉编译出第一版内核:从工具链到 SD 卡替换
这一章是整篇的核心操作段。目标很明确:在开发机上产出一个能替换进 SD 卡、板子能正常启动、uname -a显示你编的版本号的内核。中间每一步都给出可抄的命令和参数含义,照着走一遍,你就有了自己的编译基线。
3.1 工具链与依赖
4.19 内核对编译器版本有下限要求,太老的 gcc 会在编译早期直接报不支持。ARM32 目标用arm-linux-gnueabihf-前缀,ARM64 用aarch64-linux-gnu-前缀,两者不通用。在开发机上装依赖:
# ARM32 交叉工具链 + 内核构建常用依赖 sudo apt install -y gcc-arm-linux-gnueabihf make bison flex \ libssl-dev bc libncurses-dev rsync # 确认版本,4.19 建议 gcc 7 及以上 arm-linux-gnueabihf-gcc --versionbison和flex负责解析内核里的语法文件,缺了会在配置阶段报错;libssl-dev是给模块签名和部分加密代码用的;libncurses-dev是menuconfig的界面依赖;rsync用于把模块同步到卡上。这几个包缺一个,报错信息都很隐蔽,比如只提示某个.c文件找不到头文件,实际根因是flex没装。
工具链版本别盲目追新。太新的 gcc 编 4.19 会遇到一些老代码的警告被当成错误,需要额外加KCFLAGS关掉。稳妥做法是用发行版仓库里自带的版本,先跑通再说。
3.2 生成配置:defconfig 起步,增量修改
进入源码根目录,先生成基线配置,再按需增删:
cd linux-4.19.y # 以 4B 32 位为例;其他板型换对应 defconfig make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2711_defconfig # 打开图形化配置做增量调整 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfigARCH=arm指定目标架构,CROSS_COMPILE指定工具链前缀,末尾的-不能省,Makefile 会把它和gcc、ld等拼成完整命令。menuconfig里我通常会先确认三件事:CONFIG_IKCONFIG_PROC打开,方便启动后从/proc/config.gz反查实际生效的配置;CONFIG_MODULE_UNLOAD打开,方便卸载模块;CONFIG_DEBUG_INFO先关掉,能显著缩小产物、加快编译,等真要调试再开。
不想进图形界面也可以直接改.config,但改完必须跑一次olddefconfig让新增符号取默认值:
# 用脚本开关配置项,适合写进自动化流程 ./scripts/config --file .config -e CONFIG_IKCONFIG -e CONFIG_IKCONFIG_PROC # 让新增符号补默认值,避免编译时反复交互询问 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- olddefconfig-e是 enable,对应还有-d禁用、-m编成模块。scripts/config的好处是可脚本化、可 diff,比手点菜单可靠得多。改完记得核对关键项确实生效,grep一下.config就行。
3.3 编译与产出物
# -j 开核数,内存小于 8G 时建议减半,避免 OOM time make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc) zImage modules dtbs三个目标各有用途:zImage是压缩后的内核镜像,ARM32 用这个;ARM64 对应目标是Image,不压缩,启动时由固件加载。modules编译所有配置成=m的驱动,产物是.ko文件;dtbs编译设备树,产出.dtb和overlays/下的.dtbo。这三个目标可以分开跑,第一次建议一次跑完,省得漏。
编译完成后重点看这几个产物路径:arch/arm/boot/zImage、arch/arm/boot/dts/*.dtb、arch/arm/boot/dts/overlays/*.dtbo,以及各子目录下的*.ko。模块数量通常上千个,装卡时是整目录同步,别一个个拷。
如果编译中途报某个驱动找不到头文件,八成是你在menuconfig里开了某个依赖没满足的选项。回到配置里看该选项的depends on,把依赖一起打开,再olddefconfig。
3.4 装卡:boot 分区和 rootfs 分区分开处理
把 SD 卡插到开发机上,会挂出两个分区:一个是 FAT 的 boot 分区,放内核镜像和设备树;另一个是 ext4 的 rootfs 分区,放模块和固件。两个分区要分开操作,混着装会启动失败。
# 按实际挂载点替换,先确认挂的是哪两个分区 export CARD_BOOT=/media/$USER/boot export CARD_ROOT=/media/$USER/rootfs # 1) 装模块到 rootfs,路径必须指向卡上的根分区 sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- \ INSTALL_MOD_PATH=$CARD_ROOT modules_install # 2) 备份旧镜像再覆盖,出问题能原地回滚 sudo cp $CARD_BOOT/kernel7l.img $CARD_BOOT/kernel7l.img.bak sudo cp arch/arm/boot/zImage $CARD_BOOT/kernel7l.img # 3) 同步设备树和 overlay sudo cp arch/arm/boot/dts/*.dtb $CARD_BOOT/ sudo cp arch/arm/boot/dts/overlays/*.dtbo $CARD_BOOT/overlays/几个参数和文件名要盯紧:INSTALL_MOD_PATH是模块安装根路径,不写会装到开发机的/lib/modules,板子上自然找不到模块;内核镜像文件名要和config.txt里的kernel=对得上,4B 的 32 位常见是kernel7l.img,3B 系列是kernel7.img,64 位是kernel8.img,写错文件名固件会加载旧镜像,表现为“编了半天没变化”。备份那一步别省,这是你唯一的后悔药。
3.5 启动后自检三件事
卡插回板子、上电、SSH 进去,先跑三条命令确认这次替换真的生效:
# 1) 版本号应该带你自己编的构建信息 uname -a # 2) 配置项能读到,说明 IKCONFIG_PROC 生效 zcat /proc/config.gz | grep -E 'CONFIG_IKCONFIG|CONFIG_LOCALVERSION' # 3) 看启动早期日志有没有异常报错 dmesg | head -n 40uname -a里的版本串如果和编译时一致,说明内核替换成功;如果还是旧版本,回去查config.txt的kernel=和镜像文件名。/proc/config.gz读不到,通常是CONFIG_IKCONFIG_PROC没开,这条通道对后面排查“配置到底生效没”非常关键,建议每版都开。dmesg头部看有没有Unable to mount root fs、mmc初始化失败这类硬错误,有的话先回滚到备份镜像,再慢慢定位。
三件事全过,第一版内核就算落地了。接下来才是真正花时间的部分:在不重编内核的前提下改硬件行为。
4. 设备树 overlay 实战:不重编内核改硬件行为
树莓派这套 overlay 机制是我最推荐先掌握的一块。它把“某个引脚是 I2C 还是 GPIO”“某个芯片挂在哪条总线上”这类描述从内核里抽出来,启动时由固件叠加到基础设备树上。学会之后,加一个传感器、改一个引脚复用,改几行 dts、编一个.dtbo、改一行config.txt就完事,不用动辄半小时重编内核。
4.1 先看现有 overlay 怎么写
arch/arm/boot/dts/overlays/下有几十个现成例子,配套的README写清了每个 overlay 的参数。随便挑一个简单的读,比如只做引脚复用的那种,能看清 overlay 的基本骨架:/dts-v1/;声明版本,/plugin/;声明这是可叠加的片段,然后一个或多个fragment@N,每个 fragment 用target指向要挂载的节点,__overlay__里写新增内容。
compatible字段要写板子能匹配的字符串,常见是"brcm,bcm2835"这类芯片级兼容串,它决定了 overlay 在哪些板子上会被应用。写错了不会报错,只是静默不生效,这是新手最容易卡住的地方。
4.2 写一个把 GPIO 拉高的 overlay
下面这个例子把一个引脚配成输出并置高,结构完整,可以当模板改:
/dts-v1/; /plugin/; / { compatible = "brcm,bcm2835"; fragment@0 { target = <&gpio>; __overlay__ { my_output_pin: my_output_pin { brcm,pins = <5>; /* 目标引脚编号 */ brcm,function = <1>; /* 1 = 输出 */ brcm,pull = <0>; /* 0 = 不启用上下拉 */ }; }; }; };brcm,pins是要操作的引脚号,可以写多个;brcm,function是复用功能编号,不同编号对应输入、输出、I2C、SPI 等,具体对照表在源码的 pinctrl 驱动头文件里;brcm,pull控制内部上下拉,接按键这类场景要按硬件实际情况选,选错了表现是电平飘。引脚号用的是芯片编号,不是板子排针的物理序号,两者差一大截,接线前务必对照引脚图确认。
4.3 编译与加载
overlay 用dtc编译,树莓派这套要带-@参数打开符号引用,否则target = <&gpio>解析不了:
# 编译单个 overlay,输出 .dtbo dtc -@ -I dts -O dtb -o my-output.dtbo my-output.dts # 放到卡的 overlays 目录 sudo cp my-output.dtbo $CARD_BOOT/overlays/然后在 boot 分区的config.txt末尾加一行加载它:
# 加载自定义 overlay,名字不带 .dtbo 后缀 dtoverlay=my-outputdtoverlay后面跟的是去掉后缀的文件名,大小写敏感。需要传参时写成dtoverlay=my-output,param=value,参数在 dts 里用__overrides__节点声明,这是 overlay 支持运行时配置的标准做法。改完config.txt必须重启,overlay 只在启动阶段应用。
4.4 验证与回滚
起来之后别急着接外设,先确认 overlay 真的挂上了:
# 看设备树里有没有你新增的节点 ls /proc/device-tree/ # 查引脚复用状态 cat /sys/kernel/debug/pinctrl/*/pinmux-pins | grep -i 'pin 5'/proc/device-tree/是内核对设备树的实时呈现,你的节点名应该出现在这里;pinctrl目录下的pinmux-pins能看到每个引脚当前被谁占用、配成什么功能。两处都对得上,才说明 overlay 生效。
没生效时按这个顺序排查:config.txt里名字拼错没有、.dtbo是不是放进了overlays/而不是根目录、compatible串和板子匹配没有、dmesg | grep -i overlay有没有报解析错误。回滚很简单,把config.txt里那行注释掉重启即可,overlay 不改造内核镜像,这是它比直接改 dts 安全的地方。
5. 避坑与排查:五个让板子黑屏的细节
这一章全是血泪经验。下面五条是我在不同项目里真实遇到过的,每条按“现象 → 原因 → 解决”写,遇到同类问题时可以直接对号入座。
5.1 现象:上电绿灯闪,HDMI 一直没输出
原因:多数是内核镜像文件名和config.txt的kernel=不匹配,固件找不到新镜像,又没回退到旧的,启动流程直接断在这里;少数情况是编出来的镜像架构不对,比如把 ARM64 的Image当成 ARM32 的zImage用。
解决:先确认config.txt里kernel=的值,再核对 boot 分区里实际文件名,两者必须一字不差。然后把卡挂回开发机,把备份的镜像改回原名启动,确认板子本身没坏。如果备份能起、新镜像不能起,问题就在镜像本身,回去检查ARCH和工具链前缀是否匹配。
5.2 现象:insmod报 version magic 不匹配
原因:模块编译时用的内核源码版本、配置或编译器,和板子当前运行的内核不是同一份。内核会把版本串、SMP 标志、模块版本等信息编进 vermagic,任何一项对不上都拒绝加载。
解决:最稳的做法是“编模块用的源码 = 编板子内核用的源码”,同一份树、同一份.config、同一个工具链。先uname -r看板子实际版本,再回源码目录make kernelrelease对比,不一致就先重编内核。临时绕过可以加--force,但那是在赌模块和内核 ABI 兼容,生产环境别这么干。
5.3 现象:make跑到驱动阶段进程被 kill,提示Killed
原因:并行编译开太大,内存被打爆,OOM killer 直接杀掉编译进程。4.19 里drivers/下几个大文件编译时内存峰值很高,-j$(nproc)在 4G 内存的机器上很容易触发。
解决:把-j降到核数的一半甚至四分之一,或者加交换分区顶一下。判断是不是 OOM,看dmesg里有没有Out of memory: Killed process。我一般在小内存机器上固定用-j4,慢一点但不会半路翻车。
5.4 现象:overlay 加载了,/proc/device-tree里没变化
原因:三种常见情况——compatible串和板子不匹配导致静默跳过;.dtbo放错目录;dts 里target引用的节点名在当前板级 dts 里不存在。
解决:先dmesg | grep -i -E 'overlay|dtb'看固件有没有报应用失败;再确认.dtbo在overlays/下;最后对照板级 dts 确认target节点名。compatible串拿不准时,直接抄同一目录下现成 overlay 的写法,别自己造。
5.5 现象:新内核能启动,无线连不上了
原因:无线驱动被编成模块,但modules_install时INSTALL_MOD_PATH指错,模块没同步到卡上;或者新配置里把无线相关的CONFIG_CFG80211、CONFIG_MAC80211关掉了,驱动编不出来。
解决:先ls /lib/modules/$(uname -r)/kernel/drivers/net/wireless/看模块在不在,不在就重新modules_install并确认路径指向卡上根分区;模块在但加载失败,dmesg里会写缺哪个依赖模块,按提示补配置重编。改无线相关配置后,务必在menuconfig里把cfg80211和mac80211的依赖一起过一遍。
6. 把源码变成长期基线:配置差异对比与补丁固化
编通一次不算本事,三个月后还能复现同一版内核才算。我现在的习惯是:每改一次配置就留一份.config快照,每改一处代码就生成一个 patch,所有构建动作收敛到一个脚本里。这样换机器、换同事接手,都能在半小时内还原出完全一致的产物。
配置漂移用源码自带的scripts/diffconfig查,它比手动diff两个.config可读得多:
# 对比基线配置和当前配置,只看差异项 ./scripts/diffconfig .config.baseline .config输出里+是新增、-是删除,一眼能看出这次改动动了哪些开关。我会把每次定版的.config按日期存一份,配合git管理,出问题时能精确回退到某一版。代码改动则用标准 patch 流程:
# 改完驱动后生成补丁,-N 保证新文件也能纳入 git diff > patches/0001-my-driver-fix.patch # 换一份干净源码时应用 git apply patches/0001-my-driver-fix.patch构建脚本不用复杂,把前面几章的命令串起来就够用:
#!/usr/bin/env bash set -euo pipefail ARCH=arm CROSS=arm-linux-gnueabihf- DEFCONFIG=bcm2711_defconfig make ARCH=$ARCH CROSS_COMPILE=$CROSS $DEFCONFIG make ARCH=$ARCH CROSS_COMPILE=$CROSS -j4 zImage modules dtbs make ARCH=$ARCH CROSS_COMPILE=$CROSS INSTALL_MOD_PATH=$CARD_ROOT modules_installset -euo pipefail让脚本任何一步失败就停,避免“上一步失败了还继续拷文件”这种更隐蔽的翻车。-j4是保守值,机器好可以调大,但别写死$(nproc)就发出去给配置更差的机器用。
最后说个教训。早些年我有一次在板子上直接改.config编内核,改完没存快照,两周后要复现同一个镜像,怎么编uname -r都对不上,查了一整天才发现是当时顺手关了一个CONFIG_LOCALVERSION_AUTO。从那以后我每次动内核配置,都强制走一遍「先存快照、再改、再 diff」的流程,源码树本身只读不写。这份 4.19.y 源码值不值得下,取决于你把它当一次性工具还是长期基线;当基线用,上面这套流程能帮你省掉大量返工。希望帮到你。
本文还有配套的精品资源,点击获取