#Maestro - Linux-compatible-ish OS in Rust
1 messages ยท Page 3 of 1
okay after investigating for the whole day, I am now fairly sure this is an issue with how I determine if a physical page is used in multiple places or not
so it was that, and also when mapping the executable file, I was mapping the page in the cache even though the mapping was MAP_PRIVATE (had to make a copy of it instead), so when bash modified a static variable (for example), it was modified for all running bash
agree
banned
well good luck, very exciting milestone indeed
I am indeed here 
based!
I have posted my new blog article!
https://blog.lenot.re/a/page-cache
I think I will renovate my blog at some point. The design could be improved
like I could use stuff from there: https://www.fffuel.co/
I quite like your blog's design tbh
although I suck at web dev and graphic design so idk what looks good ๐ญ
yeah but the 2 cyan blobs on the left and in the bottom right corner feel awkward to me
I think that could be improved
I am currently switching the kernel to rust edition 2024 and this is rather tedious
I have new UBs appearing out of nowhere. I guess they were already present and the update is only revealing them
really? isn't there an auto upgrader
i used that
btw today I discovered multiboot2 does not care about the alignment of ELF section headers and will align them to 4 even though the kernel is a 64 bit ELF (hence requiring alignment at 8)
multiboot2, yes
when limine 
idk
is that a thing?
I don't know how Linux boots 
oh
it's basically an ELF image masquerading as a PE executable
which has a simple entry point to call into linux' main
well I guess I'll have a try at that someday
no thanks I don't like donald trump /j
It has basically the same functions as VESA, you can query the modes, set the modes. It also provides an efficient BitBlitter function, which you can't use from your OS unfortunately. GOP is an EFI Boot Time Service, meaning you can't access it after you call ExitBootServices(). However, the framebuffer provided by GOP persists, so you can continue to use it for graphics output in your OS.
Does that mean you can't use that to do modesetting later? (for example)
you wouldn't do modesetting on the GOP
it's only meant as an initial console
your actual video output should be done by a kernel driver
I see
tbh i don't think having modules is worth it
i'd rather just link everything statically
because that saves like 70% of binary size lol
this is the debug kernel
and this is the release kernel
over 900 symbols in the symtab
and i've already done stripping
if i also strip the .rustc section it goes down to 900kib
still a lot for basically nothing
my debug kernel is 17 661 096 bytes and the release one is 1 589 112 bytes
you build as a binary, right?
I did not do that. What's this section used for?
yes
linking metadata
compiler info, abi info
but it's completely useless to a dynamic linker once everything has been linked
do you have a kconfig equivalent?
or any config beyond cargo features
yeah I have a toml file which is read by the build script
how do you load modules if the kernel is stripped?

holy shit rust binaries are huge
dylib
yeah that's one of the issues with this language
by compiling as a dylib all local symbols are discarded
but they're kept if they're used globally
I don't even remember if my modules are dylibs
what's dylib
.so
turns out they are
just shared object?
but in rust
there's an ABI difference
cdylib only exports C externs
dylib can do rust dynamic linking
which is scuffed tbh
but it works
@inner pilot how do you load your modules?
iirc what I do is that I parse the ELF, and then when doing relocations, when I need the offset of a symbol that is not in the module, I lookup in the kernel's symbols
i do that
you should
this is my code
yeah I do
i need to clean up my loading code but it's basically this
but if you stripped the kernel, you don't have any symbol left, right?
yeah I tried to use lto, that breaks everything
if i compiled as a bin, yes
not if you compile as a dylib
that keeps the .dynsym .dynstr tables
your kernel is a dylib?
yes
that explains a lot
can limine load it?
is it -shared or just -pie
hmmm
so you don't support GRUB? I think GRUB does not like relocatable ELFs
i don't
i'm going to make the UEFI boot an option anyways so you can boot the image without a bootloader
i might also support the linux boot protocol because there are some platforms (like the wiiu) that only really have linux boot loaders
again, I'm still doubting the usefulness of modules in the kernel
all it does is increase size
see what linux' modules do
they're like multiple gbs in size (even compressed)
In my OS I'd like to ship only the drivers that you need, so I need to not have everything in the kernel
ld.lld: error: relocation R_X86_64_PC32 cannot be used against symbol 'g_uacpi_rt_ctx'; recompile with -fPIC
bro I am compiling with fPIC
I am glad I don't have linker issues right now. I hate those
same
i'm happy
all compressed linux drivers on arch are 100MB
that's a lot
the nice thing is that you can really compress this stuff well
because a lot of it is redundant data
someday I will have something else than PATA to access the disk
I've heard NVMe is easy to implement
nice
what's -nostartfiles equivalent for lld
-nostartfiles
error: ld.lld: error: unknown argument '-nostartfiles'
huh
how queer
I must report this to osdev server post-haste
smh
I have a memory allocator bug that appeared, that seem to be fixed by doubling the size of the boot kernel stack
huuuuuh
welp I don't know what could cause this
to how much?
i have 256kib to be safe

