Writing a Kernel in Rust: Code Quality and Performance by Luc Lenôtre

ScyllaDB 288 views 23 slides Oct 16, 2024
Slide 1
Slide 1 of 23
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23

About This Presentation

Maestro kernel began as a C-based school project and transitioned to Rust for better code quality. Now, it's in a clean-up and performance enhancement phase. This talk shares key lessons learned along the way. #RustLang #KernelDevelopment #Programming


Slide Content

A ScyllaDB Community
Writing a Kernel in Rust:
Code Quality and Performance
Luc Lenôtre
SRE at Clever Cloud

Luc Lenôtre he/him

SRE at Clever Cloud
■Programming since I am 10
■Created Maestro
■Has a blog talking about it: https://blog.lenot.re

About Clever Cloud
Clever Cloud is a european ?????? cloud company
■We aim at making developers’ life easier
●Just push your code
●Fully managed PaaS and SaaS
■Deliver you applications faster
■Highly technical stack with custom implementations

What is Maestro?

For a long time, my priority was not performance, but getting something that
works.
■Boot was taking 15 seconds on my computer
■The command ls -lR / was taking 1min10s for around 2000 files

A kernel is a big piece of software, so where do we start?
Performance issues

Profiling with QEMU (first attempt)

First attempted to write a poor man’s profiler
■Use gdb to interrupt QEMU periodically and print the stack
■Pro: Easy to implement
■Con: So slow it becomes unusable

See: https://poormansprofiler.org/

Profiling with QEMU (second attempt)

Then I tried writing a QEMU TCG plugin.
■TCG is one of the backends QEMU uses (alongside KVM)
■Allows to observe the state of the VM
■Use it to interrupt the execution and collect callstacks
■Wrote a tiny program to aggregate the stacks and make a Flamegraph

The results

The strlen function (before)
size_t strlen(const char *s)
{
size_t n = 0;

while (s[n])
++n;
return n;
}
■Checking one byte at a time is slow

The strlen function (after)
■Taken from musl’s code
■jameshfisher.com/2017/01/24/bitwise-check-for-zero-byte
#define LOW ((size_t) -1 / 0xff)
#define HIGH (LOW * 0x80)
#define ZERO(w) (((w) - LOW) & (~(w) & HIGH))

The HashMap collection
■Data structures must be reimplemented to handle memory allocation failures
■My implementation of the HashMap was rather bad
●Used Swiss Tables instead. See the talk by Matt Kulukundis at CppCon 2017
■The hash function was inefficient (XOR hash)
●fxhash has a much better distribution

Files Management: Path (before)
pub struct Path {
absolute: bool,
parts: Vec<String>,
}
■Requires as many memory allocations as components
●Example: /usr/bin/bash requires three allocations
■Splits components even if not required

Files Management: Path (after)
pub struct PathBuf(String);

pub struct Path([u8]);
■Close to the original Rust interface
■Different versions for owned and borrowed
●Borrowing avoids unnecessary memory allocations

Files Management: Path (after)
pub enum Component<'p> {
RootDir,
CurDir,
ParentDir,
Normal(&'p [u8])
}
■Reusing memory from the path structure

Files Management: File (before)
■Issue: Each access requires redoing path resolution
■Replace those fields with the mountpoint ID and inode?
●How to point to kernfs files?
pub struct File {
name: String,
parent_path: PathBuf,

// permissions, timestamps, size, etc...
}

Files Management: File (after)
How does Linux do it?
struct file {
const struct file_operations*f_op;
void*private_data;
// ...
}

Files Management: File (after)
■The NodeOps trait defines functions to perform operations on the file
■ops replaces both f_op and private_data
pub struct File {
ops: Box<dyn NodeOps>,
// ...
}

Files Management: File (after)
Implementation for /proc/meminfo (0 bytes, no allocation):
#[derive(Debug, Default)]
pub struct MemInfo;

impl NodeOps for MemInfo {
// ...
}

System calls
■When passing a pointer to a system call, the kernel has to check read/write
permissions to the pointer
■Searching data structures is expensive
■Solution: copy from userspace and catch errors
●Copying here is actually not that expensive (0.3% of the whole CPU time)

The results

The results
■Booting now takes 0.3 seconds (x50 times faster)
■ls -lR / now takes 8 seconds (x8.75 times faster)

The current state of the project
Still missing:
■Files caching
●A file access is a disk access, which is slow
●Unused RAM is wasted RAM
■Cooperative scheduling
●Minimize context switches
●Explore the use of async/await

Thank you!
Luc Lenôtre
blog.lenot.re
github.com/maestro-os
Tags