上周帮同事看一台开发机,C 盘只剩 3 个 G,红色警告。打开资源管理器一层层翻下去,元凶是一个叫ext4.vhdx的文件,八十多个 G,躺在他用户目录的AppData\Local\Packages底下。他一脸茫然:我就用 WSL 装了个 Ubuntu 跑 Python,怎么就吃掉一整块盘了?
这个问题我遇到过太多次。WSL 好用是真的好用,但它默认把所有发行版塞进 C 盘,装完就不管了;等你反应过来,.vscode-server、pip 缓存、conda 环境、Docker 镜像层层叠加,C 盘就废了。这篇就按我实际操作的顺序,把 WSL 从零装好、整体搬到 D 盘、设好默认登录用户、配好 root 密码和 sudo 权限这一整条链路讲透,顺带把 VSCode 联动、终端字体这些日常体验相关的东西也一起捋一遍。不管你是刚听说 WSL 的新手,还是用了两年但一直没敢迁移的老用户,都能照着抄。
1. 动手之前先算两笔账:版本选型和磁盘占用
1.1 WSL1 和 WSL2 不是同一个东西的两代,而是两套实现
很多教程一上来就说"装 WSL2,别用 WSL1",但不讲为什么。这个为什么很重要,因为它直接决定你后面会不会踩坑。
WSL1 是把 Linux 系统调用实时翻译成 Windows 系统调用,本质上是一个兼容层,没有一个真正的 Linux 内核。好处是它的文件系统是直接跑在 NTFS 上的,所以你访问/mnt/c/xxx的时候速度飞快,跟原生 Windows 程序读写同一个文件几乎没差别;内存占用也小。
WSL2 则是跑了一个轻量虚拟机,里面是一个微软编译的真实 Linux 内核。它的系统调用是完整的,Docker 能跑、systemd 能开、binwalk这类需要特殊内核能力的工具不会莫名其妙报错,兼容性基本等于一台真机。代价是两件事:跨文件系统访问变慢(访问/mnt/c要经过 9P 协议转发,IO 性能大概只有原生的十分之一到五分之一),以及内存会被虚拟机吃掉一块且不会自动归还。
我的判断标准很简单:
| 你的场景 | 建议 |
|---|---|
| 跑编译、跑 Docker、跑 CUDA、需要 systemd | WSL2,没得选 |
| 项目文件必须留在 Windows 盘、频繁跨系统读写 | 慎重,或者接受 WSL2 的跨盘慢 |
| 只是想要个 grep/sed/awk 工具集 | WSL1 其实够用 |
不过现实是现在装的时候默认就是 WSL2,而且 WSL1 已经停止功能更新,新项目直接 WSL2 就行。真正要注意的是虚拟化功能必须打开,WSL2 依赖它。任务管理器 → 性能 → CPU,右下角能看到"虚拟化:已启用"。如果显示已禁用,得进 BIOS 开 VT-x / AMD-V。另外如果你机器上同时还跑着老版本 VMware 或者 VirtualBox,可能会和 Hyper-V 平台抢占,表现为 WSL 启动就报WslRegisterDistribution failed with error: 0x80370102。这种情况要么升级虚拟化软件,要么在两者之间做取舍。
1.2 ext4.vhdx 为什么会一路膨胀到失控
WSL2 的每个发行版在 Windows 侧都对应一个虚拟磁盘文件ext4.vhdx。Store 版 Ubuntu 的默认位置在:
%LOCALAPPDATA%\Packages\CanonicalGroupLimited.Ubuntu22.04LTS_79rhkp1fndgsc\LocalState\ext4.vhdx中间那串_79rhkp1fndgsc是包家族名的哈希,不同发行版、不同版本都不一样,最省事的办法是直接在%LOCALAPPDATA%\Packages里搜ext4.vhdx。
关键问题是:这个文件是动态扩展但不自动收缩的。你在 Linux 里rm -rf掉一个 20G 的 conda 环境,Windows 侧的ext4.vhdx一个字都不会小。它只会在你写入新数据时继续变大。所以"我明明删了东西怎么盘还是满的"这种现象,十有八九就是这个原因。
解法有两条路。第一条是用wsl --manage <发行版名> --set-sparse true让它变成稀疏文件,或者用 diskpart 做 compact,把空洞挤出去。第二条、也是更彻底的方案,就是把整个发行版搬到别的盘,以后不会再碰 C 盘。这篇主要讲第二条,第一条放在第 3 章末尾一并说。
2. 安装路径:一条命令走通,以及走不通时的三种绕法
2.1wsl --install背后到底干了哪几件事
Windows 10 2004(内部版本 19041)及以上、Windows 11,用管理员 PowerShell 敲一条命令就够了:
wsl --install这条命令并不是魔法,它按顺序做了五件事:启用Microsoft-Windows-Subsystem-Linux和VirtualMachinePlatform两个可选功能组件;下载并安装 WSL2 的内核更新包;把 WSL 的默认版本号设为 2;从微软的分发渠道拉取默认发行版(现在是 Ubuntu);最后提示你重启。
想装指定版本就加-d:
wsl --install -d Ubuntu-22.04 wsl --list --online # 先看看有哪些可用 wsl --set-default-version 2 # 单独设置默认版本如果你是在一台已经有 WSL 的机器上折腾,或者--install因为某些原因走不通,那就手工开功能:
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart两条都执行完,重启。重启后单独装内核更新包(搜wsl_update_x64.msi),再wsl --set-default-version 2,然后从 Microsoft Store 搜 Ubuntu 点安装。这条路慢一点,但每一步都是可控的,出问题也好定位。
提示:Windows 10 长期服务版(LTSC)用户要注意版本。LTSC 2019 内核版本太低,不支持 WSL2,只能退到 WSL1;LTSC 2021(19044)是支持的,可以放心用。
2.2--list --online超时和--install报 403 的排查顺序
这两个报错在社区里出现频率极高,本质上是同一类问题:命令要去拉微软的发行版清单和安装包,网络这一环卡住了。我一般的排查顺序是这样的,从最轻的改动开始:
- 先看系统代理设置有没有残留。设置 → 网络和 Internet → 代理,如果这里还留着某个早就失效的代理地址,
wsl.exe会傻乎乎地往那个地址发请求,表现就是超时或者 403。把它关掉,重开一个终端再试,这一步解决过至少三次问题。 - 换个命令行的网络出口验证。如果你连手机热点能成功、连公司网络就失败,那说明是本地网络策略在拦,这时候直接走离线路线,别在在线重试上耗时间。
- 换 DNS。把网卡的 DNS 手动改成
8.8.8.8和114.114.114.114,然后ipconfig /flushdns清一下缓存。有些时候是域名解析被返回了错误结果,导致下载地址压根连不上。 - 拆开执行。
wsl --install其实可以拆成"开功能 → 装内核 → 装发行版"三步,如果前两步能过、只有第三步拉包失败,那就直接跳到离线导入。
在线安装最头疼的地方在于它不给你断点续传,包下到一半断了就得重来。所以只要失败超过两次,我建议直接放弃在线,走下面这条路。
2.3 离线导入 Ubuntu:最稳的一条路
离线方案的核心思路是:只要拿到一个 Linux 根文件系统的 tar 包,WSL 就能把它变成一个可用的发行版。Ubuntu 官方在cloud-images.ubuntu.com/wsl/下面长期提供 WSL 专用的 rootfs,文件名形如ubuntu-jammy-wsl-amd64-wsl.rootfs.tar.gz,几十兆到一两百兆,用普通下载工具也能拿下。
拿到包之后:
mkdir D:\WSL wsl --import Ubuntu D:\WSL\Ubuntu D:\WSL\ubuntu-jammy-wsl-amd64-wsl.rootfs.tar.gz --version 2--import三个参数依次是"发行版名字"、"虚拟磁盘存放目录"、"根文件系统包路径"。执行完立刻就能用:
wsl -d Ubuntu这条路的好处是:不依赖微软的在线分发通道、安装目录你说了算(天生就在 D 盘,省了后面迁移这一步)、tar 包可以留着当备份。坏处是导入进去的默认用户是 root,而且里面只有一个裸系统,没有预装的开发工具。这正好引出后面两章要解决的问题。
注意:从
docker export出来的容器 rootfs 也能用--import导进 WSL,但容器镜像往往缺/etc/passwd的完整配置、缺 init 系统,导进去能用但会比较别扭。如果是做开发环境,还是优先用官方 WSL rootfs。
2.4 装完必须验证的四条命令
很多人装完看终端能打开就以为成了,其实有几个信息必须要确认:
wsl -l -v # 列出所有发行版,重点看 VERSION 那一列是不是 2 wsl --status # 看默认发行版、默认版本、内核版本 wsl -d Ubuntu -- uname -a # 看内核,真正的 Linux 内核会带 microsoft-standard-WSL2 字样 wsl -d Ubuntu -- cat /etc/os-release # 确认发行版版本号wsl -l -v的输出里,STATE 列出现Running说明有实例在跑,Stopped是正常的空闲状态。如果 VERSION 列显示 1,用wsl --set-version Ubuntu 2转过去,转换过程会重新打包文件系统,几个 G 的话大概几分钟。
另外一个小坑:wsl --version这条命令只有从 Microsoft Store 安装的较新版本 WSL 才支持,老的内置版本敲下去会提示"无效命令行选项"。这不代表你装坏了,只是版本老。想要新特性可以用wsl --update升一下。
3. 把发行版整体搬到 D 盘:导出、注销、导入三连
3.1 迁移前必须确认的四件事
迁移这个操作本身不复杂,但它是"注销 + 重建"的过程,中间任何一步出岔子都可能丢数据。所以动手前先过一遍清单:
第一,目标分区空间够不够。整个过程会同时存在三份数据:原来的ext4.vhdx、导出的 tar 包、导入后新生成的ext4.vhdx。所以 D 盘至少要留出原体积的 2.5 倍。原发行版 30G,那就准备 80G 以上宽松点。
第二,先彻底停掉所有实例。wsl --shutdown。这一步不能省,因为如果还有进程在写文件,导出出来的包可能是不一致的状态。执行完可以再敲一次wsl -l -v,确认所有发行版都是 Stopped。
第三,记下发行版的准确名字。wsl -l -v里 NAME 那一列,大小写敏感,Ubuntu和ubuntu不是一回事。后面所有命令都要用这个名字,写错了就是"找不到指定的发行版"。
第四,先想清楚迁移后默认用户会不会变。这一点我踩过坑,放在 3.3 讲。
3.2 导出的具体命令和耗时预估
创建一个工作目录,然后导出:
mkdir D:\WSL wsl --shutdown wsl --export Ubuntu D:\WSL\ubuntu-backup.tar注意导出格式是.tar,不是.tar.gz,文件体积和原来差不多,不会压缩。如果你的 WSL 版本较新,也可以导出成 VHD 格式:
wsl --export Ubuntu D:\WSL\ubuntu-backup.vhdx --vhdVHD 格式的好处是能直接在 Windows 上双击挂载查看内容,想抢救文件的时候很方便;缺点是体积更大。日常迁移用 tar 就够了。
耗时方面,我实测的经验值:SSD 上 20G 的发行版大约 3 到 5 分钟,机械硬盘翻倍。导出过程中 PowerShell 是没有进度条的,看起来像卡住了,别急着 Ctrl+C。可以另开一个窗口看目标文件大小是不是在涨。
3.3 注销、导入,以及默认用户为什么会变成 root
导出成功、确认 tar 包大小合理之后,执行:
wsl --unregister Ubuntu wsl --import Ubuntu D:\WSL\Ubuntu D:\WSL\ubuntu-backup.tar --version 2--unregister会彻底删除原发行版和它的 vhdx,这一步之后 C 盘空间立刻回来了。但也就是从这一刻起,原发行版只存在于那个 tar 包里,所以千万别在导出没验证完之前就 unregister。
导入完成后打开:
wsl -d Ubuntu你会发现自己变成了 root,提示符是#,whoami返回root。这不是 bug。原因是 WSL 记录"默认登录用户"的地方在 Windows 注册表里,路径是HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Lxss,下面每个发行版一个 GUID 键,键里有个DefaultUid值。--import会生成一个全新的 GUID,DefaultUid默认是 0,也就是 root。原来的那个键随着 unregister 一起没了。
解决方案有两个,第 4 章会详细展开,这里先给最短路径:修/etc/wsl.conf。
cat > /etc/wsl.conf <<'EOF' [user] default=你的用户名 EOF exit然后在 Windows 侧:
wsl --shutdown wsl再进去看一眼whoami,应该就是你设置的用户了。
3.4 更省事的--manage --move,以及什么情况下别用它
如果你的 WSL 是从 Microsoft Store 安装的 2.0 以上版本,微软提供了一个原地搬家命令:
wsl --manage Ubuntu --move D:\WSL\Ubuntu它会直接把 vhdx 移过去并更新注册表路径,不用导出导入,也不用担心默认用户丢失。我一般会先试这条,成功了就省掉整个第 3 章的操作。
但它有两个限制:一是老版本 WSL 不支持,敲下去会说无法识别的参数;二是移动过程中如果目标目录有同名文件或者权限不足,报错信息比较含糊。所以执行前先确认wsl --version能正常输出,且目标目录是空的。
3.5 迁移后的收尾检查与 vhdx 瘦身
搬完之后别急着把 tar 包删掉,先跑一遍检查清单:
wsl -l -v # 确认发行版在,VERSION 是 2 wsl -d Ubuntu -- whoami # 确认默认用户 wsl -d Ubuntu -- ls /mnt/c # 确认 Windows C 盘挂载正常 wsl -d Ubuntu -- df -h # 确认磁盘容量正常再打开注册表确认路径:HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Lxss,找到对应 GUID,看BasePath是不是指向D:\WSL\Ubuntu\。如果还是旧路径,说明导入的时候参数写错了,需要重新--unregister再来一次。
确认一切正常、跑了一两天没问题之后,tar 包再删。
至于瘦身,如果你的发行版在 C 盘时就已经膨胀得厉害,迁移完可以顺手压一下:
wsl --manage Ubuntu --set-sparse true或者用 diskpart 这种老派但通用的方式:
wsl --shutdown diskpart # 进入交互后依次输入: # select vdisk file="D:\WSL\Ubuntu\ext4.vhdx" # attach vdisk readonly # compact vdisk # detach vdisk # exitcompact vdisk会把文件系统里已删除数据留下的空洞真正释放掉,一个膨胀到 80G、实际只用了 25G 的 vhdx,压完通常能缩到 30G 出头。这个过程比较慢,几十 G 的话可能要十几分钟。
4. 默认登录账户:为什么改了三处才生效
4.1 WSL 决定"用哪个用户登录"的三层优先级
这是最容易让人困惑的部分,因为它实际上有三个地方都能影响结果:
第一层,命令行参数。wsl -d Ubuntu -u root这种写法优先级最高,一次性的,用完不管。
第二层,/etc/wsl.conf里的[user] default。这是发行版内部的配置,跟着文件系统走,导出导入都会保留。需要wsl --shutdown之后重新进入才生效。
第三层,注册表里的DefaultUid。这是 Windows 侧记录的,由发行版启动器(比如ubuntu2204.exe config --default-user)写入,--import时会重置为 0。
这三者之间,不同 WSL 版本的行为有过细微变化。我在几台机器上实测下来,最稳妥的做法是:wsl.conf和启动器两处都设成同一个用户,别指望某一层单独生效。
4.2/etc/wsl.conf的完整写法和三个细节
先说一个很多人不知道的点:/etc/wsl.conf这个文件不能用 Windows 记事本直接编辑保存。记事本默认会加 UTF-8 BOM 头,而 WSL 解析这个文件时对 BOM 很敏感,可能导致整个文件被忽略、配置完全不生效,而且不报任何错。同理,用 VSCode 从 Windows 侧通过/mnt/c路径去改也要注意编码。
正确做法是进去用 heredoc 或者tee:
sudo tee /etc/wsl.conf > /dev/null <<'EOF' [boot] systemd=true [user] default=dev [automount] enabled = true root = /mnt/ options = "metadata,umask=22,fmask=11" mountFsTab = true [interop] enabled = true appendWindowsPath = false [network] generateResolvConf = true EOF逐段解释一下为什么这么写:
[boot] systemd=true是让 WSL 用 systemd 作为 init。开了之后systemctl能用,systemctl enable --now docker这类操作才成立。不开的话,服务得靠脚本手动拉。这个开关从 WSL 0.67.6 开始支持,老版本写了也不认。
[user] default=dev就是本节的主角。注意用户名必须真实存在,如果写了一个不存在的用户,WSL 会静默回退到 root,表现就是"我明明配了怎么还是 root",非常容易被误导。
[automount]里的options加了metadata很关键。不加的话,/mnt/c下的文件权限全是一刀切的777,chmod改了没反应。加了之后 Linux 侧才能正确记录权限位。umask=22,fmask=11是让新建文件默认 755/644,比较符合直觉。appendWindowsPath = false是从 PATH 里剔除 Windows 的路径,好处是which python不会莫名其妙指到 Windows 的 Python 上;坏处是code .、explorer.exe这些命令要用全路径。我个人是关掉的,因为 Windows 那堆路径混进 Linux PATH 经常造成版本冲突,是很多"为什么我的 python 是 3.11 但装包装到了 3.9"的根源。
改完之后一定:
wsl --shutdown然后在 WSL 里whoami验证。
4.3 发行版启动器那条老路:ubuntu.exe config
Store 安装的发行版会附带一个启动器 exe,在%LOCALAPPDATA%\Microsoft\WindowsApps\下面,名字根据发行版不同而不同:ubuntu.exe、ubuntu2204.exe、kali.exe之类。用法:
ubuntu2204.exe config --default-user dev或者不切目录直接写全路径。这个命令做的事情就是改注册表里那个DefaultUid值。
它的局限在于:只对 Store 安装的发行版有效。如果你的发行版是用wsl --import导进来的,根本不存在这个 exe,敲了就是"不是内部或外部命令"。这时候只能靠wsl.conf。
还有一个隐蔽的坑:如果你在迁移(--import)之前用这条命令设过默认用户,迁移之后这个设置会丢失,因为注册表键被重建了。这就是 3.3 那个"变成 root"问题的另一半原因。
4.4 默认用户彻底丢了的救援流程
假设你导入了一个官方 rootfs,里面只有 root,没有任何普通用户。这时候光改wsl.conf没用,因为你写的那个用户压根不存在。完整流程是:
wsl -d Ubuntu -u root进去之后:
# 先看看现有用户 cat /etc/passwd | grep -E '/bin/(ba)?sh$' # 用 adduser 创建(比 useradd 更友好,会自动建家目录、拷贝骨架文件) adduser dev # 加进 sudo 组 usermod -aG sudo dev # 确认 id devadduser是交互式的,会依次问密码、全名、房间号这些,除了密码其他直接回车跳过即可。它比useradd多做了几件事:创建/home/dev、从/etc/skel复制.bashrc等初始文件、设置默认 shell,这些细节如果漏了,进去之后会发现没有彩色提示符、没有历史记录,很别扭。
然后再写一遍wsl.conf,wsl --shutdown,重进。
注意:给用户改名比新建用户麻烦得多,因为家目录、组、sudoers 里可能都有引用。我一般不会去改已有用户的用户名,不够折腾的。
5. root 密码与 sudo 权限:最容易想当然的两个地方
5.1sudo passwd root之后,为什么"登录"还是失败
Ubuntu 的 WSL 发行版默认把 root 账户锁着,passwd -S root会显示一个L(locked)。首次启动创建普通用户之后,root 是没有密码的。要解锁:
sudo passwd root输入新密码两次,root 就解锁了。但很多人做完这一步,会去试"用 root 登录",然后遇到各种"密码错误"。这里面有几个不同的原因,得分开看。
第一种情况,你敲的是su但输错了密码。su从普通用户切到 root,需要的是 root 密码(刚设的那个),不是你的 sudo 密码。很多人脑子里两个密码混着,试三次就锁了。
第二种情况,你在某个客户端的"登录用户"选项里切不到 root。比如某些 Windows 侧的终端工具、SFTP 客户端提供"以 root 连接"的选项,但走的是 SSH,而 WSL 里 SSH 服务默认根本没启动,而且 sshd 默认配置里PermitRootLogin通常是prohibit-password。这不是密码的问题,是服务压根没开。
第三种情况,密码里带了特殊字符,或者在输入法状态下敲的。这个听起来很低级,但我见过好几次,尤其是密码里带@、#这类符号,中文输入法的全角符号和半角符号看起来几乎一样。
在 WSL 环境下,最可靠的"以 root 身份进去"的方式根本不是密码,而是命令行直通:
wsl -d Ubuntu -u root这条命令不需要任何密码,直接给你一个 root shell。这是 WSL 相对物理机最大的便利,也是最好的应急通道。想想物理服务器上 root 密码忘了有多麻烦,WSL 里一句命令解决。
5.2 忘了 sudo 密码怎么办(两分钟解决)
顺着上一节说。如果你只是忘了自己那个普通用户的密码,导致sudo用不了:
wsl -d Ubuntu -u root进去之后:
passwd dev # 重置普通用户密码 passwd -S dev # 看状态,P 表示有密码可用 exit不用重启,不用wsl --shutdown,退出后立刻生效。这个流程我一年要用上两三次,都是帮同事弄的。
5.3 sudo 权限的判定链,以及"加了组为什么没生效"
sudo能不能用,走的是一条链,任何一环断了都不行:
- 用户是否在
sudo组里(Debian/Ubuntu 系的约定;如果是 RHEL 系发行版,组名是wheel,配置也不同) /etc/sudoers里是否有%sudo ALL=(ALL:ALL) ALL这一行- 用户的组变更是否已经生效
第一条:
sudo usermod -aG sudo dev注意-aG里的a不能丢,-G单独用是"把这个用户的附加组设置为指定组",会覆盖掉原有附加组。丢了a的后果是用户可能失去其他所有附加组权限。
第二条:如果/etc/sudoers里的%sudo那行被注释掉了或者被人改坏了,那加组也没用。用visudo检查:
sudo visudo它会做语法校验,写完保存时如果有语法错误会拒绝保存,这个设计就是为了防止你把 sudoers 改坏导致彻底锁死。
第三条是最容易踩的坑:Linux 里用户的组成员关系是在登录时确定的,usermod -aG改完之后,当前这个 shell 会话里是不会生效的。groups命令看不到新组,sudo也还是不能用。解决办法有两个:
# 方案一:临时刷新当前会话的组 newgrp sudo # 方案二:退出重进(推荐,彻底) exit我见过有人加完组立刻敲sudo发现不行,以为命令写错了,反复加了好几遍,其实就是没重登。
验证:
groups dev sudo -l # 列出当前用户可以用 sudo 执行什么sudo -l会要求输一次密码,然后列出权限规则。如果输出里有(ALL : ALL) ALL,说明权限完整。
5.4 免密 sudo:什么场景值得开
WSL 里有个很实用的场景是免密 sudo:写自动化脚本、跑 CI、装 Docker 的时候,卡在交互式密码输入上很烦。配法是在/etc/sudoers.d/下放一个独立文件:
sudo tee /etc/sudoers.d/dev > /dev/null <<'EOF' dev ALL=(ALL) NOPASSWD:ALL EOF sudo chmod 0440 /etc/sudoers.d/dev sudo chown root:root /etc/sudoers.d/dev三个细节必须注意:
- 文件权限必须是
0440。sudo 会检查 sudoers.d 下所有文件的权限,如果不是 0440 或者属主不是 root,会直接忽略甚至报错。 - 文件名不要带点号,也不要以
~结尾。sudo在加载/etc/sudoers.d/时会跳过包含.或~的文件名,这是防止编辑器留下的临时文件被误加载。我就见过有人把文件命名为dev.conf,然后死活不生效。 - 不要直接改主 sudoers 文件。用 drop-in 文件的好处是,出问题时删掉这一个文件就恢复了。
提示:免密 sudo 在个人开发机上图省事可以开,但同样的配置绝对不能带到任何多人使用的服务器上。另外,如果你后面要装 Docker 并把用户加进 docker 组,那要意识到 docker 组的权限实质上等价于 root(能挂载宿主机文件系统),这是另一个层面的安全边界问题,不在本文讨论范围。
5.5 常见报错对照表
把这一章遇到的典型报错整理成一张表,出问题的时候直接对号入座:
| 报错现象 | 根本原因 | 处理方式 |
|---|---|---|
dev is not in the sudoers file | 不在 sudo 组,或组变更未生效 | usermod -aG sudo dev后退出重进 |
sudo: /etc/sudoers is world writable | sudoers 权限被改坏 | wsl -u root进去chmod 0440 /etc/sudoers |
su: Authentication failure | root 未设密码,或输错密码 | sudo passwd root |
| 输入密码时屏幕无任何反馈 | 正常行为,不是卡死 | 盲打后回车 |
passwd: Authentication token manipulation error | /etc/shadow权限异常或文件系统只读 | wsl -u root进去检查ls -l /etc/shadow |
Sorry, user dev is not allowed to execute ... | sudoers 里写了限定命令列表 | 检查/etc/sudoers.d/下的规则 |
| sudoers 改坏导致完全无法 sudo | 语法错误 | wsl -d Ubuntu -u root走应急通道修复 |
wsl -u root也进不去,报 0x8007019e | WSL 可选功能没启用 | 回到第 2 章重新开功能 |
这张表里最后一行的思路值得强调:只要wsl -d <发行版> -u root还能用,WSL 里就没有救不回来的配置问题。这个 root shell 相当于物理机的单用户模式或者 PE 环境,是最后一道保险。
6. 装完之后的日常:VSCode 联动、字体观感与几个省心配置
6.1 VSCode 连 WSL 的正确姿势,以及为什么你的项目要放 Linux 侧
VSCode 连 WSL 靠的是官方 "WSL" 扩展(包含在 Remote Development 扩展包里)。装完之后有两种连接方式:
一种是从 WSL 终端里发起:
cd ~/projects/myapp code .第一次会自动在 WSL 里下载并安装vscode-server,装到~/.vscode-server目录下,有一定体积。之后 VSCode 会在 Windows 侧开一个窗口,但所有编辑器后端进程都在 WSL 里跑,插件也装在 Linux 侧。
另一种是从 VSCode 左下角的绿色角标点进去,选 "Connect to WSL"。
这里有个性能问题必须讲清楚:项目文件放在 Linux 文件系统(比如~/projects/)和放在/mnt/c/...下,性能差距是数量级的。原因是后者每次文件读写都要经过 9P 协议从 Linux 转发到 Windows,inotify文件监听更惨,热重载、npm run dev、vite这类工具会出现几秒甚至十几秒的延迟。我做过对比,同一个前端项目,node_modules安装放在/mnt/c下耗时是 Linux 侧的 4 到 6 倍。
所以我的习惯是:WSL 里建一个~/projects,所有需要跑构建的项目放这里。如果因为某些原因必须用 Windows 侧的文件(比如要跟 Windows 上的工具共享),那就接受慢,或者用rsync做单向同步。
顺带说几个 WSL 里做开发常用的环境准备,都有各自的坑:
- Docker:装 Docker Desktop 并打开 "Use WSL 2 based engine",然后在设置里勾选要集成的发行版;或者直接在 WSL 里装
docker.io并开 systemd。前者省事,后者更轻。 - Redis:
sudo apt install redis-server,注意要开 systemd 才能systemctl start redis,否则得手动redis-server --daemonize yes。 - CUDA:WSL2 支持 CUDA,但装的是 Linux 版驱动不用装,只要 Windows 侧驱动够新,WSL 里
nvidia-smi就能看到卡。装错版本(在 WSL 里装 Windows 驱动)是最常见的错误。 binwalk这类工具:WSL2 下正常工作,WSL1 下某些依赖内核特性的功能会失败,这也是前面推荐 WSL2 的原因之一。
6.2 字体和观感:怎么调出接近 macOS 的终端体验
这个话题在社区里讨论得很多。macOS 终端好看,其实是几个因素叠加:高分屏 + 优秀的抗锯齿策略 + 字体本身的字形设计 + 合适的行高和字距。Windows 上要接近,得一项一项调。
先说字体选择。等宽字体里我个人常用的几个:
| 字体 | 特点 | 适合谁 |
|---|---|---|
| JetBrains Mono | 字形方正,连字丰富,辨识度高 | 通用首选 |
| Cascadia Code / Next | 微软官方,Windows Terminal 默认 | 图省事 |
| Maple Mono | 中英混排处理得好,圆润 | 经常写中文注释 |
| Fira Code | 连字鼻祖,生态成熟 | 老用户 |
| Sarasa Mono SC(更纱黑体) | 中文等宽,中英对齐 | 中文重度用户 |
中英混排的终端里,如果只用纯西文等宽字体,中文会回退到系统默认字体,出现"中文字比英文宽一倍、行高被撑开"的割裂感。解决办法就是选一个原生支持中文等宽的字体,比如更纱黑体,然后把它放在字体列表的后面作为 fallback。
VSCode 里的设置:
{ "terminal.integrated.fontFamily": "'JetBrains Mono', 'Sarasa Mono SC', monospace", "terminal.integrated.fontSize": 14, "terminal.integrated.lineHeight": 1.3, "terminal.integrated.letterSpacing": 0.2, "terminal.integrated.fontWeight": 400, "terminal.integrated.fontLigatures": true, "terminal.integrated.gpuAcceleration": "on", "editor.fontFamily": "'JetBrains Mono', 'Sarasa Mono SC', monospace", "editor.fontLigatures": true, "editor.fontSize": 14 }几个参数的作用:lineHeight调到 1.2 到 1.4 之间,行距一开整个界面立刻松快起来,这是最接近 macOS 观感的一步。letterSpacing加 0.2 到 0.5,字不会挤在一起。gpuAcceleration开启用 GPU 渲染,滚动明显更顺。
Windows Terminal 这边(如果你用它而不是 VSCode 内置终端),在settings.json的 profiles.defaults 里:
{ "profiles": { "defaults": { "font": { "face": "JetBrainsMono Nerd Font", "size": 11, "features": { "calt": 1, "liga": 1 } }, "antialiasingMode": "grayscale", "padding": "10, 10, 10, 10" } } }antialiasingMode这个设置是观感差异的关键。Windows 默认用 ClearType(次像素抗锯齿),字会带一点点彩色边缘,锐利但略显毛糙;改成grayscale之后,字体边缘变柔和平滑,视觉上非常接近 macOS 的渲染风格。代价是在低 DPI 屏幕上会略微发虚。1440p 以上的屏幕我建议直接上grayscale,试一次就知道差别。
如果你想要 powerline 那种带箭头和图标的分隔符(配 oh-my-zsh、starship 提示符),字体名要选带 "Nerd Font" 后缀的版本,比如JetBrainsMono Nerd Font。普通版字体里没有那些字形,会显示成方框。
6.3.wslconfig:控制 WSL 吃多少内存
WSL2 的内存占用是很多人抱怨的点:跑着跑着任务管理器里 Vmmem 进程吃到十几 G。这是虚拟机的正常行为,Linux 内核会把空闲内存当缓存用,不会主动归还。有两个控制手段。
一是在 Windows 侧的用户目录建.wslconfig(注意是全局的,放在C:\Users\你的用户名\.wslconfig):
[wsl2] memory=8GB processors=4 swap=2GB localhostForwarding=true这个文件管的是整个 WSL2 虚拟机的资源上限,和发行版内部的/etc/wsl.conf是两回事,别搞混。改完wsl --shutdown生效。
二是定期wsl --shutdown。这个命令会彻底关掉虚拟机,内存立刻归还给 Windows。我一般在跑完大编译、或者准备玩游戏之前敲一下。
processors别设太大,设成和物理核心数一样有时候反而慢,因为 Windows 侧还要用。一般设成物理核心数的一半到三分之二比较舒服。
6.4 备份习惯和彻底卸载的顺序
最后说两个运维层面的习惯。
备份:我现在的做法是每个月或者每次做重大环境变更(比如升级 Python 大版本、装完 CUDA)之前,导出一份快照:
wsl --shutdown wsl --export Ubuntu D:\WSL\backup\ubuntu-20250101.tar几十 G 的空间换一份安心,比出问题时从头配环境划算太多。文件按日期命名,保留最近两三份就够。
卸载:如果哪天要彻底清掉,顺序很重要,别搞反。
wsl --unregister Ubuntu # 第一步,删掉发行版和 vhdx,数据不可恢复 wsl --shutdown然后从"设置 → 应用"里卸载对应的发行版应用(如果有的话)。再进"启用或关闭 Windows 功能",取消勾选适用于 Linux 的 Windows 子系统和虚拟机平台。最后别忘了去D:\WSL\或者%LOCALAPPDATA%\Packages\下面看一眼,有没有残留的ext4.vhdx没删干净——Store 版发行版有时候卸载了应用但 vhdx 还留着,白白占几十 G。
我个人用下来最省心的几条经验
这套流程我在自己机器和同事的机器上前后跑过十几次,有几个反复验证过的结论。
迁移这件事,能先用wsl --manage --move就别用导出导入。导出导入虽然通用,但每次都要处理默认用户丢失、注册表路径、tar 包临时占用这几件事,而--move一条命令解决。只有 WSL 版本太老或者--move报错的时候才走导出导入这条路。
遇到任何"命令行进不去""发行版状态怪怪的""改了配置不生效"的问题,第一反应永远是:
wsl --shutdown再重进。八成的怪问题都是实例状态没刷新,或者在某个窗口里还挂着一个后台进程。这一招比看日志快得多。
最后,/etc/wsl.conf和.wslconfig这两个文件我建议你在装好系统的当天就配好,别等到出问题再回头补。尤其[automount] options里的metadata和[interop] appendWindowsPath这两项,早配早省心,晚了等 PATH 里混进 Windows 的一堆路径、pip install装到了 Windows 的 Python 上,排查起来能烦死人。文件都用 heredoc 或者tee写,别碰记事本,这一条我踩过一次,配了半天不生效,最后发现是 BOM 头,气得够呛。