尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

WSL2内核头文件缺失修复指南:从报错到编译加载内核模块

WSL2内核头文件缺失修复指南:从报错到编译加载内核模块 如果你在 WSL 里跑make突然被一句/lib/modules/5.15.90.1-microsoft-standard-WSL2/build: No such file or directory. Stop.拦下来先别慌。这个问题我前前后后踩过三四次第一次是在某个无线网卡驱动上第二次是在 vscode 的 WSL 终端里编译一个 Linux 内核模块样例每次都绕了不少弯路但其实根因只有一个WSL 用的不是 Ubuntu 官方内核而是微软定制的 WSL2 内核所以你按普通 Linux 的套路去装内核头文件根本装不上。这篇文章会把整个问题的来龙去脉、三种不同场景的解决办法、以及“就算编译出 .ko 也 insmod 不上”的进阶问题一次性讲透。不管你是只想把手头的项目编译过还是要让内核模块真正跑起来都能在这里找到对应的路。1. 这个报错到底在说什么1.1 报错拆解/lib/modules/xxx/build 是从哪冒出来的先看这行报错的模样它在 tell 你什么make[1]: Entering directory /lib/modules/5.15.90.1-microsoft-standard-WSL2/build make[1]: *** No rule to make target modules. Stop.或者更常见的/bin/sh: 1: cd: cant cd to /lib/modules/5.15.90.1-microsoft-standard-WSL2/build make: *** [Makefile:12: all] Error 2这里的5.15.90.1-microsoft-standard-WSL2是uname -r输出的内核版本号不是固定值不同 WSL 版本会不一样。你可以在终端里执行uname -r看一下大概率输出就是这串带microsoft-standard-WSL2后缀的东西。在正常的 Linux 发行版里/lib/modules/$(uname -r)/这个目录下会放着当前内核的模块信息和头文件其中build是一个软链接指向对应版本的内核头文件目录或者内核源码目录。外部内核模块在编译时Makefile 会通过make -C /lib/modules/$(uname -r)/build M$(pwd) modules这种写法进入这个build目录去复用内核的构建系统。所以当 WSL 里这个build目录不存在或者指向的目标不存在时任何依赖内核构建系统的项目都会直接卡死在“找不到目录”这一步。换句话说报错不是你的项目代码有问题而是环境里缺了内核头文件这一层地基。1.2 为什么 Ubuntu 常规做法在这里失效很多人在这个报错出现后的第一反应就是去装内核头文件。在普通 Ubuntu 上一条命令就搞定sudo apt install linux-headers-$(uname -r)但在 WSL 里你大概率会看到E: Unable to locate package linux-headers-5.15.90.1-microsoft-standard-WSL2 E: Couldnt find any package by regex linux-headers-5.15.90.1-microsoft-standard-WSL2原因很直接WSL2 的内核是微软从 GitHub 上发布的WSL2-Linux-Kernel仓库编译出来的定制内核它并不属于 Ubuntu 的软件源维护范围。Ubuntu 源里只有对应官方内核的linux-headers-*包没有微软这个定制内核的包所以apt搜不到太正常了。这也顺便解释了一个现象WSL 里uname -r显示的内核版本号和 Ubuntu 软件源里能看到的内核版本号永远对不上。你要是强行装一个通用版本的linux-headers-generic装完发现/lib/modules/下依然没有你要的目录等于白折腾。1.3 先花十秒确认你的 WSL 版本在动手之前先确认你用的是 WSL1 还是 WSL2因为两条技术路线的差异非常大。在 Windows 的 CMD 或 PowerShell 里执行wsl -l -v输出结果里VERSION列如果是2说明你用的是 WSL2如果是1那问题就更棘手一点因为 WSL1 不是真正的虚拟机没有完整的 Linux 内核/lib/modules目录根本不存在内核模块的编译和加载在 WSL1 里基本是死路。如果你还是 WSL1我的建议是先升级到 WSL2 再继续wsl --set-version 你的发行版名称 2升级之后你的系统才真正跑在一个轻量虚拟机里uname -r才会输出那串microsoft-standard-WSL2。后面的所有操作都默认你已经是 WSL2 了。2. 正规方案用 WSL2 官方内核源码补齐 build 目录2.1 安装编译内核所需的工具链既然apt没有现成的 headers那就从源码编译。第一步是把编译内核所需要的工具链装齐sudo apt update sudo apt install build-essential flex bison libssl-dev libelf-dev这几个包分别负责什么简单说下build-essential提供gcc、make等基础编译工具flex和bison是词法分析和语法分析器内核构建系统在生成解析器时会用到libssl-dev提供 OpenSSL 头文件编译内核时不少配置项依赖它libelf-dev提供 ELF 文件处理库生成内核模块时经常会用到。缺了任何一个后面的make modules_prepare都可能报出莫名其妙的错误最常见的像fatal error: openssl/opensslv.h: No such file or directory或者fatal error: libelf.h: No such file or directory。所以这一整条命令最好一次装全不要等报错了再回头补浪费时间。2.2 拉取 WSL2 内核源码并切换到你当前版本工具链就绪后从微软官方仓库拉取 WSL2 内核源码。仓库地址是https://github.com/microsoft/WSL2-Linux-Kernel。推荐用浅克隆只拉最近的历史节省时间和磁盘git clone --depth 1 https://github.com/microsoft/WSL2-Linux-Kernel.git cd WSL2-Linux-Kernel浅克隆后默认在默认分支上但这个分支不一定对应你当前内核的版本。为了确保后面编译出的头文件和你运行的uname -r完全匹配最好切到对应版本的 tag。先看一下有哪些 tag 和自己的版本相关git tag | grep 5.15.90.1输出可能是linux-msft-wsl-5.15.90.1然后切过去git checkout linux-msft-wsl-5.15.90.1为什么要做这一步因为内核模块在编译时会记录一个vermagic字符串里面包含内核版本、编译器版本、SMP/PREEMPT 等配置信息。如果源码版本和运行内核版本不一致后面编译出的.ko即使能生成加载时很大概率报Invalid module format或version magic ... should be ...的错误。所以先花半分钟对一下版本号能省掉后面一小时的排查时间。2.3 生成当前内核的 .config 配置光有源码还不够内核构建系统需要一个.config配置文件来决定编译哪些功能。这里有两种方式拿到匹配的配置。第一种方式从当前运行的 WSL2 内核导出配置。WSL2 内核默认开启了CONFIG_IKCONFIG_PROC这意味着/proc/config.gz里保存着当前内核的完整配置。执行zcat /proc/config.gz .config如果这条命令提示文件不存在就用第二种方式直接使用微软提供的默认配置文件在源码目录下有现成的Microsoft/config-wsl文件ARM64 对应的是Microsoft/config-wsl-arm64cp Microsoft/config-wsl .config这里的关键点在于你不能跳过配置直接跑make modules_prepare因为那样内核构建系统会用一个极简的默认配置生成的头文件可能与当前内核不匹配。用/proc/config.gz导出的配置和当前内核完全一致是最稳妥的做法。拿到.config后接着执行make olddefconfig这个命令会根据你当前的架构和默认选项把.config中缺失的配置项补全同时保持已有的配置不动。它不会弹出交互式界面跑起来很快。2.4 执行 modules_prepare 并建立 build 软链接配置就绪后执行一个关键命令make modules_prepare这一步会为编译内核模块准备好所有基础设施包括生成include/config、include/generated等目录以及必要的头文件和脚本。它不会真的去编译整个内核所以耗时通常在几分钟以内比全量编译友好太多。注意如果make modules_prepare报fatal error: elf.h或者找不到openssl/opensslv.h说明第 2.1 节里libelf-dev或libssl-dev没装好装完重跑即可。modules_prepare完成后源码目录就具备了被当作内核 build 目录使用的能力。接下来把这个源码目录软链接到/lib/modules/$(uname -r)/buildsudo ln -sf /path/to/WSL2-Linux-Kernel /lib/modules/$(uname -r)/build把/path/to/WSL2-Linux-Kernel换成你实际的源码路径比如~/WSL2-Linux-Kernel。验证一下ls -l /lib/modules/$(uname -r)/build应该能看到类似lrwxrwxrwx 1 root root 41 Feb 28 10:00 /lib/modules/5.15.90.1-microsoft-standard-WSL2/build - /home/yourname/WSL2-Linux-Kernel到这里你再回到原本报错的项目目录重新执行make大概率就能顺利往下走了。我实测下来这个方案对绝大多数依赖内核头文件的编译任务都有效。3. 轻量绕行有些项目其实并不需要真实头文件3.1 先确认你是不是真的在编译内核模块听到“要拉源码、要生成配置、要跑 modules_prepare”有人可能觉得太重了尤其当你只是想在 WSL 里编译一个普通 C 程序却被 Makefile 莫名其妙引到内核目录时。这时候先别急着大动干戈花十秒钟看一下项目的 Makefile判断它到底是不是在编译内核模块。打开 Makefile看它的构建命令里有没有类似这样的写法make -C /lib/modules/$(shell uname -r)/build M$(PWD) modules如果有那这个项目确实是要编译内核模块必须走第 2 节的正规方案。如果 Makefile 里只是出现了KDIR、KERNELRELEASE、KERNELDIR这类变量但最终编译命令实际上用的是普通gcc那有可能是项目为了兼容某些环境而引用了一下内核目录实际代码并不依赖内核头文件。还有一种典型场景项目根 Makefile 里某个子目标会cd /lib/modules/.../build去看看内核配置但它只是为了读取版本信息编译本身不涉及。这种情况下一个空的 build 目录就能骗过检查。3.2 空 build 目录的适用条件如果你的项目确实只是“检查目录存在”而已那可以直接建一个空目录顶上sudo mkdir -p /lib/modules/$(uname -r)/build建完再执行make原本的No such file or directory就消失了。这个方法的优点是快缺点也明显它只能骗过“目录存在性检查”一旦构建系统真的进入这个目录去找Makefile、scripts、include等文件马上就会爆出新的错误比如No rule to make target modules或者linux/module.h: No such file or directory。所以我的原则是只有当你确认项目最终用的是gcc而非内核 Kbuild 系统时才用这个绕行方案。如果你不确定别赌直接按第 2 节正规流程走。不然绕来绕去时间浪费得更多。3.3 绕行方案的代价什么时候必须回头走正规方案空 build 目录这个方案有个隐患它会让你的环境处于一种“半残废”状态。表面上 make 不再报错但内核头文件缺失的问题其实还在。之后你如果再编译别的项目遇到真正需要内核头文件的场景还是会回来面对这个错误。举个例子你在 WSL 里想编译一个基于DPDK或者eBPF的项目这些项目的构建系统会直接 include 内核头文件。空目录顶不住必须让/lib/modules/$(uname -r)/build指向一个真实、完整、版本匹配的内核源码树。判断标准很简单看编译命令里有没有-I/lib/modules/$(uname -r)/build/include这类参数或者有没有用make -C ... modules。有就老老实实用正规方案没有才考虑空目录绕行。4. 进阶编译出的内核模块想让 WSL2 真正加载4.1 为什么默认 WSL2 下 insmod 大概率失败走完第 2 节你已经能编译出.ko文件了但如果你的目标是insmod加载这个模块那才是真正的大坑。WSL2 默认内核虽然支持模块机制但微软在发布内核时做了不少定制和裁剪直接加载你自己编译的模块经常会遇到insmod: ERROR: could not insert module xxx.ko: Exec format error insmod: ERROR: could not insert module xxx.ko: Invalid module format原因涉及几个层面一是默认内核配置下某些模块依赖的功能没有开启二是自定义模块的vermagic与运行内核不一致三是微软对内核模块加载有限制。与其在报错信息里猜来猜去不如直接换一条最稳的路自己编译整个 WSL2 内核并让 WSL2 用这个内核启动。4.2 自编译 WSL2 内核并配置 .wslconfig还是在刚才的WSL2-Linux-Kernel源码目录里执行完整编译make -j$(nproc)-j$(nproc)会用你 CPU 的所有核心并行编译。WSL2 内核不算大但全量编译也要看机器性能快则几分钟慢则十几分钟。编译完成后内核镜像产物在arch/x86/boot/bzImage把它复制到 Windows 侧一个固定位置比如C:\Users\你的用户名\wsl-kernel\bzImage。然后在 Windows 用户目录下创建或编辑.wslconfig文件加入以下内容[wsl2] kernelC:\\Users\\你的用户名\\wsl-kernel\\bzImage注意路径里的反斜杠要写成双反斜杠这是 INI 文件的转义规则。配置好后在 PowerShell 里重启 WSLwsl --shutdown然后重新打开 WSL 终端执行uname -r。如果一切正常你会看到内核版本变成了你编译时的版本如果你没改本地版本号可能还是原来的5.15.x但实际已经是自己编译的内核了。4.3 安装模块到系统并处理 vermagic内核跑起来之后为了让系统能找到模块对应的目录最好执行一次模块安装sudo make modules_install这个命令会把当前源码编译出的所有模块安装到/lib/modules/$(uname -r)/下并创建对应的build软链接。之后再编译外部模块/lib/modules/$(uname -r)/build就能正常指向你的源码树了。如果你之前用的是默认 WSL2 内核现在换成了自己编译的内核要注意一个问题原来的uname -r可能和现在一样但模块的vermagic可能因为配置差异发生变化。最简单的验证方式是编译一个测试模块后执行modinfo xxx.ko看里面的vermagic是否和uname -r加一组逗号分隔的配置标志一致。如果不一致说明源码版本或配置没对齐回到第 2.3 节重新核对.config。额外提醒自定义内核意味着你需要自己维护它。微软通过wsl --update更新 WSL2 时默认内核会变但你的自定义内核不会自动更新。如果哪天 WSL 启动异常先把.wslconfig里的 kernel 配置去掉再用默认内核启动排查。5. 常见问题与排查技巧实录5.1 高频报错速查表我把这几年在 WSL 里折腾编译时最常撞上的报错整理成了一张表方便你直接对照报错信息常见原因解决办法/lib/modules/xxx/build: No such file or directory内核头文件/源码树未安装或软链接失效按第 2 节建立真实的 build 软链接E: Unable to locate package linux-headers-xxxWSL 内核不是 Ubuntu 官方内核不要用 apt 硬装去拉 WSL2 官方源码编译make[1]: Entering directory /lib/modules/xxx/build后报No rule to make target modulesbuild 目录存在但内容不全或为空在源码目录执行make modules_prepare后再建软链接fatal error: linux/module.h: No such file or directory编译器找不到内核头文件检查 build 软链接是否生效源码是否有 include 目录fatal error: openssl/opensslv.h: No such file or directory缺 libssl-devsudo apt install libssl-devfatal error: libelf.h: No such file or directory缺 libelf-devsudo apt install libelf-devinsmod: ERROR: could not insert module: Invalid module format模块 vermagic 与运行内核不匹配核对源码版本、.config或直接自编译内核并配置.wslconfig/bin/bash^M: bad interpreter: No such file or directoryShell 脚本是 CRLF 换行用sed -i s/\r$// 脚本名转换换行符最后一行虽然不是你问的build目录问题但在 vscode 里用 WSL 写代码时也经常碰到顺手放进来了。5.2 几个容易踩的坑第一个坑是git clone拉到一半断掉。微软内核仓库不算小网络不稳时浅克隆也可能失败。我的经验是加一个--filterblob:none参数先把提交历史拉下来文件内容后续按需获取体感会稳很多git clone --depth 1 --filterblob:none https://github.com/microsoft/WSL2-Linux-Kernel.git第二个坑是/proc/config.gz不存在。虽然 WSL2 默认内核开了CONFIG_IKCONFIG_PROC但如果你用的是别人分发或者自己之前编译过的内核这个文件可能没有。这时候别硬刚直接用源码目录里的Microsoft/config-wsl作为基础配置再跑make olddefconfig效果一样。第三个坑是make modules_prepare中途报错。最常见的是缺bison或flex导致生成scripts/kconfig相关工具时失败。回到第 2.1 节把所有包一次性装齐然后重新执行即可。要注意modules_prepare是幂等的重复执行不会把环境搞坏。第四个坑是软链接位置建错。有人图省事直接把/lib/modules/$(uname -r)/build指向一个空目录或者错误的版本号目录结果编译时出现各种诡异错误。我建议在创建软链接之前先确认uname -r的输出、/lib/modules/下的目录名、源码 tag 三者完全一致再动手。5.3 WSL1 用户怎么处理如果你确认自己的发行版是 WSL1说实话别在这个环境下折腾内核模块了。WSL1 是一个系统调用翻译层没有真正的虚拟机内核/lib/modules这个路径体系本身就不存在编译内核模块从根上就没有意义。应对路径有两种。第一种升级到 WSL2这是最推荐的。执行wsl -l -v看版本然后用wsl --set-version 发行版 2升级之后回到第 2 节或第 4 节按部就班操作。第二种如果你因为硬件或 Windows 版本限制只能用 WSL1那就放弃在 WSL 里做内核模块相关的工作改用双系统、虚拟机等环境。我在实际工作中还遇到过一个情况用户在 WSL1 里跑某个 C 项目Makefile 里有一条无关紧要的规则引用了内核目录导致 make 中断。这种场景走第 3.2 节的空目录绕行法反而最合适。先看报错上下文再决定方案不要一上来就升级 WSL2那也可能引入其他环境变化。这里分享一个我后来养成的习惯在 WSL 里专门保持一份干净的 WSL2-Linux-Kernel 源码目录每次 WSL 版本更新后第一时间进去切换到对应 tag跑一遍make modules_prepare和make scripts。这样再遇到任何需要内核头文件的编译任务/lib/modules/$(uname -r)/build永远都是现成可用的。对了编译内核模块时如果不想弄脏源码目录可以在源码树外面建一个目录用make O外部路径 menuconfig和make O外部路径 modules_prepare的方式把生成文件全部放到外部目录源码树保持干净。这个小技巧对后续维护特别有用推荐你试试。
返回列表