TL;DR
重新编译 WSL 2 使用的 Linux 内核,在其中加入 USB 存储设备支持即可。
最近有个关系蛮不错的同学说想借走我的树莓派打电赛用,想了想反正树莓派放在自己这儿也是吃灰,就干脆借出去了。
当然,借出去之前我肯定是要把 SD 卡上的东西先自己备份一遍的。不过我懒得手动整理文件再逐个打包备份,就想直接用 Linux 的 dd
工具直接把整张 SD 卡读出来,到时候恢复起来也省事,直接整个镜像写进去就完事,系统都不用重新刷。然而 Windows 上没有 dd
(其实是有的移植版的,我到后来才发现…),用虚拟机又嫌太麻烦,就干脆用了 WSL。
从 WSL 2 开始,WSL 的实现方式从 WSL 1 的 API 转译换成了基于 Hyper-V 的虚拟机。这能带来更接近原生 Linux 的用户体验,比如可以用 NVIDIA 显卡的 CUDA 核心来训练人工智能模型、可以运行 Docker(Windows 上的 Docker 的本质还是在虚拟机里跑 Linux 再跑容器…)、可以实现图形化界面(WSLg),但也带来了 WSL 1 所没有的一些问题,比如硬件透传:虚拟机的硬件环境都是宿主机虚拟化出来的,一般情况下宿主机上的硬件在虚拟机中是看不到的。
个人观点:WSL 1 还是 WSL 2?
对我来说,WSL 的意义在于 Linux 环境和 Windows 环境的互操作性。本质上,我使用 WSL,只是希望我能够在享受 Windows 生态的便利性的同时,也能拥有我熟悉的在 Linux 环境下做开发的那一套的便利性(大人我全都要 XD)。所以对我来说,WSL 1 和 WSL 2 的区别并不大,如果没有什么特殊需求的话,这两个我都是能用下去的。但是,既然 WSL 2 能提供更完整的 Linux 体验,我为什么要闲着蛋疼去用一个转译来的兼容层呢?
我的电脑内建的读卡器是走 USB 的。因为 WSL 2 的本质是虚拟机,我的读卡器在 WSL 2 内是不可见的。虽然微软官方在 《比较 WSL 1 和 WSL 2》 这篇文档和 WSL 的 FAQ 中提到,WSL 2 目前还不支持 USB 设备,但在 《连接 USB 设备》 这篇文档中,微软官方也提到了,可以使用 usbipd-win 这个项目来连接 USB 设备到 WSL。(其实这篇文档在我开始尝试在 WSL 2 中连接 USB 存储设备的半个月前才发布,所以…我其实算是第一批吃螃蟹的几个人之一?)
usbipd-win 是 Windows 上的一个对 USB/IP 协议的实现,而 USB/IP 是用来将 USB 封装成 TCP/IP,然后通过网络传输的一个项目,理论上有网的地方就能用。有很多人尝试过使用树莓派等设备配合 USB/IP 将有线键鼠转为无线键鼠这种操作(不过这么做的操作延迟不会很大吗…),也有大佬尝试过通过 USB/IP 将 USB 设备透传入 WSL 2 的(链接放在这篇博文底部的「参考链接」中了),不过彼时微软尚未在 WSL 使用的 Linux 内核中加入 USB/IP 所需的 VHCI 控制器支持,想这么做还得手动编译内核才行。然而,微软已经在 e445d061c3989a6990180873efc74520b385ca52
这个提交中加入了 VHCI 控制器的支持,文档也发出来了,也就意味着我们可以直接使用 USB/IP 在 WSL 2 中连接 USB 设备了。
但是,就在我兴冲冲地根据微软官方的文档将读卡器透传入 WSL 2 后,我却在 /dev
找不到读卡器的设备文件(device file),而 lsusb
和 dmesg
打印出的内容又显示 WSL 认到了我的读卡器,这属实令我迷惑。我询问了几位日常使用 Linux 的朋友,都表示没有头绪。我甚至怀疑是不是微软没有在 WSL 2 的 Linux 内核中加入 USB 存储设备的驱动,没想到这居然让我蒙对了——搜寻相关资料的时候,看到了前文提到的手动编译内核以支持 USB/IP 的大佬的博文,其中提到了 USB 存储设备的支持是可以在内核配置文件中启用和禁用的。果然,在 WSL 2 的 Linux 内核配置文件 中,我看到了:
1 | # CONFIG_USB_STORAGE is not set |
好嘛!不愧是你,巨硬,还是这种「什么都能做但又什么都做不好」的风格。
这样我们的解决方案也就很明确了:再手动编译一个 Linux 内核,启用 USB 存储设备的支持。(怎么觉得比我直接用虚拟机还麻烦…不过既然已经到了这步了,这条路就继续走下去吧)
确保 WSL 中安装好了 build-essential
、flex
、bison
、libssl-dev
、libelf-dev
、dwarves
这几个包,然后从 WSL 2 的 Linux 内核的仓库 clone 一份最新的内核源码,执行:
1 | 注意要指定 KCONFIG_CONFIG 参数 |
来用 menuconfig 这种在 CLI 里画 GUI 的工具来编辑内核配置文件(头铁的技术力比较高的大佬也可以手动编辑位于 Microsoft/config-wsl
的内核配置文件),进入 Device Drivers
-> USB support
-> Support for Host-side USB
,选中 USB Mass Storage support
( *
号是直接编译进内核,M
是编译为内核模块,内核模块需要手动加载),把下面弹出来的一堆驱动都选上;其它有需要支持的内容也可以一并选上(我这里是读卡器读的 SD 卡,所以下面再选一个 MMC/SD 卡支持),如果你有这个兴致的话,还可以在 General setup
-> Local version
中自定义你的内核版本号的后缀。保存退出,然后开始编译内核:
1 | 编译并生成压缩后的内核,我比较懒,就直接把这些驱动全编译进内核了 |
把编译好的内核复制出来,放在 Windows 的任意路径下,然后在 Windows 的用户目录(默认是 C:\Users\{username}
)下创建一个名为 .wslconfig
的文件,内容根据 微软官方文档 来:
1 | [wsl2] |
请注意这里的内核路径里的反斜杠要转义(双反斜杠)!!!否则 WSL 2 是读不到指定的内核的,而且在这种情况下 WSL 2 非但不会报错,还会直接以默认内核启动!!!这个问题坑了我好久,我一度认为是我编译出来的内核有问题呜呜呜
在 Windows 的命令行中执行 wsl --shutdown
关闭 WSL 2 虚拟机,然后再启动 WSL。如果你有修改内核版本号的后缀的话,这时执行 uname -r
,应该能看到打印出来的内核版本号有变化。这时再通过 usbipd-win 连接读卡器,成了!
(假装这里有图)
后记:我终究还是没有成功备份我的 SD 卡
不知道为什么,我的 SD 卡在 dd
读取数据时,每读取大约 400MiB 的数据就会报一次 I/O Error,再插入树莓派中好像也没法开机。本来以为是 SD 卡坏了,但是还是不死心,想着反正里面也没什么重要数据,干脆死马当活马医,直接重新刷写系统算了。没想到写入的过程很顺利,没有报错,在重新刷入系统之后再使用 dd
读取,连续读了 20G 也没什么问题,就很迷惑…
参考链接
Hyper-V 和 WSL 2 的 USB 直通 - Yadomin的博客 -
How to get USB devices working under Linux
鸟哥的 Linux 私房菜——第 24 章:LInux 核心编译与管理
Accessing USB storage devices in WSL 2? · Issues #7770 · microsoft/WSL