---
layout: post
title: 在浏览器中运行Linux的各种方法
tags: [浏览器, Linux, 虚拟机, WASM]
---

  浏览器已经无所不能了！<!--more-->   

# 起因
  前段时间跟网友交流时，有人展示了他博客里的一个Linux终端模拟项目：[jsnix](https://github.com/Erzbir/jsnix)，看起来挺有意思的，里面甚至还藏了一个CTF。不过我感觉他这个终端和博客本身并没有真正联动起来，本质上只是一个模拟了Linux Shell行为的交互界面。除此之外我还发现了另一个风格类似的[个人主页](https://github.com/Luyoung0001/myWebsite)，它虽然也走了终端风格，但功能更简单，还原度也不算高。不过它至少和博客内容做了一些基础联动——尽管目前也只是做到列出文章这种程度😂，当然有这类功能的博客应该也不少，只是我发现的不太多……于是我就想，不如我也给自己的博客加一个类似的“命令行访问”功能，应该会很有趣。当然如果真要做的话，我肯定不会满足于只实现几个模拟指令——既然要做，就要追求真实感，至少得在浏览器上运行真实的Linux终端，才不会让人觉得出戏吧😋。   

# 在浏览器中运行Linux
## 虚拟机方案
### 纯JS虚拟机
  要说到在浏览器上运行Linux，最先想到的应该就是[Fabrice Bellard](https://bellard.org)大神写的[JSLinux](https://bellard.org/jslinux/)吧，这可能是第一个在浏览器中实现的虚拟机（毕竟是最强虚拟机QEMU的作者编写的）。现在他的个人主页中展示的这个版本是WASM版本，而他最早写的是纯JS实现的。那个JS实现的版本现在在GitHub上有一个[去混淆的版本](https://github.com/levskaya/jslinux-deobfuscated)可以用作学习和研究，于是我顺手Fork了一份在GitHub Pages上部署作为[演示](https://mabbs.github.io/jslinux/)。   
  作为纯JS实现的x86虚拟机，性能估计是最差的，但相应的兼容性也最好，在Bellard当年写JSLinux的时候，还没有WASM这种东西呢，所以即使是在不支持WASM的IE11中，也可以正常运行。假如我想把它作为终端用在我的博客上，似乎也是个不错的选择，即使我完全看不懂代码，不知道如何实现JS和虚拟机的通信，它也预留了一个剪贴板设备，可以让我轻松地做到类似的事情，比如我在里面写个Bash脚本，通过它和外面的JS脚本联动来读取我的文章列表和内容，那也挺不错。   
  当然Bellard用纯JS编写虚拟机也不是独一份，他实现了x86的虚拟机，相应的也有人用纯JS实现了RISC-V的虚拟机，比如[ANGEL](https://github.com/riscv-software-src/riscv-angel)，看起来挺不错，所以同样也顺手[搭了一份](https://mabbs.github.io/riscv-angel/)。只不过它似乎用了一些更先进的语法，至少IE11上不能运行。   
  另外还有一个比较知名的项目，叫做[jor1k](https://github.com/s-macke/jor1k)，它模拟的是OpenRISC架构。只是这个架构目前已经过时，基本上没什么人用了，不过这里面还内置了几个演示的小游戏，看起来还挺有意思。   
  除了这些之外，其实能在浏览器上运行的Linux也不一定是个网页，有一个叫做[LinuxPDF](https://github.com/ading2210/linuxpdf)的项目可以让Linux运行在PDF中，它的原理和JSLinux差不多，所以需要PDF阅读器支持JS，看它的介绍貌似只能在基于Chromium内核的浏览器中运行，而且因为安全问题在PDF中有很多功能不能用，所以它的速度甚至比JSLinux还要慢，功能还很少，因此它基本上只是个PoC，没什么太大的意义。   
### WASM虚拟机
  那还有别的方案吗？既然Bellard都选择放弃纯JS的JSLinux而选择了WASM，显然还有其他类似的项目，比如[v86](https://github.com/copy/v86)，这也是一个能在浏览器中运行的x86虚拟机，不过因为使用了WASM和JIT技术，所以效率要比纯JS的JSLinux高得多。另外作为虚拟机，自然是不止能运行Linux，其他的系统也能运行，在示例中除了Linux之外还有DOS和Windows之类的系统，功能还挺强大，如果能自己做个系统镜像在博客里运行，似乎也是不错的选择。   
  另外还有一个相对比较知名的叫[WebVM](https://github.com/leaningtech/webvm)，从效果上来说和v86几乎没有区别，同样使用了WASM和JIT技术，也都只支持32位x86，然而它的虚拟化引擎CheerpX是闭源产品，既然和v86都拉不开差距，不知道是谁给他们的信心把它作为闭源产品😅。不过看它的说明文档，其相比于v86的主要区别是实现了Linux系统调用，考虑到它不能运行其他操作系统，而且Linux内核也不能更换，那我想它可能是类似于WSL1的那种实现方案，也许性能上会比v86好一些吧……只不过毕竟是闭源产品，不太清楚具体实现了。   
  既然纯JS有RISC-V的虚拟机，WASM当然也有，比如[WebCM](https://github.com/edubart/webcm)。这个项目相比于其他的项目有个不太一样的地方，它把虚拟机、内核以及镜像打包成了一个单独的WASM文件……只是这样感觉并没有什么好处吧，改起来更加复杂了。   
  以上这些虚拟机方案各有不同，但是想做一个自己的镜像相对来说还是有点困难，于是我又发现了另一个项目：[container2wasm](https://github.com/container2wasm/container2wasm)，它可以让一个Docker镜像在浏览器中运行，当然实际实现其实和Docker并没有什么关系，本质还是虚拟机，只是制作镜像的时候可以直接用Docker镜像，方便了不少，但Docker镜像一般也都很大，所以第一次加载可能要下载很长时间。另外它还有一个优势，可以使用[Bochs](https://bochs.sourceforge.io/)运行x86_64的镜像，不像v86和WebVM只能模拟32位的x86（虽然Bochs的运行效率可能会差一些），而且可以使用WASI直接访问网络，不像以上几个项目如果需要访问网络需要用到中继服务。当然访问网络这个还是要受浏览器本身的跨域策略限制。总之从项目本身来说感觉也算是相当成熟了，尤其能用Docker镜像的话……我甚至可以考虑直接用[镜像](https://hub.docker.com/r/unmayx/mabbs)在线演示我曾经的[Mabbs](https://github.com/Mabbs/Mabbs.Project)项目😋。   
## 纯WASM方案
  其实想要在浏览器中运行Linux也不一定非得要用虚拟机，用虚拟机相当于是把其他指令集的机器码翻译为WASM，然后浏览器还得再翻译成宿主机CPU支持的指令集，然而WASM本身其实也算是一种指令集，各种编译型语言编写的程序也能编译出WASM的产物，比如[FFmpeg](https://github.com/ffmpegwasm/ffmpeg.wasm)。所以Linux内核也完全可以被编译成WASM，正好前段时间我看新闻说[Joel Severin](https://github.com/joelseverin)做了这么一个[项目](https://github.com/joelseverin/linux-wasm)，对Linux内核做了一些修改使其可以被编译为WASM程序，我试了一下，貌似在Safari浏览器中不能正常工作……Chrome浏览器倒是没问题，不过即使这样用起来BUG也很多，随便执行几条命令就会冻结，体验不是很好。   
  沿着这个项目，我又找到一个由[Thomas Stokes](https://github.com/tombl)制作的[项目](https://github.com/tombl/linux)，和Joel的项目差不多，但我测了一下可以在Safari上运行，感觉这个项目更完善，不过之前那个项目上了新闻，所以⭐️数比这个更高😂。   
  于是我把它复制了一份，在我的GitHub Pages上[部署](https://mabbs.github.io/linux/)了，但直接用仓库中的源代码会显示“Error: not cross origin isolated”，然而在Thomas自己部署的网站中可以正常打开，我看了一眼貌似是因为在GitHub Pages中没有[COOP和COEP响应头](https://web.dev/articles/coop-coep)导致的。Linux作为多任务操作系统来说，当然要运行多个进程，而Linux要管理它们就需要跨线程（Web Worker）读取内存的能力，所以用到了SharedArrayBuffer对象。不过由于CPU曾经出过“幽灵”漏洞，导致现代浏览器默认禁止使用SharedArrayBuffer对象，除非在服务器中配置COOP和COEP响应头才可以用，但是Joel的项目也是在GitHub Pages上运行的啊，为什么可以正常运行？看了源代码后才发现原来可以[用Service Worker作为反向代理](/2025/08/01/sw-proxy.html)来给请求的资源加上响应头，他使用的是[coi-serviceworker](https://github.com/gzuidhof/coi-serviceworker)这个项目，所以我也给我部署的代码中加上了这个脚本，总算是解决了这个问题。   
  部署好这个项目之后我试用了几下，虽然有些操作仍然会导致系统冻结，但相比Joel的版本来说已经好多了。很遗憾的是目前这个WASM Linux还不能和外界通信，所以作用不是很大，另外如果想在里面运行其他二进制程序还是相当困难，首先在WASM中不存在内存管理单元（MMU），不能实现隔离和分页的功能，另外以WASM作为指令集的环境下编译的产物也得是WASM，所以目前来说想用它做点什么还是不太合适。   
  以上的这两个将Linux内核编译为WASM的方案其实相当于给内核打补丁，然后把浏览器看作是虚拟机来运行，有点像Xen，不过还有一种让Linux原生运行在WASM的[项目](https://github.com/okuoku/wasmlinux-project)，它将[Linux kernel library](https://github.com/lkl/linux)编译为了WASM。那么什么是LKL？简单来说它有点像Wine，就和我之前所说的[OS模拟器](/2024/12/08/simulator.html)差不多，可以提供一个环境，让程序以为自己在Linux下运行，所以说它和之前的实现有一些不一样，它不存在内核模式，更像是一个普通的程序，而不是系统了。   
  不过这个项目的体验也比较一般，它无论做什么都得按两次回车，看说明的意思貌似是因为没有实现异步信号传递，所以要手动打断`read`函数，而且也经常莫名其妙卡住，总体体验不如Thomas的项目。   
## 模仿的Linux
  其实如果只是想做到和Linux类似的功能，也有这样的项目，比如[WebContainers](https://github.com/stackblitz/webcontainer-core)，它没有运行Linux系统，但是模拟了一个环境，可以在浏览器中运行Node.js以及Python之类的脚本，而且让脚本以为自己在Linux中运行，除此之外它还能用Service Worker把环境中运行的端口映射给浏览器，可以算是真的把服务端跑在浏览器上了。这个技术还挺高级，不过想想也挺合理，毕竟有WASI，直接编译为WASM的程序也不需要操作系统就能运行，所以用WASM去运行Linux本来就有点多此一举了😂。不过很遗憾的是WebContainers也不是开源软件，要使用它只能引入StackBlitz的资源，而且全网完全没有开源的替代品……也许在浏览器上进行开发本来就是个伪需求，所以没什么人实现吧。   
  当然如果只是实现和WebContainers类似的功能，[JupyterLite](https://github.com/jupyterlite/jupyterlite)也可以实现，它可以在浏览器中像使用本地JupyterLab那样运行JS和Python，还能用Matplotlib、Numpy、Pandas进行数据处理，功能可以说非常强大，而且还是开源软件。只不过它没有模拟操作系统的环境，所以不能运行Node.js项目，也不能提供终端，所以不太符合我想要的效果……   

# 总结
  总的来说，如果想要在博客上搞Linux终端，目前来看似乎虚拟机方案会更靠谱一些，虽然相对来说效率可能比较低，但毕竟目前WASM方案的可靠性还是不够，而且考虑到还需要配置额外的响应头，感觉有点麻烦，当然我觉得WASM还是算未来可期的，如果成熟的话肯定还是比虚拟机要更好一些，毕竟没有转译性能肯定要好不少。至于WebContainers这种方案……等什么时候有开源替代再考虑吧，需要依赖其他服务感觉不够可靠。只是也许我的想法只需要模拟一个合适的文件系统，然后给WASM版的Busybox加个终端就够了？不过这样感觉Bug会更多😂。   
  至于打算什么时候给博客加上这个功能？应该也是未来可期吧😝，目前还没什么好的思路，仅仅是分享一下在浏览器中运行Linux的各种方法。   