装完 Ubuntu 20.04 之后,发现右上角根本没有 WiFi 图标,设置里也没有“无线网络”选项,那一刻的心情我太懂了。更绝望的是,这台机器正好是个无线路由器环境,身边连个网线口都找不到,手上只有一个U盘和一台能上网的Windows电脑。这时候要解决的,就是在完全离线的情况下,给这块 Realtek b852 无线网卡把驱动装上——而真正的难点从来不在“编译”本身,在于你怎么把一整套依赖工具链也一起搬过去。
这篇文章就是把我实际操作中走过的完整流程、踩过的坑以及最终稳定可复现的离线安装方案整理出来。无论你是刚装完双系统发现没网的新手,还是公司内网环境里必须离线部署 Linux 的运维,只要你手上是一块 Realtek 无线网卡,且系统是 Ubuntu 20.04,这套思路都适用。
1. 先搞清楚你手里这块网卡到底是谁家的方案
1.1 “Realtek b852”并不等于芯片型号
很多人看到网卡包装盒上写着“Realtek b852”,就跑去搜“b852 driver”,结果搜出来的东西七零八落,根本对不上号。这里必须先澄清一个关键概念:b852 是厂商的销售型号,不是 Realtek 的芯片方案型号。瑞昱(Realtek)的无线网卡芯片一般是以 RTL 开头,比如 RTL8812AU、RTL8821CU、RTL8188EU 这些。包装盒上印的 b852 可能只是某个 OEM 厂商给这个网卡起的商业代号,同一个代号在不同批次上可能还对应不同的芯片。
所以在安装驱动之前,第一步是确认芯片真实方案,而不是急着找“b852 驱动”。确认方法很简单,把网卡插到电脑上,然后执行:
lsusb如果它是一块 USB 接口的无线网卡,输出里会有一行以Realtek Semiconductor Corp.开头的内容,后面跟着的就是 USB 厂商 ID 和设备 ID,比如:
Bus 001 Device 002: ID 0bda:b812 Realtek Semiconductor Corp. RTL8812AU 802.11a/b/g/n/ac 2T2R DB WLAN Adapter如果是 PCIe 接口的网卡(比如笔记本内置的),则用:
lspci -nnk | grep -i net0bda是 Realtek 的固定厂商 ID,后面的b812才是真正的设备 ID。不同设备 ID 对应不同芯片方案。举个例子,常见的几种对应关系大致是:
| USB ID | 芯片方案 | 接口类型 | 备注 |
|---|---|---|---|
| 0bda:8812 / 0bda:b812 | RTL8812AU | USB 2.0/3.0 | 老经典,社区驱动支持最完善 |
| 0bda:c811 | RTL8811CU | USB | 单天线 AC 600M 常见 |
| 0bda:c82b | RTL8822CU | USB | 带蓝牙方案,驱动麻烦一些 |
| 0bda:8179 | RTL8188EU | USB | 老款 150M,驱动相对简单 |
按照我经手的案例,印着 b852 的网卡大多数情况下是 RTL8812AU 方案。但你在自己的机器上一定要以lsusb的实际输出为准,这一步千万不要跳,否则后面的驱动选型大概率是白折腾。
1.2 从芯片方案到驱动源码包的对应关系
确认了芯片方案之后,驱动选择就清晰多了。常见的选择有这么几类:
- Realtek 官方中文网/英文网驱动包:瑞昱官网有各型号的 Linux 驱动源码下载,但老型号的官方驱动停留在一个很老的内核适配版本上,在 Ubuntu 20.04 的 5.4 / 5.8 / 5.13 内核上经常会编译报错。我不太推荐它作为首选。
- 社区维护驱动(aircrack-ng/rtl8812au):这个仓库专门维护 RTL8812AU/8814AU 系列,内核 API 变化后更新得很快,Ubuntu 20.04 上编译通过率非常高。GitHub 上直接搜 rtl8812au,选 star 数最多的 aircrack-ng 分支就行。
- 厂商定制驱动:部分网卡厂商(如 EDUP、Comfast)会提供自己的驱动压缩包,本质上还是改过的 Realtek 官方驱动。能用,但优先级排在社区驱动之后。
如果你是 RTL8821CU / RTL8822CU,我建议找morrownr/8821cu-20210916或者对应的rtw88主线驱动思路;如果是 RTL8188EU,aircrack-ng/rtl8188eus这个仓库也维护得不错。
这里有一个很实在的经验:优先选社区维护的驱动仓库,而不是 Realtek 官方原版。原因很简单,Linux 内核的 API 变动非常频繁,官方驱动往往在新内核上会出现类似于implicit declaration of function这种编译错误,而社区维护的项目通常会在几周内修复。既然你已经处于离线场景,必然希望一次编译就成功,没有太多试错的余地,所以选社区活跃仓库更稳。
2. 离线安装最难啃的骨头:依赖,不是驱动
2.1 全新 Ubuntu 20.04 默认缺了哪些东西
驱动源码拿到手之后,编译还缺一整套工具链。很多第一次尝试离线安装的人会犯一个错误:只把驱动源码拷到离线机器上,然后发现make命令都不存在。
在 Ubuntu 20.04 上,编译一个内核模块(无线网卡驱动本质就是内核模块)需要的最小依赖大致包括:
build-essential:里面包含了gcc、g++、make、dpkg-dev等基础编译工具,是整个工具链的入口。dkms:Dynamic Kernel Module Support,也就是“动态内核模块支持”。它可以把驱动登记到系统里,将来你的内核升级时自动重新编译驱动,不至于换个内核版本网卡就挂了。linux-headers-$(uname -r):这可能是最重要的东西。编译内核模块需要当前运行内核的头文件,否则编译器不知道内核导出了哪些函数、数据结构长什么样。linux-firmware:部分 Realtek 驱动在运行时还需要从/lib/firmware/rtlwifi/目录加载固件.bin文件,缺了它驱动能装上,但 dmesg 里会报固件下载失败。
其中build-essential和dkms在全新系统上默认基本都没装,linux-headers默认只带了一个 generic 包的空壳,真正对应内核版本的那一个也没有。如果你是桌面版安装的话,linux-firmware一般默认会有,但不保证版本匹配。
2.2 理清依赖关系比记命令更重要
我建议你不要死记硬背这些包名,而是理解它们之间的关系,这样换内核版本、换 Ubuntu 小版本你都知道该带什么。
依赖链路大致是这样的:
dkms ├── build-essential(提供 gcc / make / dpkg-dev) ├── linux-headers-generic(元包,指向当前内核版本的头文件) └── linux-headers-$(uname -r)(实际编译时的头文件)dkms依赖build-essential,build-essential里包含编译器;linux-headers-generic是一个元包,它不直接提供文件,而是指向当前内核版本对应的linux-headers-5.x.x-xx-generic。在离线机器上,我们用uname -r看到的输出才是真正要匹配的版本。
举个例子,如果uname -r输出是5.4.0-150-generic,那么你需要的头文件包就是linux-headers-5.4.0-150-generic。这个版本必须精确一致,差一个小版本都没法用。
我见过一个很典型的坑:有人只拷贝了linux-headers-generic和linux-headers-5.4.0-26-generic,但离线机器实际运行的却是5.4.0-42-generic,结果 dkms 编译时一直报找不到/usr/src/linux-headers-5.4.0-42-generic。所以无论如何,离线机器上先执行一把uname -r,把这个输出记下来,后面所有打包物料都以它为准。
2.3 容易被忽略的版本匹配细节
除了内核头文件版本要和uname -r严格一致之外,还有几个小坑值得提前提醒:
第一,gcc版本不能太老。Ubuntu 20.04 默认的 gcc 是 9.x,社区驱动在适配新内核时基本都是按较新的 gcc 测试的,如果你从什么奇奇怪怪的地方拷了一个 gcc-4.8 的 deb 包来离线装,大概率是在给自己找麻烦。直接用 20.04 软件源里的默认版本最靠谱。
第二,dkms版本在 Ubuntu 20.04 中是 2.8.x,它和较新内核配合时偶尔会有兼容问题,但总体问题不大。如果遇到dkms: failed to build的报错,可以先不用 dkms 方式,直接用最原始的make && make install把模块装上,先把网卡跑起来再说。
第三,如果你是从一台 Ubuntu 20.04 机器上拷贝 deb 包到另一台 Ubuntu 20.04 机器,注意架构必须一致。绝大多数情况下都是 amd64,但也有人拿 32 位系统或者 arm64 系统当“辅机”,那装上去是完全不行的。
3. 离线物料的筹备:一台联网机器就够了
3.1 方案一:精确下载依赖包(适合网络不太好的人)
最稳妥的物料筹备方式,是在一台同样装有 Ubuntu 20.04 且能联网的机器上,用apt download把需要的软件包及它们的依赖一个个下载下来。
比如先确认需要的包列表:
sudo apt update apt depends build-essential dkms然后根据输出把依赖包都用apt download拉下来:
cd ~/offline-packages apt download build-essential dkms linux-headers-generic linux-headers-$(uname -r)这个方案的问题在于:apt download只下载你指定的包,不会自动下载这些包的依赖。你需要先分析依赖关系,再手动把每个依赖也下载一遍。对于build-essential这种依赖链比较长的包来说,用手动方式收集很容易漏。如果你一定要用这个方案,可以借助apt-cache depends去递归查询,但说实话效率不高,比较容易漏包。
3.2 方案二:直接打包 apt 缓存目录(我强烈推荐)
我实际测试过很多次之后,更推荐的做法是:在联网机器上先模拟安装一遍,然后直接打包/var/cache/apt/archives/目录。
操作方式是这样的。联网机器上执行:
sudo apt update sudo apt install build-essential dkms linux-firmware安装完成后,apt 会把所有下载下来的 deb 包保留在/var/cache/apt/archives/目录。我们直接把这个目录下的所有*.deb拷走:
cd /var/cache/apt/archives/ tar -czf ~/offline-driver-deps.tgz *.deb然后把offline-driver-deps.tgz通过 U 盘拷贝到离线机器上解压。假设解压到了/home/user/deps/目录,接下来这一步非常关键:
cd /home/user/deps/ sudo apt install ./*.deb注意这里不要用sudo dpkg -i *.deb。虽然dpkg -i也能装,但它不会自动处理依赖顺序,很可能遇到“先装了 A,但 A 依赖的 B 还没装”导致的报错。而apt install ./*.deb会把本地 deb 包作为一个整体交给 apt 处理,apt 会智能排序,自动解决包之间的依赖顺序问题,就算报错也会明确告诉你缺什么。
为什么这个方案更稳?因为apt install已经把整棵依赖树的所有包都放进了缓存目录,你打包带走的就是一套完整闭环,不需要手动收集依赖。唯一要注意的是:不要在联网机器上执行apt clean或者apt autoclean,否则缓存目录会被清空。
3.3 不要漏掉驱动源码包和固件文件
依赖包解决了,驱动源码包也别忘。去 GitHub 下载驱动源码的 zip 包,或者用台式机上已经克隆好的仓库重新打成压缩包。我这里还是建议用 zip 或者 tar.gz 格式,避免 Windows 和 Linux 之间来回传文件时出现换行符问题。
驱动源码传到离线机器之后,先做一次完整性校验:
md5sum rtl8812au-master.zip和你在联网机器上记录下来的校验值比一下。这一步看着没用,但当你面对一个 200MB 的源码包在传输过程中损坏时,就知道校验能省多少时间了。
另外,linux-firmware的 deb 包最好也带上,因为它包含/lib/firmware/rtlwifi/下的固件文件。很多网卡驱动加载时只缺一两个.bin文件,系统会在 dmesg 里告诉你缺什么名字。与其等那一刻再去单文件拷贝,不如一开始就装上完整固件包。
4. 驱动编译安装与 dkms 的正确集成方式
4.1 常规编译安装流程
先说最简单直接的方案。假设你已经拿到了rtl8812au-master源码并解压到目录里:
cd rtl8812au-master sudo make -j$(nproc) sudo make install这里-j$(nproc)表示用 CPU 所有核心并行编译,能明显缩短时间。编译过程中如果看到像Kernel preparation unnecessary for this kernel. Skipping...这样的信息,这不是报错,是正常提示。真正需要警惕的是Error 1或者Error 2结尾的输出。
编译完成后,把模块手动加载起来看看:
sudo modprobe 8812au然后立刻用dmesg | tail -20查看内核日志。如果驱动正常加载,一般会看到类似Realtek Wireless的识别信息,再配合iwconfig就能看到wlan0接口了。
但直接用make install有个问题:下次内核一升级,模块文件还在,但它和新内核版本对不上,系统会把旧模块单独放到一个目录里,新内核根本不会加载它。也就是说,你升级一次内核,网卡基本又要重新编译一次。
4.2 用 dkms 接管,让内核升级后依然稳定
为了避免“升级内核就掉网卡”,我强烈建议一开始就把驱动登记进 dkms。很多社区驱动源码根目录下自带dkms.conf,比如 aircrack-ng 的 rtl8812au 仓库就有。这种情况下操作起来很简单:
sudo dkms add . sudo dkms build -m rtl8812au -v <版本号> sudo dkms install -m rtl8812au -v <版本号><版本号>可以看dkms.conf文件里的PACKAGE_VERSION字段。比如它写的是5.6.4.2,那命令就是:
sudo dkms build -m rtl8812au -v 5.6.4.2 sudo dkms install -m rtl8812au -v 5.6.4.2装完之后模块会自动复制到/lib/modules/$(uname -r)/updates/dkms/,并且自动运行depmod注册。你可以用这个命令验证:
dkms status输出类似rtl8812au/5.6.4.2, 5.4.0-150-generic, x86_64: installed,就说明已经装好了。
如果源码根目录下没有dkms.conf,也可以手动写一个。我通常会在源码目录下创建dkms.conf,内容大致是:
PACKAGE_NAME="rtl8812au" PACKAGE_VERSION="5.6.4.2" BUILT_MODULE_NAME[0]="8812au" DEST_MODULE_LOCATION[0]="/kernel/drivers/net/wireless/" AUTOINSTALL="yes" MAKE[0]="make" CLEAN="make clean"写完后再执行dkms add .就可以了。这里有个细节要注意:BUILT_MODULE_NAME必须和你编译出来的.ko文件名一致,比如模块名就是8812au.ko。
4.3 Secure Boot 签名:一个容易被忽略的拦路虎
如果你安装 Ubuntu 20.04 的时候没有关闭 BIOS 里的 Secure Boot(安全启动),那么后续会碰到一个很诡异的现象:dkms 状态显示 installed,modprobe也不报错,但网卡就是不出现在系统里,dmesg里还有一行类似module verification failed: signature and/or required key missing的提示。
原因很简单:启用了 Secure Boot 的机器只允许加载能通过签名校验的内核模块。Realtek 社区驱动没有经过系统内置的 Microsoft 签名,所以即使模块文件放在正确的位置,内核也不会真正加载它。
解决方式有两种。第一种是进入 BIOS,把 Secure Boot 关闭,然后保存退出。这最省事,但如果你需要同时兼容其他系统的安全要求,可以用第二种方式,就是自己生成一个签名密钥并导入到系统里。
签名导入的完整体操作如下:
# 生成一对签名密钥 openssl req -new -x509 -newkey rsa:2048 -keyout MOK.priv -outform DER -out MOK.der -nodes -days 36500 -subj "/CN=My Key/" # 对 .ko 模块签名 sudo /usr/src/linux-headers-$(uname -r)/scripts/sign-file sha256 ./MOK.priv ./MOK.der $(modinfo -n 8812au) # 把公钥导入到 MOK(Machine Owner Key)数据库 sudo mokutil --import MOK.der执行最后一步时系统会要求设置一个一次性密码,重启后会在蓝色 MokManager 界面让你确认导入密钥,输入刚才设置的密码即可。整个过程不复杂,但在离线环境下第一次遇确实会卡很久,提前知道这个机制能省下大量排查时间。
5. 驱动装完还是连不上?别急,按这条链路排查
5.1 先用这几条命令确认模块和接口状态
驱动装完不代表就万事大吉了。接下来最可靠的排查方式,是依次执行下面几条命令,逐层确认问题出在哪一层。
# 1. 查看驱动模块是否加载 lsmod | grep 88 # 比如 8812au、8821au、88x2bu 这类输出 # 2. 查看无线接口是否建立 iwconfig # 3. 查看 NetworkManager 能否看到无线设备 nmcli dev status # 4. 检查是否被 rfkill 软/硬件开关锁住 rfkill list # 5. 看内核日志里有没有固件或驱动报错 dmesg | tail -50iwconfig里如果能到wlan0 IEEE 802.11AC ESSID:off/any,说明接口已经建起来了;nmcli dev status里能看到wifi类型且状态是unmanaged或disconnected,说明 NetworkManager 已经识别到设备,剩下来只是连接配置的问题。
5.2 常见故障对照表
我把实际中遇到最多的故障点整理成了一张表,你可以直接对照处理:
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
lsmod没有模块输出 | 模块未加载,可能 Secure Boot 拦截 | sudo modprobe 8812au,若报错看 dmesg;检查 Secure Boot 状态 |
rfkill list显示Soft blocked: yes | 无线被软件开关锁定 | sudo rfkill unblock all |
rfkill list显示Hard blocked: yes | 笔记本物理开关/快捷键关闭 | 按键盘上的飞行模式键或笔记本侧边物理开关 |
能找到wlan0但搜不到任何 WiFi | 国家码不对或天线接触问题 | 执行sudo iw reg set CN(按你所在地区设置),再nmcli radio wifi on |
| dmesg 报 firmware failed to load | 缺少固件.bin文件 | 安装linux-firmware,或手动把.bin放到/lib/firmware/rtlwifi/ |
| 能连上但频繁掉线 | USB 供电不足或电源管理 | 换一个 USB 口;关闭电源管理:sudo iw dev wlan0 set power_save off |
| 升级内核后失效 | dkms 未生效或未重建 | 执行sudo dkms autoinstall,或重新dkms build/install |
5.3 一个真实的排查案例:固件文件缺失
我记得有一次帮朋友装驱动,模块加载正常,iwconfig也能看到wlan0,但一执行扫描就卡住,没有任何结果。查看内核日志时发现一行很关键的报错:
rtl8812au: Firmware rtlwifi/rtl8812aefw.bin not available问题一下就清楚了:驱动本身编译成功,但它依赖的固件文件没有进到系统里。有两种解决办法,一是安装linux-firmware包,二是在联网机器上直接下载对应固件文件,拷贝到离线机器的/lib/firmware/rtlwifi/目录,然后重新加载模块。
我当时没有现成的 linux-firmware 包,就直接在联网机器上找到rtl8812aefw.bin这个文件,单独拷过去,放在/lib/firmware/rtlwifi/目录下,再执行:
sudo modprobe -r 8812au sudo modprobe 8812au再扫描一次,WiFi 列表就出来了。这个案例说明,驱动装完搜不到网络时,不要第一时间怀疑驱动编译有问题,先看 dmesg 的固件日志,很多时候问题只差一个几十 KB 的固件文件。
6. 给后来者的一点实话
这套离线安装的流程我完整走过不止一遍,从第一次折腾一下午到后来只需要十分钟,最大的体会是:离线安装的核心不在于你会不会编译,而在于准备工作做得够不够完整。依赖包、固件、驱动源码、校验值,这些物料缺一不可。建议你在联网机器上就把所有东西放到同一个目录、打成压缩包,做好一套“离线驱动安装包”,以后重装系统直接拿出来用就行。
另外还有一个隐藏救急方案:如果手边有 Android 手机,用 USB 线连上电脑,开启手机的“USB 网络共享”,Ubuntu 20.04 一般能直接识别为一个有线网卡接口,利用这个临时网络你可以在线安装依赖,等于把离线问题转化成了有线网络问题。这个技巧在不少极端场景下比钻离线依赖方便得多。
最后再分享一个小习惯:升级内核之前,先跑一下dkms status,确认所有第三方驱动都是 installed 状态。升级完重启后如果发现网卡又不见了,第一件事就是回来跑sudo dkms autoinstall,大多数情况下模块会自动重建。这套组合拳打下来,Realtek 网卡在 Ubuntu 20.04 上基本就不会再闹脾气了。