#charmOS
1 messages Β· Page 2 of 1
the trick to having good ideas is to just be well read and trick people into thinking you have good ideas by learning about and implementing other ppls good ideas
I never saw one
yes
do it work and yield and stuff
yes
neat
you can search "rusty os" on github and you'll get dozens of kernels with the same name lmfao
such original name
rewrite mintia2 scheduler in C and just put my name somewhere in the credits file
yikes
i really wanted to avoid "OS" in the name
menix == minix
nah my scheduler is complete aside from the lack of manual yield so i just need to fix it to do that
great marketing
why did i never look at minix vfs
people will confuse the names and star your repo
well menix actually stood for something
mervin unix
so rsp doesnt matter? oh ok lol
you won't guess what Ilobilix stands for 
"minimal expandable unix like system"
manix is marvin unix
this makes use of the C calling convention
counterpoint you dont have priorities
he has an AFS
Soonβ’
i was socialismmaxxing
the cores even try to be equal to each other in load
is actually fair scheduler more or less fair than completely fair scheduler?
our timeslice
actually fair just makes sure you cant have things like kernel events with higher priorities or something i think
so it's actually fair whereas CFS still has those priorities
i think
NFS isn't Network FileSystem, it's Not Fair Scheduler
i never say things that are true
paradox
@eager yew hows your implementation strategy are you more of a "completely implement each component before moving on to the next" guy or a "do each thing simply and integrate them and then build upon them later" guy
the second one
my scheduler used to be RR
no work stealing
then i came back and made it MLFQ with work stealing
and i am fixing it for manual yield
good the first is unbelievably boring
"We have menix code at home"
id also argue its only correct once you know exactly what everything needs to do which is unlikely for your first 5 or so rewrites
who the fuck does the former
i havent had to rewrite things like drivers which are just an implementation of a specification but i have indeed had 4 total allocator rewrites 3 scheduler rewrites 1 vfs rewrite and some other small things here and there
me rn
"yes please never give me any results i don't want anything working"
"we have -> at home"
.deref() 
i completely implemented like 3 subsystems in sequence and they each turned out like 10k lines and then i got so bored cuz there needs to be another like 2-3 of them before it looks like its doing anything that i stopped working for like 4 months
whole function call is super necessary for what is just a [ptr] in asm π₯
i feel sorry for u
is mintia cooked
i keep staring at my VFS code and my mind goes numb when i look at my todo!() statements
i can helps a bit my vfs works fine
i know how it works, i just don't know how to design an OKish interface
interface between what and what
where is the pain and struggle
like an API to interact with it, as in: File::open needs what arguments, returns what, etc
why cant you just start with something and then change it as your needs change
i wish i could tell you
i don't know where to start π’
i may be on the spectrum
shocker
write the function prototypes
i bet i have a suggestion for how you should do your vfs that doesnt sound like anything you read before and might be simpler to you
and is also better
@placid vessel come up with these
and then implement them for each filesystem
it's that shrimple (for me at least)
oh?
marketing
exactly
i need something like that
i just need to know what i have to implement
not even how
just what
look at the linux fs syscalls π₯
you'll need most of those
that's what i did

