在两年前的八月Microsoft 正式发布了 Windows 10 Anniversary Update 周年更新它还有着 RS1Version 1607Build 14393 等一大堆别名其中最让包括我在内的众多开发者感到兴奋的特性之一就是 WSLWindows Subsystem for Linux当时还叫 Bash on Ubuntu on Windows的正式加入

在 Windows 上原生运行 Linux 可执行文件牛逼疯了

然而 Bug10 也不是浪得虚名原本只提供给 Insider 的 WSL 在正式发布后依然问题多多不仅 zshtmux 等工具无法使用网络相关的操作更是一概欠奉还有各种各样 奇妙的 BUG基本没有可用性我在尝鲜了一段时间后也不得不重回 Cygwin 的怀抱不过好消息是在之后的更新中这些 BUG 都已被逐一消灭

经过了两年的发展WSL 已经足够成熟我也是时候完成这篇一咕再咕的博文了

开学在即仓促成文如有谬误还请指正

get-wsl

1. 我理想中的命令行界面

既然违反广告法取了这么个标题那我自然得先描述一下我的目标也就是我理想中的命令行界面应该是什么样子的如果你不清楚命令行的概念可以看看我之前写的 这篇文章

  • 好看配色字体可以自由设定
  • 支持 UTF-8 字符的输入与显示
  • 支持常见的 *NIX 命令行工具catgrepawk 等
  • 自动补全语法高亮历史记录
  • 完善的复制粘贴支持
  • 互操作性共享文件系统网络栈可调用 Win32 程序
  • 支持常用的脚本语言PHPPythonNode.js 等
  • 包管理器以及其他各种常用软件的支持
  • 快速呼出快捷键右键菜单入口

然而遗憾的是Windows 上的命令行一直以来都很微妙

2. 难用的 Windows 命令行

停停停那边的 PowerShell 爱好者 咱别动粗成吗

首先我要对标题做出一些订正Windows 原生命令行其实也可以不那么难用虽然 cmd.exe 是公认的难用到反人类毕竟是用来兼容 DOS 的老古董但后来推出的 PowerShell 已经足够强大且现代化能够称得上是一个成熟的命令行 Shell 了如果你愿意学习的话PowerShell 几乎可以满足你对命令行的所有期待这一点可以参见Is PowerShell ready to replace my Cygwin shell on Windows?

但是PowerShell 与 Bash 等类 Unix 系统上的 Shell 程序几乎是两个完全不同的世界不仅语法不同其平台上各类常用的命令行工具也基本不一致比如类 Unix 系统中的 grep 对应 PowerShell 中的 Select-Stringuniq 对应 Select-Object -Unique往深了说他们的系统设计理念都是不一样的比如很多人推崇的 Unix 哲学在 Windows 上就基本不见踪影而 COM 等概念也是 Windows 独一份

manga-system-admin-girl-sp-wsl

▲ 图片来源シス管系女子 BEGINS 特別編 まんがでわかる WSL

当然我无意在此挑起操作系统间的圣战Windows 和类 Unix 系统中的命令行哪个好用见仁见智不过对于包括我在内的很多用户都认为 Windows 命令行不怎么好用仅此而已

回到正题

虽然 Windows 的命令行一直遭人诟病但是人家的图形界面牛逼啊于是无数工程师前赴后继试图在 Windows 上创造出不输给类 Unix 系统的命令行体验 —— 却绝大多数以失败告终曾经努力过的人或者回到可爱的 Linux 上或者进入高贵冷艳的 macOS 的世界其中有先辈留下了 CygwinGnuWin32 等工具集让我们可以在 Windows 下使用类 Unix 系统中常见的命令行工具成为了不少 Windows 用户的救赎

然而就当大家都觉得也就这样了的时候Microsoft 出人意料地站了出来

带着他新鲜出炉的 WSL

3. Windows Subsystem for Linux参上

大家都把 WSL 吹得这么牛逼那 WSL 究竟是个什么玩意儿呢

简单来说WSL 是一个 兼容层有点像反过来的 Wine


首先我问个问题为什么 Linux 上的程序无法在 Windows 上运行呢

了解过一点操作系统原理的同学应该都知道这是 Windows 与 Linux 的内核提供的接口不同系统调用API 等导致的举个栗子我们想知道某目录下的内容在 Linux 下我们会使用 ls 命令而在 Windows 下我们会使用 dir 命令

