Biscuit: The benefits and costs of writing a POSIX kernel in a high-level language
One-line Summary
This paper analyzes (duh) the benefits and costs of writing a POSIX kernel in a high-level language, Go.
Paper Structure Outline
Introduction
Related work
Motivation
Why C?
Why an HLL?
Overview
Garbage Collection
Go's collector
Biscuit's heap size
Avoiding heap exhaustion
Approach: reservations
How Biscuit reserves
Static analysis to find s
Basic MAXLIVE operation
Handling loops
Kernel threads
Limitations
Heap exhaustion summary
Implementation
Evaluation
Biscuit's use of HLL features
Potential to reduce bugs
Experimental Setup
HLL tax
GC delays
Sensitivity to heap size
Go versus C
Ping-pong
Page-faults
Biscuit versus Linux
Handling kernel heap exhaustion
Lock-free lookups
Discussion and future work
Conclusions
Background & Motivation
The main reason for using low level languages like C to implement a kernel is that C supports low-level techniques that can help performance (pointer arithmetic, explicit memory allocation, etc.).
High level languages (HLL), on the other hand, have some potential advantages compared to C:
Automatic memory management: reduces programmer effort and use-after-free bugs
Type-safety: detects bugs
Runtime typing and method dispatch: helps with abstraction
Language support for threads and synchronization eases concurrent programming
With the idea of exploring the possibility of using a HLL to implement a monolithic POSIX-style kernel in mind, the authors present Biscuit, a kernel written in Go, which has good performance.
Design and Implementation
The Biscuit kernel is written using 27583 lines of Go, 1546 lines of assembly, and no C. Biscuit provides 58 syscalls and it has enough POSIX compatibility to run some existing server programs (NGINX, Redic, etc.).
Garbage collection
Biscuit uses Go's collector, which suspends ordinary execution on all cores ("stop-the-world" pause of ~10μs) twice during a collection. This hurts tail latency the most, and it's especially bad for machines that are dependent on pauses (e.g., datacenters).
Avoiding heap exhaustion
Heap exhaustion refers to live kernel data completely filling the RAM allocated for the heap. Waiting for memory in allocator might lead to deadlocks; Checking and handling allocation failure (like C kernels) is difficult to get right, and Go does not expose failed allocations. Biscuit uses reservations as a solution:
A syscall does not start until either it can reserve enough heap memory or a killer thread frees up some memory. To execute a syscall,
Evaluation
Some missing features of Biscuit:
Scheduling priority (relies on Go runtime scheduler)
Does not handle large multicore machines or NUMA
Does not swap or page out to disk
Does not implement reverse page mappings (revoke shared pages)
Security features (Users, access control lists, address space randomization)
58 out of 300-400 syscalls
The experiments used three kernel-intensive applications: CMailbench, NGINX, and Redis.
Tput: throughput in application requests per second
Prologue cycles: the fraction of total time used by compiler-generated code at the start of each function that checks whether the stack must be expanded, and whether the garbage collector needs a stop-the-world pause
Safety cycles: the cost of runtime checks for nil pointers, array and slice bounds, divide by zero, and incorrect dynamic casts
Alloc cycles: the time spent in the Go allocator, examining free lists to satisfy allocation requests (but not including concurrent collection work)
New Vocabulary
CVE: Common Vulnerabilities and Exposures
Code path: the set of specific instructions that are actually executed during a single run of a program or program fragment.
Links
Last updated