警告
由于Windows vista之后,所有驱动必须签名才可以加载,因此本方案要求禁用Secure Boot!
如果你无法接受关闭Secure Boot,请等待微软官方提出解决方案
总所周知WSL2发布了有一段时间了,但WSL2仍然不支持USB,这意味着你没有办法在WSL2里面使用主机上的USB设备,例如arduino
等设备,这显得很不方便
不过最近我找到了一个通过USBIP
来使得WSL2“支持”USB的方法,当然也踩了很多坑。
首先声明,本文基本上根据的是https://github.com/rpasek/usbip-wsl2-instructions 上的方案,其中略微有些改动。
安装USBIP
这个嘛,没什么好说的,基本上去 https://github.com/cezanne/usbip-win/releases 这里下载最新的预编译好的文件,Assets
里面除了那俩 Source Code
之外全都下载就完事了。
下载好之后首先需要安装签名的证书,usbip_test.pfx
就是这个文件,直接双击进行导入,导入密码是usbip
. 需要将这个证书导入两次,导入到本地计算机的“受信任的根证书颁发机构”和“受信任的发布者”这两个地方。
注意
一定要导入到的是“本地计算机”!!!!!!!!!!!
然后就是启用测试签名了,开一个具有管理员权限的CMD或者PowerShell都行,运行bcdedit.exe /set TESTSIGNING ON
就好了。如果提示“该值受安全引导策略保护,无法进行修改或删除”,请翻到本文开头,仔细阅读红色提示框内的内容。
然后重启。。。如果你还开了BitLocker,那你一定要在重启前准备好恢复密钥,不然。。。
如果不知道恢复密钥,可以去http://account.microsoft.com/devices/recoverykey 这里查看。前提是你登录了微软账号。
编译内核模块
是的,由于微软默认提供的内核是不带USB模块的,因此需要自己编译一下内核的USB模块。
首先需要拉下WSL用的内核源码
git clone https://github.com/microsoft/WSL2-Linux-Kernel.git
这个仓库大概是有1.4GB左右,总之我挂代理拉了两次都失败了,最后只能先加一个 --depth=1
参数,先拉一部分源码下来,然后再执行一次 git pull --unshallow
把剩下的代码拉下来。
结果fetch完进去一看,似乎tag不是很全,少了4.19.104的tag,于是只能再git pull --tags
把所有的tag拉下来。
然后就是先看看当前用的是什么版本的内核,uname -r
看了下是4.19.104-microsoft-standard
的版本,于是就先checkout到这个tag上。
其实这个时候本来想,都重新编译内核了,不如干脆改成5.x算了,不过看了下5.x的没有microsoft
的标签,担心不兼容就没测试。
然后就可以修改下配置文件进行编译了,一开始我是把 /proc/config.gz
复制过来,然后在这个基础上修改再编译的,结果编译了四五次,生成的内核都没办法启动,内核模块也没法使用。最后发现似乎再Microsoft目录下已经有一份配置文件了。于是就改用这个配置文件了。
make KCONFIG_CONFIG=Microsoft/config-wsl CC=clang LD=ld.lld menuconfig
这里先用KCONFIG_CONFIG
指定使用这个配置文件,然后我还顺便改用clang
去编译了,至于为什么,因为我之前全部都是用 gcc-10 编译的,全都没办法启动。当然当时用的是 /proc/config.gz
的配置文件,暂时没办法测试到底是编译器还是配置文件的问题。
这里主要需要修改的配置文件是 Device Drivers
里面的,在里面找到USB support
,先按空格启用,然后再按回车进入里面。空格勾选Support for Host-side USB
。然后将子选项里面自己需要用的东西进行勾选。
这里我选了大概有
- xHCI HCD support
- EHCI HCD support
- OHCI HCD support
- USB Mass Storage support
- USB/IP support
- USB/IP support => VHCI hcd
剩下的可以根据自己需要进行勾选。
然后我还改了一个地方,是内核的版本号,是最上层的配置中,General setup
里面的Local version
。我给他原来的后面加了一个-usb
的后缀。其实这个地方我本来是不打算修改的,但是当我没有修改的时候,编译出来的内核可以启动,但是内核模块没法加载,会提示找不到一个符号,然而那个符号在 System.map 里面是可以找到的,十分奇怪。总之改了个版本号之后就莫名其妙的可以了。这部分大概过几天有时间的时候再重新进行测试。
然后就是编译了
注意
记得保存!!!!!!!!
make LOCALVERSION= KCONFIG_CONFIG=Microsoft/config-wsl CC=clang LD=ld.lld -j4 all
这里还顺便加了个 LOCALVERSION
,不加的话编译出来的版本号后面会多一个“+”号,觉得无所谓的可以不加。至于-j
参数,自己根据性能改改。总之我8G的情况下只能-j4
左右了,多了会直接炸内存。。。
差不多我Surface pro 7
编译了十五分钟左右。
有坑!!!
编译到最后链接内核的时候,遇到了重复定义的问题,是一个叫做 __force_order
的变量的重复定义,在arch/x86/boot/compressed/kaslr_64.c
和 arch/x86/boot/compressed/pgtable_64.c
这两个文件里面都有,然而我翻了一下 https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git 这里,kaslr_64.c
里面是没有这个变量的,于是我就干脆也把这个文件里面的删掉了。暂时没发现有什么异常。。。
安装和配置内核
编译完成之后首先安装下内核模块
make modules_install
就好了,模块会被放到 /lib/modules/
里面,只需要检查下里面有我们需要的模块就好了。
然后是内核的部分,默认的情况下,WSL自带的内核没有启用模块兼容的,总之我的测试结果是,即使使用了和WSL一样的版本的源码编译出来的,也是没办法加载我们的模块的,于是还得顺便把内核也得换一个。
没错,WSL2的内核是可以自己换的,找到 arch/x86/boot/bzImage
,这就是编译好的内核,把他复制的Windows上的任意一个目录,这里我就直接放我用户目录里面创了个 kernel
了,也就是路径是C:\Users\<xxx>\kernel\bzImage
这里。
然后接着在用户目录里面创建一个文件,文件名是.wslconfig
,内容是
[wsl2]
kernel=c:\\Users\\<xxx>\\kernel\\bzImage
具体路径自己改下哦。
然后先关闭当前的WSL实例
wsl --shutdown
接着再开启WSL之后,用uname -r
看看内核是不是正确的(废话,不正确就不会启动了
这个适合需要测试下刚刚的内核模块能不能用,按照我自己的配置来说的话
modprobe hid-generic
modprobe hid
modprobe usbhid
modprobe usbcore
modprobe usb-common
modprobe usb-storage
modprobe usbip-core
modprobe vhci-hcd
大概就是这个几个模块,没有报错就行了,顺便还可以dmesg
看看输出。这个时候用lsusb
就应该可以看到默认的Linux Foundation root hub
设备了。
提示
上面的一堆modprobe
可以放到一个sh
脚本里面,然后加入到比如 zshrc
之类的地方启动的时候运行。
USBIP 客户端的安装
还是刚刚内核源码里面,进入到 tools/usb/usbip
目录里面,这里面是USBIP的源码,还得编译安装这个。
./autogen.sh
./configure.sh
make -j
基本上就是这三个步骤。
不过我在编译的时候其实是报错了的,首先是一个
usbip_device_driver.c:108:2: error: ‘strncpy’ specified bound 256 equals destination size [-Werror=stringop-truncation]
这么一个错误,懒得处理了,直接
sed 's/-Werror//g' -i Makefile
sed 's/-Werror//g' -i src/Makefile
sed 's/-Werror//g' -i libsrc/Makefile
把-Werror
去掉算了
然后还遇到一个链接时期的错误,基本上是一个 udev_context
的重复定义的问题,大概看了下代码,个人认为libsrc/usbip_host_common.c
文件里面的第38行的定义可以改成 static
作用域的,也就是在struct udev *udev_context;
前面加一个 static
就好。
改完编译完成,然后make install
就好。
Windows上的设置
在Windows上主要是USBIP的设置。首先找到之前下载的一堆usbip的文件。运行一下
usbip.exe list -l
查看下当前的设备,基本上输出就是
- busid 1-63 (045e:09c0)
Microsoft Corp. : unknown product (045e:09c0)
- busid 1-18 (045e:0306)
Microsoft Corp. : unknown product (045e:0306)
- busid 1-68 (046d:c084)
Logitech, Inc. : unknown product (046d:c084)
- busid 1-86 (8087:0026)
Intel Corp. : unknown product (8087:0026)
这样,记录下需要转发的设备,比如那个鼠标,是1-68
,然后运行一下
usbip.exe bind -b 1-68
然后启动服务端
usbipd.exe -d
顺手启用下debug输出。默认会在IPv4和IPv6上都监听,而且是监听的0.0.0.0
,但其实我觉得没必要,127.0.0.1
就好了。
注意
如果你看到了类似
usbip: error: get_device_path: traverse_intfdevs failed, returned: 0
usbip: error: open_stub_dev: invalid devno: 194
usbip: error: export_device: cannot open devno: 194
usbip: error: failed to export device: 1-194, err:-1
的错误,请翻到本文开头仔细阅读红色框内的文字!
WSL内的设置
WSL内的话是启动的客户端
usbip attach -r <ip> -b 1-68
这里换成自己的IP,至于怎么看IP,Windows上用 ipconfig
看看,找vEthernet (WSL)
的网卡上的IP就是了。-b
后面就是在上一步bind的ID,是可以绑定多个的。
这里就是另一个巨坑了,也不知道为什么,如果Windows上监听的是127.0.0.1
的话,这里是不会出现问题的。然而如果监听的是0.0.0.0
的话,WSL访问主机的包就会被防火墙拦截下来。。。
而且由于vEthernet (WSL)
是放在公共网络里面的,于是你在防火墙设置里面看到公共网络里面是没有活动连接的,当时我就只关了私有网络的防火墙(因为当前的网络配置的确是私有的),结果测试了很久也不行,最后干脆把公共的也关了,才发现可以。。。
其实这个是可以在防火墙里面加个规则放行下的,不过这个不在本文的讨论当中。
然后这个时候重新用lsusb
看一下,就可以看到刚刚转发的那个USB设备了。
大概就是这样。当然智能卡的话,还得顺便先启动下 pcscd
这个进程。
总结
全是坑,浪费我一个下午+一个晚上,建议大家不要轻易尝试。
🐂🍺!
我也是公网没关搞了半天!
编译出错了,查了一下错误也不知道是什么问题
cryptsetup: ERROR: Couldn’t resolve device rootfs
cryptsetup: WARNING: Couldn’t determine root device
这种方式弄完之后是不是 在wsl2里连接了usb,那在windows下就没办法用了?就像vmware一样是一个系统独占的?
是的,等于是独占的,要返回Windows使用需要unbind掉这个设备