Documentation
This commit is contained in:
parent
b9fa2e1d81
commit
0b4a9fdb1e
132
documentation/4 - musl-libc.md
Normal file
132
documentation/4 - musl-libc.md
Normal file
@ -0,0 +1,132 @@
|
||||
# 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`
|
Loading…
x
Reference in New Issue
Block a user