#Maestro - Linux-compatible-ish OS in Rust
1 messages · Page 5 of 1
i'm confuzzled
init is also a user program
in my design init starts something like kmscon or gcon as the tty, then starts a shell
ah yes
okay so my design is similar except the terminal server launches the shell instead
so the shell inherits the IO handles it needs from the terminal server
when I have a GUI, then the GUI will launch a console by launching a terminal application which will then launch the actual app
it's just output logs via the kernel logging facilities
that makes sense
i think i might do that too
but just for init
yep
this is what it looks like at the moment
(ignore the panic)
once init starts the actual console, the kernel will give up the framebuffer
I like the idea of having a userspace program for that, but what if it dies? OOM for example?
ideally the terminal allocates one buffer and then never needs to do it again
Yeah but the oom killer does not necessarily kill the process that is requesting the allocation
what is preventing it from building
I use this script to build it:
#!/bin/bash
set -e
cd *
./configure --prefix=/usr \
--host=$HOST \
--build=$BUILD
make -j${JOBS}
make DESTDIR=$SYSROOT install
seems like make is defining it sown getenv?
I think I ran into this before
fnmatch.c:124:14: error: conflicting types for 'getenv'; have 'char *(void)'
124 | extern char *getenv ();
| ^~~~~~
‼️
there is nothing in my patch about it, let me look at the recipe and if nothing there then the soruce
# if !defined _LIBC && !defined getenv
extern char *getenv ();
# endif
you have two options here, figure out why its not working or just patching that out
in lib/fnmatch.c
probably unsound build environment or something?
that sounds like something that could happen
if you doing linux compat just go all the wayt and use glibc 
no glibc bad
musl lightweight, musl good
one of us
it seems alpine linux made a patch for this issue precisely
https://gitlab.alpinelinux.org/alpine/aports/-/blob/master/main/make/getopt-gcc15.patch
‼️
compile linux on maestro
I'll try to build gcc first
btw gzip seems to make the kernel do an infinite loop
lmao
or maybe the executable is just really long to load and I am being impatient? In any case I think I should normally be able to Ctrl+C the process during execve?
no the file isn't big. there's definitely an issue somewhere
diff does not work
file does not work
find seems to partially work?
gawk seems to work?
grep does not work
gzip does not work
m4 does not work
idk how to test ncurses
I didn't test patch yet
sed does not work
I didn't test tar yet
xz does not work
wonderful
grep seems to use the syscall alarm which I did not implement
Do you not mmap your files
I do
Then it should not take that long
yeah there's an issue somewhere
Also I have to figure out why a process exits with code 0 when killed by a SIGSYS
okay I think I never set the exit status code except when calling exit. Let's fix that
task failed successfully
indeed
I'm stupid. It's actually mincore who's missing
why the hell does it need mincore 
no idea. I'm just implementing it right now since I'll need to do it at some point anyways
I think it might be so it only scans blocks which are actually present
To avoid doing no op reads of just zeroes
Basically sparse file handling
when I type grep --help, it scans a big portion of the address space for some reason, which is very long
it does that because /proc/self/maps does not exist (idk yet why it needs it)
I guess I'll implement /proc/self/maps 🤷
sed needs fchown apparently. I didn't implement it
I have implemented fchown and fchownat. Now it's rename who's broken apparently
I am debloating my system calls handling. I have 492 compilation errors to fix 
peak
peak osdev moment
New blog article (@shell tusk and @silk wren mentioned at the end)
https://blog.lenot.re/a/smp
no aml support 
Oomfie famous
For a second those circles on the screen made me think I broke my phone screen gjisnfisncos
This is pretty good, but note that AML IS bytecode
ah ok you just meant it's hard to parse
Yes
lmao
in your explanation of critical sections, you state that the counter has the following layout:
- bit 31: set by default. clear means preempt.
- bits 30..0: the number of nested critical sections.
It is worth clarifying that Linux uses some other bits of the "preempt_disable counter" for additional state likein_irq,in_nmi, etc. Otherwise LGTM
I'll do that, thanks!
I'm back to work on Maestro. I have to implement newfstatat for the find command, so I guess I'll make sure all of the variants of it are implemented
find works (I haven't tested every corner of it though)
The file command is missing pread64. Let's implement it and do pwrite64 at the same time
file is working
I like when I implement stuff and I end up with less lines of code than before
In my current PR I have added 10 syscalls and I ended up removing 342 lines in total
I am currently implementing /proc/<pid>/maps
I have a deadlock inside of it and idk how to fix it
I lock the maps list to iterate onto it, and I write a line to userspace memory for each.
But, if the userspace page we are writing to isn't allocated yet, it triggers a page fault, which in turns tries to allocate the page, but that requires locking the maps list, which is already locked
I have a temporary fix that I don't like (swap a spinlock for a rwlock and add another spinlock somewhere else). I'll have to change it later
Now grep seems to want sigaltstack
reader-writer mutex, read locks can nest?
that's the solution I went for and that I don't like
it feels cumbersome to me
oh you use a preempt disabling rwspinlock for that instead?
I don't disable preemption, only interrupts (I don't even remember why)
I fail to see how that would work with waiting for IO etc., because if you schedule away to block when holding the maps lock, and someone else tries grabbing the maps lock, now you can never switch back to the original task and you are deadlocked.
indeed, good point
the maps lock really should be a sleepable mutex (spinlocks rwspinlocks and the like aren't sleepable because of this specific issue)
I'm noting that somewhere. I need to rework my sleepable mutexes first
They currently need a memory allocation to enqueue the sleeping tasks and that's really bad
why do they allocate?
you can have a preallocated node either on the stack (if you don't swap kernel stacks) or in the thread structure.

it has a Vec of PIDs of tasks waiting on it. An insertion in a vector might require a memory allocation
that's a bad design. I didn't know what I was doing at the time
then it should be as simple as replacing that Vec<PID> (why PID? why not Thread * or something which makes more sense in-kernel?) with an intrusive linked list which doesn't allocate.
PID because I didn't want to have unsafe code
but I'll do a stack or thread thing. That's been on my todolist for a few weeks
well I could even do it now, once I've committed my sigaltstack impl
If I decide to do it on the stack, how do I make sure the task is removed from the queue if it turns into a zombie?
it's easy: disallow turning tasks into zombies except when running in that task's context, and only turn yourself into a zombie when you are not holding any shared resource.
if T1 wants to kill T2, T1 has to enqueue a function to run in the context of T2. this can be done e.g. via signals.
thanks!
How do you do your (unit) tests, if any? I'd already asked Marvin since he's also writing in Rust but he doesn't have any tests.
Yes I do, and I use the thing mentioned here: #1287456798407790684 message
I've read that but I'm tempted to make my own framework as I'd like to make tests run at specific levels of running (e.g. page table test would run between PMM and VMM init)
I just run them once virtual memory is setup
If i wanted to do kernel self tests, I'd just run them instead of init
Just finish the kernel boot, then run tests and terminate again
Doesn't get there because page tables are broken. You know, something you could more easily fix if you had tests.
If you don't even get to the point where you can run tests, you don't need tests to find failures
The point of tests is to prevent regressions, not to debug broken code
I would argue that testing small subcomponents of the VMM (e.g. converting generic PTE format to what the CPU actually wants and back) would make it easier to narrow down what could be broken. In addition, this is literally a regression because VMM worked before the refactor (it just didn't have all the features I'll need).
or rather: while some test failures can hint at what's wrong, that's not their primary purpose
tbh i don't think the overlap of "failure before the kernel even finishes booting" && "tests help with debugging it" && "can be tested inside the kernel in a meaningful way" && "can't be tested outside the kernel" is big enough to justify the cost
Page tables can be tested outside the kernel, you just can't actually use them like that.
If the kernel doesn't finish booting because PTs are broken, something must be seriously wrong and that failure is easy to find and fix
I have this issue where my kernel does not work when built on one specific computer and I have no idea why it does that
#osdev-misc-1 message
Turns out this wasn't page tables being broken - something testcases would've told me earlier.
I'm not claiming that tests won't help, so I'm not sure what you're trying to say
It sounded like it
I claimed that the cost of having self tests running before boot is done outweighs the benefit
I'm not saying that tests are useless, I'm saying they should run on top of an already working kernel (even if they do run in kernel space)
be the change you want to see
I have to finish implementing sleeping mutexes and condvar
maybe writing a blog article will motivate you? 🥺
I don't have anything to say
I think I don't lack motivation. I lack time
well a bit of motivation too during the past few days but it's fine now (I was kinda depressed)
I remember you said you were running into some trouble, what were you running into?
when making sleeping mutexes
I don't have sleeping reasons and I didn't feel like adding those
I noticed I had timers that could wakeup a thread if setup with SIGEV_NONE, and I realized today I don't even need to wake it up I think? I can just do nothing. So I guess I don't need sleeping reasons anymore
the only wake up reason I think I even have in astral is interrupted which is for signal stuff
other than the normal wake up reason
I felt my mutex implementation could not work because if a thread got woke up by something else than the mutex, it could acquire the mutex even though it's not its turn
I'll try to make it work this evening probably
you can always just have specific sleeps not be interruptible. are there any cases in maestro where you would even want to be woken up by waiting on a mutex that you are not explicitly saying that its an interruptible sleep?
my mutexes dont support that
there is no reason to, if they want to wait on a resource interruptibly they use another primitive
so that's the reason why Linux has two kinds of sleep states? so that it may get woken up by a signal when its an interruptible sleep?
I am not really familiar with linux design but it could be
I pretty much do the same in astral, theres a distinction between normal sleep and interruptible sleep
they have interruptible sleeps and non-interruptible ones
Linux has quite a few states. There's TASK_INTERRUPTIBLE (the best kind of sleep), TASK_KILLABLE (like TASK_INTERRUPTIBLE but can only be interrupted by SIGTERM or SIGKILL iirc), TASK_UNINTERRUPTIBLE.
actually IIRC TASK_KILLABLE might actually be named __TASK_KILLABLE in typical Linux manner and be a flag ORed with TASK_UNINTERRUPTIBLE. I'm unsure.
anyhow, the idea with TASK_KILLABLE is that e.g. in page fault handler path, if you are killed by someone, you no longer need to satisfy the page fault, because the faulting access is effectively aborted.
a side note, wake-up reasons need not be propagated to the task which slept (AFAIK Linux doesn't, for example). any task which wakes up can check for itself if the resource it waited for is available, otherwise look at pending signals/timeout.
TASK_UNINTERRUPTIBLE sleeps and big in-kernel loops that don't check pending signals anywhere are what translate into processes which have a long delay between you pressing C-c and the process exiting.
thanks! @silk wren
okay I remember why I did that. I use my timer implementation in nanosleep, with SIGEV_NONE, and so I added a wakeup so that the sleeping thread can get woken up when the timer fires, without having to use a signal
it seems Linux uses restart_blocks for that. Now I have to figure out how they work
my timer code is so shitty, I'll have to rewrite it
the timers queue is a binary tree and to move an element to the back I have to free it and re-allocate it to re-insert it
okay that's for restart_syscall that I did not implement yet
so, I am starting to fix my timers so that it does not interfere with sleeping mutexes
okay I cannot finish this refactor now because I'm going to need intrusive binary trees to do it correctly. And honestly I don't feel like implementing those
funny thing, that's exactly what i'm working on right now :^) (timers)
and, thankfully, i use this funny crate called intrusive_collections
I am psychologicaly unable to add a dependency to this project
my hands just won't do it
You depend on a compiler
And emulator
well yeah of course
when do you need to remove and re-insert an element in the tree? the only case that I can think of is if you change the deadline of a timer, but when would you ever do this?
when the timer expires, I execute the associated action, and then I move it in the the queue for the next time. My queue is ordered by the timestamp of the next time the timer expires
oh I just have timers that are one-shot. If you want a repeat behavior, then you have to enqueue it yourself in the handler.
yeah, makes sense. But the issue I have is that enqeuing requies a memory allocation because I don't have intrusive binary trees (yet)
Yesterday I've been trying to fix signals. Since Ctrl + Z doesn't work anymore for some reason
I have changed the way processes state work a bit. Now a process cannot transition from Sleeping or Stopped state to Zombie. It has to go back to Running first
Idk if it's a good thing wrt SIGKILL though?
Maybe an off-by-one issue?
What's that?
An off-by-one error or off-by-one bug (known by acronyms OBOE, OBOB, OBO and OB1) is a logic error that involves a number that differs from its intended value by 1. An off-by-one error can sometimes appear in a mathematical context. It often occurs in computer programming when a loop iterates one time too many or too few, usually caused by the u...
but now that i think of it it wouldn't really make sense for it to be that
nah I don't think that's the issue
at least you have proper signals unlike me
I've made some fixes yesterday and now the issue I have is that when I do Ctrl + Z, the process stops, but then resume immediately for some reason
Turns out it's non-trivial to implement
also I've realized my wait* syscalls aren't implement correctly I think. wstatus shoud probably be a field in the process's structure, and wait* should retrieve it from the child process's structure?
alright guys. After trying to refactor my signals and process state handling, turns out need the TTY not to do deadlocks to make it easier to debug (how queer????) so I am rewriting the TTY right now
I am getting fed up by this crap
okay I don't have a deadlock in the TTY anymore, now back to signals and process states
Signals are sucky
okay guys. I have fixed signals. I can now use Ctrl + Z to pause a process and go back to bash, and I can use the fg command to resume it
It used to work a few months/years ago but I broke it and I had never fixed it until now
how to signal
you should remove signals from maestro as an act of protest to the unix overlords
That would be funny in a Linux compatible system
lmao
Are there cases where, for a given mutex, I would sometimes want to lock them so that they can be interrupted by a signal, and some times not?
It has to take a boolean to know if it can be woken up by a signal, but I want to know if this boolean should be a function argument or a constant generic
Next blog article's title: UNIX signals are a pile of garbage
(kidding)
lol
I am looking at intel virtualization instructions. It looks funny to implement
I kinda want to do KVM now
idk how similar AMD virt instructions are to Intel though
this is a very cool project, if it's any consolation
I might "steal" some of your code for my own microkernel project if that's acceptable
I would consider filing PRs but like I said I have my own kernel I'm working on
Yes, go ahead
I think sleeping mutexes work, but I have reference counting issues. The process structure is never freed
still stuck on the reference counting issue
something something reference not dropped when removing an element from an intrusive linked list, implemented here:
https://github.com/maestro-os/maestro/blob/stabilise/utils/src/collections/list.rs
I use it in the process's structure so that I can have a queue of processes waiting on a mutex
the reference counter of the process is incremented when I insert it in the list, and is supposed to be decremented when I remove it
okay guys, I think I've got something
I have added a unit test in userspace on the linked list, on the function that removes an element and I get a segfault
omw to use valgrind for the first time in years
okay guys, it was fucking stoopid
To decrement the reference counter on the Arc, I was doing drop(Arc::from_raw(val)), where val is an &Arc<...>
So I was basically building a Arc<Arc<...>> from a raw pointer, then decrementing a random location on the stack when it was dropped
I've lost a week on this 👍
omw to support gzip
currently the kernel freezes when I run it. It seems to do a page fault
alright I've been mixing up RIP and CR2 for the last 20 minutes or so
btw I am pretty sure it's because of the ELF loader trying to zero mapped pages after the file's end. Resulting in a SIGBUS, which is not caught, so it fails in a loop
alive OS
take a screenshot and label it AI
I have a function named oom::wrap which retries a given closure until it passes without an allocation error, calling the OOM killer (which I didn't implement yet, so this function is useless) at each failure, in order to avoid having to handle memory allocation failures in some places. I absolutely hate this function
Turns out the few places left where I use it are all places where I should be using intrusive linked lists instead. And now that I have those, I should probably get rid of that shitty function
gzip almost works, there's just an error message when I use gzip -d ... saying the file at ... does not exist, but the decompression worked anyways 🤷
time to print syscalls to see which one failed
fixed. It was an issue when creating a directory entry in my ext2 implementation. When splitting a free entry to create the new used entry, I wanted to fill the remaining space with a new free entry, but instead I was filling the whole block, making the next entries disappear
tar seems to work
alright guys, I think it may be time to attempt building binutils ‼️
as in, make binutils work for maestro or build binutils in maestro
binutils already works
I want to build it on maestro
(nevermind, unpacking does not work. A syscall seems to be missing)
inb4 the syscall to set the create date
very likely
it's mkdirat
okay, what about I implement all the missing *at system calls so that I don't have this issue anymore? cause I already have the original (non-at) version for almost all of them
imma do that tomorrow though I think. Time to sleep
I implement like if not most then all the non *at as calls to *at syscalls :P
yeah, linux does that too
I didn't do it at the beginning because *at syscalls scared me at the time
now when I implement the *at version, I remove the previous syscall's impl and replace it with a call to the *at
thx
I have implemented mkdirat, mknodat, renameat and readlinkat during my lunch break. I'll implement execveat and probably openat2 when I finish my workday
I guess I'll also do futimesat and its non-at counterparts
The grind never stops
just realized I've implemented sleeping mutexes but I use it almost nowhere. I haven't replaced spinlocks yet
indeed
Damn you should
Spinlocks everywhere is not good
🦀 BLAZING 🔥 FAST 🚀
I use spinlocks on directory entries, but I guess I'll have to replace them by RCU eventually
I think so
depends on whether you use intrusive linked lists or not
It's mostly the preemption inhibition to worry about
But that should be easy with a guard type
yeah
i need to speed my shit up as well
also maybe RR is not the best scheduling algo
i do have sleeping mutexes, but uhh
I don't use them anywhere
it's pretty common for me to just implement/refactor stuff and the OS becomes faster without even knowing why
I use sleeping mutexes by default
Of course
There you use spinlocks
I also use spinlocks if it needs to be acquired from an ISR or with interrupts disabled
Unfortunately, acquiring a spinlock is unsafe because disabling interrupts is unsafe.
why is disabling interrupts unsafe?
Because Rust has assumptions broadly incompatible with it
For example, memory allocations are not ISR-safe
I may look into making a custom linter to guard against this so I can mark the interrupt guard as safe and have the linter check for invalid handling of interrupts.
I don't see the issue with memory allocations
My allocator is not reentrant
mine is because it's wrapped in a global spinlock 
No that's literally what I mean, that is not reentrant
a global spinlock which disables interrupts
It would be reentrant if you could get interrupted in the middle and have the same CPU run the allocator again in the ISR
However I use a spinlock on the page allocator
right
And so it is not reentrant
My idea for the linter would see something like #[irqlint::isrsafe] and #[irqlint::isr]
So it could properly infer what is isr-safe code from the attributes
I would make a crate of it, perhaps the two of you could also find such a thing useful
maybe
okay openat2 is too complicated for me to do it today. It takes a structure with plenty of options in it
https://docs.rs/crossbeam-epoch/latest/crossbeam_epoch/ has an RCU impl
Epoch-based memory reclamation.
but it has an unsafe API
https://docs.rs/sdd/latest/sdd/ has a safe API
Scalable Delayed Dealloc
I've just noticed the fix makes the CI fail. sigh
tar xvf binutils-2.45.tar.gz seems to be working but very slowly
it seems to be going back and forth between the gzip process and tar process. I'll check if adding sleeping mutexes everywhere does any good
sadly not no_std
I think it has significantly improved performance
although it's still pretty slow
I think tar is parallelizing stuff. Let's try having 8 CPU cores (no it does not)
I've built the kernel in release mode and it's MUCH faster
unsurprisingly, I have a lot of time stamp ... is ... s in the future
and tar fails with: tar: binutils-2.45/bfd/po/fi.po: Cannot writeBus error
bus error, spooky
first hypothesis: shitty ELF loading
make also does a Bus error when I run it in the incompletely extracted binutils directory
nevermind, it does it regardless of the directory
grep too smh
I think your bus has errors
have you tried checking for any broken solder joints on your bus?
actually I have rebooted and now there's no Bus error anymore
QEMU's famous solder joints 
okay, having a pipe capacity of more than 512 bytes makes it faster 
and now there's a deadlock
512 bytes is tiny
wtf
512
thats like
smaller than pipe_buf
on linux
indeed. I mixed up pipe_buf with the pipe capacity when I implemented this
I don't have the guaranteed minimum atomic transfer size on pipes, idk how to implement it btw
I have it implemented in astral, its a bit messy but it works
its kind of just, if you want to write <= pipe_buf you can sleep until theres space, otherwise you can write it in parts
its not even a linux only thing, its a posix thing
https://pubs.opengroup.org/onlinepubs/009604499/functions/write.html
Write requests to a pipe or FIFO shall be handled in the same way as a regular file with the following exceptions:
There is no file offset associated with a pipe, hence each write request shall append to the end of the pipe.
Write requests of {PIPE_BUF} bytes or less shall not be interleaved with data from other processes doing writes on the same pipe. Writes of greater than {PIPE_BUF} bytes may have data interleaved, on arbitrary boundaries, with writes by other processes, whether or not the O_NONBLOCK flag of the file status flags is set.
If the O_NONBLOCK flag is clear, a write request may cause the thread to block, but on normal completion it shall return nbyte.
If the O_NONBLOCK flag is set, write() requests shall be handled differently, in the following ways:
The write() function shall not block the thread.
A write request for {PIPE_BUF} or fewer bytes shall have the following effect: if there is sufficient space available in the pipe, write() shall transfer all the data and return the number of bytes requested. Otherwise, write() shall transfer no data and return -1 with errno set to [EAGAIN].
A write request for more than {PIPE_BUF} bytes shall cause one of the following:
When at least one byte can be written, transfer what it can and return the number of bytes written. When all data previously written to the pipe is read, it shall transfer at least {PIPE_BUF} bytes.
When no data can be written, transfer no data, and return -1 with errno set to [EAGAIN].
thx
I won't find the cause of my deadlock tonight. Altough I've made some good progress today. 8 system calls implemented
gn everyone
didn't do shit today because I felt like attempting to implement the Raft algorithm
Raft is a consensus algorithm designed as an alternative to the Paxos family of algorithms. It was meant to be more understandable than Paxos by means of separation of logic, but it is also formally proven safe and offers some additional features. Raft offers a generic way to distribute a state machine across a cluster of computing systems, ensu...
looks like I've fixed my deadlock? I just replaced a spinlock that blocks interrupts by a sleeping mutex
okay nvm, it's not fixed
I am moving the spinlock inside of the structure handling the page directory (VMem) so that I don't have to lock it when I just want to bind it. That should prevent some deadlocks
mood:
[strace 5] openat(-100, 0x7fffff7fd800 = "binutils-2.45/bfd/elfnn-loongarch.c", 559553, 384)
alright guys, I've fixed another deadlock, I am trying again to unpack that binutils tar
this is so slow
I think I'll go fixup https://github.com/maestro-os/kern-profile and then I'll try to figure out why it is so slow
QEMU seems to be using only around 30% of its CPU cores
my TTY's history is contained in a linear buffer. When I want to write a new line, I do memcpy to shift all of the content up one line (erasing the oldest line)
Turns out, when I use the strace feature of my kernel, it spends ~40% of its time in that precise memcpy 
omw to make a ring buffer instead
another thing my kernel spends massive amounts of time on is mapping and unmapping ranges on memory in page directories.
Notably, when I unmap stuff, I use a loop to check if a table has no more entry left, to know if I can free it
Instead, I should not attempt to free it, and reclaim it only when the system is low in memory (or when the page directory is destroyed)
also my function to map a range is just a loop which calls the function to map a single page (horrible)
Is there a better way of doing it?
I’d assume you could make some function to map more pages than just one at once
indeed. when you are going down the tables, you can do all of them at once instead of re-doing the recursion for each page
That makes sense
Yeah my map range is
Bullshit
int vmm_unmap_range(vmm_region_t *region, uintptr_t va, size_t pages) {
for (size_t i = 0; i < pages; i++) {
if (vmm_unmap(region, va + i * PAGE_SIZE) < 0)
return -1;
}
return 0;
}
well yeah, I have the same 
Same concept for map_range()
I may re-do this better once I've finished refactoring my TTY





lmao
Would you do this for a direct mapping too?
Or just anonymous
my kernel spends 89% of its time in between grub and launching the first process in map_range
That is quite a lot
Damn 😭😭
recursions that could be avoided
sudo rm -fr —no-preserve-root /
Remove French language pack
and still the kernel boots pretty fast. roughly 0.3 seconds on my computer, on qemu, when built in release mode
So how many times is map_range() called
I think it's not called that much, but it's being called on large ranges
Makes sense
Looking at mine now, about 60% of my kernel boot time is mapping ranges too 😭
Unfortunately I suck shit at programming so it takes about a whole second to boot my kernel
now you know how to make it faster 
Yessir
it used to take like 15 seconds to boot mine 2 years ago
Dang. Yeah it takes me about 1 second until I jump into userspace
What would even make it take that long
Mine took this long when I would map and write a to the entire page directory on boot
oh yeah I currently do that. I map all of the physical memory that can be allocated
maybe I shouldn't?
It’s not necessary as far as I’m concerned
You can do it lazily
like, if I don't map it, then I have to do it when I attempt to access it. But if I map it when accessing it, I need to allocate memory for the page tables to map it, when needs to already be mapped in order to be used
I think a solution to this is to pool memory within your vmm for those allocations
The real way to make it faster is to use larger pages
Oh yeah, that too
I don't think going through the pagetables is that expensive
It's like 4 memory accesses
I don't use large pages because I haven't figured a way to tell in my API which level I want to be a large page
I have a flag for that
I have what I call large pages and huge pages
Huge are the largest supported by the architecture
and large is the second largest
So like on a machine with no support for 1 gib pages then large and huge will refer to the same thing
But on a machine with 1 gib pages then large = 2mib and huge is 1 gib
Maybe I should rename them tho I'm not sure to what
Maybe large and largest, or level 2 and 3
I used to pass a "level" argument to the mmap family
enum VmLevel {
Small,
Medium,
Large
}
the actual values are defined per arch
My impl does hugepages implicitly
Just so long as the mapping is sufficiently aligned
how does that work when using the flags MAP_HUGE_2MB or MAP_HUGE_1GB on mmap?
Well I don't have mmap yet but I'd simply do those by trying to alloc a 2M or 1G block from my buddy alloc. If successful, those will be sufficiently aligned and implicitly create hugepage mappings in the page table code.
alright guys, fuck my TTY's code, I am rewriting big chunks of it
alright. I've refactored my TTY and now the kernels spends 98.4% of its time in map_range during boot 







XD
(turns out the TTY now panics sometimes, investigating)
also the code is dirty. I have to clean it before I commit that
when I run tar xvf ..., QEMU is using only 0.5% of my computer's CPU. I think there's a problem
Hmmmm, add a break point at that spot and check regs, check if the stack pointer and instruction pointer are different. I’ve noticed sometimes when the cpu somehow executes the stack it does that
I think the kernel's stack is mapped with no-exec
Oh then hmmm
I would recommend placing a log at every context switch and see what happens
fortunately, I have a feature for that
Ok lmk what happens
I’ve been experiencing a similar issue actually
That’s why I wanna know what happens for you
first I review my TTY refactor's modifications, then I am investigating that
Considering tar is i/o heavy perhaps it is just unoptimized disk drivers?
that's likely
Yeah I was thinking that maybe ur nvme driver is inefficient somewhere
I don't have nvme, only pata 
Yeah that’s a whole can of worms
I’m working on nvme rn as a side project
One of the (I shit you not) 103 things in my OS todo list
I've heard nvme is simple. maybe I should do it. That would also make the OS a lot faster
It is simple
my pata driver doesn't even support interrupts. It only does polling
Yeah the spec is super light
A decent OSdev Probably could implement it in one night
Especially since there is a lot less legacy garbage involved
that means it will take me 2 months 
Then I dont think the low cpu use is a slow disk driver?
Since the cpu would be spinning waiting on it
You’re above a decent OSdev bruh.
It would take MY ASS 2 months
could be allocations too
at least for mine improving my pmm sped it up by like 20x
rfc3514 said that the issue was qemu not using the cpu so the cpu wouldve been halted most of the time
Could also be locking issues if they are using sleeping mutexes
Cant really say without them figuring it out
I suspect it might be the pipe implementation
since there is a pipe in between tar and gzip to uncompress before untarring
wait, maybe I could first unzip, and then try to untar, so that I am not using the pipe. That would allow me to test that hypthesis
okay it makes it faster, but not by much
maybe that means I should attempt to implement nvme and see if that's better?
I still have no idea why it's slow, but I've tried launching ./configure --help on binutils and...
I have rebooted and it does not seem to panic anymore. Likely a concurrency issue I guess? Or memory corruption
I have quite a few bugs to fix
okay so the kernel panics every time I run configure. And also when running neofetch
neofetch is deadlocking the kernel on this try
okay I have a panic but different this time. index out of bounds. Also I think I have a deadlock while printing the panic message
my kernel is completely destroyed 
alright, time to sleep. gn
all those crashes may be due to a physical memory allocator issue. investigating
turns out configure in binutils has a space in its shebang #! /bin/sh. That fucks up its parsing in my kernel
also it seems I didn't handle the optional argument you can have in the shebang. So when neofetch has #!/usr/bin/env bash, the kernel looks for a file at the path /usr/bin/env bash
plenty of scripts do this
I'll fix that after work
I have a memory leak that has been standing here for a long time and I think I might have found the cause of it (or at least, a part of it)
When I open a file, I create a handle to it. But when I am not using it anymore, I need to explicitly call a function to close it. I did not implement that on Drop because it might return an EIO upon closure (in case the file was removed and this was the last handle to it, in which case the inode has to be removed from disk, which might cause a disk access, which might fail if for example, the USB stick has been unplugged)
Turns out there are plenty of places where I never call that function to close the handle 
I have fixed shebangs handling, now let's try to figure out why bash segfaults when running neofetch
I still haven't done shit since
I guess I have to rebuild bash in -g3 to figure this out
alright, so far the only clue I have is that the segfault happens when freeing malloced memory
looking at info mem, there seem to be a single page mapped in userspace, at 0x8000 (I have lazy allocations)
bash's entry point is not in this page. Did memory mappings get fucked up at some point?
git bisect time
apparently this commit broke bash scripts:
https://github.com/maestro-os/maestro/commit/5d98d94bbe8b70e2e5a0e26c688b959beae9bce6
okay that's a TLB issue. Although there seem to be another issue making neofetch do an infinite loop. However adding invlpg makes binutils's ./configure run for a bit before crashing because of missing system calls
I think this is also making ./configure very slow
gotta fix that at some point
alright. ./configure seems to miss sched_getaffinity and set_thread_area.
Actually set_thread_area is implemented but I just had it commented in the syscall table for some reason???
it seems to be missing getgroups32 too (my bash is built for 32 bit, the rest is in 64 bit). I guess I'll implement setgroups32 at the same time (and their 64 bit counterparts too)
Why is your bash 32-bit?
Because I've never recompiled it when I've switched to 64 bit 
Lol
do you support compat mode?
Yes
I am currently attempting to optimise map_range to use the PAGE_SIZE flag.
When this is done, I'll attempt to optimise unmap_range so that it does not free tables (only unsets the PRESENT flag) so that they can be reused if remapped later. I'll also implement that thing where unused tables are freed if the system runs out of memory
dead project
I have implemented the PAGE_SIZE flag on map_range btw. But not the unmap_range thing.
Last time I worked on this I was implementing getgroups and setgroups
i believe it makes it use huge pages when appropriate
as in, if len >= 2M && virt & 2M == 0 && phys & 2M == 0 then map with 2M pages and probably the same for 1G
yes
this is done. also I've finally merged or closed the PRs other people made on Maestro that I've been procrastinating upon for 2 years
tomorrow I'm implementing sched_getaffinity and sched_setaffinity
current state of things
I am pretty sure the Not a directory error is due to doing stat on the terminal's file descriptor or something
Booting system with Linux kernel, release 0.1.0
wdym
had to replace the field in uname from maestro to Linux to make configure work
I would like to avoid this btw, do I have to patch every package I am building? 
probably. or you could have some "personality" like thing where uname behavior can be controlled by prctl or something
i.e. set prctl(PRETEND_TO_BE_LINUX, 1) before running configure.
Like, implement a command for it, to which I pass another command I want to do, and the command does the prctl, then executes the other command which inherits the prctl?
yeah something like that
// pretend_to_be_linux.c
int main(int argc, char **argv)
{
prctl(PRETEND_TO_BE_LINUX, 1);
return execve(argv[1], argv+1, environ);
}
and then it's just pretend_to_be_linux ./configure ...
https://github.com/maestro-os/visto does this a "x server from scratch" kinda thing or just the xserver to work on you os
what does this mean
does he create another x sevrer like his own thing from scratch or just make the xserver work on his system
im a bit new to the programming world that why im suck a little
Looks like a new X server based on the description
Wayland is based
based
write a portable wayland compositor in c and I will port it to astral
will be in Rust, sorry :3
why don't you just port rust to astral?
I mean I could

Does it work?
it just colors your whole screen in red so far
Ah well that's a good start
been implementing a mocklinux command yesterday to make programs believe they are running on Linux. Not done yet
I've been implementing it in Rust, but maybe it would be a lot easier to do it in C
(for this particular command)
There's no way in Rust to just get a slice over argv. You can only get an iterator of OsString. So if you want to give to exec* you have to collect it, which does a memory allocation
Also I know this allocator is iterating on a Vec, which is built at startup using the program's arguments. That's yet another useless memory allocation
lmfao really
yeah, that's crap
Why don't they just give and take a slice of OsString
Just make OsString have the same repr as char *
OsString is the owned version (so that's yet other memory allocations). I think they should be using OsStr instead?
OsStr isn't null terminated and I think it's a slice underneath
I get that this conversion nonsense fits slightly better into Rust's memory model or whatever but this is kinda dumb
yes
at the very least I would expect the vector to be built lazily (maybe it is, unsure) and I would like to have a unix-specific function where I can just retrieve the raw array
IDK about other compiled to machine code languages but I can hardly imagine any of them do conversions this weird
Since rustc lets you change the signature of main, maybe it's time to make some PRs to them 
I might attempt that
first have to check if someone already proposed the change (likely)
anyways, for now I'll write this in C and come back to it later
shamelessly stealing this code :3 (modifying it a bit to add extra checks)
:3 is the best emoticon (it's impossible to change my mind)
and yet i rarely use it why
You haven't learned how great it is yet
make is working (if we don't count the fact that there is no way for the clock to know the current time, so the kernel currently thinks it's in 1970, which causes make to print a warning because of modification in the future
)
how do I make it so that my kernel has the current time? I guess I need network support to use NTP if I don't want to use some legacy stuff?
rtc
that's legacy, isn't it?
no?
you'd have to use limine for that
i call that a skill gap
There's also a uefi runtime service (but on x86 it just reads rtc anyway)
fixing the Not a directory error (it's due to a wrong implementation of *at syscalls). And then I'll implement the RTC I guess
when I'll have the RTC I will also be able to add fsck in the CI to check whether integration tests are fucking up the filesystem (likely)
I commented this check because fsck gives an error if the timestamp of a file is too low
also fork is still slow as fuck because unmapping a range of memory is still slow. I'll have to improve that
Do you have CoW?
yes
when I fork I unmap all writable mapping so that it triggers a page fault at the next access
I broke pipes by trying to fix them
I've refactored to add a pipefs and now pipes won't wakeup a process reading from it when the writing end is closed. This is a reference counting issue
I might have fixed it. ./configure seems to be doing stuff (I think)
I think my pipes may be dropping some data for some reason?
configure:4532: checking whether the C compiler works
configure:4554: gcc conftest.c >&5
conftest.c:2:4: error: expected '=', ',', ';', 'asm' or '__attribute__' before string constant
2 | RT ""
| ^~
configure:4558: $? = 1
configure:4596: result: no
configure: failed program was:
| #define PACKAGE_URL ""
| RT ""
| /* end confdefs.h. */
|
| int
| main ()
| {
|
| ;
| return 0;
| }
configure:4601: error: in `/home/luc/binutils-2.45':
configure:4603: error: C compiler cannot create executables
See `config.log' for more details
my guess is that those sources are going through a pipe and the pipe drops a part of it for some reason
the kernel is now reading time from the RTC at boot. I don't have warnings from make anymore
i hope you use ACPI ™ to detect if RTC exists and other related properties
I've only checked if the century register is there
the answer is yes: there are filesystem errors
btw I also figured my timekeeping is too slow, there's like 1 second passing every 3 seconds
do you know why
not yet
are you using the PIT to calibrate
HPET iirc
but timekeeping is running with the RTC, which I don't calibrate iirc
also I represent the current time with a 64 bit unsigned representing the unix timestamp in nanoseconds, which I am not convinced is a good idea
probably not
ah, i only use the RTC to get the boot time and from there everything uses the HPET
RTC is slooow in vms so when i get the unix time i only read rtc at startup and then add hpet seconds to it (probably not a good idea but its faster)
maybe I should have the HPET do timekeeping, yeah
that would be an issue if there is no HPET though, but idk if I should bother about that
you could use a one shot PIT to calibrate the TSC
The issue with the hpet is that it is piss slow, if possible support multiple timekeepint sources
My timekeeping priority is kvmclock -> invariant tsc -> hpet
oh i forgot about kvmclock
how do I know if the tsc is invariant?
its a cpuid bit iirc
and if it is variant I should switch to the hpet instead?
if you want it to be invariant in qemu there's a flag for that, i think something like -cpu host,+invtsc should work
variant is junk most of the time because the clock speed can change, so yeah
so I guess I'll do kvmclock -> invariant tsc -> hpet -> rtc
you could also do a one shot PIT but iirc PIT is quite inaccurate in qemu
I guess I'll rework all of that once I get binutils/gcc to build as this should not be a blocking issue
acpi timer being forgotten once again: 💔
oh yeah that exists too 😭
yeah I guess it could be used too. Before the HPET?
Idk I said it as a joke lmao
Idk how good of a timer it os
right
*steal menix code
hm?
how do you guys do it?
isn't that how linux does it too?
It's a fine idea
You're not going to run out anytime soon
You have about 9,223,372,036.855 seconds since January 1, 1970
which is april 11, 2262
your kids' kids' kids aren't likely to live by that time, it's more than 200 years in the future
if you want insurance though just store it at the microsecond level
your clock is likely off by several milliseconds at least, anyway 😛
nah
I do invariant TSC -> HPET -> ACPI PM timer
and for calibrating the TSC I use the others
kvmclock i dont really get the appeal
I store everything in nanoseconds
Alright, guess I'll keep doing nanoseconds then
it goes to show just how big a 64-bit value is
But what about on x86?
?
oh right you can just use uint64_t
Lately I've been trying to optimize memory mapping/unmapping by freeing pages only when the task dies or when the system is under memory pressure
alright guys I am fed up so that's gonna be for later
let's debug that pipe issue instead
turns out I am not on my usual computer and I don't have binutils, gcc and all that stuff built on the test disk I have here, so I instead I was working on my package manager so that I can have packages pre-built and stored on a remote server, so that I don't have to rebuild them for each computer I work on
Smart
actually I was talking about only the pages used by paging (PDPT, page directories, pages tables, etc...)
my message was missing information
I would like to keep the entries and the underlying tables to make mapping/unmapping faster by reusing those tables
Ahh I see
I’m currently making a queue of pages to free and having a worker thread do it for me
I am trying to upload packages to a s3 so that I don't have to rebuild them for each computer I work on, and:
upload failed: repo/dist/x86_64/binutils_2.44.meta to s3://pkg.maestro-os.org/dist/x86_64/binutils_2.44.meta argument of type 'NoneType' is not iterable
I fucking hate python
@late oar how did u create the logo?
with a text editor. I wrote the SVG myself
I found it to be easier than using a drawing software
Wow. That cool!
Wow
fr
But how did u make it look so good?
xD
it took 6 months of thinking and a lot of shitty attempts before coming up with something good
Wow okay
btw I forgot to mention, but OKLCH might be useful for you if you want pretty colors
https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl
CSS Color Module 4 adds oklch(), and we gain P3 wide-gamut support, boost code readability, and improve developer-designer communication.
Ok. I have heard about it. Thx!
still working on the package manager but since I am getting my usual computer tomorrow I guess I'll stop and continue debugging binutils's configure instead
fun fact: I can't build gcc anymore (on Linux) and I can't figure out why
aside from that, everything that I've managed to build is now on https://pkg.maestro-os.org
https://pkg.maestro-os.org/index has the list of packages in the repo, and each package can be downloaded with https://pkg.maestro-os.org/dist/x86_64/<package_name>_<package_version>.tar.gz (this is the package manager's job, not to be done manually)
gcc seems to be building so far (I didn't fix anything)
I think I am remembering I might actually never have packaged gcc in a tar with my build system, instead I probably installed it directly on the final disk
last time i went on holiday I implemented a cache for my package manager so that it doesn't re-download sources each time I attempt to build a package, as I noticed it was sucking all of my mobile data 
dead project
this evening I'm continuing debug of configure
dead project
found my bug: O_APPEND not implemented
damn you are super close
alright, rename and renameat2 are buggy, and the No error information messages are actually supposed to be Structure needs cleaning (the filesystem is fucked up)
been fixing up rename* during my lunch break, continuing after work
also the CI doesn't work anymore for some reason (can't find qemu)
btw I think I won't manage to fulfill my goal for this year which was to be able to code from Maestro itself (have gcc + vim + git with network)
networking won't fit before the end of the year (hoping I can even start working on vim)
managed to fix the CI, but the github actions runner only works on my local computer for some reason
fixed, that was a docker cache issue (the github runner runs in a docker)
let's get back to fixing rename and renameat*
My rename is a piece of shit, see: #filesystems message and following messages
Starting to think maybe implementing NVMe would make me save time when testing binutils/GCC compilation
@silk wren how do I do cycle-checking for rename? Do I iterate starting from the destination parent directory, going up until the root of the FS, checking if one of the node is the source file to be moved?
as far as I know, it comes down to "the destination directory mustn't be located inside the target to be moved"
The goal of this project is to translate the wonderful resource http://e-maxx.ru/algo which provides descriptions of many algorithms and data structures especially popular in field of competitive programming. Moreover we want to improve the collected knowledge by extending the articles and adding new articles to the collection.
no but this is not information that you can reasonably maintain, so you're going to have to traverse parent pointers to test is_ancestor_of.
so that's what I've described? 
dynamic cycle detection is hard for arbitrary graph changes
so yeah, in this case just walking up the path is the best solution
this is equivalent to dynamically maintaining the strongly connected components in a directed graph
and also equivalent to garbage collection
and the theoretically best known algorithms to that are not feasible in practice
yeah I'll walk up the path and have a per-filesystem rename mutex
why do you need a rename mutex
to check for cycles, to make sure there's no change in between the time I do the check and the time I do the actual rename
dont you need to lock just the vnode to move and the target destination
why not
i thought about this once and i'll be damned if i can remember the details of the renaming problem at the moment
in the meantime check out this fun code from dragonflybsd's tmpfs https://github.com/DragonFlyBSD/DragonFlyBSD/blob/d34567cafe70ead50df792facb580617c031e924/sys/vfs/tmpfs/tmpfs_subr.c#L1453
Didn't we have this conversation in #filesystems a few days ago? 
we did but i have my doubts over whether or not we ACTUALLY need the whole rename lock
funny
currently taking a break to avoid becoming crazy
I think I managed to rewrite rename correctly for ext2, although I haven't tested it yet because my kernel does not build. I have yet to fix the implementation on the tmpfs first before I can test all of that
@late oar do you use any tar parsing crate in maestro?
I'm thinking of using tar_no_std for the mlibc-demo-os filesystem but I am not sure how good that is
No. Do you want that for the initramfs?
I do it by parsing cpio myself
oh you use cpio
and yeah, to have shared loading support you can't just !include_bytes an elf :P
Yeah, easier to parse
Kernel still not building. I keep advancing on it (slowly) though
this evening I think I'll make a Maestro banner for my linkedin
Maybe a discord emoji with maestro's logo too. And I'll have to look at making stickers and tee shirts at some point

womenix
I don't have nitro so this is the best you guys will get on this server
L
no u
alright guys I am fed up. I am now refactoring the whole VFS
I have that thing Linux has where you have vtables represented by file_operations and inode_operations but instead of having a different one for regular files, directories, etc..., I had a single one per filesystem where I had a condition checking the file type (which kinda defeats the purpose).
So I am currently refactoring this crap to have an inode_operation per filesystem type and file type
that will simplify my code, making it easier to make rename work
also I'll probably be able to remove a mutex that wraps the target of a symlink in my tmpfs implementation (should be useless since this is set at the moment the symlink is created and can never be changed afterwards, but my code is not well architectured)
btw I've realized I have another issue with my VFS: When a filesystem is mounted at two places, I can have two dentrys pointing to the same actual entry on the filesystem (that's expected), however I've never implemented the thing to make sure when you remove a file on one mountpoint, the entry on the other mountpoint must be removed too
I think Linux does it lazily by calling d_revalidate while doing path resolution?
i dont get what you are trying to say. Also i think d_revalidate is for network filesystems
if the filesystem is mounted twice in different places of the filesystem, when you access a file in one, then access the same file on the other, you get two dentry in your vfs tree for the same file, right?
no its just one dentry
you create a dentry tree for that new mount and then the mount points point to the root dentry of that mount point/tree
so you don't duplicate the dentries for each mount if thats what youre asking
yeah
and on linux iirc if a dentry is a mountpoint you set a flag that it is a mountpoint and on lookup go to a hashtable indexed by that dentry's address -> to get the mount which has the pointer to the root dentry
https://elixir.bootlin.com/linux/v6.18/source/fs/namespace.c#L212
I think I have that lying somewhere in my code
bruh
a path is a tuple of dentry (which is where in that particular filesystem a location points) and mount (which is where in the global hierarchy of mounts that specific path points).
so the same dentry tree can live at multiple locations in the filesystem hierarchy, just with different mounts.
yeah I saw that in linux but I didn't understand why it was necessary at the time
also I don't remember why I needed to have different dentry for two different mountpoint of the same filesystem
Having two different dentries for the same location within a filesystem is essentially a guaranteed headache because you will have to come up with some invalidation scheme or otherwise workaround the issue of the trees going out-of-sync with eachother. And the two hardest things in programming are:
- cache invalidation
- naming things
- off-by-one errors
- scope creep
so this problem is best solved by not having two dentries for the same location within a filesystem.
yeah, thx 👍
It's a good thing I talk about this project here. You guys can give me advises when I am about to write crap and that saves me a lot of time 
12304 brains are better than one, who would've guessed? 
lmao
this is done. I am now trying to fix filesystem corruption
also the CI is now doing fsck to check for filesystem errors after running integration tests
I didn't change the VFS to have only one dentry per file per filesystem yet
also at some point I'll have to implement that thing to check whether there's a mountpoint inside of another before unmounting (I guess that's just each mountpoint having a pointer to its parent mountpoint, and a linked list to its children mountpoints)
I am deleting a lot of code in my tmpfs implementation. Basically I had structures inside of it to represent files, and then at each operation I was modifying both these structures, and the VFS entries. I am removing those inner structure to only use VFS entries to make everything easier
I need to have a unique inode ID for each inode in the tmpfs, right? How do I do that? bitmap of allocated inode ID?
just bump allocate them
why is this a problem?
you could bump allocate an inode every nanosecond and still not overflow in your lifetime
ig on 32 bit ino_t you have to keep track of allocations though
typedef uint64_t ino_t;
lol real
a bitmap would suck though, if you want to store 1<<32 bits you need 512MiB of storage
unless you can somehow store a sparse bitmap
yeah that's true
i'm not that smart it just sounds like a pain in the ass
i think thor did have a resource allocator of some sort that could be used for that?
not sure how that works
something like a set of intervals should work well
so that the size of that only grows linearly in the number of allocated inodes
but then you'd have to scan for a free inode number, no?
no, you just store free intervals
oh, free intervals
but you have to do coalescing when you free
when you said it grows with number of allocated inodes i thought it would store used intervals lol
so you need some kind of ordered map (= a tree etc)
yeah that's true
but it sounds relatively simple
and much better than a bump allocator, especially if your ino_t is 32-bit
that sounds like I should just use 64 bit inode IDs 
I am pretty proud of the fact that this project's codebase is actually pretty sane now (that wasn't the case two years ago). I don't have to rewrite everything all the time
so I am using 64 bit inode IDs (was already doing that actually) and I have a bump allocator for the tmpfs
rn I am trying to make all integration tests pass. I still have directory listing and rename that are broken
numa support:
Oh yeah that's gonna be a big refactor. But it won't be coming soon
I have that issue with the tmpfs where, if you do getdents on a directory, and remove files in it, then getdents won't show the remaining files and that's due to the way I implemented it.
getdents takes an iterator on a hashmap containing the dir entries, then skips n elements (where n is the offset of the open file description). So once I did getdents once, this offset is updated, and after I remove entries, the offset is out of bounds, so the iterator does not return anything even though some entries remain
i think that's fine, pretty sure linux also doesn't really solve that
this happens pretty much never
The rust stdlib seems to rely on Linux handling that
I think I have a null pointer deref (almost, there's an assert tripping before reaching it)
very likely an issue in the hashmap implementation
🚀 blazing null derefs
indeed
but that's unsafe code so that's my fault, not rust's fault
still trying to figure this crap out
@inner pilot would you fix the bug for me if I gave you 100€ for it? 
which bug
I have this crash when unlinking a file after renaming it:
-- KERNEL PANIC! --
CPU: 0 Reason: unsafe precondition(s) violated: NonNull::new_unchecked requires that the pointer is non-null
This indicates a bug in the program. This Undefined Behavior check is optional, and cannot be relied on for safety. Location: /home/luc/.rustup/toolchains/nightly-2025-05-10-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/panicking.rs:226:5
Callstack:
<ffff80000021818a>: ???
<ffff80000040a0ef>: ???
<ffff800000403c44>: ???
<ffff800000403cc7>: ???
<ffff8000003ed74c>: ???
<ffff8000003ebf08>: _ZN4core3ptr8non_null16NonNull$LT$T$GT$13new_unchecked17hdb4cd419be62f0d0E
<ffff8000003ec114>: _ZN4core3ptr8non_null26NonNull$LT$$u5b$T$u5d$$GT$20slice_from_raw_parts17h92dfa8257571eabcE
<ffff8000003ecc4a>: _ZN5utils11collections3vec12Vec$LT$T$GT$8as_slice17h1a42c19653d8d5e9E
-- end trace --
Halting...
I think it's the hashmap/hashset implementation being invalid and probably caused by this insert https://github.com/maestro-os/maestro/blob/4949eacb04e92ab46e72d20b19ab8c1573b863bf/kernel/src/file/vfs/mod.rs#L911 or the remove after it
That happens while running the integration tests
hashmap is implemented in here: https://github.com/maestro-os/maestro/tree/4949eacb04e92ab46e72d20b19ab8c1573b863bf/utils/src/collections/hashmap
(it's a swiss table implementation using simd. yes that was a bad idea in kernelspace and that will have to be changed at some point)
simd in the fucking kernel?
yeah don't ask
bro nuke it immediately
turns out simd is not even enabled so the compiler emulates it

lol
-100 performance
yeah
writes simd implentation for the challenge of it
worked pretty well until today (surprisingly). and i didn't touch it in 7 months
maybe I should just nuke it yeah
now I have to figure out how this is usually implemented without simd 
@inner pilot do you happen to have a hashmap implementation for your kernel?
If you use the one in the stdlib I have bad news for you
what is it
Stdlib hashmap is a SIMD Swiss table too
Why no hashmap?
not available in alloc
Yeah but simd
yes the abseil one is simd
ripperoni
the point of the design is to work with SIMD
since you can check like 16 elements at once
iirc alloc has no hashmap because of getrandom
apparently not
Something something protection against denial of service
You can
but not the libc 
An open-source collection of core C++ library code
it's really cool
common google engineering W
Yeah I used that to implement mine
- a talk some guy from google did
I should look into implementing one
i already have my hamt thing which is good but who wouldnt want another datastructure?
The more I think about it, the more I think "Google" is a cringe name for a company
So 90s
Apple 💀
💀
do you guys think I could just reuse swiss tables but without simd (linear probing) or should I use another method?
yes of course
simd just allows you to do like 16 compares at once which is why its good with this design
alright imma start this refactor after dinner
it was simd without using actual simd instructions anyway
yeah but in this case maybe there's a better algorithm?
hashmap
oh
just a simple chaining hashmap works fine too
the point of swiss table is to improve cache locality without having to deal with the disadvantages of open addressing
yeah but I liked that I could fit everything in a single memory allocation when doing a collect
do you really need collect?
id just do a chaining hashmap with avl trees or something
swiss table is really good with SIMD but without it it just ends up like regular open addressing
and you can have per-bucket locks too which is good
if i do chaining hashmaps, then I think I should make them intrusive. Otherwise I will end up in a lot of cases with having to make an allocation (for the linked list node) to store an Arc, which itself allocates memory to store the final object
however, intrusive stuff is annoying in rust
what do you use hashmaps for
what i would do is implement one ad-hoc everywhere I need and make the links intrusive
does that work with safe rust
in a lot of places
no

I think you can make intrusive stuff safe, except unlinking
only with atomicptrs
just do regular chaining hash tables
they also allow you to do fine grained locking on buckets
btw there are tricks for making something like swisstable's probing work without the simd instructions
its obviously not as good
but its still not totally terrible
need motivation to do that
since the beginning of this project I have used my own collections to make sure I can handle memory allocation failures, but right now I am wondering if this was a good idea
instead, maybe I should make all allocations non-fallible and when we run out of memory, we always attempt to shrink caches or kill processes in order to get back the memory I need to make the allocation?

why is it the right thing though?
alright guys I am fed up of the hashmap so I am nuking it and I am making the chaining one, with per-bucket locking, intrusive lists and that kind of stuff
Bro has a j*b 🤮
disgusting
couldn't you just kill the allocating process?
what if the allocating process is more important than the other processes that allocated memory before?
Maybe I should create a new branch and work on something else in order to get motivation again
What about NVMe?
xorg?
That's also a good idea
nvme was relatively easy even without interrupts
got a friend who's really good at rust debugging my hashmap implementation and she didn't find any issue so far, so I am starting to think the issue might not actually be in the hashmap but maybe it is a buffer overflow overwriting the hashmap
Yeah
very long working session 😔
lol
Your goal is to not ever reach OOM and having to kill random processes
Doing it like Linux sucks
Fwiw Linux has an option to disable overcommit
does it do automatic swapfile expansion
Dont think so
Also Linux has lots of way to make it so its not random processes
You have oom_score_adj, you can write a psi monitor to implement userspace oom killer which systemd does, you can use cgroups
Etc etc
oom killer still sucks
Also the way it starts slowing down dramatically (takes several seconds to switch VTs, etc) in low free memory conditions w/o swap isn’t much better
maybe I could also implement virtio-block aside from NVMe, and write a blog article about storage sometime
a NVMe will also show up as an IDE controller for backwards compat, doesn't it?
how do I avoid considering them as two different devices?
idk I'd expect it to be that case since SATA has this kind of black magic, doesn't it?
for backwards compat
what
I think he means like the PCI class
yes
There will only be one pci device per nvme controller, so you won't have to worry about the second question here.
I may have misconfigured qemu but I remember seeing an IDE controller showing up too
I now have to figure out how the submission and completion queue work together
And find out how to get an interrupt when an operation is done (if that's doable)
should I have an I/O subimission queue per CPU core and disk?
per-cpu IOSQ is something that's typically done, yes
but that of course requires your interrupt system to be able to allocate and route per-cpu interrupts properly
so many hobby OSes don't really end up doing it, they just do 1 IOSQ and call it a day
you can get very good performance that way too, so i wouldn't bother with per-cpu IO queues for now
Thanks!
that may be the dvd drive
oh right
I boot the kernel with a .iso as a cdrom. Good catch
ye
I have officially failed my goal for 2025 👍 (be able to code from Maestro itself). gotta make a blog article to say that
implementing nvme is a bit harder than I expected but it's fun to do 👌
how close did you get?
pretty far. I could not make binutils's ./configure run (I was trying to build binutils and gcc on Maestro itself to test stability). I didn't start porting Vim nor git and I didn't start the network stack
the only thing I have pretty much is binutils, gcc and make working
I see, impressive progress nonetheless.
thanks!
I could not run configure because rename was corrupting the filesystem. So I rewrote a chunk of the VFS, but I've got stopped by a hashmap bug (use after free)
ugh
I've got a friend who think they found the issue on the hashmap, but they didn't make a PR yet
(something something shitty remove function)
I was about to rewrite the hashmap to make it intrusive and use chaining (so that I can use less memory allocations and have per-bucket locking) but in the end I don't feeling like doing that right now
which parts do you find hard to implement?