当我们在 Linux 上执行 ls 命令ls 会调用 getdents 这个系统调用Linux 内核收到请求将目录的内容返回给应用程序当我们在 Windows 上执行 dir 命令dir 会调用 NtQueryDirectoryFile 这个 APINT 内核收到请求将目录的内容返回给应用程序虽然系统不同但基本上都是一个道理

然而当我们把 Linux 上的应用程序拿到 Windows 上运行时应用程序和内核就双双懵逼了比如 ls 会尝试调用 getdents 系统调用理想化的情况下暂不考虑可执行文件格式等问题Windows 的 NT 内核一看心说这他娘的什么东西老子不认识啊啥情况啊ls 也想尼玛内核怎么不回话啊咋回事儿啊……两边语言不通应用程序自然无法正确执行

但是有了 WSL情况就不一样了

依然拿 ls 举例当我们在 WSL 中运行 ls 命令时ls 会调用 getdents 系统调用这个系统调用接口是 WSL 提供的Windows 本身并没有这个接口WSL 收到这个请求明白了应用程序是想要知道目录的内容于是把 Linux 的系统调用转换为 NT API NtQueryDirectoryFileNT 内核收到 WSL 的请求将目录的内容返回给 WSLWSL 再把返回的内容包装好后返回给 ls

也就是说WSL 在 Linux 应用程序与 Windows NT 内核之间起到了翻译者的作用很简单的道理既然 NT 内核无法理解 Linux 应用程序的 POSIX 系统调用那就弄个翻译来将 POSIX 系统调用实时转换为 NT 内核能理解的 API 调用突出一个见人说人话见鬼说鬼话

只要实现了足够多的系统调用翻译那么理论上 WSL 可以完全模拟成一个 Linux 内核


相信各位都听说过鼎鼎大名的 Cygwin同样是能让 Linux 应用程序运行在 Windows 上WSL 和 Cygwin 有什么不同呢其实差别还是挺大的

虽然 Cygwin 提供了完整的 POSIX 系统调用 API以运行库 Cygwin*.dll 的形式提供但其依然工作在 User Mode而 WSL 中的 Linux 应用程序进程会被包裹在一个叫做 Pico Process 的东西里这个东西里发出的所有系统调用请求都会被直接送往 Kernel Mode 中的 lxcore.syslxss.sys 处理

同样是将 POSIX 系统调用转换为 Windows 中的 APICygwin 是转换成 Win32 API 的调用因为它架设在 Win32 子系统上很多内核操作受限于 Win32 的实现比如 fork而 WSL 则是转换为更底层的 NT API 调用WSL 是与 Win32 平行的子系统直接架设在 NT 内核上可以通过 NT API 原生实现 fork 等系统调用

wsl-architecture

▲ WSL 架构示意图图片来源Windows for Linux Nerds

最重要的一点如果使用 CygwinLinux 应用程序的源码必须 link 至 Cygwin 运行库Cygwin*.dll修改源码重新编译后才能在 Windows 下运行这些重新编译后的 Linux 应用程序在调用 POSIX API 时不会直接去请求内核而是会去调用 Cygwin 运行库由运行库翻译成 Win32 API执行调用后返回结果这也就意味着重新编译后的应用程序需要依赖 Cygwin 运行库才能正常运行有时候你会碰到的缺少 Cygwin1.dll报错就是这个原因而且这样编译出来的可执行程序是纯正的 Win32 PE 格式封装只能在 Windows 上运行

而在 WSL 下我们可以直接运行未经任何修改的 ELF 格式 Linux 可执行程序

ls-exe-in-cygwin

▲ Cygwin 目录下被编译成 Win32 可执行程序的 Linux 应用程序们

最后总结一波

WSL 就像是一个翻译官就算那些未经修改的 Linux 应用程序们操着一口纯正的 POSIX 系统调用语法WSL 也能快速准确地将其翻译为 NT 内核能听懂的 API 调用

而那些使用了 Cygwin 重新编译后的 Linux 应用程序就像是改造人一样变成了 Win32 应用程序的形状还被套了个翻译机程序自己源码中说的是 POSIX经过翻译机Cygwin 运行库之后就变成 Win32 API 调用了这样 NT 内核也能听得懂

但是每次添加新程序都要改造多麻烦啊还是 WSL 原生态更健康


以上只是我对 WSL 的粗浅解释其具体实现原理可以参考官方博客上的 这一系列文章

4. 安装 WSL拥抱可爱的 Linux

好了不说废话让我们开始安装 WSL注意WSL 仅支持 64 位系统且本文中所描述的安装方法仅适用于 Windows 10 Fall Creators Update秋季创意者更新RS3Version 1709Build 16299及以上版本

