Building a unikernel that runs WebAssembly – part 1
flavio.castelli.me
flavio.castelli.me
Options that come to mind are:
- build your application as a linux kernel module, load it into a normal kernel, and generally ignore the userspace that runs anyway
- take Linux and hack it down pretty aggressively plus splice your code into it
- find some github unikernel effort and go from there (which I think the OP does)
- take some other OS - freebsd? - and similarly hack out parts
Other?
I like the idea of a x64 machine running a VM connected to a network card as a generic compute resource that does whatever tasks are assigned by sending it data over the network. It's not been worth the hassle relative to a userspace daemon, but one day I may find the time and would be interested in the HN perspective on where best to start the OS level hackery.
> fully-standalone, specialised unikernel that runs under a Xen or KVM hypervisor.
Or maybe xen / kvm are no longer called operating systems?
I'm interested in having my code be responsible for thread scheduling and page tables - no OS layer to syscall into - but am not as keen on DIYing the device drivers to get it talking to the rest of the world.
> I replace the [QubesOS] Linux firewall VM with a MirageOS unikernel. The resulting VM uses safe (bounds-checked, type-checked) OCaml code to process network traffic, uses less than a tenth of the memory of the default FirewallVM, boots several times faster, and should be much simpler to audit or extend.
NanoVMs has OSS tools for golang unikernels on multiple hypervisors and cloud platforms, https://nanovms.com/dev/tutorials/running-go-unikernels
But MirageOS does exactly that, last I looked. As does RustyHermit.
> I'm interested in having my code be responsible for thread scheduling and page tables - no OS layer to syscall into [...]
You might be confusing Xen and KVM here? Xen and KVM are rather different in this regard.
KVM runs on a full Linux kernel (as far as I know). But running your application as unikernels on top of Xen is more comparable to the old Exokernel concept.
> The Unikernel Linux (UKL) project started as an effort to exploit Linux’s configurability.. Our experience has led us to a more general goal: creating a kernel that can be configured to span the spectrum between a general-purpose operating system, amenable to a large class of applications, and a highly optimized, possibly application- and hardware-specialized, unikernel... other technologies occupying a similar space have come along, especially io_uring and eBPF. io_uring is interesting because it amortizes syscall overhead. eBPF is interesting because it’s another way to run code in kernel space (albeit for a very limited definition of “code”).
Code, https://github.com/unikernelLinux/ukl
> Unikernel Linux (UKL) is a small patch to Linux and glibc which allows you to build many programs, unmodified, as unikernels. That means they are linked with the Linux kernel into a final vmlinuz and run in kernel space. You can boot these kernels on baremetal or inside a virtual machine. Almost all features and drivers in Linux are available for use by the unikernel.
There are projects that permit statically linking a traditional kernel with a traditional application into a unikernel. NetBSD pioneered this with their rump kernel build framework, and I believe there's at least one Linux build framework that mimics this. The build frameworks cut out the syscall layer; an application calling read(2) is basically calling the kernel's read syscall implementation directly. Often you don't need to change any application source code. The build frameworks handle configuring and building the kernel image, and statically linking the kernel image with your application binary to produce the unikernel image.
- take Linux and hack it down pretty aggressively plus splice your code into it
But rather than starting with a Linux distro and hacking it down, I'd start the other way: Boot the kernel directly (via a UEFI bootloader). You can embed a basic filesystem structure (/dev, /proc, /etc, etc.) in a binary blob inside the kernel file itself on build (kind of dumb that this is required at all, but it is)). The kernel itself has basically everything you'd need (for any reason you'd want a unikernel).
Nanos has had strace/ftrace/gdb, plenty of apm/monitoring such as cloudwatch and all sorts of tools in/around that realm for years now.
https://docs.ops.city/ops/debugging
https://nanovms.com/dev/tutorials/using-netconsole-for-debug...
https://nanovms.com/dev/tutorials/debugging-unikernels-using...
https://nanovms.com/dev/tutorials/profiling-unikernel-syscal...
https://nanovms.com/dev/tutorials/debugging-nanos-unikernels...
https://nanovms.com/dev/tutorials/profiling-and-tracing-nano...
1. Minimizing an existing general-purpose OS
2. By-passing the OS
3. Starting from scratch
You can read more in detail about this here from Unikraft's documentation[0].
[0]: https://unikraft.org/docs/concepts/design-principles#approac...
ops pkg load eyberg/wasmedge:0.9.1 -c config.json
You could replicate this is seconds and then push that image to AWS or GCP also in seconds.That's a lot more mature than RustyHermit, last I looked.
He made a few crates which handles the boot process, paging, x86 structures and more.
What I hope most is endurance. There are many programs that we are not able to run anymore. The best examples are probably older games. I hope WASM will change that, although I'm a little bit nervous about adding new features, because simple specs have a higher chance of surviving, but the future of binaries looks exciting.
Maybe. As inconvenient as accessibility is, with any luck the need to make web content legible to screen readers will also keep adblockers working. Even with wasm, I don’t think the DOM is going anywhere any time soon. I haven’t seen any proposal to replace it.
Back then "the network is the computer" people ended up shipping thin X clients: https://en.wikipedia.org/wiki/Network_Computer in order to do richer applications.
I have very mixed feelings about WASM. There is a large... hype-and-novelty screen held up in front of it right now.
There are many Bad Things about treating the web browser as nothing more than a viewport for whatever UI designer and SWE language-of-the-wek fantasy is going around. Especially when we get into things like accessibility, screen readers, etc.
As for the people treating WASM as the universal VM system outside the browser... Yeah, been down that road 30 years ago, that's what the JVM was supposed to be? But I understand that's not "cool" now, so...
Sigh.
But it's also missing, like, a garbage collector and other things that the JVM offered up and did really really well. People are doing dumbass stuff like running garbage collected interpreters inside WASM, inside V8 (which has its own GC) in the browser. It's like nested dolls, just pointless tossing of CPU cycles into the wastebin. Their (or their VC's) money, but jeez.
You can say "oh, that's coming" (GC extensions in WASM) but that hardly inspires confidence because it took 20 years for the JVM to reach maturity on this front. Best case scenario we'll have a decent GC story in WASM in 10.
Eventually one of them emerges as the main one, and then there are all the others not necessarly having access to everything like in the early days.
One sees this in the Amsterdam toolkit, IBM TIMI, TDF, and more recently CLR, where it seems to mean C# Language Runtime instead of the original Common Language Runtime, since the .NET Framework to .NET Core transition, and decrease of investment into VB, F# and C++/CLI development and feature parity with C#.
The thing that nags me with WASM is how so many people try to sell it, as if it was the very first of its kind.
I don't get that vibe. Just ask, how do you get to write applications with good, predictable performance, perhaps with multithreading and explicit memory management, in the browser?
It doesn't matter how much of this has existed before in some form or shape. It's ablut the "product" more than it is about grandiose ideas (and the product might not be completely there yet, at least it wasn't some 3 years ago)
Or talking about how "safe" WASM happens to be, while there are already some USENIX papers slowly making their appearance regarding WASM based attacks.
1. WASM as a browser tech for delivering rich applications inside the browser. On this one I will shrug. I understand the motivation. I don't particularly like it, because my vision of the "web" is not that, but it's a lost battle and I don't have a horse in this race. It's effectively the resurrection of Java applets, but done better, and more earnestly. It's going to solve the kinds of problems you're talking about, I guess, but introduce new ones (even more inconsistency of UX, accessibility features, performance issues, etc.)
2. WASM as a general / universal runtime for server side work. On this, I see a lot of hype, thin substance, a lot of smoke but no fire, and I'm quite skeptical. It looks to me like classic "Have a Hammer, Going to Go find Nails" syndrome. I was initially enthused about this aspect of WASM but I had a job employed working with WASM for a bit and I found a lot to be skeptical about. And while likely will be using WASM in some fashion similar to this for a project I have, I am also not convinced that WASM itself makes a lot of sense as some sort of generic answer for containerization, and looks to me like duplication of effort, claims of novelty where there is none, unhealthy cycles in the tech industry, etc.
Anyways, I think the person you're replying to, and myself, are primarily talking about #2 -- as was the original article
I believe and agree with most of you wrote ;)
The main problem with HTML/CSS/JS is programmers want more than these languages offer. With WASM you can pick up language(must compile to .wasm) that fits your use case best. This is the freedom most programmers want.
There will always be programmers who will draw their custom buttons(instead of modifying DOM from WASM) and ignore accessibility. They can do this with JS as well, but most of them don't.
But is odd after all these years the browser killed off a big junk of "native" apps on the desktop, but in mobile, there's a whole other story.
Which makes me think the problem all along was about distribution, not technology.
> the JVM was supposed to be? But I understand that's not "cool" now
Both of these criticisms in the same post?
As opposed to the basket of kittens known as JavaScript?
Next up, I want to configure the hypervisor with a WireGuard connection (possibly through something like Tailscale to establish connections?)...
So I have WebAssembly over here on this machine, talking directly to this WebAssembly over there. Based on configuration and capabilities being passed in. Rather than based on the process opening TCP connections to random locations.
https://nanovms.com/dev/tutorials/running-nanos-wireguard-vp... .
Has anyone contemplated running Zephyr as a unikernel? https://docs.zephyrproject.org/latest/boards/x86/acrn/doc/in...
But perhaps someone could make a "wasm-but-it's-actually-RISCV-underneath" kinda thing.
But my prediction is that those will always stay niche, because running WASM on conventional stock hardware will always be faster in general. Mostly because WASM was designed to run fast on stock hardware, and the economics of scale for conventional general purpose processors are much better.
Compare also how the 'International Conference on Functional Programming' started out as the 'Functional Programming and Computer Architecture' conference, but then people figured out how to compile lazy functional programming languages like Haskell to run efficiently on conventional hardware.
Similar also for the Lisp and Java machines: one reason see we don't see things like them anymore is because compiler technology has caught up.
hence why e.g. some cloude on the edge provider convert you docket image to a micro vm when running it
so maybe some use can be found there
through wasm in micro vm in the edge probably will have a hard time competing with wasm as a sandbox on the edge as such provider probably have an Easter time to add useful boundary features/integrations
But to me the value-sell of unikernels is: 1) Perf; squeak out some extra cycles by throwing overboard things you don't need and pulling things into "ring 0" that you do 2) Simplify; Potentially reduce complexity by ditching some of the things you don't need and 3) Security; Potentially change attack surface ... again, by....
To be clear: I don't think this is right for writing microservices and webapps like most of the people on this forum are employed doing... I think the use case is more for people building infrastructure (databases, load balancers, etc. etc.)
[0]: https://www.destroyallsoftware.com/talks/the-birth-and-death...
I can't put my finger on it, but somehow this looks familiar (hint: it starts with a "J", too)
One more recent effort that also implements the same idea is the Phantom OS [1].
In reality, I think there is always going to be a hypervisor to separate the various workloads, and the hypervisor is likely to keep using paging, to support dynamic memory partitioning -- though perhaps with a larger page size, so as to not create too much pressure on the TLB.
The CPUs were microcoded, so the bytecode was for all practical purposes Assembly.
Swapping object graphs out to disk (and substituting entry points by swap-in proxies) was a thing in Smalltalk systems, and I expect Lisp machines must have had their own solutions. For that matter, 16-bit Windows could (with great difficulty) swap on an 8086, and other DOS “overlay managers” existed. Not that I like the idea, necessarily, but this one problem is not unsolvable.
I still remember using overlays on Turbo Pascal, Turbo Basic and Clipper.
Amiga also didn't had a MMU, and we all "enjoyed" our Guru Meditation momments.
the more I think about it the less it makes sense
- js engine rely on vmm, and wasm does so, too (in many ways)
- close to every non embedding, non trivial program I have seen is in subtle ways based on the assumption of vmm
- some vm technology, especially around micro vms uses vmm, too. And Unikernels only really make sense as VMs
Could you expand on this?
The “javascript instruction” is FJCVTZS, which is a rounding mode matching x86 semantics, which is incidentally what JS specifies for double -> int32 conversions, and soft-coding it on top of FCVTZS it is rather expensive (it requires a dozen additional instructions to fix up edge cases).
This is beneficial to javascript (on the order of a percentage point on some benchmarks suites, however pure javascript crypto can get high double digits gains), but it’s also beneficial for any replication of x86 rounding on ARM, including but not limited to emulating x86 on arm (aka Rosetta 2).
The hardware mmu does have costs: tlbs are quite small and looking things up in a several-layer tree adds a lot of latency. If vm were fine, no one would care much about hugepages, and yet people do care about them. (Larger pages means fewer tlb misses and fewer levels in the tree to look up when there is a miss)
> Consequently, modern processors have extremely large and highly associative two-level TLBs per CPU — for example, Intel’s Skylake chip uses 64-entry level-1 (L1) TLBs and 12-way, 1,536-entry level-2 (L2) TLBs. These structures require almost as much area as L1 caches today, and can consume as much as 10 to 15 percent of the chip energy.
Bhattacharjee, Abhishek. "Preserving virtual memory by mitigating the address translation wall." IEEE Micro 37.5 (2017): 6-10.
However, the 4kiB page size that is typically used and is baked into most software was decided on in the mid-1980s, and is tiny compared to today's memory and application working set sizes, causing TLB thrashing, often rendering the TLB solution ineffective.
Whatever overhead software memory protection would add is likely going to be small in comparison to cost of TLB thrashing. Fortunately, TLB thrashing can be reduced/avoided by switching to larger page sizes, as well as the use of sequential access rather than random access algorithms.
As I understand, the Smalltalk world put a lot of engineering effort into making the software-based model work with performance and efficiency. I don’t think the results were encouraging.
A lot has happened since Smalltalk.
Then again, that's the theory, in practice there are many reasons why hardware moved from early segment based architectures to paging, and memory isolation is only one of them.
segmentation was an evil everyone both from the hardware and software side was very happy to get ride of
whoever reintroduced segmentation will probably be burned on a stick by computer developers in the afterlife (/j)
Then, at runtime, it then does nothing at all. Which is very fast.
[0] https://www.microsoft.com/en-us/research/publication/deconst...
Also spectre.
You could of course add dedicated hardware to lower the overhead specifically of memory access permission checks. In fact most CPUs already do, it is called an MMU.
[1] https://www.theseus-os.com/Theseus/book/design/idea.html
Maybe watch the project founder's talk? https://youtu.be/n7r8zO7SodE?si=nswWcFrkTj7K1GpZ
In the video, his argument was that the browsers are single-process anyway, and if everything runs in that process, we don't need that separation. However, since then, we've learned that single-process browsers are a security nightmare, so these days browsers are actually not single-process anymore to provide proper sandboxing.
But I love how close to correct that video is, and it's interesting to see in what ways it turned out to be wrong.
Apple has spent a long time hardening the JavaScriptCore web sandbox to run untrusted code. We’ve come a long way since JailbreakMe’s web-based jailbreak, but ultimately memory safety requires participation from all parts of the stack and JavaScriptCore and V8 are still both written in C++. You can trigger memory-safety vulnerabilities in the host VM using guest code.
wasmtime is supposedly a hardened WebAssembly runtime written in Rust, but it’s also a JIT, and I have no idea if anyone has put it through its paces security-wise yet. The idea is that WebAssembly can have JIT-like performance without JIT-like security concerns thanks to a simpler translation layer and minimal runtime.
I could see an argument for dropping some layers if the VM isolation become stronger
No its not, when it comes to end-user app performance, experience or privacy.
Sure, by adding security we can have another reason to let developers end up with golang app compiled to wasm running within electron sandboxed through API redirection (OS + antimalware/antivirus/BPF based EDR) and use it for, like, listening music in a very secure way..
With all these layers happily streaming all kinds of telemetry to knows where, with owning nothing but a bunch of numbers behind a ton of DRM layers, and with no ability to change things to the point where we can't have an app's theme matching system colors because crossplatform compatibility/security reasons.
Case 1, firefox:
> dom.security.unexpected_system_load_telemetry_enabled > security.app_menu.recordEventTelemetry > security.protectionspopup.recordEventTelemetry > security.certerrors.recordEventTelemetry
I don't want to accept developer's assumption that these have to be enabled by default.
Case 2, Windows: can't even do a build of a trusted codebase under IntelliJ without antimalware adding up, like, +150% to build time. While IntelliJ (or some of its extensions or plugins that creep up during development) is happily reporting that performance issue back to its masters. Ugly.
An exploit to the runtime in such a system obviously would of course be a disaster of upmost proportions, and to have any chance of a decent performance you'd need a very complex (read exploitable) runtime.
The question is.. if you have full isolation and separation of the processes etc... why are you bothering with the WASM now?
WASM can help with portability.
Any sandbox layer can help with anomaly/exploit/bug detection, accelerating fixes to untrusted code, or a neighboring sandbox layer.
"Phrack: Twenty years of Escaping the Java Sandbox" (2018), https://www.exploit-db.com/papers/45517
> Put some WASM in a JVM in the WASM. In an OS. In a hypervisor.
Intel TDX comes to mind.
So I don't think there's an automatic win in terms of performance by ridding yourself of it. Especially if you're running through the (pretty slow) WASM VM layer anyways.
For some applications (e.g. databases), running unikernel or closer to kernel and having direct access to the MMU could be a big win (e.g. see https://github.com/tuhhosg/exmap & https://github.com/viktorleis/vmcache & https://www.cs.cit.tum.de/fileadmin/w00cfj/dis/_my_direct_up...).
For general applications esp those written to a POSIX standard or making assumptions that the machine they're running on looks like a typical modern day computer? Dubious. You'd end up writing a bunch of what the VMM layer does in user code.