1. 为什么 WSL2 的 USB 这么难搞:架构差异是关键
1.1 先搞清楚 WSL1 和 WSL2 的本质区别
很多朋友第一次在 WSL 里插 U 盘、接安卓手机,发现 lsusb 下面空空如也,第一反应是"驱动没装"。其实压根不是驱动的问题,而是 WSL2 和 WSL1 的系统架构完全不同。
WSL1 是通过系统调用翻译层把 Linux 调用映射到 Windows 内核上运行的,它和 Windows 共享了同一套设备驱动栈,所以你在 WSL1 里直接访问 /dev/ttyUSB0、/dev/sda 这类设备节点时,底层走的就是 Windows 的 USB 驱动栈。换句话说,WSL1 里能看到 USB 设备不是因为它自己有驱动,而是它"借"了 Windows 的设备视图。
WSL2 则是一个真正的轻量级虚拟机,基于 Hyper-V 虚拟化平台,跑的是独立的 Linux 内核。既然是虚拟机,它就只具备虚拟化出来的虚拟设备(虚拟网卡、虚拟磁盘、虚拟串口等),宿主机上插着的 USB 设备在虚拟机内部是物理隔离的,Linux 内核根本看不到这些设备的总线信息。这就是为什么网上铺天盖地的教程都在说"WSL2 不支持原生 USB",这个说法在 2021 年之前确实成立。
1.2 那为什么市面上最终选择了 usbipd-win 这个方案
既然 WSL2 是虚拟机,最朴素的想法就是给虚拟机添加 USB 控制器,像 VMware、VirtualBox 那样把设备直通进去。但 WSL2 的管理界面非常精简,微软没有暴露类似 VM 设置那样的图形化面板,要改 WSL2 的虚拟化配置只能通过 .wslconfig 文件,而微软官方一直没有开放 USB 直通配置项。
所以社区里开始摸索不同的路子,有打算用网络转发把串口数据包转发进 WSL2 的,也有尝试用 socat 做 TCP 桥接的,但这些都是应用层方案,没办法覆盖通用 USB 设备。
最终被大多数人接受的是基于 USB/IP 协议的 usbipd-win。它的核心思路很简单:在 Windows 侧把 USB 设备"导出"成网络资源,在 WSL2 内部通过 Linux 内核自带的 usbip 客户端去远程"导入"这个设备。这样对 WSL2 里的 Linux 内核来说,那个设备就像插在本机 USB 总线上一样,节点会出现在 /dev/bus/usb/ 下,设备文件也能正常生成。
我最早用这个方案的时候也有点担心,毕竟要经过一层网络协议转发,延迟和兼容性会不会有问题。实际用下来的结论是:对于绝大多数设备(串口、U盘、ADB、网卡),usbipd-win 的表现都相当稳定,而且微软官方已经在 Windows 11 的文档里把它列为推荐的 WSL2 USB 访问方案,这基本说明这条路走对了。
1.3 在开始之前你需要知道的边界
在动手之前,有几个边界条件必须说清楚,不然装到一半发现不行会非常尴尬:
usbipd-win要求 Windows 10 21H2 或更高版本,Windows 11 全版本都支持。老版本 Windows 10 上运行会遇到内核组件缺失的问题。- WSL2 内核需要在 5.10.60 或更高版本,因为早期的 WSL2 默认内核没有启用 usbip 相关的内核模块。好在现在通过
wsl --update更新的内核都自带支持。 - 不是所有 USB 设备都能正常透传。USB 声卡、需要特定 Windows 驱动的加密狗、以及某些实时性要求极高的采集设备,透传后可能不稳定,这点后面展开讲。
理解了这些背景,接下来安装和配置的时候就不会再发牢骚了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备:先把 WSL2 和 usbipd-win 装到位
2.1 确认 Windows 版本和 WSL2 状态
第一步不是急着下载工具,而是确认自己的环境到底处于什么状态。
在 Windows PowerShell 或者 CMD 里依次执行:
bash复制winver
弹出来的窗口里查看版本号是否不低于 21H2。
接下来确认 WSL 版本:
bash复制wsl --status
如果里面显示的默认版本是 V1,那要先转成 V2;如果没有安装任何发行版,执行:
bash复制wsl --install -d Ubuntu-22.04
这一步会把 WSL 功能组件、虚拟机平台、Linux 内核更新包全部搞定。安装完重启一次系统,然后打开 Ubuntu 终端确认 uname -r 的内核版本号。
如果内核版本低于 5.10.60,手动去 WSL 官方 GitHub 仓库下载最新的内核安装包执行即可。在较新的系统上也可以直接运行:
bash复制wsl --update
升级完内核之后,Ubuntu 里执行 lsusb,如果提示找不到命令,先装一下:
bash复制sudo apt update && sudo apt install -y usbutils
这时候 lsusb 输出里应该只有虚拟机默认的虚拟设备,这在后面配置完 usbipd 之后会发生变化,记住此刻的输出内容,后面可以对比。
2.2 安装 usbipd-win
usbipd-win 的安装方式有两种,任选其一:
方式一:winget 命令安装
bash复制winget install usbipd
winget 会自动处理好环境变量。
方式二:MSI 安装包手动安装
到 GitHub Releases 页面下载最新的 .msi 文件。这个安装包对管理员权限有要求,右键以管理员身份运行即可。装完之后,它会在 Windows 服务里注册一个 usbipd 服务,默认开机自启。
安装完成后,重新打开一个管理员 PowerShell,运行:
bash复制usbipd list
这个命令会列出当前 Windows 上所有 USB 设备的总线 ID、名称和状态:
text复制BUSID VID:PID DEVICE STATE
1-1 046d:c52b USB Input Device Not shared
1-3 0bda:8153 Realtek USB FE Family Controller Not shared
2-4 8087:0029 Intel Bluetooth Not shared
看到这个输出,说明 usbipd-win 工作正常。
2.3 WSL2 内部安装 usbip 客户端工具
在 Ubuntu 终端里执行:
bash复制sudo apt install -y linux-tools-generic
安装完成后,usbip 命令不一定在 PATH 里,需要手动找到它。不同内核版本对应的工具名有点差异,常见的查找方法是:
bash复制ls /usr/lib/linux-tools/*/usbip
找到路径后,可以做一个软链接方便调用:
bash复制sudo ln -s /usr/lib/linux-tools/$(uname -r | cut -d'-' -f1 2>/dev/null || echo '*')/usbip /usr/local/bin/usbip
不过不同系统版本里这个路径可能不太一样,更稳妥的做法是直接忘掉软链接,每次用完整路径调用,或者把实际存在的路径记下来。我在 Ubuntu 22.04 上实测,linux-tools-generic 装完后,usbip 工具会出现在 /usr/lib/linux-tools/5.15.0-xxxx-generic/usbip 这种带具体内核版本号的目录里。建议先跑一下 find /usr/lib/linux-tools -name usbip 找路径,再决定怎么链接。
还有个关键点:WSL2 内核需要加载 vhci-hcd 模块。执行:
bash复制modprobe vhci-hcd
如果提示 Module not found,说明内核版本太老或者缺少对应模块,检查一下是否执行过 wsl --update。加载成功后在 dmesg 里能看到 USB/IP 相关的日志。
3. 核心实操:共享、附加、分离的完整流程
3.1 在 Windows 侧共享 USB 设备
找到设备在 usbipd list 中的 BUSID,然后在管理员 PowerShell 中执行:
bash复制usbipd bind --busid 1-3
bind 的含义是把这个设备的绑定关系注册到 usbipd 服务上,让它可以被"导出"。执行完之后再看 usbipd list,那个设备的 STATE 会变成 Shared。
这里有一个非常容易踩的坑:bind 之后设备并不是立刻出现在 Windows 设备管理器里的普通设备列表之外,它仍然在正常工作,只是同时开放了对外共享的能力。需要留意的是,如果你绑定的是系统独占的设备(比如内置摄像头、蓝牙适配器),绑定后 Windows 那边可能出现短暂断连,这是正常现象,设备会自动重新枚举。
3.2 在 WSL2 中附加设备
回到 Ubuntu 终端,先看一下 WSL2 里能不能看到共享出来的设备:
bash复制usbip list -r 127.0.0.1
127.0.0.1 是因为 WSL2 和 Windows 共享了 localhost 网络。这条命令会列出 Windows 上所有处于 Shared 状态的设备。
然后附加设备:
bash复制sudo usbip attach -r 127.0.0.1 -b 2-4
这里的 2-4 是设备在 Windows 里的 BUSID。执行成功后,立刻在 WSL2 里执行 lsusb,你会看到设备出现在了 Linux 的 USB 总线列表中。
再验证一下设备节点:
bash复制ls -l /dev/bus/usb/002/
如果之前那个设备是 USB 转串口模块,此时 /dev/ttyUSB0 就应该出现了。
从原理上讲,这一步其实完成了两件事:
- Windows 侧通过 usbipd 将 USB 请求封装成 TCP/IP 包发送给 WSL2 的 vhci-hcd 虚拟主机控制器。
- WSL2 内核把收到的请求解包后重新构造成一个 USB 设备挂到虚拟 USB 总线上。
所以你在 WSL2 里看到的设备,对 Linux 内核来说是真实存在的设备,只是物理传输路径绕了一圈。
3.3 附加后的设备访问权限
很多时候 lsusb 能看到设备,但访问 /dev/ttyUSB0 时提示 Permission denied。这不是 usbip 的问题,而是 Linux 下的普通权限问题。当前用户需要加入 dialout 组:
bash复制sudo usermod -aG dialout $USER
类似地,访问 U 盘时如果遇到挂载权限问题,可以先用 lsblk 查看设备名,再手动挂载:
bash复制sudo mkdir -p /mnt/usb
sudo mount /dev/sdb1 /mnt/usb
WSL2 默认没有自动挂载 USB 存储设备的能力,这里需要手动处理。我也见过有人尝试在 /etc/fstab 里配置自动挂载 USB,但实测下来非常不妥——因为 usbip 附加的设备在重启和热插拔之后设备路径会变化,fstab 写死路径很容易导致开机挂载失败,反而拖慢终端启动。
3.4 分离设备与解除共享
用完设备之后,正确顺序是先在 WSL2 里分离,再在 Windows 侧解除共享。
在 WSL2 里执行:
bash复制sudo usbip detach --port=<端口号>
端口号不是 BUSID,而是 attach 时分配的虚拟端口。如果不记得端口号,可以通过 usbip port 查看当前所有已附加的设备:
bash复制usbip port
输出中会显示类似 Port 02: <Port in Use> 的信息,同时列出对应设备。分离后再到管理员 PowerShell 里解除绑定:
bash复制usbipd unbind --busid 2-4
到这里,整个生命周期就完整了。有一点必须强调:如果在 WSL2 里没有执行 detach,直接关掉 Ubuntu 终端或者重启 Windows,usbipd 会在设备层面保持 attach 状态,下次启动 WSL2 时可能出现设备看不到,需要重新 detach 再 attach 的情况,这是我踩过的真实教训。
4. 我在真实使用中遇到的坑和处理方法
4.1 设备描述符请求失败的设备能不能透传
热词列表里有非常高频的一条:"未知 USB 设备(设备描述符请求失败)"。这种设备在 Windows 设备管理器里显示黄色感叹号,在 usbipd list 里也会出现,但很多人在 attach 时会发现 WSL2 里 lsusb 看不到,或者看到的是没有任何厂商信息的空设备。
原因在于 USB/IP 协议在传输过程中要复现设备描述符,如果 Windows 侧驱动栈已经无法完整读取设备描述符,那么导出的信息就是残缺的。这种情况下透传基本没戏,就算强行 attach 成功,Linux 内核也无法识别设备类型。
解决思路很简单:先在 Windows 侧修复设备描述符读取问题。常见原因是静电积累或者供电不足,把设备拔下来,按住电源键放电,或者换一个 USB 口重新插,很多时候设备描述符就能恢复。如果恢复不了,说明设备本身硬件有问题,换线换口换设备,别指望 WSL2 能魔法般解决 Windows 都救不回来的硬件故障。
4.2 附加后 WSL2 风扇狂转或者"尚未准备就绪"
不知道你有没有遇到这种情况:在 WSL2 里 attach 一个 USB 设备,比如一张千兆网卡,结果整个电脑的风扇开始狂转,然后 WSL2 终端卡住,过一段时间提示 "WSL2 尚未准备就绪"。
这个问题的根源一般不是 usbipd,而是设备中断风暴。部分 USB 网卡在透传之后会产生大量中断请求,WSL2 的虚拟化层处理不过来,最终导致整个虚拟机卡死。
我的处理经验是:
- 优先使用 USB 3.0 口而不是 USB 2.0 口,带宽更大,中断频率相对低一些。
- 如果是 USB 网卡,可以在 Windows 设备管理器里把"允许计算机关闭此设备以节约电源"关掉。
- 实在不行,考虑把设备插在 USB 3.0 Hub 上再透传,实测某些设备经过 Hub 之后中断行为会温和很多。
另外补充一个容易被忽略的点:WSL2 默认使用的内存和 CPU 资源是有限的,通过 .wslconfig 可以调整:
ini复制[wsl2]
memory=8GB
processors=4
但这只是缓解,不是解决中断问题的根本手段。设备本身的问题优先从设备侧解决。
4.3 为什么部分 USB 设备在 WSL2 里识别为 usb-storage 但挂载乱码
U 盘透传成功之后,lsblk 能看到 sdb 之类的设备,但 mount 的时候报错或者显示乱码,这通常是文件系统类型和编码的问题。
对于 FAT32/NTFS 格式的 U 盘,挂载时用:
bash复制sudo mount -t drvfs /dev/sdb1 /mnt/usb
等一下,这个方法在 WSL2 里不适用。WSL2 是真正的 Linux 内核,不能直接用 drvfs 挂载裸设备。正确的方式是指定文件系统类型:
bash复制sudo mount -t vfat -o codepage=936,iocharset=utf8 /dev/sdb1 /mnt/usb
如果是 exFAT,先确认内核是否支持:
bash复制sudo apt install -y exfat-fuse exfatprogs
然后用:
bash复制sudo mount -t exfat /dev/sdb1 /mnt/usb
NTFS 则需要 ntfs-3g:
bash复制sudo apt install -y ntfs-3g
sudo mount -t ntfs3 /dev/sdb1 /mnt/usb
注意,内核版本较老的 WSL2 可能不支持 ntfs3 驱动,需要回退到 ntfs-3g。这部分细节如果不注意,很容易在"识别了设备但打不开"这个环节卡很久。
4.4 热插拔场景下的设备状态管理
这里分享一个真实工作流中的痛处:在 WSL2 里用 usbipd 附加 USB 转串口设备调试嵌入式板卡时,调试过程中经常需要拔掉 USB 线重新插。如果你没有提前 detach,Windows 侧重新插拔后 USB 设备的 BUSID 可能变化,原来 attach 的会话就断了,WSL2 里的 /dev/ttyUSB0 消失。
正确的处理流程是:
- 先
usbip detach --port=<端口号> - 拔掉设备,重新插上
- 在 Windows 侧重新查
usbipd list确认新的 BUSID - 再
usbip attach
如果你嫌每次手动 detach 太麻烦,我目前的经验是直接写成一个小脚本:
bash复制#!/bin/bash
# usbip_reattach.sh
sudo usbip port
但这只是查看,真正一键重连还是需要手动确认 BUSID。usbipd-win 本身没有提供基于 VID:PID 的自动 attach 机制,这个限制目前没有完美的自动化方案,至少在 2024 年的版本里还是没有的。
5. 典型场景实操:ADB 调试、串口、网卡与抓包
5.1 在 WSL2 里用 ADB 连接安卓手机
这是很多人入坑 WSL2 USB 的第一个场景。把手机开启 USB 调试模式,插上数据线,在 Windows 的管理员 PowerShell 里 usbipd list 找到手机对应的设备。
注意:有些安卓手机在 Windows 上会枚举出多个 USB 接口(ADB 接口、MTP 接口、RNDIS 网络接口),设备管理器中通常显示为两三个独立的设备。你需要把 ADB Interface 那一个设备 bind 并 attach,不要贪多,全部 bind 进去反而可能让 WSL2 里出现多个设备节点,adb 不知道该连哪个。
在 WSL2 里执行(假设已经安装 adb):
bash复制adb kill-server
adb devices
如果一切正常,你应该能看到设备的序列号,状态是 device,而不是 unauthorized 或者 offline。
最开始的坑经常出现在权限上:adb devices 显示 unauthorized,这是因为 Windows 侧的 USB 描述符在 attach 之后被重新枚举过一次,Android 系统认为这是一台新的宿主机,需要在手机屏幕上点击"允许 USB 调试"弹窗。点完之后基本就稳定了。
5.2 USB 转串口模块(CH340/FT232/CP2102)在 WSL2 里的表现
串口设备是嵌入式开发中最常见的 USB 设备。我测试过 CH340、FT232RL、CP2102 三款主流芯片,在 WSL2 里的表现如下:
| 芯片 | WSL2 内核模块 | 设备节点 | 稳定性 |
|---|---|---|---|
| CH340 | ch341 | /dev/ttyUSB0 | 稳定,偶见丢包 |
| FT232RL | ftdi_sio | /dev/ttyUSB0 | 很稳定 |
| CP2102 | cp210x | /dev/ttyUSB0 | 很稳定 |
之前有人问我为什么 CH340 偶尔丢包,后来排查发现是我用的 USB 线质量太差,供电不稳导致。换了根粗线之后问题彻底消失。
使用串口时建议先用 dmesg | tail -20 看看内核有没有加载对应驱动,再用 minicom 或者 screen 连接:
bash复制sudo screen /dev/ttyUSB0 115200
在 WSL2 里运行串口工具访问这个设备时,和原生 Linux 相比几乎没有区别,这点可以放心。
5.3 USB 无线网卡和千兆网卡透传的注意事项
WSL2 本身有虚拟网卡,为什么还需要透传 USB 网卡?常见原因是需要抓包、需要接入特定 VLAN、或者 WSL2 里跑某些需要物理网卡特性的应用。
透传 USB 网卡后,WSL2 里会出现 wlan0 或者 eth1 之类的接口。在 /etc/netplan 或者用 ip 命令配置 IP 即可。但需要注意一个细节:USB 网卡透传后可能没有 WLAN 的 Regulatory 信息,因为 WSL2 内核默认的 wireless 配置有限,某些网卡会提示 cfg80211: Calling CRDA to update world regulatory domain,导致 5GHz 频段无法使用。
我的建议是:如果只是普通的有线网络访问,WSL2 自带的虚拟网卡已经够用;如果你需要抓包或者接入特殊网络,再考虑透传 USB 网卡。
5.4 USB 抓包与 WSL2 的边界
热词里有 "usb抓包",想表达的场景大概率是在 Linux 下用 Wireshark 配合 usbmon 抓 USB 流量。在 WSL2 里 lsusb -t 能看到设备树,但 usbmon 模块在 WSL2 内核里默认不可用,所以直接抓包是不行的。
在 WSL2 里做 USB 抓包,需要用到前面提供的 usbip 透传思路,但 usbip 只是把设备接到虚拟总线上,虚拟总线上依然没有 usbmon 支持。这种情况下,更实际的做法有两种:
- 在 Windows 侧用 Wireshark 的 USBPcap 工具抓包。
- 在 WSL2 里跑一个用户态的 USB 分析程序,比如用 pyusb 直接和设备通信并记录交互数据。
pyusb 访问 WSL2 中透传后的 USB 设备是可行的,因为设备节点已经存在,PyUSB 通过 libusb 访问 /dev/bus/usb 下的设备即可。这是热词里 "pyusb 访问 usb 驱动" 的一个典型场景。装依赖:
bash复制sudo apt install -y libusb-1.0-0-dev
pip install pyusb
然后在 Python 里加载设备。注意 pyusb 默认使用内置的 backend,需要明确指定 libusb1。这块是另一个话题,这里不展开,但可以确定在 WSL2 里是能跑的。
5.5 关于"WSL2 安装 CUDA"与 USB 的误区
热词里出现了 "wsl2安装cuda",这里必须澄清一点:WSL2 的 CUDA 支持走的是 GPU 直通方案,和 USB 透传没有关系。NVIDIA 官方为 WSL2 提供了专门的支持方式,通过 /dev/dxg 设备访问 Windows 侧的 GPU,这个机制和 usbipd-win 完全不在一个体系里。
但两者也有交集:如果你要在 WSL2 里跑 CUDA 相关的代码,同时又要连接 USB 设备做数据采集,比如 USB 摄像头、USB 采集卡,那么 usbipd-win 透传这些设备是可行的。GPU 计算和 USB 设备访问是两条独立通道,并行使用没有问题。前提是 Windows 侧已经安装了支持 WSL2 的 NVIDIA 驱动,这个需要在 Windows 里装,不是装进 WSL2 发行版的。
6. 根据个人实测经验给的几条建议
最后分享几点实操下来最有价值的心得。
usbipd list 显示设备处于 Attached 状态时,Windows 侧是无法正常使用这个设备的。如果需要 Windows 和 WSL2 同时访问同一个 USB 设备,目前做不到,usbip 的机制就是独占式的。这一点在团队协作时尤其重要,比如共享一台测试机器时,先确认别人是不是已经 attach 了设备。
usbipd-win 服务在 Windows 的"服务"管理器中显示为 usbipd,默认为自动启动。如果某天发现 usbipd list 总是报错,第一反应应该是去看看服务状态是不是被禁用或者停止。我遇到过 Windows 更新之后服务启动类型被重置的情况,重新把服务设为自动并启动即可。
权限问题的优先级高于一切。WSL2 里所有 usbip 操作都需要 root(attach/detach 至少需要 sudo),Windows 侧所有 usbipd 操作都需要管理员 PowerShell。不要试图用普通用户权限运行 usbipd bind,报错会提示拒绝访问,白白浪费几分钟。
最后一个小技巧:附加设备之后,如果要在多个终端窗口里同时访问同一个串口设备,建议只用其中一个终端实际打开设备节点,其他终端用 socat 做转发,否则会出现端口占用和读写冲突,这是 WSL2 下最容易忽略的"没问题但很奇怪"的问题之一。