第一步打开控制面板中的程序与功能点击左侧边栏的启用或关闭 Windows 功能选项在弹出的窗口中勾选适用于 Linux 的 Windows 子系统然后点击确定可能需要重启

如果你懒得用 GUI也可以直接在 PowerShell 中以管理员权限执行命令

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

enable-windows-optional-feature

第二步打开 Microsoft Store搜索WSL挑选一个你喜欢的 Linux 发行版然后点击安装截至目前商店中可用的发行版有 UbuntuopenSUSESUSE Linux Enterprise ServerDebian 以及 Kali Linux

microsoft-store-wsl

第三步在开始菜单中找到你刚刚安装的发行版打开它等待几分钟的初始化过程设定好用户名与密码后不需要与 Windows 的相同用过 Linux 的选手应该都懂的就会自动进入 Linux 环境

至此你已经完成了 WSL 的安装

你也可以同时安装多个发行版它们的数据都是独立的互不影响

initialize-wsl

5. 使用更专业的终端模拟器

我猜你现在正在对上面那个窗口发呆

—— 这个新宋体他娘的是个什么情况

如果你正在使用中文 Windows 系统而且之前并没有修改过 Win32 Console 的默认配置那么你的 WSL 终端默认就会是这样的新宋体就是这么 Hardcore惊不惊喜意不意外

好吧不开玩笑Windows 这个控制台窗口就是很多人讨厌它的原因之一难用又难看丑这一点倒还有解决方法经过一番设置后还算能看我以前就写过一篇关于 自定义 Windows 控制台字体 的文章难用却是实打实的尽管 Win10 上的控制台已经改进了不少可以看看 Microsoft 的官方博客Windows Command Line Tools For Developers但其依然是最难用的终端模拟器之一或许没有之一

因此为了实现我们的目标一个更强大的终端模拟器是必须的

终端模拟器是什么为了这个回答这个问题我专门写了一篇文章去看看吧:P

我个人比较推荐的终端模拟器有

  • wsl-terminal

    专门为 WSL 开发的终端模拟器基于 mintty 与 wslbridge稳定易用

  • ConEmu

    Windows 上的老牌终端模拟器功能极为强大要啥有啥

  • Hyper

    基于 Electron 的跨平台终端模拟器好看和可扩展性是卖点BUG 不少

还有其他各种各样的终端模拟器选个自己喜欢的就好反正不管选哪个都比默认的那玩意儿要好用🌚

另外设定终端模拟器的 Shell 入口时有个坑需要注意一下参见下文 6.4

my-terminals

▲ 我正在使用的终端wsl-terminal 与 Hyper好看是第一生产力

6. 让我们更深入一些

以下是 WSL 的一些优化技巧

6.1 使用软件源镜像

由于众所周知的原因各大发行版默认的软件源在中国大陆的访问速度都很屎

我目前使用的是 清华大学的 Ubuntu 镜像源

6.2 安装 zsh 与 oh-my-zsh

想要快乐地使用命令行一个趁手的 Shell 是必不可少的

我个人习惯使用 zsh安装步骤不再赘述我的自定义 oh-my-zsh 主题

# ~/.oh-my-zsh/custom/themes/robbyrussell-ascii.zsh-theme
# Modified from robbyrussell, the default theme of oh-my-zsh.
# > blog git:(source) x $

local ret_status="%(?:%{$fg_bold[green]%}>:%{$fg_bold[red]%}>%s)"
PROMPT='${ret_status} %{$fg[cyan]%}%c%{$reset_color%} $(git_prompt_info)$ '

ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg_bold[blue]%}git:(%{$fg[red]%}"
ZSH_THEME_GIT_PROMPT_SUFFIX="%{$reset_color%} "
ZSH_THEME_GIT_PROMPT_DIRTY="%{$fg[blue]%}) %{$fg[yellow]%}x%{$reset_color%}"
ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg[blue]%})"
# ~/.zshrc
ZSH_THEME="robbyrussell-ascii"
plugins=(git zsh-completions zsh-autosuggestions zsh-syntax-highlighting)

6.3 安装多个发行版

Windows 10 Fall Creators Update 之后WSL 支持同时安装多个 Linux 发行版直接在 Microsoft Store 中搜索想要的发行版并点击安装即可这些发行版可以同时运行并且数据互相独立你可以使用 wslconfig.exe 来查询已安装的发行版或者更改默认的发行版

multiple-wsl-distributions

删除发行版也很简单直接卸载对应的商店应用即可记得备份哦

6.4 多种进入 WSL 的方式比较

