132 lines
4.5 KiB
Markdown
132 lines
4.5 KiB
Markdown
# Musl libc in the Mittos kernel
|
|
|
|
The Mittos kernel is linked with Musl libc which provides functionality such as fine-grained memory management and string formating.
|
|
|
|
The [musl library](https://musl.libc.org/) is a tiny c standard library impelentation which is easy to use. It is however highly linux-specific and therefore relies of the availability of a large number of linux syscalls.
|
|
|
|
In building musl for the kernel, I patch away this reliance entirely and replace syscalls with calls to internal functions that are prototyped and can then be provided by the kernel or a separate library when needed.
|
|
|
|
By syncing a patch directory into the musl source with rsync before compilation:
|
|
```
|
|
> rsync -a src/patch-musl/ external/musl/
|
|
```
|
|
I can provide ovrrides for specific parts of the musl code such as the following which replaces all `syscall` instructions with calls to external functions:
|
|
|
|
`src/patch-musl/arch/x86_64/syscall_arch.h`
|
|
```c
|
|
#define __SYSCALL_LL_E(x) (x)
|
|
#define __SYSCALL_LL_O(x) (x)
|
|
|
|
extern long syscall0(long);
|
|
extern long syscall1(long, long);
|
|
...
|
|
extern long syscall6(long, long, long, long, long, long, long);
|
|
|
|
static __inline long __syscall0(long n) {
|
|
return syscall0(n);
|
|
}
|
|
...
|
|
static __inline long __syscall6(long n, long a1, long a2, long a3, long a4, long a5, long a6) {
|
|
return syscall6(n, a1, a2, a3, a4, a5, a6);
|
|
}
|
|
|
|
#define VDSO_USEFUL
|
|
#define VDSO_CGT_SYM "__vdso_clock_gettime"
|
|
#define VDSO_CGT_VER "LINUX_2.6"
|
|
#define VDSO_GETCPU_SYM "__vdso_getcpu"
|
|
#define VDSO_GETCPU_VER "LINUX_2.6"
|
|
|
|
#define IPC_64 0
|
|
```
|
|
|
|
The kernel is then linked with `-L/opt/sysroot/usr/lib -lc`, but must provide the functions `syscall0(long)` through `syscall6(long, long, long, long, long, long, long)` itself.
|
|
|
|
At first, those can be very simple:
|
|
```c
|
|
long syscall0(long num) {
|
|
PANIC("Unknown syscall: %d()\n", num);
|
|
}
|
|
```
|
|
This will clearly tell me when I try to use a c function that requires support for a syscall, such as `malloc` which will result in the panic message "Unknown syscall: 12(0)".
|
|
|
|
Looking into the musl source code, the file `arch/x86_64/bits/syscall.h.in` tells me that syscall 12 is `brk`, so that's a good thing to start implementing.
|
|
|
|
Once that's done, I put a reference to my `k_brk` function into an array and update the `syscallN` functions to call my implementations if available:
|
|
```c
|
|
long syscall0(long num) {
|
|
long retval = 0;
|
|
if(syscall_handlers[num])
|
|
retval = syscall_handlers[num](0, 0, 0, 0, 0, 0);
|
|
else
|
|
PANIC("Unknown syscall: %d()\n", num);
|
|
return retval;
|
|
}
|
|
```
|
|
|
|
The `k_brk` implementation is very simple too. Just allocate pages until the brk requirement is fulfilled:
|
|
```c
|
|
static long _brk = KERNEL_BRK0;
|
|
long k_brk(long brk, long, long, long, long, long) {
|
|
if(brk) {
|
|
while(_brk < brk) {
|
|
vmm_set_page(kernel_P4, _brk, pmm_alloc(), PAGE_GLOBAL | PAGE_WRITE | PAGE_PRESENT);
|
|
_brk += PAGE_SIZE;
|
|
}
|
|
}
|
|
return _brk;
|
|
}
|
|
```
|
|
|
|
And now I can use standard `malloc` and `calloc` in my kernel. After implementing `mmap` as well, I also have `realloc`. Nice!
|
|
|
|
## printf
|
|
In order to enable `printf` for debug output, I only need two "syscalls" mocked - `ioctl` and `writev`.
|
|
|
|
When writing to stdout, musl will call `ioctl` with command `0x5413` to get the terminal size:
|
|
```c
|
|
long k_ioctl(long fd, long cmd, long arg3, long, long, long) {
|
|
if(fd == 1 && cmd == 0x5413) {
|
|
struct {
|
|
unsigned short ws_row;
|
|
unsigned short ws_col;
|
|
unsigned short ws_xp;
|
|
unsigned short ws_yp;
|
|
} *wsz = (void *)arg3;
|
|
wsz->ws_row = 24;
|
|
wsz->ws_col = 80;
|
|
return 0;
|
|
}
|
|
long retval = -1;
|
|
PANIC("Unknown IOCTL request: fd: %d, command: %x\n", fd, cmd);
|
|
return retval;
|
|
}
|
|
```
|
|
|
|
And it will then call `writev` with the vectors of data to write:
|
|
```c
|
|
#include <sys/uio.h> // for struct iovec
|
|
long k_writev(long fd, long iov, long iovcnt, long, long, long) {
|
|
if(fd == 1)
|
|
{
|
|
size_t len = 0;
|
|
struct iovec *v = (void *) iov;
|
|
for(int i = 0; i < iovcnt; i++) {
|
|
debug_putsn(v[i].iov_base, v[i].iov_len);
|
|
len += v[i].iov_len;
|
|
}
|
|
return len;
|
|
}
|
|
long retval = 0;
|
|
PANIC("Unknown writev request: fd: %d\n", fd);
|
|
return retval;
|
|
}
|
|
```
|
|
|
|
I then have a fully featured printf implementation to help me in "printf debugging".
|
|
|
|
## Relevant files
|
|
- `toolchain/build-musl.sh`
|
|
- `src/patch-musl/arch/x86_64`
|
|
- `src/kernel/lib/musl-glue.c`
|
|
- `src/kernel/include/musl-glue.c`
|
|
- `src/kernel/memory/kbrk.c` |