diagramming it might take less energy than verbally explaining it and then ill have something i can spam whenever people say the word vfs
i can't listen to verbal instructions anyways
so you know all your commands you have like mkdir cd chmod and things, just implement interfaces necessary for the basics of that and later on you can add if you need
that was my logic
it worked fine
ohhh shoot i just remembered i have to implement the functions necessary to respect file flags like secure delete on ext2
ts gonna be so painful
not very good but it's ok
sigh i am not a fan of intels specs
yes thank you intel i was absolutely looking forward to writing all your documentation as code comments because i dont want to constantly have to be referencing your manual this is just what i wanted
basically involves completely eliminating the concept of the vnode cache
when a vnode's refcount drops to 0 you immediately delete it
sounds bad right
can that be performant
well you also have a dirent cache and the entries therein keep a reference to the vnode
what you reclaim is entries in the dirent cache with a refcount of 0 when there are too many of them
what this lets you do in the fs drivers is they can each create, keep track of, lookup, delete vnodes in their own way
which is most appropriate imo
a unix-like filesystem will want to keep a hash table keyed by inode number that yields the vnode
(or something)
whereas FAT can keep a tree of vnodes looked up by name as children from their parent
this is superior to the centralized vnode caching you sometimes see which enforces a particular lookup scheme, for example the haiku one makes all vnodes have a unique "vnid" which is simply equivalent to the inode number for unixlike filesystems, but then the FAT driver needs like 5000 lines of code to maintain fake vnids for its vnodes
it also makes vnode reclamation easier
yea the linux kernel on line 853 of fs/fat/inode.c has fake inode calls
it's interesting
they have it elsewhere too
I use unique identifiers in my vfs for faster lookups and to compare two vnodes as being the same thing on the filesystem
iunique is a bit silly, i wonder why they moved away from using the dentry position as the inode number
would collisions happen there or somethin
inode numbers are only unique within a file system
BUGS
and you can't have 2 dentries in the same position
the dirent position can change from the file being renamed or whatever
but you still need to keep it unique
so if another file is created in the same position you need to tell that you cant give it the same id
and give it some other id
and it gets messy
is there a better way to generate unique inodes
like linear search cant possibly be the only method
probably a hash function but then that collides
anyway my way i just described is way better and avoids this whole thing
well you still need inode numbers to satisfy userspace
userspace tools will use (st_dev, st_ino) pairs to track files
just give it incrementing numbers
no need to even check for uniqueness
roll over could cause minor chaos but only in userspace lol
u64 solves that too
oh deary me i have 18 quintillion inodes whatever shall i do π¨
also the approach i took with my asid sequence number
i love 64-bit monotonic counters
should i do this for pids or no because i really dont want to have a system with a long uptime and see pid 2 million or something
would be a little bit silly
eh i used to get that on linux since i had a script that'd run a bunch of commands every second
i just use a free list within the pid table to allocate pids and keep a 12 bit sequence number for each pid table entry so that pids arent reused too quickly
ez and allows me to have O(1) pid lookup
smarto
speaking of O(1) time i think my cryptography in the kernel might be screwed because the CSPRNG isnt O(1)
chacha20 π
sidechannel π₯ π
sequence number is necessary because if you delete and create processes repeatedly the same pid table entry will be reused each time since its a singly linked free list
so if the seq number wasnt there itd reuse way too fast
p sure they also have real crypto sources
but chacha20 isnt linear time so you can try to guess if you know the timing
chacha salsa
crypto is fake anyway
lore
incredible
crazy
lmao this is shockingly common in hobby oses
i think my "scheduler issue" was a printf bug where printf's lock would enable interrupts at the end
i changed that and it's gone
and when i ran my scheduler with more printf's before this it would fault more
now it all goen
craxy
ipi ftw
0x79 is such an odd number
0x20 is just the first ISR that's free
just use that or the highest vector possible
it's a perfect square
0x20 is usually where irq 0 ends up after people remap the pic 
using the pic
I was hard coping with making a non interrupt context switch so I'ma just do the sane thing for now and just generate schedule yields with the interrupt handler and when my brain starts working later I'll refactor it into a proper non interrupt yield but right now I want to work on other things π₯π₯π₯π₯
That's crazy because I feel like the interrupt thing is over complicated and the other way should be easier
I was having stack corruption bugs and I'm also getting bored of scheduler stuff anyway
it's better to get things done and then fix it when you can think of a fix naturally than force yourself to think of a fix and fixate on something
at least for me
this is why I go on walks when I get really problematic bugs
Am I tweaking or does this not switch to the IST defined for the ISR?
huh
why would it not
I had an issue where upon forcing a reschedule the stack would get corrupted
And turns out the way i rescheduled
int 48
Does not switch to the stack for the isr
I literally had a similar issue back in March
whar π
Yeah
is the timer interrupt calling the isr
is your asm syntax incorrect
No the syntax was correct
how does this occur
int 0x30
Brother
Im using intel
is it inline assembly
did you do the .intel_syntax noprefix
-masm=intel
and then .att_syntax prefix at the end of the segment
Yes
that's a wild bug then
oh boy
oh this one's gonna be a doozy I bet
it's always something wild when you gotta search the docs for 10 minutes
I'm finding bruh
But I swear I read in the manual somewhere
That hardware exceptions/interrupts trigger an IST switch
yeah software interrupts don't use the ist
was that the bug
Ok where did you find it
oh this is just information I knew lol
I swear it was chapter 7 volume 3
Yes
It did lol
what was the fix
I fixed it by sending an ipi to the cpu itself
just rewrite the scheduler logic to not require an interrupt?
crazy lol
this is actually kinda silly but yeah understandable lol
I don't think my scheduler needs that
it works fine without that thingy mabob I can just manually call a software interrupt
hilarious bug tho
I know π
oh yeah here's the pseudocode for my scheduler if anyone wants to just get the jist of it without reading that codebase
function schedule():
save_current_thread_state()
if not should_attempt_steal():
goto regular_schedule
if too_many_stealers():
goto regular_schedule
victim_core = find_victim_core()
if victim_core is NULL:
goto regular_schedule
victim_thread = pick_thread_from(victim_core)
if victim_thread is NULL:
goto regular_schedule
switch_to(victim_thread)
return
regular_schedule:
next_thread = pick_from_own_mlfq()
switch_to(next_thread)
ah I know what I can do next for my scheduler
No
so basically, once I get to having thread priority levels, I want to avoid wasting time trying to work steal because that is an expensive operation
if it detects high system load and a currently high priority thread, rather than checking constantly if we can steal work, we can instead add batch stealing, and attempt a batch steal when we have a lower priority thread currently being scheduled because who cares about those goobers
that seems like a reasonable place to do that
this way I can avoid constantly checking for a steal opportunity and instead do one large steal to mass rebalance many threads when the opportunity presents itself
this will probably also help with steal success rates because this will have less cores that are able to perform a steal (but similar impacts when I do steal work) because I have a hard cap that says only half the cores can be work stealing at once
how does work stealing work in your os
There isn't any
π²
do you plan to add it
Nah
so if a core just becomes severely overloaded as time goes on it's just "oh well... that's too bad..."? lol
understandable
this isn't difficult to implement jsyk
It shouldn't happen
I know
Again I doubt
I never thought a lot about the scheduler
Get a thread run it and repeat
how about in a case where one core (purely by chance) has many interactive (constantly running tasks like DE threads and whatnot) assigned to it, while other cores get threads that quickly die
then over time that core that may be running all those interactive threads would have a much higher load
π₯
Yeah
Well it can happen
Yeah lmao
Rn it's just fix and implement shit
I'll think about different scheduling algs after that
ok well I mean if you ever get bored just remember that this is pretty fun to think about and implement because it's nice to change things up
Yeah
I'll go back to filesystems and come back later tomorrow because I don't wanna think about threads and tasks and cores and locks and mutexes and yields and async and all that ANY MORE
idk how people can put up with this for so long
next april fools I'm gonna try to put my entire OS in one file and compile it like that
hopefully it will be a relatively more solid project by then so that it will either take 5 minutes or crash the computer
will be very silly
back to implementing support for microsoft's bum ass filesystem
or maybe xhci
that seems more pleasant
it seems like steals are multiplefold more inefficient than regular schedule events
might wanna do batch stealing
wut the heck is a steal?
let's say you have cores 0-3
core 0 is heavily overloaded, having 40 threads on it
cores 1-3 are bing chilling, with almost no workload
cores 1-3 realize this and go "Hey, let's remove threads from 0's queue, and give that work to ourselves"
now, core 0-3 all have 10 threads each
mucho faster π
now if ur wondering "why would i need this in the first place?"
let's say you have the following scenario
you have a desktop come alive at the start of your user login, as well as a bunch of small scripts
purely by chance (as the scheduler does not know how long a thread will run for when you first load it), the desktop threads all go on core 0 and the script runner threads all go on the rest
after the scripts end, now you have an imbalanced system, and it is work stealing that allows it to balance out
Ahhh, that makes sense!
switched to a bitmap, regular scheduling events are sucking mildly less
i think i need more infrequent steals
ok yes regular schedules are noticeably faster once the steals stop
the problem is that the steals don't stop at the start, but i think that is OK
I think i might introduce something where after enough non-steal scheduling events (failed steals due to none available), i will stop checking for steals
10 microsecond scheduling overhead π¬
I think i should avoid load recalculating and just take a heuristic based on thread count
COOKING
i think this is good enough
the load computation heuristic was wasting too much time so i just use thread counts as loads and it's good enough
2.3 microseconds π€©
i dont really need it too much faster but i might take a peek around and see if i can get it faster
work steals are still less than ideal so i might do something where they give up after waiting on a lock for too long
this is what happens when u dont prematurely optimize π₯
optimization is now pretty trivial
i think i'm gonna do a spin_trylock instead of waiting turns
ok how did that work so well
we are down from a few million clock cycles so whilst this is still hot garbo, it is better
the fact that this simple optimization cut the time by 2-4 times is wild
load balancing is slower but i think that this is overall better because more work is done and this test case is pretty extreme
it's testing one core with 50 more active threads than the others
and it still balances in <25s even with very very slow reschedule events
ok i will probably go and figure out batch stealing once i optimize a few more things, regular schedule events take 7,000 clock cycles which is "Good enough for me", and steals take about 100 times that much sometimes which is "Holy moly this is bad"
but steals happening don't really affect regular schedule events too much so i should be ok
ok because constantly stealing sucks, this is the plan
I'm gonna do a periodic steal attempt in the main schedule event, with budgeted batch stealing, as well as a full rescan if a very large influx of threads is detected
should be fine
I always attempt to see if a steal is viable on every scheduling attempt
see here
yeah but how do you determine if something should steal
i would only do it if the load difference is too high
if the load is equal on all cores then there is 0 point in stealing
yes this is what I do
but steals use lots of locks and do lots of waiting
so increase the threshold
no because that still causes singular steals
I will steal once every X scheduling opportunities and steal in batches as well as have a mass forced steal if a large influx of threads is detected
it's just the locks that cause the slowdown
batch stealing should help
what i do is during thread creation it tries to find the optimal cpu to place it on from the beginning
but see this would still cause stealing scenarios
you don't know a thread's runtime
I wonder if I should name my scheduler lol
CASS the charmOS Adaptive Scheduling System idk
You never migrate after that?
the workstealing only appeared slow because i was PRINTING TO THE TERMINAL and that takes AGES
this is actually perfectly fine
The measurements are under kvm/real hw right?
yep
oh yeah i tested that tmpfs thing today and i apparently did it right (I think) first try
time to stress test
lsp crashout
ok fixed it lol
i am a little bored so i will try to enter userspace for fun
ok time to jump to it now
snooooooozefest
man...
syscalls time
asldkfjal;sdfjaweo;ijha;or
how have i not page faulted yet
sus
ah i finally got one nice
i wonder if i will have 100 lines of includes by next year
oops i wasnt marking the pages as executable
rookie mistake
lol this guy keeps page faulting π₯
funy
iretq here is faulting for some reason
weird π₯
i am dumb it is not a page fault
my GPF handler was faulting
π₯
i got to userspace or something
we have now returned to userspace after a syscall
i forgot that nasm wants you to write
o64 sysret
instead of
sysretq```
ok now that I got this bs working I shall integrate it with my scheduler later and spend 20 years writing syscalls
yippee π
i had the funniest bug that ended up being a file read bug where i wrote an extra line
elf loading is just fine
ran a program
i was not supposed to get this far
whatever
i will keep going for the funny
i fogort to allocate rsp0 so my fault handlers didnt trigger from userland
0 minutes since last idiotic bug
longest streak was 7 hours lets see if i can beat it
feeling bored gonna do proper logging
current logging lowk goofy
the . is saying that the filesystem was mounted and the 'yabadabadoo' is saying the tmpfs mounted
wild stuff
wait I need to start doing unit tests
shouldn't be too bad
__attribute__((constructor)) my beloved
test
ping!
should i do connectivity and networking tests later on
sounds fun tbh
pong :(
this is starting to feel like macro abuse tbh
Hey! I like pong! XD
reminds me of rust's cargo test
[TEST]: RUNNING load gdt ... PASS
[TEST]: RUNNING load idt ... PASS
i wish i knew how to check those things
i would absolutely add them
hmmm i wonder if i should keep all tests in one file
ykw this is funny
it'll probably grow to many thousands of lines but who cares
got bored and added more to testing infra and more tests
it should not say all ok
oops
might add like timing and stuff tho
idk
ok probably should actually write tests instead of wasting time on this
less yappy now
fixd it
nice π₯
the really nice thing about tests and testing NOW is that I find bugs that I would've otherwise missed that would've otherwise caused huge damage
like I forgot to check if a file was actually a file and not a directory before writing to it in ext2 and that caused insane filesystem corruption
but I found that bug with unit tests
yippee
it tends to be really stupid stuff like that that I screw up and not large issues
just forgetting an if statement and whatever
first degree skill isuse
probably gonna go and refactor a buncha things now that i can be confident i wont just make the system implode with my unit tests
80 line functions π¬
not very good π
shoutout to the lemonlime bootloader
wow these are already starting too look so much more aesthetically pleasing
small functions my beloved
i feel like i made this look worse
i really hope this is under a mutex
not yet
i'm almost around to implementing mutexes
cant say i'm a fan of this but it's fine
biggest spaghetti code ever
You are way out of order
Yah lol
Also you're leaking memory on error
Because of try
?
try will just attempt that operation and goto cleanup if it fails
wdym
So you cant use try in functions that don't have the cleanup label?
it's specifically for this function
I really didn't like how repetitive it looked without it
but I'll probably go back since this might be super dumb
Usually try is just like the rust question mark operator
ah ok understandable
You were supposed to do that loooooong before an fs driver
also i wanted to see bread/brelse 
oh yeah I don't have buffer caches yet either thanks for the reminder
or block caches or whatever it's called
You will be in severe pain for this
sounds like you're gonna skin him for this
it's only about 3 thousand lines of filesystem code I'm not too worried
I would claim that locking strategy determines kernel design
If you write code without worrying about locks you're making a blob
I'll probably be able to fix this just fine over the next few days
this kernel is really small rn
Generally speaking the real structure of a kernel is the structure of its locks
#1 design constraint maybe
A what
A dlob
Who needs locks anyway
Either have big process lock or idk
Consult serenity code

lock the whole computer so you technically are SMP but only one thing can ever run at once
π§ biggest lock
oh yeah speakin of that scheduler stuff i made it properly go to idle and stop the timeslice stuff if it doesnt need it
i manually yield to tell it to go back to scheduling stuff
mild improvement
ok so that doesn't work like at all
unless you are doing a nonpreemptible uniprocessor-only kernel
Are u?
no
what's wrong here
get_curr_thread gets the thread that the current core is running
probably a bad name here
what if you get preempted between the load of m->owner and the null check?
oops yeah i forgot about spin locks
should probably do that
spin lock
probably still problematic
i'll go take a look at linux or freebsd's mutexes before i do anything even dumber
linux mutex docs are pretty good
I disagree with many things s about this
how do i fix it
it worked in a test
Why do you remove yourself from a queue
I mean, i could have the scheduler just skip over blocked threads but i figured that might add overhead to my main scheduling loop when picking threads to run
so i figured removal and then re-queueing would be better
Why are you still on a queue while running??
oh wait i forgot about that
oops
yeah it isnt on a queue nvm
ok i fixed the problems the mutex works
probably still work to do
i know linux's mutex has 3 paths it can take or something
i believe it makes an attempt to busy wait and acquire
mines 1200 lines so yeah
yep π
first mutex prototype done
time to read lots and lots about this
seems cool
lock convoys βοΈ
nah that's quite complicated
works ok on a few more cores
should be good
its broken
how come
every thread here gets a turn at the mutex
i think i'm not properly printing the core number of the thread i wake
something odd happening there probably
but every thread comes around and has its turn
what happens when you scheduler_enqueue the thread before it scheduler_yields
wdym
I sure can't wait to directly copy the hashmap implementation from menix to my kernel to avoid reinventing the wheel so I can have a filesystem cache
thanks for being OSS menix
really appreciate it
bread brelse
real
where was that code from
I'm adjusting this for a hashmap now lol
someone from this server designed this fs
filesystem cache design
truly peak
yes
you get to see your beloved bread and brelse soon
isn't that fun π€©
yea
i need to implement this
meh it's not that necessary imo and i dont want to build a page cache too early
it works tho, the cache has sped things up a lot
integration test is down ~40ms
pretty decent i'd say
> git commit -a
[main 484983a] [ext2 + fs]: block cache
19 files changed, 310 insertions(+), 228 deletions(-)
``` love to see it
now i gotta go through and use the cache entry locks everywhere
one pleasant side effect of this is that I no longer need a bunch of memory management code in the filesystem code because the generic cache essentially serves as the "memory manager" for all the buffers and frees and allocates them as necessary
I think I got down from about 30 calls to malloc/free to about 3-6 with the fs cache
nice
pretty good cache hit rates
I might turn my cache hashmap into a generic hashmap but idk, it's got some fs-cache specific features so it might not be "trivial" to do that, I'll probably just use a generic hashmap elsewhere and use the custom one for the fs block cache
I think i might make batch evictions in the cache possible by introducing something like a maybe_evict flag
this way, when you call eviction the first time (probably before the batch eviction as it will only decide to batch evict under higher pressure), it will go and mark a bunch of entries as maybe_evict
this would probably allow the batch eviction to very easily just go and throw out all of the entries marked as maybe_evict instead of constantly finding the next-last-recently-used one
void fs_cache_decay(struct fs_cache *cache, uint64_t now) {
for (uint64_t i = 0; i < cache->capacity; i++) {
struct fs_cache_entry *entry = &cache->entries[i];
if (!entry->occupied || !entry->value)
continue;
if ((now - entry->access_time) > STALE_THRESHOLD)
entry->maybe_evict = true;
}
}
``` maybe i could have something like this get called every now and then to re-check the cache
@steel herald we have dollar store bread
gonna put locks everywhere soon
this looks like filesystem cache, not block cache
it is block cache
those are separate things
wait what
cache of filesystem blocks isn't a block cache?
o
Welll
now I'm a little perplexed
at least on linux those are separate things
oh ok well they're the same here
what is it on linux
are blocks there just regions of disk or what
now we have low budget bread
so how would this cache work for multiple filesystems with different block sizes on the same disk
each filesystem has its own block cache
it's that shrimple
the block size is dynamic and generic
yeah thats called a filesystem cache
wait what
infy@DESKTOP-IGT40G0:~/linux$ cat /proc/meminfo | grep Cached
Cached: 2175728 kB
infy@DESKTOP-IGT40G0:~/linux$ cat /proc/meminfo | grep Buffers
Buffers: 24312 kB
on linux
what are the blocks in the context of linux
ohhhh ok well this is already called a filesystem cache anyway so I guess that's ok in naming
will I also need a block cache alongside my filesystem cache so that it goes and makes an entry in the block cache for a particular disk when it does a filesystem read/write
so basically the idea is actual file data is stored in page cache
filesystem metadata is stored in buffers
The former is "Cached" the latter is "Buffers"
wait so this would be like copying the file data to swap, right?
since that's kinda what page cache is?
well a per-filesystem page cache is a pretty bad idea ofc, u want this to be a generic system that all filesystems get for free
swap is exactly like a normal file, but it covers anonymous memory instead of file mappings
it's not a per filesystem "page cache" it's just a cache for per filesystem block data (the data inside of blocks)
the cache is just a hashmap in memory
no it will have its own way of making caches
the underlying cache itself is generic but each filesystem uses it differentlu
@stuck nexus can u explain pls
basically, there is this structure
struct fs_cache_entry {
/* allocated upon new reads */
uint8_t *buffer;
uint64_t size;
/* number of this entry */
uint64_t number;
/* used with a counter - not a real 'timestamp' */
uint64_t access_time;
struct mutex lock;
bool dirty;
bool no_evict;
};
struct fs_cache_wrapper {
uint64_t key; // block number
struct fs_cache_entry *value; // pointer to cache entry
bool occupied;
};
struct fs_cache {
struct fs_cache_wrapper *entries;
atomic_uint_fast64_t ticks;
uint64_t capacity;
uint64_t count;
struct spinlock lock;
};```
each filesystem makes its own instance of this
and uses the fields as it needs
why would I rather have a per-disk cache in memory?
the data of whatever is inside of filesystem inodes/blocks or whatever it needs
so filesystem metadata
kinda sorta, it's not metadata it's the actual data (for now) but yea
it sits in memory
so what's the issue here
TLDR you want to have a centralized thing that works for everyone
instead of forcing every filesystem to do their own bolierplate and their own private cache
but how does this benefit the system performance wise
that's kinda why I did this
I wanted it to go a bit faster
a centralized block cache is exactly the same in terms of performance
so why would this be better
different filesystems handle things differently so I wanted to implement independent ones for each
why is reusing code a good thing in your opinion
literally all filesystems have metadata stored in blocks
thats kind of how they work
they dont need to implement private caches every time
hmmm ok well this should be pretty trivial to refactor into that
so would this cache just store the LBAs of the blocks or what
a centralized system also makes it easy to implement eviction on memory pressure
what linux calls page reclaim
yeah
by default most ram (depending on the watermarks) gets eaten by page/buffer cache
then when memory is suddenly needed all the blocks are evicted and pages are freed
and linux for example has a thread that runs every second and writes out dirty blocks to disk
is this cache per-disk or global global (as in all disks use the same cache in memory)
the question doesnt make much sense
what are you even asking lol
different disks have unique data at the same lba
oh wait yes it should be one per disk nvm
thanks for the help infster i shall go fix this
if you're interested
this function retrieves a block from a disk device
basically this entire file is the buffer cache implementation
thats the classic thing they use in unix
i kinda hate it because brelse sounds like "else branch"
bcache_get, bread, same thing π₯
so am i su pposed to call thiss a block cache
well it is a block cache right
an anonymous LRU cache of disk blocks referenced at some point
not belonging to any file
all done, thanks for the suggestion infster
cool
why cant bcache_get handle this transparently lol
I thought i might have cases where I need to handle things differently and not evict, idk tho, i will probably fix it up to do that
i just thought it would be confusing to have a get function also decide to call evict when it's named get
no big deal
nvm yea i can trivially change this
it drops stuff transparently
u can take the entire code from line 18 to 26 and paste it into bcache_get
thats literally how its supposed to work
π
u can even have a generic superblock struct like linux, then u dont even need ext2_block_read
because u can just do bcache_get_fs_block(fs->superblock, block_idx)
and superblock contains all that math in lines 8 to 10
because all filesystems work like that anyway
yea i was planning on doing that too, might do that once i finish the FAT VFS function implementation
at least like a struct fs_coordinates idk lol
what are coordinates here
like where the fs is?
i mean i have a partition struct
generic_partition
will probably do generic_superblock
base block, number of disk blocks per fs block
so that generic block cache can do disk requests with correct offsets without you handcrafting the math in every filesystem
bread fix
thats better
thanks infy for all ur help with my exceptional stupidity π i honestly really appreciate this community for that
static inline struct buffer_head *
sb_bread(struct super_block *sb, sector_t block)
{
return __bread_gfp(sb->s_bdev, block, sb->s_blocksize, __GFP_MOVABLE);
}
Its funny because thats literally how sb_bread works
i copied thems...
it takes s_blocksize from the superblock
gpl 3..
nah i just mean its definitely the correct thing to do
is __GFP_MOVABLE saying whether or not it can be evicted?
not sure what movable would mean here
this is for memory compaction
movable means it can be transparently replaced by a different page
because there's nothing referencing this address via the HHDM for example
all userspace allocations are movable by default
if you know C# it has stable pointers and all others are movable by default
same thing here
now the real trouble is dynamically resizing the silly block caches
you should probably store them at least in pages, with some amount of readahead
i mean the actual expanding isnt the issue, it's knowing when and how much to expand/shrink
idk that will be figured out as i make this project
u expand forever
the key part is that your PMM can go and evict everything with refcount == 0 if it needs the memory
so each cache entry would get its own page?
yeah i would store them as a collection of aligned blocks
so e.g. if someone requests block 18 you go read 8 blocks starting at block 16
so during caching, i could mark a bit or something on the PTE saying "hey these pages are available for eviction"
then, the page evictor will remove the block cache pieces, and the next time I try to access that thing in cache I can just load it from disk again?
sounds alright
nah
pmm_alloc():
pfn = alloc_fastpath();
if (!pfn) {
block_cache_lru_evict();
pfn = alloc_fastpath();
}
return pfn
you block cache should have an lru as well as a hash table
so u can walk the lru and discard least used blocks
linux stores the lru as part of the struct page i think
so they're linked to each other
sounds awesome sauce thanks infster
good luck with your OS I hope you get to "gdt init... ok" soon
[TEST]: 164 block cache hits, 11 block cache misses decent ig
forgot that it would be smart to just page align these block cache buffers so that the drivers dont need to allocate their own and memcpy a bunch, ez fix tho, noticeable speedup too
hmmm the maestro page cache thing looks kewl
might take a further gander at it
ya i think these are the fancy writable ones, indeed, there are Writable devices that are referred to Compact DIsk Read Only Memory devices
truly a misnomer of all time
wait i think the standard ATAPI ones are also writable too
yea nvm lol
writable read only memory π
if u want disk advice, you should probably start off with virtio and do NVMe next
buuuuuut you'll need that fancy microkernel driver interface first
I was debating whether or not I'd do virtio or just jump straight to NVMe (since I can't use virtio on hw)
going from no block device driver experience to straight to NVMe is doable but you might wannna consider starting off on something easier to understand basic concepts shared across devices
logical block addresses, sector sizes, stuff like that
ahh okay
you are also wrong so u have made a grave error in pinging me
but ur lucky this time i feel like SHIT and dont have the energy to type a wall of text about this topic
is this more correct
page cache - file contents, for r/w operations
buffer cache - block level file metadata
or what
whatever
i think i mightve just said what infster said
ohh bcache allows faster storage to act as cache for slower storage it seems
bcache (abbreviated from block cache) is a cache mechanism in the Linux kernel's block layer, which is used for accessing secondary storage devices. It allows one or more fast storage devices, such as flash-based solid-state drives (SSDs), to act as a cache for one or more slower storage devices, such as hard disk drives (HDDs); this effectively creates hybrid volumes and provides performance improvements.
By reading the information from disk only once and then keeping it in memory until no longer needed, one can speed up all but the first read. This is called disk buffering, and the memory used for the purpose is called the buffer cache.
Most operating systems have buffer caches (although they might be called something else), but not all of them work according to the above principles. Some are write-through: the data is written to disk at once (it is kept in the cache as well, of course). The cache is called write-back if the writes are done at a later time. Write-back is more efficient than write-through, but also a bit more prone to errors.
oh i think i've created a buffer cache then
block cache seems to be
take thing from slow disk -> put it on fast disk
no thats exactly why i pinged u
i only have a vague idea
you dont need to have both
the NT way is to only use a page cache to cache both file data and filesystem metadata
however this has complications
if you have a page of disk data
a 4kb page
with 512 byte sectors
there are 8 sectors on disk within that page
if you use the page cache to cache the disk as just like a linear bag of bytes
and theres mixed sectors of metadata and file data within a page
then you might have that file data in two places: as the raw sectors within that page of disk cache and also within the file's own page cache
this is called "cache aliasing" and is bad
imagine a scenario where you write to the file and dirty a page in its cache
and theres also a dirtied page of metadata that happens to contain the sector of file data you modified
if the former is written out, and then later the latter is written out, then the result is that the old file data is still on disk and is different from whats in the file's page cache
that kind of thing
whats the solution?
NT avoids this by having disk metadata writes go through the filesystem driver which gives it a chance to "mask off" file data sectors from the write before passing it on to the disk driver
some filesystems dont freely mix metadata and file data like FAT and theres just a hard cut-off between the two and then its easier because you can check if a metadata write straddles that boundary and truncate it
i wonder how linux solves it tho
theres another fancy solution that im starting to prefer which is to actually just use a buffer cache for disk metadata, but the buffer cache is a management layer atop a bag-of-bytes page cache for the disk
which keeps a bit mask of which sectors within each page were modified
and does the masking-off of the writes for you
so its basically a page cache with a special writeback mechanism; it doesnt go through the same writeback that other pages do
i think this is (essentially) what the modern kernels aside from NT do
ah
writeback is specialized for these pages, but reclamation isnt
I currently only cache LBAs in the per disk cache and the cache entry is just a buffer of whatever is at that LBA
writing is write through for now
other than you might have some special buffer cache metadata to clean up and free and whatever when you reclaim a buffer cache page
I'll probably also mark these cache buffer pages with some special bit among the available pte bits so eviction is easier
later on I will probably implement write back
how does linux do write back? I swear i saw something somewhere about a per-second sync event to write back all dirty cache entries
that seems a little bit strange to me though
from what ive seen the NT filesystem drivers accomplish this by reusing a structure they use to cache the mapping of file offsets to disk offsets
which is like an AVL tree of on-disk extents
this happens to also be perfect for the purpose of representing which sectors on disk are metadata or not
they add sectors to this "mask" (really an avl tree of extents) whenever they modify metadata on-disk
is this reusable for block based filesystems that don't have extents
this has nothing to do with whether the on disk fs uses extents
extents are just used because its memory efficient
if you add sector 5, 9, 6, 8 in that order
you only need to track 2 structures, not 4
because 5,6 8,9 can be represented as extents
oh so they're just used to track things on the disk rather than in the filesystem ok sounds great
I thought "NT filesystem drivers" was talking about the NTFS filesystem
ok great
holy smokes some of my driver code is spaghetti
in the true spirit of osdev driver code :^)
gotta fix it up for sure π
look up netbsd's unified buffer cache
optimized the cache a little more, filesystem tests are 7% faster in the VM, dunno if this translates to real hardware or not tho, i'll probably benchmark these eventually
i think i've cut the time in half, so it only takes about 40% as much time compared to before i added the cache, so that's alright i guess
i wonder if i can change device driver waiting event loops to use microseconds instead of milliseconds
that's probs a big one
hmmm i'll see lol
wow ok that brought it down by an unreal amount
[1 ]: ext2_integration_test... ok (10 ms)``` lmao this was taking 50 ms prior to that
nice π
Or ya know, use goddamn interrupts
i'll do that later
i was gonna do that too
Poll mode is only used for super fast hardware when the cpu is relatively free to do shit faster
When each request takes microseconds usually
ok i'm tryna figure out how to refactor things to do IO and stuff with interrupts now, I think i have everything in place i just need to track a few contexts so i wake up the correct guy when the interrupt fires
struct nvme_wait_context {
struct thread *waiting_thread;
bool completed;
uint16_t status;
};
``` might do something like this and then match the CID to figure out which thread should be woken up
this way, interrupt fires -> i check thte CID of the completed request -> i look in table to find thread to wake up -> i wake up thread -> i tell scheduler "hey bud, please run this thread ASAP"
something like that....
i think a bit of a trouble i might have is if people decide to suddenly unplug the drive because all threads that are waiting on that drive would just never wake up 
You'd want to set up a timeout anyways no?
welll.... if i go the route of having interrupts (which is what I'll be doing), and the interrupt never wakes the thread that is blocking on the device, the thread just kind of stops running again
I dont know if it's viable to just check all blocking threads with a timeout, that seems a little bit unnecessary? I guess I could somehow figure out a way to keep the timestamp of when the thread started blocking and then occasionally run through and see if a thread has been blocking for something absurd like 10 seconds and then just force it to die
i dunno....
maybe a potential way to do this is
record timestamp of most recent blocking event start -> occasionally check threads -> see if what they are blocking on still exists -> end the thread if what they're blocking on suddenly disappears
idk
Devices are my absolute weakpoint, so I don't speak with confidence, but to me it sounds like you'd want a timeout to be able to gaurentee forward progress. You don't have to be accurate too, just every once in forever run a timeout check
so maybe i could once-per-second or longer check all blocking threads and make a ping to whatever they're blocking on to see if it exists?
i dont really know how to go about this
i know people will probably never just randomly unplug a SATA disk from their computer tho lol
but if they do i dont want the machine to just have processes that never wake up again
An easy solution is to have your forever long enough that any wait for this period is unreasonable, unless you cant gaurentee such time
but the trouble is, if for some reason there is a comically slow device i dont want to all of a sudden go "this thread has been waiting for too long, i feel like killing it" because then that could cause unexpected behavior too
I just need to try and figure out some way to ping and see if a device still exists and only remove a thread if the device suddenly vanishes
if its slow enough that "forever" isn't long enough aren't there bigger issues at hand at that point?
sounds like a problem.... for future me π₯
i'll have a think about this
apparently the "spawning a worker thread to go do work every second" thing that I thought was strange is common and reasonable
I might get that up and running to evict things from caches and stuff
wdym
well, I thought it would be strange to have the kernel spawn a worker thread at intervals like that, but I looked around in actual OSes and found they have something similar
it's not necessarily fixed to one second, but for example linux has kworker, kswapd, etc.
so I figured I might spend a little bit to just write short functions that can run periodically to clean things up or make the system run a little better
also how come you follow this thread but not others lol, is this a random choice
thanks for all the help tho mister sky π
I follow it π₯Ί
snowos ftw
...uh, wut does ftw mean XD
ik my irl last name is public on my github profile but dont call me by it
scary
oopsies
normally you dont SPAWN IT each second, you spawn it once and it sleeps until theres work to do
someone probably pinged me here at some point and now i see things here
ohh ok, my concern was how I would implement heuristics to tell the threads when they should go do work, but that should be trivial
sometimes I see my laptop suddenly get an influx of a dozen or so kworkers and the fans spin up real bad out of nowhere, I think that happened when kde decides to index the filesystem
btrfs moment
as a fellow btrfs user, do you use btrfs outside of VMs and your personal computer
I don't use btrfs on my NAS
xfs over there
marvin@aquaduct ~> mount | grep btrfs
/dev/nvme0n1p2 on / type btrfs (rw,relatime,nodatasum,nodatacow,ssd,discard=async,space_cache=v2,subvolid=5,subvol=/)
marvin@aquaduct ~>
yep π
i use btrfs on everything
has it ever been a problem
no
I was a little concerned that data loss might happen with it or other shenanigans
oh ok
quite the opposite
we have lock_buffer at home π₯ π
I get to have the very Fun, Pleasant Time of implementing writeback in the buffer cache π
going good
LMAO the code behind this is INSANELY cursed but the tests now work with the scheduler and in multithreaded mode
this is horrendous
downright diabolical
but it works π
NVMe with ISRs works fine but extremely extremely slowly
now i just gotta find the source of the slow and we're all good
ok the slowness was literally just because the idle task existed lol
all gooood
sweet
ok time to fixed the cursed horrendous register saving
i fixed it
the tests are marginally slower now but this is probably because this is a VM and on real hardware i am saving time and pain
CI is failing
their cooked ubuntu version's assembler doesn't support a thing I used
also courtesy of the most gracious infy for reminding me of this
giving credit where credit's due fr
CI is passing
I rewrote the att asm to intel asm and it's fine
intel syntax wins again
muahahahaahahahaha
imagine not having intel syntax by default
This
a kitten named lizzie gave me macro tips using att syntax so I used that and then switched back to the saner syntax
probably gonna finish xhci soon now that I have proper interrupt driven io handling
nice
nice
fancy language
Indeed
an ahci driver should really not know anything about the scheduler
let alone switch the state of the current task
you should have a block io packet struct
that is submitted to a block device
and that can be waited on
so that the submitter has a chance to do some extra work and not even get blocked if the request completes fast enough
wait wdym by having a block io packet that can be waited on
ohhh I understand
ok thanks infster
ok i have mostly figured this out, I'm gonna introduce async read and writes into my generic_disk abstraction but still make the default reads and writes fully blocking, so if i need async or blocking i can pick
Blocking is just async that waits right after submission
yes
block device gameplay
π₯
something something async io something something
π§
this will be great help in my block cache for prefetching perhaps
time to implement bio stuff for the rest of my block devices π§
but like
why cant this be a generic function
there's 0 ahci specific code here
other than that well done
i only have this on ahci right now lol
once i do it for other drivers i'll make it generic + introduce abstractions for that
thanks infster you've got the most goated suggestions π
truly a saint
i read finnster
it's really silly, I just have two symbols in my linker script .kernel_test_start and .kernel_test_end, and all i do is I insert struct test s into that region and have a runner that goes through that region and executes the functions for every test
i couldnt figure out any better way and this was simple
void tests_run(void) {
struct kernel_test *start = __skernel_tests;
struct kernel_test *end = __ekernel_tests;
uint64_t unit_test_count = 0;
uint64_t integration_test_count = 0;
for (struct kernel_test *t = start; t < end; t++) {
if (!t->is_integration)
unit_test_count++;
else
integration_test_count++;
}
k_info("TEST", K_TEST,
"running %llu " ANSI_CYAN "unit" ANSI_RESET " tests...\n",
unit_test_count);
run(true, start, end);
k_info("TEST", K_TEST,
"running %llu " ANSI_MAGENTA "integration" ANSI_RESET " tests...\n",
integration_test_count);
run(false, start, end);
}```
literally
and yeah yeah i know double underscore is undefined behavior because compilers reserve it
so your kernel logs it?
ya I log it in the run function by having each test have a success field and a should_fail field and a skipped field
so once the test function returns it just checks those statuses
struct kernel_test {
const char *name;
test_fn_t func;
bool is_integration;
/* TODO: fancy state machine with enum? */
bool should_fail;
bool success;
bool skipped;
uint64_t message_count;
char **messages;
} __attribute__((aligned(64)));```
you can also report back messages by adding them to the messages array and incrementing the message count