新版支持同时安装多个发行版那自然不能像以前那样只提供一个 bash.exe 入口了

秋季创意者更新之后的 Windows 提供了 多种进入 WSL 环境的方式

  • wsl.exe

    打开默认发行版中的默认 Shell

  • <distroname>.exe

    打开指定发行版中的默认 Shell

  • bash.exe (DEPRECATED)

    打开默认发行版中的 bash Shell

    如果你更改了默认 Shell 却总是打开 bash就说明你使用了这个入口

你也可以通过这些入口直接在 WSL 中执行命令并返回结果

  • <distroname> -c [command]
  • bash -c [command]
  • wsl [command]不再需要指定 -c

invoking-wsl-in-many-ways

6.5 与 Windows 的互操作性

WSL 与 Windows 之间的互操作性 (Interoperability) 很牛逼怎么个牛逼法呢

Windows 下的所有盘符都挂载在 WSL 中的 /mnt 目录下可以直接操作WSL 中的所有数据则存放于 C:\Users\{你的用户名}\AppData\Local\Packages\{Linux发行版包名}\LocalState\rootfs 目录中不要在 Windows 中修改这些文件这会造成文件权限错误

$ ls /mnt
c  d  e
$ mount -l
rootfs on / type lxfs (rw,noatime)
C: on /mnt/c type drvfs (rw,noatime,uid=1000,gid=1000)
D: on /mnt/d type drvfs (rw,noatime,uid=1000,gid=1000)
E: on /mnt/e type drvfs (rw,noatime,uid=1000,gid=1000)

你可以在 Windows 命令行环境中直接调用 WSL 中的命令

PS C:\temp> wsl ls -al
total 0
drwxrwxrwx 1 prin  prin  4096 Sep  7 19:04 .
drwxrwxrwx 1 prin  prin  4096 Sep  7 18:38 ..
-rwxrwxrwx 1 prin  prin     4 Sep  7 19:04 foo.txt

你也可以在 WSL 中调用 Windows 中的命令行程序

$ which ipconfig.exe
/mnt/c/Windows/System32/ipconfig.exe
$ ipconfig.exe
Windows IP Configuration
...

你可以在 WSL 中直接启动 Windows 应用

$ notepad.exe "C:\temp\foo.txt"

你还可以通过 pipes 与 Windows 程序通信

# 复制内容至 Windows 剪贴板
$ cat foo.txt | clip.exe

你甚至可以把 Windows 命令和 WSL 命令混着用

PS> ipconfig | wsl grep IPv4
IPv4 Address. . . . . . . . . . . : 192.168.1.114

$ ipconfig.exe | grep IPv4 | cut -d: -f2
192.168.1.114

$ ls -al | findstr.exe foo.txt
-rwxrwxrwx 1 prin  prin 4 Sep  7 19:04 foo.txt

$ cmd.exe /c dir
 Volume in drive C is Windows
 Volume Serial Number is B263-****

 Directory of C:\temp

2018/09/07  19:04    <DIR>          .
2018/09/07  19:04    <DIR>          ..
2018/09/07  19:04                 4 foo.txt
               1 File(s)              4 bytes
               2 Dir(s)  194,422,341,632 bytes free

同时WSL 与 Windows 共享网络栈也就是说你可以

  • 在 WSL 中启动 web server在 Windows 上使用浏览器访问
  • 在 Windows 下启动 MySQL/Redis 服务器在 WSL 中连接
  • 诸如此类

如果你对 WSL 与 Windows 之间互操作的原理有兴趣可以参考一下这些文章

6.6 DrvFs 文件权限问题

虽然 WSL 中可以直接访问 Windows 磁盘的内容但如果你曾经这么做过你应该对这样绿油油一片的 ls 不会感到陌生为什么 NTFS 文件系统中的文件到 WSL 下权限就全部成 0777 了呢

ls-with-wrong-file-permission

这主要是 DrvFs 中 Linux 文件权限的实现导致的

