拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Linux内核编译到云环境多用户部署:从源码、initramfs到资源隔离

拿到一个新内核源码在手边配置菜单在屏幕上最后编译出来的内核要跑在云环境里、还要支撑一堆用户的业务——这套流程听起来不复杂但我在实际折腾过几轮之后发现每一步都有不少值得细挖的地方。这篇内容就围绕“Linux内核从源代码获取到云环境多用户部署”这条链路展开把我自己编译、安装、上线、踩坑的过程记录下来。适合谁看准备自己动手编译内核的Linux用户、要在云主机上做内核定制或升级的运维同学以及对内核编译启动链路感兴趣但还没找到系统性文章的开发者。我最早接触内核编译其实是出于一个很朴素的动机发行版自带的内核版本太老而我手头一块新硬件的驱动至少要内核 6.x 才支持。那时候我直接拉主线源码、敲了几条make命令自以为编译成功就万事大吉结果重启后直接进不了系统。后来反复折腾才明白从源代码到云环境多用户部署中间隔着配置、编译、initramfs、引导、批量分发、用户资源隔离好几道关卡任何一环出问题前面全白干。这篇文章就会按我实际操作的顺序来写把链路拆开讲清楚尤其把我在云环境部署和多用户资源管理上总结出来的经验一并放出来希望能帮你少走几趟弯路。1. 先看全貌别急着敲命令很多教程讲内核编译通常到“make install”就结束了仿佛看到“Kernel installed”就算大功告成。但如果你真正要部署到生产环境尤其是云环境里的多用户场景后面还有一大堆事要处理。我先把这个完整链路铺开方便你心里有张地图。1.1 从源码到上线完整链路其实有七步这个链路我整理下来差不多是下面这样确定内核版本与目标环境是普通云主机、虚拟化环境还是嵌入式设备有没有实时性要求获取内核源代码从官网、镜像站或发行版仓库拉取源码。准备内核配置确定开启哪些功能、哪些驱动编译成模块或内建生成 .config。编译内核与模块生成内核镜像 bzImage 和可加载模块 .ko。安装内核并生成 initramfs把内核拷贝到 /boot生成内存文件系统。单机验证在一台测试机上重启、观察启动日志、验证业务。批量部署到所有云主机并处理多用户资源管理打包、分发、回滚预案、用户隔离和限额。这七步像一个流水线把内核从“源代码”变成“生产系统里稳定运行的内核”。我见过不少朋友在第二步或者第四步花了很多精力结果在第五步的 initramfs 上翻车导致重启起不来。所以我已经养成了一个习惯每次动手前把整条链路过一遍评估每一步的风险和备份方案。1.2 “多用户部署”到底是啥意思标题里的“多用户部署”我理解有两种场景而这两种场景在云环境下经常同时存在。第一种场景是一台云主机上跑多个用户或多种业务。这种情况下内核要为不同用户提供公平的资源分配、隔离和限制能力涉及 limits、cgroup、sysctl 等机制。第二种场景是多台云主机组成的集群环境内核要在一批机器上统一升级和维护。这种情况下关键在于打包和自动化分发以及万一新内核有问题时怎么快速回滚。我自己的项目正好两种场景都占了测试区有五六台云主机生产区有几十台机器每台机器上又有若干个租户账号在跑业务。所以下面讲的内容会兼顾这两种场景你可以按自己的情况对号入座。2. 源代码获取从哪拉、拉哪份、怎么选源码获取是整个链路的起点。这一步看起来简单其实里面有个关键决策到底是拉主线内核还是拉发行版内核源码。不同选择直接影响后面的配置和部署方式。2.1 主线仓库与发行版内核仓库到底该拉谁如果你是做嵌入式、驱动开发或者想尝鲜新特性一般是去内核官网或者它的 Git 仓库直接拉主线源码如果你是在云主机上跑业务我反而建议参考发行版自带的内核配置用主线源码或者发行版源码来二次构建。主线仓库的优点是功能最新、代码结构干净缺点是缺少发行版的大量稳定性补丁而且默认配置不一定适配你的云平台。发行版源码例如 Debian/Ubuntu 的 linux-source 包、RHEL 系的 kernel src rpm则带着厂商的大量补丁和成熟的默认配置编译出来的内核在兼容性上更稳比如云平台的 virtio 驱动、网卡固件这些坑发行版基本都帮你填好了。实际项目里我常用的组合是从主线仓库拉一个 LTS 版本源码然后把当前发行版的 /boot/config-$(uname -r) 拷贝过来当配置底子。这样既有新版内核的特性又保留发行版的硬件兼容配置两全其美。2.2 版本号背后的含义LTS、stable、mainline 怎么选Linux 内核版本号看起来就是数字在涨实际上有明确分类。mainline 是正在开发的主线版本新功能都在这里stable 是在 mainline 基础上修 bug 的稳定版本LTS 是长期维护版本一般维护 5 年以上适合服务器和生产环境。还有一些特殊分支比如 RT实时内核补丁适合工业控制、音视频采集这类对延迟有严格要求的场景。我的建议很简单云环境多用户部署只选 LTS。以常见的内核版本为例6.6 和 6.12 都是 LTS生命周期长云平台厂商和新硬件的支持也会往这些版本上靠。不要追最新主线因为主线更新太快你刚部署完上游可能已经修掉了好几个严重 bug而你总不能每个版本都升级一遍。有一点要特别注意如果你用了云厂商的监控 Agent 或安全组件它们可能会做内核模块的兼容性检查。旧模块编译进新内核时经常出现 “Module version mismatch” 这类问题所以升级前先确认云平台对内核版本的支持范围不要闷头升到最新。2.3 拉取源码时的操作细节无论你选哪种源码拉取方式都差不多。最完整的是全量克隆 Git 仓库但仓库体积很大完整克隆会占不少磁盘空间。如果只是想编译一个特定版本建议用浅克隆加标签的方式只拉取指定版本速度会快很多。git clone --depth 1 --branch v6.6 --single-branch https://github.com/torvalds/linux.git linux-6.6 cd linux-6.6如果你在国内访问 GitHub 不稳定的话可以用国内镜像源或直接从 kernel.org 下载 tar.xz 压缩包。压缩包方式也简单wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.tar.xz tar -xf linux-6.6.tar.xz cd linux-6.6无论用哪种方式建议顺手校验一下 sha256 校验值官方页面会给出对应的哈希值。别嫌麻烦内核源码一旦被篡改编译出来的东西谁也不敢用。我一般还会把源码目录单独挂在一个有充足空间的磁盘上源码加编译产物加起来轻松超过 10GB别放在/分区很小的机器上否则编到一半磁盘满了会非常尴尬。2.4 源码目录结构先认识几个关键目录拿到源码后第一次打开大目录会让人有点晕。其实核心就几个arch/放不同 CPU 架构相关代码drivers/是驱动的大本营里面按设备类型分了无数子目录fs/是文件系统实现kernel/是核心调度、进程、信号等代码mm/是内存管理net/是网络协议栈。编译时你不需要读完这些代码但排查问题时要能快速定位到对应目录。比如云主机网卡丢包你大概率要去drivers/net/下面找驱动代码文件系统报错去fs/ext4或fs/xfs看。知道这些目录的位置遇到问题才不至于大海捞针。工具链方面编译环境一般需要 gcc、make、flex、bison、libncurses-dev、libssl-dev、bc 等。不同发行版包名略有差异缺了哪个编译时一般都会明确报缺什么。我踩过最典型的一个坑是缺 libssl-dev结果编译到一半报了一堆关于证书头文件的错误补装后重新配置才通过。所以动手之前一次性把工具链装齐能省不少事。3. 内核配置理解了 .config你就赢了一半内核配置是整条链路里最核心、也最容易被低估的一步。很多人以为配置就是打开 menuconfig 点几个选项其实 .config 文件里的每一行 CONFIG_XXX 都对应内核源码里的一个开关配置的精细程度直接决定内核的体积、性能和稳定性。3.1 配置界面背后的原理Kconfig 与 .configLinux 内核用 Kconfig 语言来管理配置菜单源码里每个子目录下的 Kconfig 文件描述了这个目录支持哪些选项以及选项之间的依赖关系。你运行make menuconfig时看到的文字界面就是这些 Kconfig 描述被解析后的结果。保存后生成 .config 文件里面每一行就是一个 CONFIG_XXXy/m/n 的决定。这里面的 y、m、n 分别代表y 表示编译进内核镜像m 表示编译成可加载模块n 表示不编译。这个选择非常关键因为它直接影响启动流程。比如根文件系统所在磁盘的驱动如果编译成模块而 initramfs 里又没包含这个模块内核启动时就找不到根设备直接 panic。配置时如果对某个选项不确定可以在 menuconfig 里按/搜索符号名或者直接看Documentation/目录下的说明。更多时候我会优先以发行版自带的 .config 为基础避免自己从零一路点过去漏掉关键驱动。3.2 三种配置起手式我推荐你选第一种配置的第一步是生成一个初始 .config常见的有三种方式。第一种基于当前发行版配置做底这也是我在云环境里最常用的方式cp /boot/config-$(uname -r) .config make olddefconfigolddefconfig会用新内核源码里的默认值来补齐当前 .config 中缺失的新选项。这样既能继承发行版上已经验证过的配置又能自动适应新的内核版本是性价比最高的方案。第二种用make localmodconfig快速裁剪。这个命令会分析当前系统已加载的模块只保留这些模块对应的配置生成的 .config 非常精简编译速度快很多。但它有一个风险如果当前机器没有加载某些驱动这些驱动就会被裁剪掉换到别的机器上可能无法启动。在云主机上你可能只加载了 virtio 相关模块结果编出来的内核换到物理机或者另一家云平台就起不来。第三种从默认配置开始自己慢慢点。make defconfig或者针对架构的make x86_64_defconfig会生成一个通用配置但默认配置一般不含发行版的定制优化很多文件系统、安全模块、云平台驱动都没开启。除非你对内核非常熟悉否则我不推荐这种方式。3.3 云环境必须检查的几个配置项基于发行版配置做底之后还要重点确认云环境相关选项。我曾在一次自编译内核后重启发现虚拟机黑屏无法登录最后排查到是 virtio 驱动没编进去。云主机最常见的虚拟化底层是 KVM磁盘和网卡绝大多数走 virtio 协议所以这几个配置格外重要配置项推荐值说明CONFIG_VIRTIO_PCIyvirtio 设备的 PCI 传输层必须内建CONFIG_VIRTIO_BLKyvirtio 块设备驱动云主机系统盘依赖它CONFIG_VIRTIO_NETyvirtio 网卡驱动CONFIG_VIRT_CONSOLEy虚拟串口控制台排障时非常依赖CONFIG_NVME_COREy如果系统盘是 NVMe SSD建议内建CONFIG_EXT4_FSy最常见的 rootfs 文件系统CONFIG_XFS_FSy很多云主机默认用 xfs为什么这些驱动要 y 而不是 m因为如果 m内核对根设备的访问必须依赖 initramfs 里打包的模块。虽然 initramfs 也能做到这一点但一旦 initramfs 生成不完整或模块依赖有误系统就会启动失败。把存储和网络相关驱动直接编进内核启动阶段就少一层依赖排障也简单。3.4 编译成模块还是内建经验法则是什么除了上面这些关键驱动选择 y其余组件可以根据需要选择 m让内核镜像更瘦、加载更灵活。经验法则有三条第一启动早期必须用的东西尽量 y。比如系统盘驱动、常用文件系统、网卡驱动。第二不常用的设备驱动 m 就够了。比如各种 USB 外设、蓝牙、打印机编成模块需要时再modprobe能显著减小内核镜像体积。第三完全用不到的可以直接 n。比如你的云主机根本没有蓝牙可以把蓝牙协议栈整体关掉但前提是确认不会影响其他功能。裁剪的收益主要是编译时间和体积对运行性能影响不大没必要为了追求极简而过度裁剪否则容易误伤。4. 编译、安装与 initramfs让内核真正可启动配置完成后下一步就是编译和安装。这一步的坑主要不在编译本身而在安装后的 initramfs 生成和引导配置。很多人以为make install就完事了结果重启才发现系统起不来。我在这里把完整流程和背后的逻辑讲清楚。4.1 从 make 到 make install每一步在干什么编译命令看起来简单但每一步都有明确分工make -j$(nproc) bzImage make -j$(nproc) modules sudo make modules_install sudo make installmake bzImage编译内核主体生成arch/x86/boot/bzImage也就是最终要拷贝到 /boot 的内核镜像文件。make modules编译所有配置为 m 的模块生成大量 .ko 文件。make modules_install把这些模块安装到/lib/modules/$(uname -r)/目录下这个目录名跟内核版本严格对应不能随便改。make install把 bzImage、System.map 等文件拷贝到 /boot并触发安装脚本。-j$(nproc)是并行编译参数nproc能拿到 CPU 核数一般建议设置成 CPU 核数的 1.5 倍左右。编译时内存如果不足可以把并行数调小否则容易出现 OOM编到一半进程被杀掉磁盘上留下一堆不完整的对象文件后面再 make 会非常混乱。内核编译对磁盘 IO 也比较敏感建议把源码放在 SSD 上时间能差好几倍。4.2 initramfs 为什么不能省initramfs 是早期用户空间文件系统内核启动时先把它加载到内存然后通过它挂载真正的根文件系统。为什么需要这么一层因为内核镜像本身装不下所有驱动尤其很多存储、文件系统驱动是 .ko 模块必须先从某个地方加载它们才能访问根分区。initramfs 就是“先帮你把驱动拉起来再挂根目录”的桥。直接编进内核y的驱动不需要 initramfs 也能工作这也是我刚才强调关键驱动要 y 的原因。但发行版默认配置里很多文件系统、加密模块仍然以 .ko 形式存在所以还是要把 initramfs 生成好。常见的生成命令是sudo update-initramfs -c -k 6.6.0-custom这里的版本号要和实际编译出来的内核模块目录对应上最好是make modules_install之后去/lib/modules/看一眼目录名再填。RHEL/CentOS 系则对应使用 dracutsudo dracut --force --kver 6.6.0-custom生成好之后检查 /boot 目录下是否同时存在 vmlinuz、initramfs、System.map 这三个文件缺一个都会出问题。很多人在这一步翻车就是以为make install会顺手生成 initramfs但在一些发行版上它并不会必须手动补这一下。4.3 引导配置与默认启动项切换安装完内核和 initramfs 后还需要更新引导程序。Debian/Ubuntu 系通常用 GRUB一行命令即可sudo update-grubRHEL/CentOS 系用sudo grub2-mkconfig -o /boot/grub2/grub.cfg更新完成后最好先确认一下新内核是否出现在启动菜单里。我在实际项目中还习惯在 GRUB 配置里设置一个“默认启动旧内核”的保险让 grub 的 saved_entry 指向当前正在运行的内核这样新内核如果起不来重启后还能回滚。sudo grub2-set-default 0这条命令在 RHEL 系里会把默认启动项设为菜单第一项但不同机器的菜单序号不一样我觉得更稳妥的方法是把新内核的 menuentry 名字明确写到/etc/default/grub的GRUB_DEFAULT变量里并保留旧内核的启动项方便手动选择。4.4 重启前的备份方案必须做重启前一定要确认机器能被远程访问尤其是云主机。自编译内核最容易出的问题是起不来此时如果 SSH 不可达你又没有云控制台的 VNC/串口访问权限就只能靠快照或救援模式来恢复了。我一般按这个流程操作创建一个云主机快照确保可以回滚整个系统盘。保留当前正在运行的内核和 initramfs 不动。确认 GRUB 菜单里有旧内核启动项。在 /boot 目录下检查新内核文件和 initramfs 文件名是否匹配。屏幕输出启用串口控制台这样云控制台能看到启动日志。做完这五步再重启心里会踏实很多。如果机器起不来优先去云控制台看启动日志通常能看到 panic 信息或“unable to mount root fs”之类的原因。5. 云环境实战从单机验证到多用户部署内核编译好了也在这台机器上启动成功了接下来的问题是怎么稳妥地把这个内核部署到所有云主机上并且让这些机器上的多用户环境正常工作。这里面的门道不只是在每台机器上重复敲命令那么简单。5.1 先在单机验证别直接上生产我强烈建议把部署流程分成两级先在测试机验证再上生产。测试机验证不是简单地“能启动就行”我一般会做这样几轮检查检查uname -r和/proc/cmdline确认当前启动的是新内核。查看journalctl -k -b或dmesg重点看有没有 GPU 报错、驱动加载失败、文件系统警告。跑一遍核心业务用例确认网络、磁盘 IO、数据库连接正常。观察云平台监控 Agent 是否正常上报比如内存、CPU、磁盘指标是否和以前一致。持续运行 24 到 48 小时观察有没有定时任务或者低峰期才出现的问题。为什么这么谨慎因为有些隐藏问题不会马上暴露比如某些驱动在负载高时才崩或者某个模块和云平台的 VPC 网络组件有兼容性冲突。测试机上多花两天生产环境就能少折腾好几天。5.2 多用户资源隔离limits、cgroup 与 sysctl回到“多用户”的话题。在一台云主机上跑多个用户或业务时最怕一个人把 CPU、内存、文件描述符全部吃光导致其他人被牵连。内核和系统层面有几个工具可以用来做隔离和限制。第一个是/etc/security/limits.conf它基于 PAM 实现限制单个用户或进程组的资源使用。常见配置像这样* soft nofile 65535 * hard nofile 65535 developers soft nproc 512 developers hard nproc 1024nofile是文件描述符上限nproc是进程数上限。对于大量并发连接的服务文件描述符很容易耗尽所以这个必须调。第二个是 systemd 的 user slice。现代 Linux 系统上每个登录用户都会有一个对应的 systemd slice比如用户 uid 是 1001会生成user-1001.slice。可以通过 systemctl 动态设置这个 slice 的资源上限systemctl set-property user-1001.slice CPUQuota200% systemctl set-property user-1001.slice MemoryMax8GCPUQuota200%表示最多占用两个 CPU 核心的算力MemoryMax8G表示最多使用 8GB 内存。这样即使租户程序跑飞也不会拖垮宿主机上其他用户。这种方式的优势是不需要改内核配置运行期直接生效调优方便。第三个是内核 sysctl 参数。多用户环境下有几个参数我每次部署都会检查参数建议值说明fs.file-max2097152系统级文件描述符总数上限fs.nr_open1048576单个进程可打开的最大文件数vm.max_map_count655300进程内存映射区域数量上限Java 应用尤其依赖kernel.pid_max4194304系统 PID 编号上限用户多、进程多时需要调大这些参数可以写到/etc/sysctl.d/99-custom.conf再执行sysctl --system生效。多用户环境里vm.max_map_count是最容易踩坑的业务一旦跑起来就报 “cannot allocate memory”但内存明明还有富余最后定位到是这个参数太小调大后就正常了。5.3 批量部署打包、仓库化、灰度推送单机验证通过后批量部署要讲究方式方法不能拿着 U 盘一台台机器装。云环境里最合理的做法是把自己编译好的内核打包成发行版标准的 deb/rpm 包放到内部仓库然后通过自动化工具批量分发。打包这件事直接手动打很繁琐建议用发行版自带的打包工具。Debian/Ubuntu 系可以借助make deb-pkg它会在源码目录直接生成几个 .deb 包包括 linux-image、linux-headers、linux-libc-dev 等。RHEL 系也有类似的make rpm-pkg。生成之后把这些包放到内部的 apt/yum 源里所有云主机就能通过标准的包管理命令升级内核了。灰度推送的流程我是这样设计的测试区机器全部升级观察 3 天。选一台流量较少的边缘节点升级观察 1 天。每批升级不超过集群总量的 20%分批推进。每台机器升级前打快照升级后确认内核版本和关键业务状态。配置一个回滚预案一旦发现异常批量执行 grub 切回旧内核并重启。一键自动化方面Ansible 是个不错的选择。把“备份 grub 配置、安装新内核包、更新 grub、重启、等待 SSH 恢复、确认内核版本”这些步骤写成 playbook几十台机器几分钟就能完成升级。但自动化脚本在重启前一定要加一个等待确认步骤防止一批机器同时重启后全部失联。5.4 多台云主机升级时的环境差异每台云主机的硬件配置、操作系统版本、已加载模块可能都有差异即使你用的是同一套内核包也不能保证每台都一帆风顺。我遇到过这样的场景同一批云主机大部分升级顺利偏偏有一台重启后网卡驱动加载失败排查发现是那台机器的网卡固件版本偏老和新内核驱动不兼容。所以灰度升级不是走形式它是真能提前暴露环境差异的。处理环境差异的常见手段是保留多内核版本。不要把旧内核的包卸载掉grub 里留着旧内核启动项至少在确认新内核稳定运行一个月后再清理。多用户环境涉及的第三方内核模块也需要注意比如某些安全软件、性能监控工具、数据库加速模块它们需要针对新内核重新编译或适配升级前检查一遍云厂商和软件厂商的兼容性清单是必须的。6. 实战中踩过的坑问题排查速查表最后这部分我把编译部署过程中遇到过的典型问题和排查思路整理成一张速查表方便你在紧急情况下快速对照。这里面的每一行都是我真金白银踩出来的。6.1 编译阶段的问题编译阶段的问题一般比较直观报错信息里基本能定位到缺什么。症状可能原因解决办法fatal error: openssl/xxx.h: No such file缺少 OpenSSL 开发头文件安装 libssl-dev / openssl-develBTF: .tmp_vmlinux.btf: pahole (pahole) is not available缺少 pahole 工具安装 pahole或禁用 CONFIG_DEBUG_INFO_BTFrecipe for target modules failed磁盘空间不足或内存不足清理编译目录减少 -j 并行数make: gcc: Command not found没有安装编译工具链安装 build-essential / kernel-devel 相关包pahole 这个问题在较新内核上经常出现因为 5.16 之后内核默认开启 BTF 调试信息依赖pahole生成 BTF 数据。如果只是日常编译部署不涉及 BPF 调试可以在配置阶段关掉CONFIG_DEBUG_INFO_BTF但编译节点上直接装 pahole 更省事。6.2 启动阶段的问题启动阶段的问题风险最大因为机器可能完全不可达只能靠云控制台或者本机屏幕看日志。症状可能原因解决办法Kernel panic - not syncing: VFS: Unable to mount root fs根文件系统驱动没编进去或 initramfs 缺失检查根磁盘驱动是 y 还是 initramfs 包含对应 .kodracut: FATAL: No or empty root argument内核启动参数里没传 root 设备检查 grub 配置里的 rootUUID 是否正确No working init foundinitramfs 里缺少 systemd 或 init 程序重新生成 initramfs 并确认对应的 init 程序存在initramfs unpacking failedinitramfs 文件损坏重新执行 update-initramfs检查 /boot 空间排查启动问题一定要会用救援模式或云平台的“救援磁盘”“VNC 控制台”。从救援模式启动后把原系统盘挂载上chroot 进去重新生成 initramfs、更新 grub是常用手段。我每次修完这类问题都会第一时间检查 /var/log 和 journald 里的启动日志把根因记下来避免下次再踩。6.3 多用户运行时的资源类故障这种问题最隐蔽因为不像编译报错那样直接通常表现为业务偶发失败、进程被杀、内存申请失败。症状可能原因解决办法error: cannot allocate memory in static TLS block虚拟内存映射数量超限调大 vm.max_map_count观察是否还有告警out of memory: Kill process内存不足CPU 或内存被某个用户占满用 systemctl set-property 给对应用户 slice 设置 MemoryMaxfork: Cannot allocate memory系统进程数或内存不足调大 kernel.pid_max检查可用内存Too many open files文件描述符耗尽调大 limits.conf 中的 nofile再调 fs.file-max这里尤其提醒一点多用户环境下给不同用户设置合理的 cgroup 限额比事后查 OOM 日志要省心得多。而且 systemd 的 user slice 是动态调整的可以先给一个保守值运行一段时间后看监控数据再放宽不用一上来就限制死。还有一个经验云主机上如果跑了很多容器要注意容器本身也有 namespace 隔离Linux 内核对这些隔离特性的支持情况直接决定能跑多少个容器。频繁创建销毁容器时如果出现 namespace 相关的报错一般要检查kernel.pid_max和内存映射数量这几个参数。写在最后的一点体会这套流程走完一遍我的最大感受是真正值钱的不是最后那几条 make 命令而是你建立起的整体排查思路。从拿到源码到云环境多用户部署每一环都在教你怎么理解系统启动、驱动加载、资源隔离这些底层机制。以后哪怕不自己编译内核只是用发行版更新内核你也会留意 initramfs、grub 回滚这些环节排障时更快找到方向。对一个初次尝试的人来说我真心建议先找一台测试机按照编译、装 initramfs、配置 grub、验证回滚这条路完整走一遍。我第一次就因为不清楚 initramfs 的作用升级内核后重启失败后来靠着一台物理机屏幕和串口日志一点一点排查才把链路彻底弄明白。这个过程很费时间但它带来的收获远超过一个能用的新内核本身。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门