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

文章详情

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

Ubuntu 20.04内核源码下载与编译:版本匹配、驱动适配与工程实践

Ubuntu 20.04内核源码下载与编译:版本匹配、驱动适配与工程实践 1. 内容整体设计与思路拆解1.1 核心需求解析为什么要在 ubuntu20.04 上折腾内核源码先说结论你在 ubuntu20.04 上下载内核源码大概率不是为了“看看 Linux 内核长什么样”这种闲情逸致。结合我接触过的实际场景大家做这件事基本逃不出下面四类需求第一类是驱动开发与适配。比如热词里反复出现的 nvidia-dirver-535、ax211 无线网卡这些硬件在 ubuntu20.04 上经常出现驱动不兼容、内核模块加载失败的问题。要解决这类问题光靠 apt 装驱动往往不够你得自己编译内核模块而编译模块的前提就是拿到与当前内核版本精确对应的内核源码。第二类是嵌入式与交叉编译。做嵌入式 Linux 开发的朋友都清楚yocto、buildroot 这些构建系统动辄就要拉取指定版本的内核源码做定制裁剪。ubuntu20.04 的 5.4/5.8/5.11/5.13/5.15 系列内核都有对应版本选错源码版本会导致编译链直接断裂。第三类是内核功能裁剪与安全加固。比如公司要过等保、要做安全审计需要重新编译内核去掉不必要的模块或者给内核打上特定的安全补丁这时必须从源码层面操作。第四类是学习与调试。想读一读进程调度、内存管理的代码或者用 ftrace、kprobe 做内核调试没有源码在手很多调试手段都用不上。回到标题本身“ubuntu20.04 下内核源码下载分析”这句话虽然看着简单但它其实暗含了一个关键前提你不是在普通用户目录下面随便 download 一个 tar.xz 包就完事了。你要面对的是版本匹配、校验、目录规划、依赖安装、编译环境验证这一整条链路。这篇博文我按我自己的实操流程来写尽可能把每一步的“为什么这么做”也讲清楚。1.2 方案选型直接 apt 拉源码还是从 kernel.org 手动下载说到下载内核源码网上教程基本两条路一条是用apt source linux另一条是去 kernel.org 下 tarball。我两条路都用过这里直接说结论和建议。apt source linux这条路的本质是从 ubuntu 自己的软件源里拉取 ubuntu 内核团队维护的源码包。注意这个源码包和 kernel.org 上的主线内核是有差异的——ubuntu 会对内核打很多自己的补丁包括安全补丁、硬件支持补丁、或者是针对自家虚拟化、容器特性的修改。所以如果你编译的内核模块最终要加载到当前 ubuntu 系统上必须用apt source拉下来的源码而不是主线内核源码这一点很多人第一次踩坑就是栽在这上面。我见过有人拿着 kernel.org 的 5.15 主线源码去编译 nvidia 驱动模块结果 insmod 的时候直接报version magic不匹配白白折腾一下午。从 kernel.org 手动下载这条路适合的是嵌入式开发、内核学习、或者你确实需要的是纯粹的主线内核源码。它的好处是版本全、干净、无 ubuntu 定制补丁坏处是你得自己处理依赖而且编译出来的模块大概率没法直接 insmod 到 ubuntu 系统上。我个人的建议是ubuntu20.04 环境下面优先走 apt source 方案。原因很简单省心、匹配、可维护。内核源码和驱动模块的版本强制校验关系就像一个密码锁你拿错钥匙是绝对打不开的。当然我会在后文把两条路都完整演示一遍你按需取用。2. 环境准备换源、依赖安装、内核版本确认2.1 确认当前内核版本和系统版本开始之前先把底数摸清楚。你的 ubuntu20.04 到底跑的是哪个内核版本这个信息直接决定你后面下载什么源码。在终端里依次执行下面三条命令lsb_release -a uname -r apt-cache policy linux-sourcelsb_release -a是看系统发行版本确保你确实是 20.04.x而不是 18.04 升上来的残留系统。uname -r是看当前正在运行的内核版本号比如输出5.15.0-91-generic这个版本号有三个字段主版本号 5、次版本号 15、修订号 0后面的-91是 ubuntu 的 ABI 号generic是内核 flavour。apt-cache policy linux-source是看软件源里面有哪些 linux-source 包可用。这里有个细节值得说一下uname -r显示的版本和你下载的源码版本必须完全对应到 ABI 号这一层。比如你系统跑的是5.15.0-91-generic那你下载源码的时候就应该找linux-source-5.15.0-91这个包或者对应 ABI 91 的源码目录。如果只匹配到5.15.0编译模块时极大概率报version magic不匹配。这不是猜的是我踩过坑之后专门对比过/lib/modules/$(uname -r)/build下的 version 文件才想明白的。2.2 更换 apt 软件源国内环境下载提速的关键ubuntu20.04 默认软件源在海外直接apt source的速度有时候让人崩溃尤其是拉大源码包时几百 KB/s 的速度真的能急死人。这一步虽然不是必需但我建议所有国内开发者先做。编辑源列表文件sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak sudo nano /etc/apt/sources.list把文件里的http://archive.ubuntu.com/ubuntu/和http://security.ubuntu.com/ubuntu/替换成国内镜像地址。以清华源为例20.04 的源配置如下deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-updates main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-backports main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-security main restricted universe multiverse替换完成之后记得执行sudo apt update这里多说一句换源之后务必检查输出里有没有NO_PUBKEY之类的报错如果有说明镜像源的 GPG 公钥没有导入需要执行对应的公钥添加命令。遇到的具体报错我放在第 5 章排查部分讲。2.3 安装必要依赖编译内核和模块需要的最小工具链源码下载只是第一步如果你后面要编译内核或者编译模块依赖不装齐后面会花式报错。我整理了一个最小依赖清单这部分是 ubuntu 内核开发的基础环境强烈建议完整安装sudo apt update sudo apt install build-essential libncurses-dev flex bison libssl-dev \ libelf-dev dwarves bc rsync kmod cpio逐一说一下这些东西是干嘛的build-essential包含 gcc、g、make 等基础编译工具没有它你连 Makefile 都跑不起来。libncurses-dev提供 menuconfig 图形化配置界面需要的 ncurses 库。如果你用make menuconfig这个必须有。flex和bison两个经典的语法解析器生成器内核编译过程中需要它们处理一些语法文件。比如解析设备树和某些配置脚本时都会用到。libssl-dev提供 OpenSSL 的开发头文件内核编译过程中签名相关的工具需要用到。libelf-dev提供 ELF 文件操作库编译内核模块时需要读取和分析 ELF 格式的.ko文件。dwarves提供 pahole 工具用来生成 BTF 信息。如果你内核开了CONFIG_DEBUG_INFO_BTFubuntu 一般默认开没有 pahole 编译会报错。bc一个计算器语言解释器内核某些 kconfig 脚本和 Makefile 里会调用它做数值运算。rsync、kmod、cpio分别用于文件同步、内核模块管理和 initramfs 打包。这套依赖看着多但都是必要的。真别偷懒我见过有人没装dwarves编译 5.15 内核的时候卡在 BTF 生成阶段报pahole not found查半天才反应过来。2.4 注意事项不要用 root 直接下载和编译接下来是一个很多人容易忽略的细节下载源码和编译操作不要直接用 root 用户执行。虽然 sudo 提权没有太大问题但在内核开发的场景里proot 权限下生成的一些临时文件、配置文件容易让后续的目录权限管理变得混乱。我自己一般会建一个专门的用户或者使用当前普通用户只在需要写系统目录时用 sudo这样后续清理、备份、切换版本都方便。还有一点源码解压目录尽量不要放在/root或家目录之外需要额外权限的地方建议统一放在/home/用户名/kernel-src这样的位置。倒不是因为性能而是因为后面编译模块、配置环境变量时路径短一点、权限干净一点能少很多不必要的麻烦。3. 核心细节解析与实操要点3.1 方法一apt source 下载 ubuntu 内核源码先把 deb-src 源打开这是apt source能不能用的大前提。默认情况下ubuntu 的/etc/apt/sources.list里的 deb-src 行是被注释掉的。你需要把它们取消注释或者直接添加。如果你用的是清华源把前面的源配置再复制一份把deb改成deb-src即可deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal main restricted universe multiverse deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-updates main restricted universe multiverse deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-backports main restricted universe multiverse deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-security main restricted universe multiverse改完执行sudo apt update。然后安装dpkg-dev工具这个包提供apt source需要的底层工具链sudo apt install dpkg-dev接下来就可以根据你想下载的内核版本执行对应命令了。如果你只是想为当前系统下载完全匹配的源码最简单的做法是apt source linux这条命令会拉取当前系统软件源中默认的 linux 源码包。不过在部分 ubuntu 版本上apt source linux下载到的可能是元包或对应内核 flavour 的源码最好还是明确指定版本号apt source linux-image-$(uname -r)举例来说如果uname -r返回5.15.0-91-generic那么上面的命令实际执行的是apt source linux-image-5.15.0-91-generic这样确保拉到的源码和当前运行内核完全对应。执行之后当前目录下会出现类似这样的文件和目录linux_5.15.0-91.orig.tar.gz linux_5.15.0-91-ubuntu.dsc linux_5.15.0-91-ubuntu.debian.tar.xz linux-5.15.0-91/其中.dsc文件是 Debian 源码包的描述文件包含了源码包元信息。.orig.tar.gz是上游原始源码 tarball。.debian.tar.xz是 ubuntu 的 debian 打包目录和补丁集合。解压后的目录linux-5.15.0-91就是完整的源码树。下载完成后目录里可能还会带着debian子目录这就是 ubuntu 的打包脚本目录里面包含了很多补丁文件、config 文件等。如果你只是编译模块不用管它们如果你想手动编译整个内核需要理清里面的debian.master、debian.oem等不同 flavour 的 config 文件。注意apt source必须在普通用户目录下执行不能使用 sudo否则会报 “dsc file is not readable” 之类的权限问题。如果当前用户对该目录没有写权限就先chmod或者切换目录。3.2 方法二从 kernel.org 手动下载主线内核源码如果说 apt source 是“精准匹配”那 kernel.org 手动下载就是“自由选择”。适合需要主线内核、稳定版内核、或者长期维护版内核的场景。首先是访问内核官网。国内访问 kernel.org 的速度波动较大建议直接用命令行工具下载方便断点续传cd ~/kernel-src wget -c https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.150.tar.xz wget -c https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.150.tar.sign这里解释一下我为什么用 5.15.150 举例5.15 是 ubuntu20.04 后续 HWE 内核栈里常遇到的版本同时也属于长期维护版本LTS。如果你是学习目的建议选一个 stable 分支的版本比如 5.15.x、6.1.x 这样文档多、踩坑姿势可以搜到的也多。不同版本放在不同的目录下比如 5.x 版本的源码放在v5.x/目录下6.x 版本的在v6.x/目录下。你可以在官网的索引页确认具体路径。下载完 tarball 之后强烈建议验证一下签名或者哈希防止下载过程中文件损坏或被篡改。内核官网一般发布sha256sums.asc文件你可以对照一下sha256sum linux-5.15.150.tar.xz然后打开官网的sha256sums.asc对比哈希值。如果一致再解压tar -xvf linux-5.15.150.tar.xz cd linux-5.15.150解压完成后源码树就准备好了。后续要编译的时候可以基于当前系统的 config 复制一份作为起点cp /boot/config-$(uname -r) .config然后执行make oldconfig来生成适配当前工具链和硬件的配置文件。3.3 源码目录结构速览你要找的代码分布在哪里源码树下来之后很多人看着根目录那一堆 Makefile、Kconfig 文件夹直接懵了。按我的经验下面几个目录是新手最该认识的kernel/核心代码比如进程调度kernel/sched、信号kernel/signal.c、时间管理kernel/time、printk 日志系统等。想理解 Linux 内核的运行机制这个目录最值得啃。mm/内存管理子系统包括页分配、slab 分配器、vmalloc、内存回收等。排查内存泄漏或者做内存调优时这里的代码就是真相来源。drivers/驱动代码占了整个内核源码很大比例。nvidia、ax211 这类设备的驱动都在这个目录里不过 nvidia 闭源驱动不在这里面。net/网络协议栈TCP/IP、socket、netfilteriptables/nftables 的底层都在net/ipv4、net/netfilter子目录下。fs/文件系统ext4、xfs、btrfs、procfs、sysfs 等各占一个子目录。include/内核头文件特别是include/linux/这个目录是写内核模块必须要引用头文件的主要来源。arch/架构相关代码x86 架构在arch/x86/下arm64 架构在arch/arm64/下。交叉编译时这里最常动。Documentation/内核开发文档很多子系统都有专门说明。遇到编译错误的 Kconfig 选项先来这查一下。如果你只是编译一个外部模块理论上只要include/、arch/下对应架构的头文件以及Makefile存在就行不需要完整编译整个内核。但前提是你已经有了生成好的Module.symvers如果有导出符号依赖的话和include/config/下的配置头文件。所以我建议第一次用源码还是老老实实先跑一遍配置和准备流程。3.4 两种下载方式的对比与选择建议我把两种方式放在一张表里做对比方便你根据自己的场景做选择对比维度apt sourcekernel.org 手动下载版本匹配与当前系统精确匹配含 ubuntu 定制补丁主线/官方 stable 版本无 ubuntu 补丁下载便捷度配置好 deb-src 之后一条命令完成需要自己选版本、验证、解压编译模块兼容性加载到当前 ubuntu 系统上基本无兼容问题直接编译的模块可能因 version magic 不一致无法 insmod适用场景驱动适配、模块编译、ubuntu 内核定制嵌入式开发、内核源码学习、主线内核移植体积与磁盘占用较大可能包含 debian 打包信息相对干净只有纯源码网络依赖依赖软件源速度换国内源后很快依赖 kernel.org 或 CDN 速度国内可能较慢我的选型建议如果你的最终目的是解决“ubuntu20.04 上某个驱动编译失败”比如 nvidia 驱动、AX211 网卡适配直接走 apt source 拿匹配的源码不要碰主线内核。如果你是要研究内核本身的实现或者要做 arm64 交叉编译、嵌入式系统移植从 kernel.org 选择 LTS 版本下载更合适。再补充一个重要细节如果你打算编译整个内核并替换当前系统的内核而不是只编译模块请一定先备份好现有的内核镜像和 initramfs。替换失败导致无法开机的情况并不少见我后面会专门再说一次备份和回滚的事。4. 实操过程与核心环节实现4.1 完整流程演示从空目录到源码就绪下面我演示一个标准的操作流程把所有步骤串起来。假设你已经完成了章节 2 的环境准备换源、装依赖。现在开始下载源码mkdir -p ~/kernel-src cd ~/kernel-src apt source linux-image-$(uname -r)执行过程中 apt 会把源码包下载到当前目录然后自动解压。我这边执行完的输出大概是这样Need to get 201 MB of source archives. Get:1 http://mirrors.tuna.tsinghua.edu.cn/ubuntu focal-updates/main linux 5.15.0-91.101 (dsc) [7,245 B] ... Fetched 201 MB in 2min 30s (1,338 kB/s) dpkg-source: info: extracting linux in linux-5.15.0-91 dpkg-source: info: unpacking linux_5.15.0-91.orig.tar.gz dpkg-source: info: applying ubuntu patch看到applying ubuntu patch这行说明补丁已经应用完成源码目录可以直接使用了。接着验证一下源码目录的完整性和版本信息cd linux-5.15.0-91 head -n 5 Makefile输出应该能看到类似这样的内容# SPDX-License-Identifier: GPL-2.0 VERSION 5 PATCHLEVEL 15 SUBLEVEL 0 EXTRAVERSION -91其中VERSION和PATCHLEVEL组成了5.15SUBLEVEL是0EXTRAVERSION是-91。这个组合就是内核版本号5.15.0-91和你的uname -r输出对应上了说明源码版本匹配正确。到这一步源码下载的工作就算完成了。接下来你可以根据自己的目标决定是编译内核、编译单个驱动模块还是纯粹做源码阅读。4.2 基于下载源码编译一个外部模块的标准示例光把源码下载完不算完我顺手演示一下怎么基于这套源码编译外部模块。这里以最常见的 hello world 内核模块为例把整个链路走通。在源码目录外的任意位置创建一个模块目录mkdir -p ~/kernel-module cd ~/kernel-module创建hello.c#include linux/init.h #include linux/module.h #include linux/kernel.h static int __init hello_init(void) { printk(KERN_INFO Hello, kernel module!\n); return 0; } static void __exit hello_exit(void) { printk(KERN_INFO Goodbye, kernel module!\n); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(Your Name); MODULE_DESCRIPTION(A simple hello world module);创建Makefileobj-m hello.o all: make -C /lib/modules/$(shell uname -r)/build M$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M$(PWD) clean注意这里的关键点-C指定的路径是/lib/modules/$(uname -r)/build。这个目录通常是一个软链接指向/usr/src/linux-headers-$(uname -r)。如果你的系统上这个软链接指向的目录不是我们下载的源码目录那编译器用的还是 linux-headers 包里的头文件而不是 apt source 下载的完整源码。这里有个容易让人迷惑的点apt source下载的是完整源码但编译模块时默认用的却是linux-headers-*包安装到/usr/src的头文件目录。那 apt source 的源码还有用吗有用但要看场景。如果你想编译一个外部独立模块比如某个网卡驱动系统里已经装好了linux-headers-$(uname -r)那直接用/lib/modules/$(uname -r)/build就够了不需要我们下载的完整源码。如果想基于完整源码来编译你需要把系统的 build 软链接重新指向源码树或者手动把源码树配置好这一步容易把人绕晕。我的建议是外部模块编译优先确保linux-headers-$(uname -r)已安装。如果你确实需要基于完整源码编译再考虑切换软链接指向。完整源码的主要价值在于阅读代码、修改内核、重新编译整个内核、以及编译内核自带的模块。安装 headerssudo apt install linux-headers-$(uname -r)然后编译模块make正常的话会生成hello.ko。加载并验证sudo insmod hello.ko dmesg | tail -n 5 sudo rmmod hello dmesg | tail -n 5dmesg里能看到Hello, kernel module!和Goodbye, kernel module!说明模块编译、加载、卸载整条链路都正常。4.3 编译整个内核的操作要点以 5.15.0 源码为例如果你走的是“我要整体编译一个内核”这条路操作流程大体是cd ~/kernel-src/linux-5.15.0-91 cp /boot/config-$(uname -r) .config make oldconfig make menuconfig # 可选按需调整配置 make -j$(nproc) # 编译内核和模块 sudo make modules_install sudo make install sudo update-grub几个值得展开的细节make oldconfig的作用是把旧配置文件转换成当前源码树能识别的格式。执行过程中会提示你“新内核引入了哪些新配置选项”一般直接回车选择默认即可。如果不想交互可以直接yes | make oldconfig不过这样过于粗暴有可能选到默认并不合适的选项。对于新手我建议交互式执行遇到看不懂的选项直接回车大多数默认值都能用。make -j$(nproc)是并行编译$(nproc)会返回 CPU 核心数。以最常见的 4 核 8 线程机器为例-j8编译 5.15 内核大约需要 10~20 分钟具体看磁盘 IO 和内存。如果你是虚拟机建议内存不低于 4GB否则编译过程中 GCC 进程可能被 OOM killer 杀掉。sudo make modules_install会把编译好的模块拷贝到/lib/modules/5.15.0-91/目录。make install会安装内核镜像和 System.map 到/boot并更新 grub 配置。这里必须提醒编译整个内核前务必确认磁盘剩余空间不低于 10GB。5.15 内核完整编译下来中间产物加上最终安装的模块占用的空间可能超乎你想象。我见过有人编译到一半磁盘满了最后只能把源码树删掉重新来。4.4 配置内核源码环境时常用的工具与技巧除了上面这些我再分享几个实际工作中经常用到的工具和技巧。如果你下载的是 kernel.org 主线源码经常遇到编译某个模块时提示缺少generated/autoconf.h之类的文件这是因为源码树还没有生成配置文件。解决办法是make defconfig make prepare make modules_prepare这个流程会生成编译模块必需的中间文件。make defconfig会用架构默认配置生成.config相当于给源码树做了一次“初始化”。如果后续只要编译模块不需要执行完整的内核编译。另外scripts/config脚本是一个很好用的配置修改工具可以免去打开 menuconfig 的麻烦scripts/config --enable CONFIG_XX scripts/config --disable CONFIG_YY scripts/config --set-val CONFIG_ZZ 64修改完配置后执行make olddefconfig生成新的.config。还有一个经验如果你在虚拟机里做内核编译注意 CPU 核数设置。很多人的虚拟机默认只有 1 核make -j1编译 5.15 内核能跑一两个小时极其痛苦。建议虚拟机分配 4 核以上或者改用宿主机的物理机操作。5. 常见问题与排查技巧实录5.1 问题速查表apt source 报错与网络异常我在实际环境和帮助别人排查的过程中遇到最多的问题集中在下面这些场景。整理成表格方便你对照排查现象可能原因解决方式E: You must put some source URIs in your sources.listsources.list 中没有 deb-src 行检查并添加对应的 deb-src 源然后sudo apt updateE: Unable to find source package for linux-image-5.15.0-91-generic软件源里的包索引不全或版本不对应先sudo apt update再用apt-cache search linux-source确认可用的源码包名E: Cannot get debconf version or debconf is not installed缺少 debconf 工具执行sudo apt install debconf如果还报错再装debconf-utils下载速度极慢默认源在海外更换清华、阿里等国内镜像源404 Not Found拉取源码时出现 404软件源缓存过期或镜像同步不完整sudo apt update刷新索引或更换其他镜像源dpkg-source: error: unrepresentable changes to source当前目录有残留的构建文件干扰了解包换个干净的新目录重新apt source针对 404 的情况多说一句ubuntu 仓库中旧版本的源码包只会存在于旧版本仓库里如果你的 sources.list 配的是focal即最新更新源某些历史版本的内核源码包可能已经不在当前镜像中此时可以尝试在源配置中增加focal-proposed仓库或者直接从 kernel.ubuntu.com 的存档页抓取源码包。不过这个操作对新手来说有点复杂我更推荐的做法是如果有精确版本需求优先使用apt source linux拉当前系统匹配的版本而不是去追历史版本。5.2 headers 和源码版本不匹配时的典型报错这里单独说一类高发问题/lib/modules/$(uname -r)/build软链接失效或者 headers 版本和源码版本对不上。常见报错是编译模块时出现Makefile:...: *** /lib/modules/5.15.0-91-generic/build: No such file or directory. Stop.或者/usr/src/linux-headers-5.15.0-91-generic: No such file or directory原因基本都是没装对应版本的 linux-headers或者软链接指向的目录被误删了。解决办法sudo apt install linux-headers-$(uname -r)如果已经安装过但软链接还是断了可以手动检查并重建ls -l /lib/modules/$(uname -r)/build sudo ln -sf /usr/src/linux-headers-$(uname -r) /lib/modules/$(uname -r)/build还有一种情况内核更新后没有重启仍然在旧内核上执行uname -r但/lib/modules下已经有新内核的 headers。这种情况建议先重启让系统运行在新内核上再继续编译操作。5.3 网络异常排查ubuntu20.04 常见的网络问号问题如果你是在虚拟机里装的 ubuntu20.04很多人会遇到一个经典问题右上角网络图标变成问号而且apt update也连不上网。这个问题的根源大多不是内核源码下载的问题而是网络驱动或 DNS 配置的问题。排查顺序一般是ip a ip route show cat /etc/resolv.conf ping -c 4 223.5.5.5 ping -c 4 www.baidu.com如果ping 223.5.5.5通但ping www.baidu.com不通说明 DNS 有问题。修改/etc/resolv.conf加上nameserver 223.5.5.5 nameserver 8.8.8.8如果ip a里根本没有网卡 IP可能是网卡没有被正确驱动。VMware 虚拟机环境下先检查虚拟网络编辑器是不是把网卡桥接或 NAT 配置错了。如果你用的是 AX211 这类新网卡那基本就是缺少对应的无线网卡驱动这也是你下载内核源码、编译网卡驱动的典型动机之一。其实“ubuntu20.04 网络问号”这个问题和内核源码下载是强相关的——因为新网卡在默认内核里没有对应驱动你就得自己编译驱动模块而编译驱动模块又依赖内核源码。所以这一节和整篇文章的链条是紧密相连的。不过提醒一句如果是要解决新网卡驱动先确认内核版本是否在网卡驱动的支持范围内有些新版网卡驱动要求在较新的内核版本上才能编译ubuntu20.04 的 5.4 内核说不定得先把内核升级到 5.15 或更高版本。5.4 编译驱动时 version magic 不匹配的解决方案如果你是下载了主线内核源码去编译 nvidia 驱动或者 ax211 网卡驱动然后 insmod 时报了类似下面的错误version magic 5.15.0 SMP mod_unload modversions should be 5.15.0-91-generic SMP mod_unload modversions 这说明驱动模块的内核版本标识和当前运行内核不一致。原因很直接——源码版本和当前内核版本不匹配。解决方案有三个按优先级排列使用与当前内核完全一致的源码。ubuntu 系统上直接走apt source linux-image-$(uname -r)或者安装linux-headers-$(uname -r)不要用 kernel.org 的主线源码。如果必须使用主线源码编译模块可以尝试给模块的 Makefile 加上CONFIG_MODVERSIONS兼容处理或者用modprobe --force强行加载。但这里我必须明确说生产环境千万别这么干这属于绕过内核安全机制模块行为不受保障。升级或更换内核版本。如果当前内核确实太旧适配不了新硬件可以考虑安装 HWE 内核Hardware Enablementubuntu20.04 上执行sudo apt install linux-generic-hwe-20.04安装之后重启内核版本会更新到较新的5.15.x系列然后再用对应的源码/headers 去编译驱动成功率会高很多。这个方法在我的实战里是解决 ubuntu20.04 上新网卡驱动问题的首选。比如 AX211默认的5.4.0内核基本没法识别装完linux-generic-hwe-20.04后新内核直接就能工作连编译驱动都省了。5.5 网络下载工具的加速建议内核源码动辄几百 MB如果网络条件不好下载真的很折磨。除了换软件源我再提供几个下载加速的思路使用wget -c断点续传。如果下载到一半断了不用重新开始-c参数可以直接接着下载。使用aria2c多线程下载。如果你的网络环境支持aria2c比wget快非常多sudo apt install aria2 aria2c -x 16 -s 16 https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.150.tar.xz-x 16表示每个服务器最多使用 16 个连接-s 16表示将文件拆分成 16 段并行下载。这个方法在国内环境下实测提速明显特别适合下载 kernel.org 的大文件。使用镜像站。国内一些大学和云厂商会同步 kernel.org 的源码包比如中科大镜像、清华镜像虽然没有 cdn.kernel.org 收录得那么全但基础版本基本都有。可以先用搜索引擎查一下当前可用的镜像路径注意甄别安全来源。5.6 下载后的完整性确认与目录整理建议源码下载完成不代表万事大吉我最后一步习惯性做三件事验证 tarball 的 SHA256。无论是 apt source 还是手动下载确定文件完整无损坏是第一优先级。尤其是手动下载强烈建议核对官方sha256sums.asc或.tar.sign签名。查看源码目录空间占用。du -sh ~/kernel-src/*看清楚每个源码目录占了多少磁盘方便后续清理。建立目录命名规范。我一般会按照内核版本-日期-用途来命名目录比如linux-5.15.0-91-nvidia-build、linux-5.15.150-learn避免过两周自己都忘记这个目录是干嘛的。内核源码这个东西一旦开始用了你可能会在机器上留好几个版本如果没有清晰的目录管理习惯后面找代码会非常痛苦。6. 实战场景扩展从源码下载到驱动开发的应用链路6.1 场景一解决 ubuntu20.04 安装 nvidia 显卡驱动的内核源码依赖nvidia 驱动安装是 ubuntu20.04 用户里最高频的问题之一。热词里出现的“ubuntu20.04 anzhuang nvidia”“apt install nvidia-driver-535”本质上都是同一个问题nvidia 闭源驱动模块需要针对当前内核版本重新编译。当你执行sudo apt install nvidia-driver-535之后系统会尝试通过 dkms 自动编译 nvidia 内核模块。如果此时缺少与当前内核匹配的 headers 或者源码编译就会失败。常见的报错信息是ERROR: Unable to find the kernel source tree for the currently running kernel.或者DKMS build failed: kernel preparation unnecessary, skipped解决办法就是确保装好 headerssudo apt install linux-headers-$(uname -r)然后重新触发 dkms 编译sudo dkms install -m nvidia -v 535.xxx或者直接重装驱动sudo apt install --reinstall nvidia-driver-535如果还不行再考虑是否要和完整源码关联。其实对于 nvidia 这种闭源驱动dkms 依赖的是 headers 而不是完整源码树所以没必要非下载全套源码。只有当你是手动编译某些依赖 GPL 头文件的开源驱动时完整源码才会真正派上用场。6.2 场景二AX211 网卡在 ubuntu20.04 下的驱动适配思路AX211 是 intel 较新的 Wi-Fi 6E 无线网卡默认在 ubuntu20.04 的 5.4 内核下可能不工作但到了 5.15 内核上一般就能被识别。这其实是硬件的固件和内核驱动版本决定的。如果你发现lspci -nnk | grep -i network能看到网卡但是ip link里面没有 wlan 接口大概率是驱动没加载。此时可以看看dmesg | grep -i iwlwifi dmesg | grep -i ax211如果打印出Direct firmware load for iwlwifi-ty-a0-gf-a0-83.ucode failed with error -2之类的信息说明缺少对应固件。解决办法是安装sudo apt install linux-firmware这个包包含了大量无线网卡固件。如果系统里这个包版本太老可以先升级到较新版本或者从 intel 官网手动下载对应固件放到/lib/firmware/下然后重新加载iwlwifi模块。这种情况下和内核源码的关系在于如果你的内核版本太低即使有固件也加载不了驱动因为内核的 iwlwifi 驱动太老不认识新网卡的硬件 ID。此时你是选择升级内核最简单还是自己下载新内核源码把drivers/net/wireless/intel/iwlwifi这部分单独编出来折腾就看你的具体场景了。如果只是普通使用我强烈建议前者省时省力。6.3 场景三嵌入式内核源码下载与交叉编译的衔接嵌入式方向的朋友看这篇内容重心可能不太一样。嵌入式场景通常是这样的你在一台 x86 的 ubuntu20.04 开发机上工作但目标平台是 arm64 的板卡。你需要下载某个指定版本的内核源码然后用交叉编译工具链去编译。交叉编译的第一步是安装工具链sudo apt install gcc-aarch64-linux-gnu第二步是下载源码并配置目标架构cd ~/kernel-src wget -c https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.34.tar.xz tar -xvf linux-6.1.34.tar.xz cd linux-6.1.34 make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- defconfig make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- -j$(nproc)注意这里每一步命令都带了ARCHarm64和CROSS_COMPILEaarch64-linux-gnu-这是交叉编译的核心告诉 make 用目标架构的配置以及用哪个前缀的交叉编译器。嵌入式场景下源码下载的版本选择尤其重要。通常要严格跟随你的 BSPBoard Support Package文档比如有些板卡会固定要求 Linux 5.10 或 5.15 的某个小版本因为厂商的补丁只在这个版本上验证过。这时候从 kernel.org 下载对应版本源码再叠加厂商补丁就是标准工作流。6.4 场景四内核源码阅读和调试的工具链配置最后讲讲“源码下载完成后怎么高效地读代码”。很多初学者拿到源码不知道从哪里下手这里分享几个我常用的工具和做法首推的是VSCode clangd。在内核源码根目录生成compile_commands.jsonclangd 可以提供精准的跳转和补全。不过内核编译会生成大量临时文件首次生成 compile_commands 会比较慢。次选是vim/emacs ctags/etags。虽然古老但依然高效。执行make tags make cscope生成好标签文件后跳转函数定义、查找全局变量会很方便而且不挑 IDE。如果只是查一个子系统我建议直接从上文提到的Documentation/开始很多子系统都有Documentation/admin-guide/和Documentation/core-api/的说明文档比直接看代码高效很多。调试层面如果你想知道某个系统调用最终走到了内核的哪个函数可以用strace追应用层用perf或ftrace追内核层。内核源码的价值在于“当你有疑问的时候它永远在那里等你翻”。把它下载好、整理好、配置好后续开发和排障的效率能提升一大截。回过头来再说说源码下载这件事本身。很多新手有个误区觉得下载内核源码就是把 tarball 解开放在那后面用到再说。其实不是这样。源码的版本匹配、环境依赖、目录规划、编译链路验证每一步都有实际的工程考量。我前面花了大量篇幅讲 apt source 和 kernel.org 两条路线的选择不是故作高深而是因为这一个选择就决定了你后续一整套工作流是走“系统兼容”还是“自由定制”的路线。从我的个人经验来看ubuntu20.04 上绝大多数驱动和内核相关的痛点都起源于最初的源码版本没选对。把这篇文章里的下载和匹配流程走通你后面无论是装 nvidia 驱动、解决网卡识别异常还是介入内核开发都会顺手很多。
返回列表