在 WSL 中Microsoft 实现了两种文件系统用于支持不同的使用场景

  • VolFs

    着力于在 Windows 文件系统上提供完整的 Linux 文件系统特性通过各种手段实现了对 InodesDirectory entriesFile objectsFile descriptorsSpecial file types 的支持比如为了支持 Windows 上没有的 InodesVolFs 会把文件权限等信息保存在文件的 NTFS Extended Attributes 中记得我上面警告过你不要在 Windows 中修改 WSL 里的文件吗就是因为 Windows 中新建的文件缺少这个扩展参数VolFs 无法正确获取该文件的 metadata而且有些 Windows 上的编辑器会在保存时抹掉这些附加参数

    WSL 中的 / 使用的就是 VolFs 文件系统

  • DrvFs

    着力于提供与 Windows 文件系统的互操作性与 VolFs 不同为了提供最大的互操作性DrvFs 不会在文件的 NTFS Extended Attributes 中储存附加信息而是从 Windows 的文件权限Access Control Lists就是你右键文件 > 属性 > 安全选项卡中的那些权限配置推断出该文件对应的的 Linux 文件权限

    所有 Windows 盘符挂载至 WSL 下的 /mnt 时都是使用的 DrvFs 文件系统

由于 DrvFs 的文件权限继承机制很微妙最后导致的结果就是所有文件的权限都变成了 0777而且由于早期的 DrvFs 不支持 metadata所以你无法给这些文件 chown/chmod只能对着绿油油的 ls 干瞪眼不过好消息是Windows Insider Build 17063 之后DrvFs 也像 VolFs 一样支持给文件写入 metadata 了

要启用 DrvFs 的 metadata 支持你需要添加参数重新挂载磁盘

# 修改成你自己的盘符
$ sudo umount /mnt/e
$ sudo mount -t drvfs E: /mnt/e -o metadata

不过如果仅仅是执行了这个虽然支持了文件权限的修改但磁盘下的文件权限默认依然还是 0777除非你给它们整个 chmod 一遍如果你不想这么做也可以指定其他的 mount 参数

$ sudo mount -t drvfs E: /mnt/e -o metadata,uid=1000,gid=1000,umask=22,fmask=111

这样磁盘下的文件的默认权限就是 0644ls 也不会再是绿油油一片啦

ls-with-fixed-file-permission

不过每次使用时都要重新挂载未免也太烦我们可以通过另一个新特性 Automatically Configuring WSL 实现自动挂载在 WSL 中创建 /etc/wsl.conf在其中填写如下内容

[automount]
enabled = true
root = /mnt/
options = "metadata,umask=22,fmask=111"
mountFsTab = true

# 这个文件里还可以添加其他配置项,有兴趣的可以看看上面的链接

重启终端所有的盘符就会使用上面的配置自动挂载啦可以使用 mount -l 查看

另外如果你想要给不同的盘符设定不同的挂载参数上面的方法对所有盘符都有效如果你想在 WSL 中运行 Windows 下的应用程序就得每次都 chmod +x 一下所以我一般都会把 C: 排除掉就需要手动修改 /etc/fstab首先确保 wsl.conf 中的 mountFsTabtrue然后编辑 /etc/fstab添加如下内容

# 不在此列表中的盘符会使用 wsl.conf 中的参数挂载
# 格式可以自己去查 fstab 的帮助文档
E: /mnt/e drvfs rw,relatime,uid=1000,gid=1000,metadata,umask=22,fmask=111 0 0

6.7 其他关于 WSL 的折腾

虽然 Microsoft 开发 WSL 出来主要是着重于命令行环境的使用但经过测试WSL 是可以通过 X Server 执行 GUI 应用程序的甚至还可以在 WSL 里面用 Wine 执行 Windows 程序……🤔

也有人试过在 WSL 中运行完整的 DE体验似乎还不错有兴趣的同学可以去试试

另外你也可以通过某些神秘的方法用上 Microsoft Store 未提供的 Linux 发行版比如 Arch Linux

如果你对 WSL 的底层实现有兴趣也可以去围观一下 WSL 的官方博客

  • https://blogs.msdn.microsoft.com/wsl/
  • https://blogs.msdn.microsoft.com/commandline/tag/wsl/

7. 总结

虽然 WSL 很不错但是其比起真正的 Linux 系统还是有很多不足Docker 等涉及未实现的内核特性的软件无法使用Raw socket 相关的操作依然容易出错I/O 性能相比之下较为孱弱等如果你日常开发中需要使用到那些 WSL 未提供的 Linux 特性那么还是乖乖跑 VM 或者装 Linux 吧

对我来说WSL 最大的意义就是让我能够用我熟悉的 Linux 那一套去操作 Windows

如果你和我的需求一样那么比起 CygwinVM 等解决方案WSL 有着完整的 Linux 环境强大的互操作性更低的资源占用离不开 Windows却又羡慕 Linux 下强大命令行工具的各位相信你们会喜欢 WSL 的

而且最近几年 Microsoft 在笼络开发者方面的努力大家有目共睹这里就容我夸上一句

Microsoft干得漂亮