help
I use 16 kib
let's use gdb to check if this is really the reason
I used to have that. I already doubled it once
if I'm calculating correctly, I use 2648 bytes of stack
I'm not so sure though
no yeah that's correct
just fill your stack with 'A' and check how much of it isn't overwritten
I thought some of my unit tests were run twice because of a weird UB. Turns out I just had 2 tests with the same name
When crashing, my kernel is using 114 736 bytes of stack
wtf
do you use ton of recursive functions or something?
I don't think so. I'll recheck to be sure (maybe something is going wrong)
there's only 6 functions deep to reach the test that crashes

turns out someone else pointed out that issue on reddit
https://www.reddit.com/r/rust/comments/1dl0igg/rust_stack_usage_is_bad/
lots of stack allocs?
the guy on reddit says the rust compiler relies on llvm removing unnecessary allocations
which are not removed in debug mode I guess
that's so dumb
yeah
i guess just slap an opt level 1 on there
offloading work to llvm? lol
isn't it going to make it annoying when using gdb?
might
idk I've never used any optimization level other than 0 or 3
I use 03 and it works fine
as long as the symbols are there, gdb doesn't seem to care
AFAIK -O1 shouldn't affect control flow structure that much. The real mindbending optimizations like certain forms of loop unrolling, vectorization, other black magic that idk tends to happen at -O2 or -O3.
But gdb should work with binaries produced at either. Just that the one-to-one mapping to source lines might go away with optimization.
well to be sure I will just be adding more stack when building in debug mode 
but now I am curious. Maybe the issue is also present in release mode. I'll have to check that
okay I am done switching to Rust 2024
what the hell
what's causing this?
Rust I guess
-O0
we have determined that rust doesn't remove unnecessary alloca's in opt level 0
ah
today I am removing unused code (yesterday I removed #![allow(dead_code)] just to see and there are massive amounts of useless stuff)
fun fact this actually affects binary size 
how
no clue
i also recently removed the allow dead_code and it shrinked my binary
idk why

on my side it does not
exact same size
weird
ah wait it might be because i'm a library and it included unused symbols
or whatever
maybe I'm just demented
likely
I found a useless #![allow(unused_imports)] somewhere in my code for some reason. Removed it. Got 95 warnings of unused imports 
PTSD sounds like the name of a config bit in x86/amd64
Indeed
and so it begins
In file included from ../../../gprofng/libcollector/collector.h:29,
from ../../../gprofng/libcollector/iolib.c:35:
../../../gprofng/libcollector/iolib.c: In function '__collector_create_handle':
../../../gprofng/libcollector/../src/collector_module.h:123:22: error: too many arguments to function '__collector_util_funcs.write'; expected 0, have 3
123 | #define CALL_UTIL(x) __collector_util_funcs.x
| ^~~~~~~~~~~~~~~~~~~~~~
../../../gprofng/libcollector/iolib.c:231:11: note: in expansion of macro 'CALL_UTIL'
231 | CALL_UTIL (write)(2, errbuf, CALL_UTIL (strlen)(errbuf));
| ^~~~~~~~~
../../../gprofng/libcollector/../src/collector_module.h:115:13: note: declared here
115 | ssize_t (*write)();
| ^~~~~
how does this even happen?
I am not even started cross-compiling stuff. Right now I just want to build binutils for my current computer, with a target to x86_64-unknown-linux-musl
also bash's management of the tty is the worst thing I have ever used. The text on my screen is just a pile of shit and I have to guess whatever I am editing
okay fuck this I am going to sleep
so I managed to cross compile binutils. I just updated from 2.42 to 2.44 and it fixed it. But I think I just actually needed to add --enable-gprofng=no
pretty nice. does it run?
Idk yet. I am trying to cross compile GCC now
I may give up on this for now. I am kinda fed up with cross compilation right now
I guess I'll just test binutils for now
so I tried running ld, objdump, readelf and nm
they all do nothing and return with at status code 0
rebuilding my kernel with the option to print system calls to figure out what's going on

okay so it seems the program is segfaulting during execve?
I just recalled I probably didn't implement support for ELF interpreters, and this program is asking for it
yeah, so next step: implement ELF interpreter support
It's actually after. I said that because it crashed before execve returns but I just remembered execve does not return on success
so I ran ld and it printed:
musl libc (x86_64)
Version 1.2.5
Dynamic Program Loader
Usage: ld [options] [--] pathname [args]
I think I've failed something lmao
okay so musl does not give a shit about AT_EXECFD. I must map the program in memory from the kernel and pass it the phdr with AT_PHDR if I understood correctly
guys, I think I've got ld, readelf and objdump running (didn't try to pass any argument to ld)
screenshot of objdump -S /usr/bin/cat while running
nm works too
I tried copying a .o file to the disk to test ld with something and now the filesystem is corrupted. I guess that means it's time to sleep 
well I've decided to give it a new try before and I've got this:
Tried adding crti.o, etc... and now only the last line of error remains
amazing!! nice one
yeah great work
is that running native linux apps or did u have to cross compile them
Just cross compiled to use musl instead of glibc
Target triple is x86_64-unknown-linux-musl
It uses a syscall that I don't understand
(can't remember how it's called)
rseq?
or something like that
to detect preemption in userspace
its not that bad lol
Probably
easier to cross compile GCC than to implement a syscall 
ld produces a file with a size of zero and I have no idea why
Dumb question, has networking been implemented yet? I remember reading about it on iirc a post on the website but can't remember when nor what the current status is
Maestro + Uutils + Cosmic + Fish shell when 
from a quick look in the repo it seems to yes, including tcp to some extent. idk how complete it is
the default flavour of Maestro will likely ship busybox but there will be an uutils package too as an option
Also I want to have my own DE instead of Cosmic. I guess one could swap it for Cosmic anyways if they want too
I remember that I made the choice a long time ago that on Maestro you could not catch a SIGSYS (signal you get when you attempt to execute a syscall that does not exist). But I can't remember why
(this is not the case on linux iirc)
why does a nonexistent syscall cause a sigsys?
should that not just return enosys and be done?
sigsys is only really used for seccomp
huh, I wasn't aware of that
why does the ELF I linked with ld attempts to get an interpreter at /lib/ld64.so.1 even though I don't have any file with this name anywhere?
i think that's just the default interpreter path ld uses?
that's what's defined in the ld defaults
you shouldn't really link with ld
link with gcc instead
so I have to cross compile gcc then
yeah iirc gcc explicitly specifies --dynamic-linker ...
I'll try to specify myself and see what it does
so now I have the right path for the interpreter. When I execute the resulting program on maestro it gives me a segfault. When I execute it on linux it works. So there's something off in the kernel I have to fix
there's this line in musl 1.2.5:
} else if (type==REL_GOT || type==REL_PLT|| type==REL_COPY) {
which triggers me
(no space in between REL_PLT and ||)
the musl code style is odd in general
like look at this check that the two ranges don't overlap in memmove ```c
if ((uintptr_t)s-(uintptr_t)d-n <= -2*n) return memcpy(d, s, n);

the style is "markov chain of assorted old c software"
i still wonder why the left side has the -n
i you add n to both sides of the inequality you lose the -2* part on the right side
unless it's for overflow or something
no one is brave enough to use this style anymore https://github.com/v7unix/v7unix/blob/master/v7/usr/src/cmd/sh/cmd.c#L65
dear god
What the actual fuck
it's modular ๐
windows
lol
stephen bourne came from britain where algol-68 was a very popular language at the time
so he tried to recreate it with c macros
I can't get the program I am linking with the ld on my kernel to work (musl's dynamic linker can't find the printf symbol). That makes me unhappy
(I am trying to link a .o that I compiled somewhere else (Linux). Maybe that's not something I should be doing anyways)
So I guess I will attempt again to cross compile gcc to make it run on my kernel, then I'll compile the .o from my kernel itself and see if that changes anything
@late oar what's the point of separating Nodes and Entrys? don't they kinda do the same thing?
(Noob question)
you can have several hard links to the same inode
Node is an inode and Entry is a hard link
entity?
like a vfs primitive
yes
I talk about all of that here: https://blog.lenot.re/a/page-cache
Entry is one too?
If you mean by "entity" something that is on disk, then Entry represents a directory entry
that's not really what i meant, but whatever
actually very comprehensive read
๐
I hate cross compilation
i love cross compilation
How
tried jinx or xbstrap?
I have my own thing https://github.com/maestro-os/blimp
hm
The cross/ dir has the necessary to build a toolchain, and then I use the builder/ with the descriptors in https://github.com/maestro-os/blimp-packages
i can only say that cross compiling with jinx and xbstrap was a breeze compared to running my own builder
(I'm sure you didn't want to hear that)
(Yes
)
currently each time I attempt to build a package, the builder is re-downloading the sources because I have no caching on this. I guess I should do that (that could save me both time and mobile data)
mobile data
yes I am not at home right now
I used like half of my monthly quota in like 4 days. I have to do something about this 
140GB/mo
for ~10 euros/mo
I should change my subscription btw. I can get more for less expensive now (like I have seen 200GB for 9 euros recently)
France is kind of an outlier in this
every one in every country is actually getting scammed 
does france have sim govt ID requirements
good question
jesus that is cheap
not cheap enough imo 
lmao
for me its pretty cheap too but i have a contract
its like 5 euros for 30gb of data, unlimited national minutes and like 100 international minutes iirc?
I can get 3 months unlimited data for 25โฌ
i get unlimited data for like 10 eur
5G ja standard nowadays
you get multiple gigabytes? 
we get like 250MB + 500 texts + 500 minutes for $15/mo over here
250mb????
united states?
i get 4GB for like 7,99โฌ/28 days ๐
update: I am giving up on clang + musl. I am attempting cross compilation with gcc + musl right now
๐
Just support that damned syscall glibc wants lol
never 
(I want a musl system, glibc's bloated)
@late oar https://musl.cc/
musl.cc provides static cross- and native- musl-based toolchains for Linux, Windows, and macOS, targeting architectures like ARM, MIPS, PowerPC, RISC-V, S/390, and more.
I used this
use mlibc
That could indeed be more convenient
or mlibc
damn
(Didn't test it on Maestro yet)
bash-5.1# gcc test.c
Error relocating TODO: vsprintf: symbol not found
Error relocating TODO: memcmp: symbol not found
Error relocating TODO: remove: symbol not found
Error relocating TODO: fwrite_unlocked: symbol not found
Error relocating TODO: sscanf: symbol not found
Error relocating TODO: signal: symbol not found
Error relocating TODO: freopen: symbol not found
Error relocating TODO: memset: symbol not found
Error relocating TODO: stpcpy: symbol not found
Error relocating TODO: memmove: symbol not found
Error relocating TODO: fflush: symbol not found
Error relocating TODO: strrchr: symbol not found
Error relocating TODO: kill: symbol not found
Error relocating TODO: strncasecmp: symbol not found
Error relocating TODO: ftell: symbol not found
Error relocating TODO: printf: symbol not found
Error relocating TODO: fgetc: symbol not found
Error relocating TODO: fread_unlocked: symbol not found
Error relocating TODO: putchar: symbol not found
Error relocating TODO: exit: symbol not found
Error relocating TODO: strerror_r: symbol not found
Error relocating TODO: fileno_unlocked: symbol not found
Error relocating TODO: mkstemps: symbol not found
Error relocating TODO: putc_unlocked: symbol not found
Error relocating TODO: __fsetlocking: symbol not found
Error relocating TODO: atoi: symbol not found
Error relocating TODO: ungetc: symbol not found
Error relocating TODO: memchr: symbol not found
Error relocating TODO: fputc_unlocked: symbol not found
Error relocating TODO: fputs_unlocked: symbol not found
Error relocating TODO: strtok: symbol not found
Error relocating TODO: getc_unlocked: symbol not found
Error relocating TODO: sprintf: symbol not found
Error relocating TODO: stderr: symbol not found
Error relocating TODO: stdout: symbol not found
Error relocating TODO: stdin: symbol not found
wonderful
(I think that's an issue with the way I load programs with interpreters)
at least this is working
ld, readelf, objdump, etc... are not working anymore but I think that's because I compiled them statically at the time
now I have /usr/bin/gcc instead of TODO
pretty nice progress
cd kernel before cargo build
ohh sorry, thanks
FINALLY
now I have to try compiling something
The issue was the memory map of the process I think. I was overwriting things with mmap
and I think something is still fucked up since gcc can't manage to run cc1. execve fails with ENOMEM
nice!
I've got cc1 executed (turns out 128MB of memory is not enough, I just increased to 1GB since the executable is like ~300MB). Now I need to implement the sysinfo system call
it's @tawny drum
I have sysinfo, now gcc is segfaulting (I think) after cc1 returned successfully. Investigating...
bro scrolled up
I acc pressed the jump to top button
that's mint's old account
wow 1100 stars in 1 day?
how did you achieve that? (asking for a friend
)
a friend of mine posted my first blogpost on hackernews
ah
bruh
aims to be
well not fully, I am not going to reimplement 100% of it
you know it's like glibc and musl. Linux is glibc and Maestro aims to be musl
ever thought of having a linux driver compat layer? 
that would be very complicated I think
yea, i get that part
i meant, maestro is going to target the linux OS type
while i'm targeting linux_like
I didn't even know this existed 
lol
as you can see it's for OSes that differ from linux abi
or only have a few linux interfaces
based 
maestro + uutils + cosmic would be peak
default will likely be maestro + musl + busybox + custom DE
ahh based
curious, your X implementation ("volva" I think?) is it from scratch or is it xorg-based?
oh wait
it's rust isn't it
nvm im stupid
xorg reimplementation in rust
ah, noice
noice
jumps to address zero. broken PLT I guess?
it happens right after cc1 exits, so I guess they somehow shared the same memory space and cc1 corrupted gcc's?
some of the issues I have to fix right now (I'll be using this message as a reminder):
- the above issue (I still don't know where it comes from)
waitpidreturns in a loop whenWCONTINUEDis passed to it (I just check the status of the process and return if it's running. Instead I should add a flag to the process structure that I set when killed withSIGCONTand clear inwaitpid)- I should not read the whole ELF when executing it but rather read only the relevant structures (ehdr, phdr and interpreter path I think?), otherwise it's very slow when the ELF is big, which is the case for
cc1and that makes it very annoying to debug - I must make sure
brkandmmapcannot overlap each others (maybe that's the cause of the first issue)
why not implement brk with mmap?
that's what I am doing but I just remembered I didn't put any limit on brk and it might just write over stuff allocated with mmap
the opposite is also true, mmap might select an unallocated region that shoud be reserved for brk (which will then be rewritten on top by later brk allocations)
Okay so the brk thing was not the issue but at least now I have support for MAP_FIXED_NOREPLACE
I have a beginning of a clue: when wait4 returns in gcc after cc1 exits, it returns to the address 0 because RCX (which contains the return address for sysretq) is set to 0 for some reason I have to figure out, when a signal is caught (which is SIGCHLD I think)
okay so I was not interpreting the sa_handler field in sigaction correctly. A value of zero should mean we ignore the signal
anyways so, now I have gcc calling cc1, as and collect2 successfully. Only remains ld which is failing because it can't manage to find libz.so.1, which prevents relocating /usr/lib/libctf.so.0.
I think I am getting close โผ๏ธ
On a side note, I really have to make ELF execution faster because that's really long
and I think I have a memory leak preventing me from running the command several times
okay, I suspect libz might be shipped with the glibc and I don't have it because I use musl
I'll try building zlib myself tomorrow, time to ๐ค
compiled zlib and fixed it. Now I have ld running but it fails with this error:
/usr/lib/gcc/x86_64-unknown-linux-musl/15.1.0/libgcc.a: file not recognized: file format not recognized
COME ON, I AM GETTING CLOSE
plays epic music in background
does a terrible little dance to epic music in background
I now suspect my lseek being broken might be the cause of this bug
An offset of 18446744073709550593 probably isn't normal
negative value cast to unsigned?
is lseek supposed to take signed offsets?
maybe
that explains a lot
yoo congrats
Yes, in the case of SEEK_CUR at least
also SEEK_END
oh yeah
๐ฅณ
so what did you have to go through to get here
namely, what did you do to make mlibc compile for your architecture
or whatever libc you are using
maestro is doing linux abi compat so it's just glibc?
pain 
or are you using mlibc
I use musl for this
ah
oh linux compat
makes sense actually
oh ok
i half expected that you just copied binaries from your host :^)
yeah but supposing i wanted to use mlibc for a userspace distro with my boron kernel
what do i need to do
i imagine i need to create the sysdeps
implement them using my OS' syscalls
I mean, I could but also I want to use musl because the glibc's bloated
i mean that's more or less it
I think I have one last fix to make before I can actually merge my PR. chmod changes do not seem to be flushed to disk so when I reboot the executable does not have the execute permission anymore 
yes but i mean like setup, compilation, that kinda thing
do i need to make a cross compiler? do I need to configure something?
aside from that, I had to rewrite all of the ELF loading thing because I was doing it wrong
is it then easy to just configure something with my target's toolchain and make?
do I need linux for this or can I use wsl (1)
i mean mlibc uses meson but building it is pretty standard as far as meson projects go
oh for that question i was talking about things like bash and gcc
you need a cross file that specifies what compiler etc to use, meson setup builddir --cross-file my-cross-file.txt <extra opts> --prefix=/usr, ninja -C builddir DESTDIR=/path/to/my/sysroot ninja -C builddir install
I cross compiled gcc by following: https://www.linuxfromscratch.org/lfs/view/12.3/index.html
The gcc you see running on the screenshot is the stage 2. I run the stage 1 on Linux
well for most autotools stuff you'll need to at least modify config.guess/config.sub so it knows about your os
for gcc you need extra patches to add your target
likewise for binutils
this is probably better continued in a different channel and not in the maestro thread tho :^)
i see
anyway i dont have plans right now to do this, i just need to mentally prepare lmao
glibc uses a system call that I am missing. I didn't understand what it does at the time (I still don't btw, I didn't look back at it) so I just used musl instead 
I will implement it eventually
which one out of curiosity
rseq
yes
all it does is userspace registers a struct with the kernel that saves the rip of abort pointer and the end of the critical section pointer
if userspace is preempted with an active struct like that, its rip is moved to abort on resume
so it can retry the per-cpu operation
I see
ah i was thinking it was restartable sequences
very good (and only) docs here https://manpages.opensuse.org/Tumbleweed/librseq-devel/rseq.2.en.html
btw what's needed to make a real time kernel? Is implementing rseq sufficient or is it more complicated than that?
i dont think rseq is real time related
yeah
real time just means u dont have stuff that can disable preemption for an unbounded time
or something like that
huh very harsh punishment
Restartable sequences must not perform system calls. Doing so may result in termination of the process by a segmentation fault.
i mean you could return an error code from a syscall?
this is meant to be like locked store or wahtever
for per-cpu data
not heavy work etc
i mean i get it's purpose
idk why they forbid them
and it makes sense that syscalls aren't allowed
maybe too much extra code needed
from what ive seen in code
when u syscall there isnt a register frame passed to rseq for some reason
so they hard segfault u
hard as in can't even handle the signal?
uhh not sure
lets try
so maybe Maestro is real time then? I am not sure
force_sig just ignores the mask but otherwise seems to be handleable
does this mean u can syscall under rseq

kinda funny but ig
the main idea is probably that its super likely u do switch cpus between syscalls
becaues its a rebalance checkpoint and might block or take too long
yeah
so u would retry a ton of time
and it might just never work
Expose a new system call allowing each thread to register one userspace
memory area to be used as an ABI between kernel and user-space for two
purposes: user-space restartable sequences and quick a...
this explains a ton as well
how so
mmap broken, did not support interpreter, wrong stuff in auxilary vector
damn
I think I have a deadlock when running g++
So I currently have the following issues to fix:
- Giving the machine more than 1GB of RAM in 64 bits makes the kernel crash (I fucked up virtual memory at boot apparently. PSE cannot be used on page map level 4 entries)
Giving the machine more than 1GB of RAM in 32 bits makes the kernel crash while the first process is executing because the address in the page directory entry is too high it seems (idk why yet)โ (was trying to free the page pointed to by the last level of the page directory, which contains user data)The ELF parser reads the whole file in order to execute it (even though it requires only a few structures), which makes everything slowโ- The kernel thread that flushes dirty memory pages runs regardless of whether there are pages to flush or not
- The kernel thread that flushes dirty memory pages iterates on all pages regardless of whether they are dirty or not
Executingโ (yes it's due to an OOM, see the above issue)g++makes an infinite loop in the flush task (idk why yet). I suspect this might be due to an OOM? In which case my OOM handling is fucked upI need that kernel thread to also flush inode metadata (I guess I need to add a LRU on inodes too)โ (I just modify the data structure of the filesystem and it gets automatically flushed)
that would have made init so much easier ๐
but now I wonder why this is working with 1GB or less
I've written the fix to avoid reading the whole ELF file. Now that's a lot faster
Since it is using a lot less memory, I can now run g++, which exits successfully.
But when running the resulting program, I get:
Error loading shared library libstdc++.so.6: No such file or directory (needed by /cpptest)
Error loading shared library libgcc_s.so.1: No such file or directory (needed by /cpptest)
Error relocating /cpptest: _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc: symbol not found
Error relocating /cpptest: _ZNSolsEPFRSoS_E: symbol not found
Error relocating /cpptest: _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_: symbol not found
Error relocating /cpptest: _ZSt4cout: symbol not found
might need to fix something in the compilation toolchain
@late oar does maestro's fs mount work reliably? I'm looking for clues as to how to implement my vfs but most implementations ive come across now are incomplete
yes it does
however I am not checking yet for nested mounts on unmount, which I should be doing
right
i just need a way to structure it in my head
also helps that it's in rust :3
the main function you would be interested in is here: https://github.com/maestro-os/maestro/blob/21118d70d0a186777d9bc7d0cc78509935c3d483/kernel/src/file/vfs/mountpoint.rs#L224
The VFS has a tree of Entry (equivalent to Linux's dentry), and when you create a mountpoint, it also creates an Entry for the root directory of the new mountpoint. The associated Node (inode) is the root of the mounted filesystem
When doing path resolution, we can just go through the Entry tree like nothing happened
that's similar to what i do
do you also do negative lookups?
i.e. path misses are cached in negative dentries
you also need to make sure you cannot remove the directory if it's a mountpoint
And you must make sure you cannot unmount a mountpoint if there's another one in it.
You can do this by having a reference to child mountpoint in your mountpoint structure. If there's any child, you cannot unmount
yes I do
neat
this is represented by having None in the node field of my Entry
I talk about it in my last blog article: https://blog.lenot.re/a/page-cache
The next planned architecture support will be for ARM. Although this is not a priority at the moment, and will come much later in the future.
are we talking aarch64 or 32-bit arm?
32 bit arm is absolute hell
also, Entry::parent should be a Weak<Entry>, no?
you have Arc<Entry> which has a HashSet<Arc<Entry>> and each Entry has a parent: Option<Arc<Entry>>
you create cyclic references without using Weak
idk if that matters, but i just wanted to point that out
aarch64
you cannot remove an entry if it has at least one child (that's on purpose), and when you remove an entry from the HashSet, it drops the reference to the parent. So that's fine
I don't have Weak because that means I need to maintain a second counter even though I might not need it in a lot of places
are you not using the alloc crate?
No because I want allocations to be fallible, so I have my own implementation of all the data structure which return an AllocResult<...> for functions that make memory allocations
ahh, that's smart
that's one thing i absolutely hate about rust
it's so nice to be able to use alloc and all that
but wait.. it panics on alloc
too bad, time to implement everything yourself :^)
i bet there are crates that have fallible allocation compatible containers though
yeah. They justified that by saying that most people are not interested in dealing with allocation failures (which I agree with) but that makes it annoying for embedded
probably
not necessarily
technically you can make room for new allocations
on alloc failure
or write to a pagefile
yes u can recover
but like
not always
and what if no swap
and nothing to evict
no dirty pages you can writeback
no caches to flush
completely oom
you have to return null
and then alloc shits itself
does any mainstream kernel handle that?
with you own implementation you can at least propagate that
i guess
i guess at that point linux will start killing processes
isnt oom killer implemented in userspace tho? 
it's messy but it's something you should probably handle
you could still have a kernel side one
instead of assert!(phys_addr.is_some()) 
did i do that
๐
i find it funny because the allocator_api can fail
but that isn't allowed as a global allocator
nope
its a kernel function
there's a userspace oomd that monitors psi counters
but its not reliable
I was thinking about OOM handling this morning and I was telling myself I should have a popup window on OOM which asks the user to select a process to kill.
And if that fails for some reason, then the kernel would select one itself
yeah
linux calculates a score for all processes
and picks one with the worst score
yes
that might even be the process that invoked the current allocation
so it will kill itself to satisfy its own allocation
or rather, it will kill itself because it cannot satisfy it
no like
it literally will spin in __alloc_pages until it makes some progress killing itself
and alloc pages will succeed

because it doesnt know whether its safer to abort the current allocation or let it succeed
it might be for some imporant kernel metadata keeping
makes sense
so it just lets it finish
so basically actually returning NULL for kernel allocations is possible but very very unlikely
My allocator takes a flag which says whether it can fail on OOM or not
thats basically only for high order allocations
im curious which allocations u count as fatal
If it cannot fail, then it spins trying to shrink caches, and if that fails, I panic (I didn't implement process killing yet)
The first I think of is page table allocation because I don't want to handle failure here
failure means I have to rollback the modifications I've made and that's annoying
u dont have to technically
if its a user page table
u can just reclaim it later
like try to find unused tables on OOM?
but then you don't get the page back until the process dies
yes but u literally sigkill it right there
what if that's a process that's running for a long time?
so its better to panic the kernel instead?
well a proper solution would be to have an actual oom killer
and then it kills whatever is currently appropriate
or u could not overcommit memory
just kill the lowest pid 
Goodbye init 
nobody needs that anyways
actually, isn't pid 0 the kernel?
in Maestro, pid 0 is the idle task (just a loop with sti, then hlt in it)
I have a different pid for each kernel task
the flush task (writes dirty pages back to disk) is pid 2 in practice (I launch it after the init process)
I just realized I think this task can be killed from userspace and that's a problem
why not have it in the same process
all kernel tasks use the kernel page table after all
yeah but I can have several processes sharing the same memory space
I do that thing that linux does where a process and a thread are actually the same thing but with different IDs
that's a thing?
hm, my threads and process are different things, but i see what you mean
for me a process is a struct that contains a bunch of tid's
that's pid -1 for me 
signed pids 
tid and pid is the same namespace in linux
is there a reason why
a process has at least one thread
yes
and the first thread's tid == pid
isnt pid a 32-bit value?
implementation defined
then probably
on Maestro it's an unsigned 16 bit value
I guess I should increase it to 32 bit
for PID allocation I have a bitfield on which I iterate to find an unused one. Not the most efficient thing but I will into optimizing the day it becomes a problem
why 16
couldn't you just iterate all processes and look for the first free pid that you get?
idk
that's what I do
well I iterate a bitfield instead of processes so I guess not
ye
I guess I could do that yeah
my processes are in a binary tree. Maybe there's a more efficient way to find a hole than just iterating over the whole tree
also I still have a global lock over my process scheduler. That's gonna go away when I implement SMP
and all my locks are spinlocks. that's inefficient as hell
ah
well my processes are stored in a binary tree as well, but threads are stored per CPU
how should I implemented mutexes? like if the resource is not available after 100 spins, I put the process in sleeping state?
when I implement SMP I will do a scheduler per CPU. And then I'll need some mechanism to make a CPU move processes from others if it doesn't have enough work I guess
yea i'll have a rebalancer that runs on every thread spawn/kill
when a process state changes too? like when one goes to sleep or is stopped. you probably want to use the CPU for some other running process
depends on the priority
a lower priority running thread might have to give way to a higher priority sleeping thread
at least if it's blocked
if a process is blocked, I put it in sleeping state
and it's woken up when the thing it is waiting for becomes available
i differentiate between blocked and sleeping
what's the difference in your OS?
I'm not that far yet, but I'd like to make it clear that a sleeping thread is predictably halted
e.g. via usleep
i will also do per-task time slice scheduling that doesn't rely on a constant timer interrupt
Maestro running Minecraft when
You will need a lot more syscalls for java 
minecraf
this is due to those libraries being located in /usr/lib64 while the linker is looking /usr/lib. I read the LFS book earlier today and I remember it saying gcc (iirc) should be patched so that libraries are put in /usr/lib, so imma do that
is it really relevant ? my archlinux has that symlink
that might cause issues?
on 64 bit systems /usr/lib and /usr/lib64 are symlinks to each other
arch linux has /usr/lib32/
and 32-bit libs are in /usr/lib32
yes
but that's not x32
i think if you have x32 stuff you'd have a /usr/libx32 dir but i am not sure
i think i meant x86_32
well x32 isn't IA-32
i am kinda surprised you didn't know that already tbh
mods, screencap this
i know that it is but idk what the right terms was
my gcc patch has 3 different lib dirs
uh oh
i think i fucked this up then
xd
it should probably be the other way around
it won't be used
it can be anything
IA-32 is i[3-6]86 is "x86_32"
x32 is an ABI variant of 64-bit x86 code where pointers are 32-bit wide
no one uses it
what was the point
it's obsolete and dead
well
one sec
The x32 ABI is an application binary interface (ABI) and one of the interfaces of the Linux kernel. The x32 ABI provides 32-bit integers, long and pointers (ILP32) on Intel and AMD 64-bit hardware. The ABI allows programs to take advantage of the benefits of x86-64 instruction set (larger number of CPU registers, better floating-point performance, faster position-independent code, shared libraries, function parameters passed via registers, faster syscall instruction) while using 32-bit pointers and thus avoiding the overhead of 64-bit pointers.
from Wikipedia, the free encyclopedia
lmfao
good to know
PID reuse is not something you want
okay but if you run out, you want to reuse those at some point 
So you use a 64 bit number 
on a side note, I now have g++ working
Also I don't see how that would ever happen as sleeping threads aren't even on queues
what if my system spawns 18,446,744,073,709,551,615 processes ๐ข
also, even if you spawned a process every nanosecond you wouldn't be alive to see the day
Yeah true
and then you have the issue that pid_t is an int so you can't represent the PID if its higher than the max of a 32 bit value
that's why you use a libc like mlibc where you can just define pid_t as a long
built differentโข
exactly, I'd live up to see all of my 18,446,744,073,709,551,615 processes be happily spawned =D
I just realized implementing memory swap should be really easy since my last refactor of memory management
might do that sometime this year
so, the next steps are:
- fix my flush task to make sure file metadata are written to disk
- make a blogpost about ELF loading, gcc and stuff
- fix the kernel crash when booting with more than 1 GB
- fix OOM handling
Then I don't know if I should try to port more stuff first, or implement SMP
Make the blog run in maestro
I don't have network support yet

coming by the end of the year if I have time
this year's goal is to be able to code on Maestro itself (preferably with git, so that would require network support)
next year's goal will likely be a desktop environment
it's possible to do git without internet.
yes but lame
you just distribute the repo via physical USB sticks
honestly way more fun than internet git
also coding in rust without internet is going to be annoying
like you get a USB stick from the man himself, you know it's legit
just download everything you need on a separate computer and load it via USB sticks
imagine if Linux would be distributed by a network of people giving each other physical USBs with patches and tarballs and compiled kernels
why
just don't depend on so many crates 
I think that would be very unsafe
when maestro meson#
the kernel is supposed to be that thing below the random project thanklessly maintained by a single developer in nebraska since 2003.
lmao
meson is coded in python, right?
ye
I'd like to avoid relying on python if that's possible
why
bruh
๐
yes but it's written in python, meaning I would need python to build a distro
you just need that on the host
bad
huh
lmao

the overhead from a build system written in Python in comparison with compiler optimisation passes is irrelevent, vanishingly small.
lol
๐
thank god
first rust operating system rewritten in C
I'm starting to tell myself maybe lazy-freeing page tables might actually be a lot better since that means I don't have to maintain a counter on the number of used entries to know when to free it.
I can just let the page table there after an unmap, and it will likely get reused the next time the process does mmap anyways so I am saving a free and alloc.
Also if the system runs out of memory I can iterate on processes to look for unused page tables and free them before killing anyone
you can also do something where, at munmap time, you look at where the neighbouring VMAs are and free all page tables that are in between them, that you know that no one needs.
โผ๏ธ
smart
not invented by me
but thanks anyways lol
you can look at davix mm/vmap.cc if you want an example of how this might look (however it might scare you away from wanting to do this)
I'll check, thanks
(just keeping this message around as a TODO list, don't mind me)
(turns out that's fake news, you can do it with a Discord bot)
thread image is back โผ๏ธ
How ??????????
Nice logo
you can just set flags to 0
@late oar did you know you can kinda do fallible allocations with ::alloc now?
with the allocator_api
no I did not
well, depends on what "kinda" means
I know there's some fallible functions on collections but there's almost nothing
There's a fallible push on Vec I think but aside from that there's not much
btw I've tested this: https://www.a1k0n.net/2006/09/15/obfuscated-c-donut.html
and it's almost working (just missing support for some ansi escape codes I think)
These last few months (and all of last year), working on Maestro have been pretty exhausting and kind of annoying (I really had to force myself to work on it regularly)
But now that I have done the annoying bug fixing and refactoring part, it is getting a lot easier to work on it. I am implementing new stuff instead of fixing the old ones and I am starting to get fun working on this again ๐
yesterday evening I have tested some small cpp programs made by a friend and they are all working as expected ๐
https://github.com/AntoineJT/cpp-things
Some random little things done in C++ to learn about it - AntoineJT/cpp-things
I should record this btw
...a program like a donut showing a donut...
ext2
nice
comes from here: https://www.a1k0n.net/2006/09/15/obfuscated-c-donut.html
ext2 great choice
I haven't looked at ext3 and ext4 yet but I guess it's just ext2 with more features?
Basically, ext3 adds journaling which is probably the most complicated part of those two filesystems
obfuscated c yumyum
people, the time has come to start implementing SMP
enumerating CPU cores on the system with ACPI: โ
hell yeah
Did you roll your own table scan code?
yes
I now have a rwlock instead of a simple spinlock around the list of processes
currently trying to setup the APIC
seems reasonable enough for a single core system
Yes
skull
I think I'll open Maestro to external contributors. I guess I need to write contributor guidelines
Wow, Gg fantastic work !
May i ask you if you started with https://os.phil-opp.com/ as a base ?
This blog series creates a small operating system in the Rust programming language. Each post is a small tutorial and includes all needed code.
