#charmOS

1 messages ยท Page 3 of 1

placid vessel
#

menix cancelled

#

i'm stuck reading freebsd internals

#

such interesting stuff

eager yew
#

I agree

#

ule best scheduler

#

cfs and eevdf too woke

#

I ain't readin allat

steel herald
#

fancy state machine:

enum test_result {
    TEST_RESULT_SUCCESS = 0,
    TEST_RESULT_SKIPPED = 1,
    TEST_RESULT_FAILED = 2,
};
eager yew
#

too complicated for me

#

๐Ÿ˜จ

steel herald
#

whats the advantage over just capturing printf?

eager yew
steel herald
#

ohh

#

do u have a userspace runner for starting kernel tests/checking results in CI?

eager yew
steel herald
#

for my bootloader i would just emit a special sequence of bytes via 0xE9 lol

#

and then shutdown qemu from inside

eager yew
#

morse code error message reporting better tbh

#

use a local runner and record the audio fr trl

steel herald
#

least insane method

eager yew
#

nvme async bio support

#

next up is IDE ( ๐Ÿคฎ ) and also ATAPI

#

and i will rember to introduce atapi support for ahci

#

๐Ÿ’ฅ

short lichen
#

uniata

eager yew
eager yew
#

ugh... async IO or any remotely performant IO on IDE is gonna be impossible

#

since it's got no queues, no slots, no anything

#

every thread just has to wait for the previous thread to finish the entire command because i cant track waiters in a way that lets me know which thread to wake up

#

๐Ÿ™„ ts pmo

eager yew
# stuck nexus why

I am pretty sure (and I can go rtfm again if I'm wrong about this) that IDE doesnt allow you to submit another command if the previous one isnt finished

#

so I can only have one thread block on the device at a time because it absolutely has to finish the command to unlock the device and let another thread read or write on it

#

it is kind of unfortunate

stuck nexus
#

at all

#

!

#

idk why youre saying that like it obviously follows

#

you can just queue up the requests in a linked list in memory

#

start the next one in the ISR that finished the last one

#

no threads involved

eager yew
#

ohhh yeah i forgot about requests

#

yea i had made those i should use them here

#

๐Ÿ’ฅ

eager yew
#

but what about synchronous IO

#

wouldnt you still need to wait for the requests to one by one complete

stuck nexus
#

just wait on your own request and youll get woken up whenever its gotten to and completed

eager yew
#

oh wait this is identical to what i do in existing drivers where blocking IO is just async io with a block and yield

#

ok nvm thanks mister sky

eager yew
#

who up creating they IO queues rn

eager yew
#

apparently the interrupt vectors have to be different across queues

#

was having a bug where core 3 would catch core 0's queue interrupt

#

funky

eager yew
#

first iteration of async prefetch logic

#

i hopes it works

#

hmmmm i dont think the prefetcher should have start_lba and end_lba probably

stuck nexus
#

That's also known as readahead btw

eager yew
#

readahead

#

I'll call the "asynchronously loading of blocks of an inode during a read" 'readahead' and I'll just keep the name of "prefetch" for the block cache since it'll probably be used elsewhere

#

readahead works fine ๐Ÿ’ฅ

#

nice

#

no noticable speedup in tests since I don't do any aggressive reading or writing

eager yew
#

I think I will implement an IO scheduler for these asynchronous requests over time, idk

I will definitely do delayed batch cache writeback though because that is kind of important ๐Ÿ’ฅ

#

yeah I'll perhaps need a way of queueing, coalescing/merging, and scheduling the block device asynchronous IO requests, but I dunno if that will have a big overhead or not

#

maybe I will implement reordering and reprioritization of asynchronous IO requests by introducing priority levels

reordering will probably make HDDs happier too since there's less seeking to do

eager yew
#

i used the IWYU program and now my main file doesn't have FIFTY LINES OF INCLUDES ANYMORE ๐ŸŽ‰ ๐ŸŽ‰ ๐ŸŽ‰ ๐ŸŽ‰ ๐ŸŽ‰

#

huge win fr

#

my main worry was just that changing headers would greatly increase compile times if I used headers i didn't need in some files

#

hopefully this will be good in the long term ๐Ÿ™

#

around 500 lines away from the next power of two in LOC ๐Ÿ’ฅ

#

i love powers of two

placid vessel
eager yew
eager yew
placid vessel
#

just make a generic init like c menix had

eager yew
#

boooooring

#

maybe yeah

placid vessel
#

all init functions are in their own section and then you just call them as pointers

#

:3

eager yew
#

literally how I do unit tests

#

good idea marvel

gentle gazelle
placid vessel
gentle gazelle
#

so is [[gnu::section(...)]]

eager yew
#

__attribute__((section)) type stuff

placid vessel
#

also i had a difference between init types

#

there was early init and normal init

eager yew
#

๐Ÿ’ฅ

placid vessel
#

early init being before any allocators were live

#

now i just have an initgraph

gentle gazelle
#

there is also .preinit_array but i don't know if there's an attribute like early_constructor or whatever

placid vessel
#

destructor galaxybrain

gentle gazelle
#

could also use init priority to sort allocator init before everything else halfmemeleft

placid vessel
#

[[gnu::{con,de}structor]] doesn't have any semantics other than being placed in init/fini right?

gentle gazelle
#

elf constructors can have an associated priority iirc

#

but i think it applies to the whole section per object file

placid vessel
#

as a kernel you can probably just ignore that

#

and execute them in order

gentle gazelle
#

i mean the linker sorts them

#

since after linking you don't have the individual object files

eager yew
placid vessel
#

why is this an ifdef

eager yew
#

very important features

gentle gazelle
eager yew
#

once I make an ncurses config menu like linux I'll have GDT_INIT as a config option

#

very very important

placid vessel
#

is that encoded in the elf?

gentle gazelle
#

yes, although i'm not 100% certain how

#

lets see

placid vessel
#

maybe a linker internal section?

#

extra phdr?

gentle gazelle
#

oh it's literally just .init_array.<priority>

#
qookie@selenium /tmp ฮป gcc -c -x c - -o test.o <<< "
__attribute__((constructor(200))) void foo() {}
__attribute__((constructor(201))) void bar() {}
"
qookie@selenium /tmp ฮป readelf -SW test.o | grep " .init_array"
  [ 4] .init_array.00200 INIT_ARRAY      0000000000000000 000058 000008 08  WA  0   0  8
  [ 6] .init_array.00201 INIT_ARRAY      0000000000000000 000060 000008 08  WA  0   0  8
qookie@selenium /tmp ฮป 
placid vessel
#

linking sounds really cool

#

i need to write a linker at some point

gentle gazelle
#

i've written a shitty elf linker for my 65c816 project

eager yew
#

I just remembered that block devices have flush commands due to write caches ๐Ÿ’ฅ

#

I need a flush function pointer in my block device abstraction now, oopsies

#

oh yeah I'll also do that rcu thing

#

seems pretty simple and I have everything necessary to do it

eager yew
#

I'll probably implement a simple defer function to run something after some time has passed with one shot hpet interrupts

eager yew
#

yippee I get to make a min-heap, again, using macros, for event scheduling, for IO and timer-deferred functions

gentle gazelle
#

use a pairing heap :^)

#

you can make an intrusive one pretty easily

eager yew
#

I'll probably wrap over my current send_bio_async (which is instant) for each device with a generic block device IO scheduler

this will allow me to queue up requests and then execute a per-device dispatch()

that will then automatically pick the highest priority commands first to send, and send as many commands in parallel as the underlying device can handle

#

dispatch() could probably also coalesce requests, but I don't think that'll happen often since my block cache automatically makes sure it isn't sending requests that are at adjacent LBAs

eager yew
#

I'll probably also make a per device dispatch_threshold so if it sees enough things have been enqueued it'll automatically start sending some requests (not all of them tho), so that poor devices like IDE drives don't immediately get inundated with dozens of requests whereas NVMe devices would probably be fine

eager yew
#

this is kind of an outline of the block device IO scheduler

  • the reordering is fairly generic, but with device specific policies, it can be more optimized, so it'll be implemented per-device. the general rule of thumb is high priority before low priority, with priority being more important than transfer direction

  • the request coalescing is a per-device policy, and each bio has its own driver_data private pointer, so there is no need to make a new aggregated bio structure as I can simply use that. then, each bio has a skip field and an is_aggregate field, allowing me to identify which ones to submit (and how to submit them), as well as which bios I can continue coalescing.

  • coalescing works by attempting a try_coalesce() which will return true if a coalesced block was added to the list/heap, and it will continue until a certain limit is reached, or if coalescing cannot continue

the general coalescing policy that the block device scheduler can use for a quick, "rule of thumb" heuristic before passing the task off to the underlying driver is

  • bios must be contiguous
  • data transfer must be the same direction
  • priority levels must match

I can probably also add custom block device flags such as NO_SCHEDULE for ramdisks and such, which will turn the entire scheduler into a no-op and such

#

oh yeah this is only for asynchronous lol, synchronous events are instant, always-go-first, do-not-pass-go, do-not-collect-two-hundred-dollars, and come before all asynchronous requests

this is mostly because I only really use sync IO in filesystem detection and forcing a read into the cache, whereas stuff like prefetches and (eventually) writeback are all asynchronous (and will become the more dominant form of blkdev IO activity)

eager yew
#

I think I'll have 5 or 7 priority levels, and allow priority boosts if two requests have a potential optimization (such as a potential coalesce opportunity), so that the less urgent request is boosted to the priority level of the higher priority request (to prevent priority inversion) and allow the optimization to happen

#

I can probably also integrate that hpet timed function deferring thing to allow the scheduler to automatically start submitting requests if enough time elapses between request enqueues even if the dispatch threshold isn't reached

#

as for dispatching, I think it would be beneficial to have partial dispatch and full dispatch functions, where the partial dispatch would only execute the higher priority commands (up to a certain specified priority level), and the full dispatch would execute all commands

this way I can attend to high priority requests first under heavy load, and low priority requests once there is less work

maybe I could also integrate priority boosts here, where if a request has been in the queue after X dispatch events have passed, it will get boosted to the highest priority for immediate submission on the next dispatch or the next time dispatch_threshold is reached

#

5 priority levels seems good, I'll have URGENT, HIGH, MEDIUM, LOW, and BACKGROUND

#

๐Ÿ’ฅ

eager yew
#

I have just realized that there is no need for a separate block cache because block devices are files here in unix land so I can just register as VFS nodes and give them normal page caches like everybody else

#

I will do that later probably

eager yew
stuck nexus
#

yes

#

there is cache aliasing potential

eager yew
#

yes

stuck nexus
#

basically the same sectors in your disk page cache could be in file page caches

#

causing major confusion

eager yew
#

indeed I have thought about that

stuck nexus
#

you dont have to worry about this if you do a buffer cache for metadata

eager yew
#

does unix allow arbitrary r/w of mounted block devices

stuck nexus
#

doing that alongside a page cache for file data is a valid strategy

#

i believe @solid crescent plans on doing this in keyronex

stuck nexus
#

at your own risk

#

theres no attempt to keep coherency between what you wrote there and whats in page caches

#

and vice versa

eager yew
stuck nexus
#

you can do a page cache for both youll just need to make sure you can take the extra steps required to "mask off" the page-out IO coming from the disk's page cache, on a per-sector basis

#

im leaning currently more toward doing a buffer cache for metadata

#

i think its simpler and more flexible

#

i also think it could probably be implemented atop the page cache for the disk

#

and just exempt those pages from the normal write-out

#

and have specialized machinery for doing write-out from the buffer cache

stuck nexus
#

and also supports ordering of sector write-outs

stuck nexus
# gentle gazelle wdym?

so that if you alias any file data sectors within the disk page cache, you dont overwrite them inappropriately

#

which could lead to incoherency between the disk and the file's page cache

gentle gazelle
#

ah

#

why not have a page cache that specifically only covers fs structures?

stuck nexus
#

also this is only an issue for filesystems which freely intermix sectors of data and metadata, FAT does not but some others do

gentle gazelle
#

we do that in managarm

#

ah well, ext2 also doesn't

eager yew
stuck nexus
#

this actually is the original thing that INTRODUCES the problem

gentle gazelle
#

but it doesn't affect ext2 either

#

so it works fine :^)

stuck nexus
#

NT has to solve this problem for NTFS and their solution was to make all of the NTFS metadata be part of "virtual files" and to access it through page caches for those

#

for example theres a "bitmap file", the inode table is a file, etc

#

and that successfully evades the issue

#

HPFS (the OS/2 filesystem, which NT supported originally) is a more interesting case because theres no real way to do that trick and it freely intermixes sectors of data and metadata

eager yew
#

or no

stuck nexus
#

and for that they maintain structures in-memory that it basically uses to mask off the page-out IO going to the disk from the disk's page cache; whenever they access metadata, they add those sectors to this mask, and then during write-out they look up the mask (which i think is kept as an AVL tree of disk extents) and use it to "mask off" any sectors that should not be written (basically by splitting up the IOs)

#

it accesses the disk through a virtual file it creates so that it receives the IO and has a chance to do this before passing it on otherwise unmolested to the disk driver

eager yew
#

this does sound like big problem

#

what if I just block r/w on mounted devices

stuck nexus
#

thats not even related to this issue

#

im leaning more toward just using a buffer cache for metadata like i said

#

it doesnt have to be a pure buffer cache

#

you can get the benefits of a page cache by using the page cache machinery for reading and for tracking the pages and stuff, but then use special buffer cache machinery for write-outs

eager yew
stuck nexus
#

and do other special stuff like when you read a page of disk metadata through this "buffer cache", you allocate enough buffer structures to describe each sector within the page individually

#

basically yes

#

the buffer cache does the write-outs itself using direct noncached IO

#

and the page cache never writes these special pages itself

eager yew
stuck nexus
#

i mean you can put a flag in their per page frame struct thing

#

that says "dont write this out"

#

doesnt have to be that complicated

#

i havent actually implemented this scheme this is just my current idea

#

youd use buffer cache like interfaces to access the data

eager yew
#

ok thanks, then all I'll have to do is I can keep my block/buffer cache, use it for metadata only, then have a page cache with "no write out" pages that hold metadata, and fs drivers will only check the block/buffer cache to guarantee they get untouched versions of metadata?

#

or what

stuck nexus
#

basically

#

yeah

stuck nexus
#

the write-out thread walks this list and tries to do the IO as contiguously on disk as possible, in as large chunks as possible

#

but when you mark a sector modified you can also supply a flag saying "it must be written out in the order i marked it modified" and then itll be forced to the end of the list

#

and you can use this for stuff like journaling filesystems where theres all kinds of special strict orders stuff has to be written

#

to ensure integrity

eager yew
#

thanks mister sky this is really really helpful ๐Ÿ‘๐Ÿ‘๐Ÿ‘

#

I'll probably do my async request scheduling first and then this right afterwards since I can have a proper writeback system

stuck nexus
# stuck nexus and when you mark a sector modified, its buffer structure gets insertion sorted ...

you could keep a list of dirty extents and update that and that would make it a lot faster to insertion sort sectors in (because thered be way fewer items on the list cuz dirty sectors tend to be clustered together and can be represented as a single item with a range) but that would require another allocation dependency, at metadata modification time, so it depends on whether you can tolerate that

#

i can tolerate that

#

so thats prob how ill do it

#

actually no you could probably use the buffer at the start of the extent as the extent

#

and thats safe because it wont go away until the write-out is complete anyway

#

so theres no allocation dependency after all

stuck nexus
#

at the time you look up a sector (cuz if that results in a read of a new page into the page cache then youll need to allocate an array of buffer structures for each sector within the page)

#

but theres an allocation dependency there anyway because of the need to allocate memory to put the sector data in

#

so thats fine

#

theres also an optimization possible which is that if you know youll only be reading the metadata, you can ask for a read-only view of the sector data

#

and what this does is just give you a direct pointer to the page cache data for the sector

#

without potentially causing a buffer structure to be allocated

#

so that could be faster

eager yew
#

alrighty thanks ๐Ÿ‘ I'll probably optimize last but I'll keep these in mind

stuck nexus
#

@solid crescent second ping but see above for my current ideas on a modernized buffer cache built atop a page cache and reusing all the read-in machinery including clustering and readahead and the viewcache and so on, so that only the write-out needs to be implemented specially

#

probably FAAAAAR from the first to come up with this, this might be what UBC already does i dunno i need to read more about it

eager yew
#

I forgor I needed to prevent preemption when modifying current thread state ๐Ÿ’€ I had a bit of code where I would mark the current thread as blocked and purely by chance I would get preempted and my thread would just disappear ๐Ÿ’ฅ

it fixed tho

eager yew
#

decided to implement a bit of logic to track the amount of times a request was boosted in priority due to starvation

this way, if I have a request at the lowest priority, it won't just slowly climb up prioirities at a constant rate, and instead scale fast, where on the first boost it goes up one priority, then on the second it goes up by 2 or 3, and then the 3rd boost forces it to get submitted

similarly I made the amount of time an already boosted request has to have been waiting in queue for to have the next boost happen lower

this way, if a request has already been boosted, it doesnt need to wait that same amount of time to get boosted again and waits a bit less

#

idk hopefully this works out

#

I think I could probably steal ideas from this tomfoolery for my thread scheduler lol

eager yew
#

I might have a priority inversion inversion problem where old starved requests are unfairly boosted ahead of higher priority requests

#

oh brother

#

priority inversion inversion inversion when

eager yew
#

I'm kinda missing rust with their impl blocks where you can just say self so I dont have to pass a pointer around everywhere

#

eh whatever

eager yew
#

javascript reference

eager yew
#

ok i was dillydallying but defer works fine now

eager yew
#

somehow my entire bio scheduler which I wrote in one go without tests for the most part worked first try when I wrote tests for it lol

#

time to stress test

#

bugs are hiding from me

eager yew
#

I have found one single bug in the bio scheduler so far.... very suspicious

steel herald
#

If you have userspace

#

That thing can destroy disks

eager yew
steel herald
#

Yeah np

eager yew
#

asynchronous IO is so ๐Ÿ˜‹

steel herald
#

It can do pretty much anything, multiple threads pinned to CPUs, any types of requests, random, any block size, any backend etc

eager yew
#

my plan is to eventually have the filesystem drivers use asynchronous operations internally but still have a fully synchronous userspace exposed API

#

today's brainrot has been making small adjustments to these constants, recompiling, checking tests, changing again, recompiling once more, and doing it until things look good

#

config brainrot

steel herald
#

What did you read for designing the block scheduler?

molten night
#

Why not automate it

eager yew
#

linux allows you to select many different schedulers for IO

eager yew
#

this isn't that bad

steel herald
#

Ill have to read that at some point

eager yew
eager yew
# steel herald Ah cool

I decided to go with a centralized generic scheduler with per device policy for things like reordering and coalescing

#

this way I don't have to build multiple schedulers

steel herald
#

Thats the way

#

U want to share as much code as possible

molten night
#

Personally ill never give up on an opportunity to waste 3 hours to save 30 mins

eager yew
#

basically, an old request that got boosted would get placed in a high priority queue that has new requests in it, and that old initially lower priority request would go before all the high priority ones

#

priority inversion inversion

#

the bandaid fix is just to say if (queue_has_too_many_requests(q)) do_not_boost();

#

I might make the scheduler try to estimate a perceived_priority at a finer grained level in the future tho

#

this is truly scheduler brainrot part 2: the block device strikes back

steel herald
#

Don't know much about that stuff

#

How do u build your sglists btw?

eager yew
#

sglists?

#

scatter gather list?

steel herald
#

Yes

eager yew
#

for coalescing requests?

#

it's just a list of addresses ๐Ÿ’ฅ

steel herald
#

In general

eager yew
#

I'll definitely go back and add more to it

#

since scatter gather is cool

#

for now I just did that since the bio sched was a bit more important

steel herald
#

Well if user says read N blocks into this virtual address

#

What do u do

eager yew
steel herald
#

Yes but each virtual page is backed by a random phys page, so u have to walk the ptes and accumulate a list of phys pages right

eager yew
#

good point tho

#

i'll probably make proper sglists

#

yea I'll definitely do that now that u bring it up

#

๐Ÿ‘ ๐Ÿ‘

eager yew
# steel herald Damn
> git commit -a
[main e3b9bc1] [nvme]: create prp list no matter what
 1 file changed, 70 insertions(+), 19 deletions(-)```

thanks ๐Ÿ‘ I just construct an array of prps lol
#

this is technically not needed but might as well

eager yew
#

it just array

#

back to bio sched

eager yew
#

coalescing works

#

NVMe doesnt really need reordering

#

so prob not gonna do that

eager yew
#

I might add fastpaths that skip any optimization like coalescing and just put a thing in the queue

#

the funny thing about this is that the primary reason I have for making this bio_scheduler at all is because my thinkpad t400 (the machine I want to run this on) is an HDD machine, and the optimizations that scheduling asynchronous events allows for can reduce the wear and tear on the device

#

this is probably so incredibly extra for that ๐Ÿ’€

last lichen
#

What's the job of a bio scheduler?

eager yew
# last lichen What's the job of a bio scheduler?

it schedules asynchronous Block IO (BIO) requests to manage priority levels, perform boosts on starved requests, allow the block device to optimize requests via reordering and coalescing (reordering helps on HDDs to reduce seek times, and coalescing merges adjacent requests into one), and other stuff ๐Ÿ‘

#

my current bio scheduler uses a 5 level MLFQ, which is mostly an optimization so that dispatching requests doesn't have to do any list traversal and it just unlinks the list head of a given queue and sends it off

it also has per-device function pointers so that each device can implement its own policy on how it wants to perform things like coalescing and reordering, and it also allows some devices like NVMes to entirely skip, or barely perform any coalescing and reordering if it doesn't need it

#

I didn't wanna do anything fancy so the queues are just intrusive doubly linked circular lists

#

in the future I may add grouped requests and the scheduler will try to make requests in the same group fire off at the same time or similar times, and that would probably be just by inserting the requests right next to each other in the list

#

I think for groups, I will give each bio a group_id, which is -1 if there is no group

then, when I enqueue requests and detect there is a group, I will create and track the group as its own queue

after I send all the enqueues for a certain group, the sender can execute a group_seal(disk, group_id) which will tell the scheduler to start sending requests in the group, and the whole group can have a priority level just like with the regular priority levels

#

group IDs can be trivially created with 64 bit monotonically increasing counters once again

eager yew
#

holy baloney my scheduler queueing is slow... 15 microseconds for enqueue ๐Ÿ˜ฌ but I'm on an ARM M4 Mac RN so maybe once I get back on my x86 host it won't be that bad

#

it should be like 3-5 microseconds ideally

#

(this is counting coalesce, reorder, and other optimizations)

gentle gazelle
#

just port to arm trl

eager yew
eager yew
#

I can't wait to get home to my x86 machine tomorrow to properly benchmark my asynchronous request scheduler ๐Ÿ’ฅ

15 us per enqueue is like a maximum of 60,000 request enqueues per second ๐Ÿ’ฅ and since the algorithm is fully locking I can't make it faster with SMP ๐Ÿ˜จ

hopefully this is just an emulation thing

eager yew
#

I think my async bio scheduler will make block write ordering easier since I can just change the priority of the write request depending on what is in the contents of the block I'm writing (metadata, file data, etc)

isn't the preferred policy to write data blocks before metadata blocks

#

probably

#

no journaling filesystem moment ๐Ÿ˜ข

eager yew
#

fancy schmancy

#

now I just gotta do this for my FAT driver

eager yew
#

I should probably do devtmpfs ๐Ÿ’€ completely forgot about that

#

then I make the FAT driver use the asynchronous IO stack

eager yew
#

I am actually surprised that the boosts naturally submitting requests over time is actually not horrendously inverting priorities

#

a few of them are slightly skewed due to coalescing and timing but for the most part it's good

eager yew
#

it is actually funny how these are consistent even after running 8,000 requests of varying randomized priorities, lbas, and everything

eager yew
#

I think i can just get away with a FIFO queue for IDE async waiter/request tracking

#

time to get to that today

eager yew
#

getting kinda yappy

#

I decided to steal maravillosa's idea of having linker segments for init functions for PCI stuff

#

it is very nice

eager yew
#
> git commit -a
[main 71640f6] [ide ata]: asynchronous IO beginnings (first we block and interrupt)
 5 files changed, 115 insertions(+), 38 deletions(-)
``` ๐Ÿ’ฅ
#

it going good

#

I gotta do atapi cdrom after this yay

eager yew
#
x git commit -a
[main 90a69bf] [ahci]: bio interfaces for bio sched implemented
 9 files changed, 59 insertions(+), 44 deletions(-)``` took a detour and implemented scheduler API for ahci
#

back to IDE

eager yew
#

porting quicksort gameplay

hard talon
#

can you port tiktok?

eager yew
hard talon
#

thank you i cant focus without that

#

youre a livesaver

eager yew
#

yk, while I'm still dillydallying in driver land I might start doing some PM (Power Management) stuff

#

seems cool

eager yew
#

"the code is the documentation" moment

eager yew
#

๐Ÿ’ฅ IDE bein funky with my async io stack, been having this issue for a bit now

#

idk no one even uses IDE so I might just not have async with it

eager yew
#

funky wacko boom

#

ATAPI could probably share the request management system with IDE since they're both manually tracking requests with lists

#

on ahci i could just do one request per slot and nvme could just do one request per queue idx ๐Ÿ’ฅ

eager yew
#

ok the whole bug was that i was using an 8 bit integer instead of a 16 bit integer

#

kind of crazy

#

bug fixed

#

lol i find it a bit funny that just tweaking a few small numbers in each block device's config can make the requests go from having horrendous timing and insane priority inversion to being perfectly sane and normal

#

i guess this will be very customizable

#

maybe i can figure out a way to get userspace to customize this, idk

eager yew
#

ok cool beans the IDE/PATA async IO interface is fully implemented and works ๐Ÿ‘ I will probably make this code generic and make it work for the other things like ATAPI

eager yew
# pearl kindle sysctls?

probably yea, but I will likely only let userspace customize the configurations for each block device to a certain degree so that userspace can't just horrendously break block device IO scheduling

#
static struct bio_scheduler_ops ide_bio_ops = {
    .should_coalesce = noop_should_coalesce,
    .reorder = ide_reorder,
    .do_coalesce = noop_do_coalesce,

    .max_wait_time =
        {
            [BIO_RQ_BACKGROUND] = 35,
            [BIO_RQ_LOW] = 25,
            [BIO_RQ_MEDIUM] = 10,
            [BIO_RQ_HIGH] = 4,
            [BIO_RQ_URGENT] = 0,
        },

    .dispatch_threshold = 96,

    .boost_occupance_limit =
        {
            [BIO_RQ_BACKGROUND] = 64,
            [BIO_RQ_LOW] = 56,
            [BIO_RQ_MEDIUM] = 48,
            [BIO_RQ_HIGH] = 40,
            [BIO_RQ_URGENT] = 32,
        },
    .min_wait_ms = 1,
    .tick_ms = 25,
};``` so these are the configs for each block device
#

and i guess i can figure out a way to get userspace to toggle the numbers around for each block device

pearl kindle
#

how do you decide the urgency?

eager yew
#

requests are boosted too

eager yew
# pearl kindle how do you decide the urgency?

for example

#define EXT2_PRIO_DIRENT BIO_RQ_MEDIUM
#define EXT2_PRIO_INODE BIO_RQ_MEDIUM
#define EXT2_PRIO_DATA BIO_RQ_HIGH
#define EXT2_PRIO_BITMAPS BIO_RQ_BACKGROUND
#define EXT2_PRIO_SBLOCK BIO_RQ_LOW```
#

urgency typically just signals to the scheduler that during boosts, the less urgent requests are boosted less often and take longer until dispatch

#

and also during manual dispatch it goes from high urgency queues to low urgency ones

#

nothing beats a jet 2 holiday

#

I will add request grouping soon so you can guarantee that a group of requests is submitted at the same time but I don't have anything that needs this rn so it's not a priority

steel herald
#

did u figure all of this out by just reading linux sources?

eager yew
#

by "make stuff up", it's just my own optimizations and design decisions that differ from linux

for example, I'm pretty sure linux doesn't do a generic scheduler with per device policy, and instead has many different schedulers that you assign to each device

#

so I just made a generic scheduler and allowed each device to implement its own ways of doing certain optimizations, and device flags to skip certain optimizations (like NVMe is so heckin fast that I skip all optimizations)

#

unit and integration tests are my lord and savior ๐Ÿ™ they have allowed me to find so many edge case bugs because I wrote them so aggressively

#

and I can very confidently change code and instantly get verification as to whether or not it worked

#

truly amazing

#

devtmpfs will be pretty fun now since I have all of these policies for each block device that the user gets to toggle

steel herald
#

cool

eager yew
#

I lowk feel like writing a floppy driver for no reason

#

bah idk if I ever feel funny enough I'll do it

#

the PATA/IDE generic serial FIFO request queue async IO implementation is pretty portable to other drivers so I'll make it generic and then use it for ATAPI and floppy

eager yew
#
struct generic_disk {
    enum disk_flags flags;
    enum generic_disk_type type;
    enum fs_type fs_type;
    void *fs_data;
    char name[16];
    uint64_t total_sectors;
    bool is_removable;
    void *driver_data;
    uint32_t sector_size;

    /* both of these take full priority over the async operations.
     * do not pass go, do not collect two hundred dollars, submit instantly.
     *
     * these are sync and blocking
     *
     * these are not used in many areas though, and such, we can get away
     * with instant submission for the most part*/
    bool (*read_sector)(struct generic_disk *disk, uint64_t lba,
                        uint8_t *buffer, uint64_t sector_count);

    bool (*write_sector)(struct generic_disk *disk, uint64_t lba,
                         const uint8_t *buffer, uint64_t sector_count);

    /* immediate asynchronous submission */
    bool (*submit_bio_async)(struct generic_disk *disk,
                             struct bio_request *bio);

    void (*print)(struct generic_disk *disk); // this one for physical disk

    struct bio_scheduler_ops *ops;
    struct bio_scheduler *scheduler;
    struct bcache *cache;
    uint64_t partition_count;
    struct generic_partition *partitions;
};``` I now get the great joy of figuring out how to expose this to userspace via devtmpfs
steel herald
#

Any disk is removable btw

#

Because the controller can be unplugged at runtime

eager yew
#

true...

eager yew
#

TRY part 2 is here

#

I didn't like the look of

enum errno e = operation();
TEST_ASSERT(!ERR_IS_FATAL(e));``` so I just wrapped it over `FAIL_IF_FATAL` and now we have `TRY` part 2
#

I wanna see if I can potentially get test timeouts working but idk, I don't need that rn

#

I mean it should be trivial, I can just defer_enqueue(kill_if_timeout, test_data, timeout);

#

๐Ÿ’ฅ

eager yew
#

ykw just for old times sake I'm gonna rename my bcache_ent_release to brelse()

#

nostalgia

#

and then make a bread over bcache_ent_get and bcache_ent_acquire

#

bread ๐Ÿค‘

eager yew
#
static inline void bcache_ent_release(struct bcache_entry *ent) {
    refcount_dec(&ent->refcount);
    bcache_ent_unlock(ent);
}``` we have `brelse` at home ๐Ÿ’”
eager yew
#

your wish is granted

#

this is inconsistent with my naming scheme tho so I'll just call em ext2_block_read and bcache_ent_release

eager yew
#

oh yea here is brelse

eager yew
#

been thinking about the split cache thing where filesystems maintain internal private block caches for metadata and filesystem block device partitions have page caches that have certain pages marked as no_writeback

I think the FS would just keep an internal structure to keep track of data vs metadata addresses

#

probably an augmented AVL tree would be fine

eager yew
#

devtmpfs time today ๐Ÿ’ฅ

#

been busy because of some summer homeworks

eager yew
#

hmmm.... should do multicore tests ๐Ÿ’ฅ

eager yew
#

this thread name lowk too long

#

charmOS

short lichen
eager yew
#

๐Ÿ’ฅ

#

oh well

eager yew
#

I am a zygote in internet years

#

is this shocking to you

short lichen
#

Dawg I thought you were 20

#

Anyways

eager yew
#

how have I achieved unc status ๐Ÿ’”๐Ÿฅ€

#

man this happens shockingly often

short lichen
#

Anyways study hard get into good college ๐Ÿ‘

eager yew
#

when I email professors they keep thinking I'm lying about my age and then I meet them in person and they go "damn. bro really is a child."

#

hilarious stuff ngl

eager yew
short lichen
#

Ikr

eager yew
#

no work today because I need to learn gnu octave to rewrite something into python

#

scientific computing gameplay

eager yew
#

i should probably list out the things imma do/wanna do soon

  • bio scheduler request groups
  • devtmpfs
  • complete FAT driver refactor with all the new stuff
  • xhci finish
  • xhci devices and stuff
  • cleanup async functionality in AHCI because it really sucks rn
  • start work on ehci because that is what the thinkpad t400 that i will run this on has
  • e1000 work
  • UDP stack with maybe a networking scheduler?
    i dont think i can make a generic scheduler that can be used in a bunch of places, who knows
  • TCP too (later on)
#

and in order, it will go

devtmpfs, AHCI driver cleanup + AHCI ATAPI support, bio scheduler groups, FAT refactor, XHCI, e1000, UDP, TCP, EHCI

#

rn i need to learn gnu octave for that thingy mabob that is completely unrelated to this

eager yew
#

I think I have joined the matlab haters

#

this language was not fun

#

it's like someone tried to merge C and python

#

very strange language

#

we will return to our scheduled programming tomorrow then

eager yew
#

i rembered to add block cache status checks in the tests but these are a bit unnecessary since i need to wait for the async writeback to finish

#

๐Ÿ’ฅ

#

nice

eager yew
#

ah I think I am getting how to do my scheduler correctly

struct thread should only contain the 6 callee saved registers

if interrupt preemption happens, there is no need to save the rest of the registers because they will simply be on the stack of the thread and will just get loaded back in when context returns to this thread

manual yield will just call some inline asm function to switch out the register state and ret

#

so if thread A is preempted via interrupt, it saves its registers on the stack, and then passes in the callee saved registers into the schedule function

schedule function loads the new thread, but does NOT return to the first preempted thread and iretq until it is scheduled again

after some work, the scheduler comes back around to the previously preempted thread, and restores the callee saved registers, and hits a ret, transferring control back to the interrupt handler, where it will pop all registers and iretq and go back to what it was doing

#

๐Ÿ’ฅ

#

something like that

#

ok now that my prefrontal cortex has decided to be functional I think I will rewrite the scheduler's save and restore logic properly

#

yippee

eager yew
#

I'm feeling funny and I might port my feed forward neural network I wrote a while back to C and then used fixed point arithmetic and then port it to my kernel

#

I might use it in thread scheduling if I get bored

#

just for stuff like reordering and balancing and stuff

#

I dunno how I will train the goober though

#

probably hardcode the weights with a script during compile time

hard talon
#

certainly not something i was expecting to read

eager yew
#

this is so meta, I get to use a neural network to train my neural network

eager yew
#

I'm just putting my neural network code to good use

I was using it as a calculator before this

#

definitely a

:3

moment

#

I had a much more sane and regular version of this in C

robust sorrel
eager yew
#

I just gotta dig it up

eager yew
eager yew
#

I just thought it would be interesting to make my thread scheduler neural network based

#

because it's 2025 and who doesn't have machine learning in their kernel

#

I was told that if i don't have a neural network in my kernel I won't be a cool kid and I'll lose aura

#

just kidding

#

I just think this will be funny is all

robust sorrel
eager yew
#

idk what I'm saying

#

I just need my neural network code to be used for something useful besides a vibes based calculator

#

what better use than thread scheduling

eager yew
#

so I just need a good use for it

#

this is a maximally unserious project

#

I will probably stick to just my feed forward neural network because it is simple and fast and anything slower incurs too much of an overhead for it to be good in kernel

#

honestly I might not even rewrite it to C because I support rust in the kernel

#

so just fixed point arithmetic and some clean up

#

ok I think the way I will integrate this is by having the scheduler ask the neural network "which of these threads is best to run right now?" and the neural network returns the best thread to run

so for example it might input something like

[ 
  recent_cpu_time,
  wait_time,
  priority_nice,
  io_wait_ratio,         // io-bound?
  last_cpu_id,
  num_wakeups_recently,  // interactivity
  core_current_load,
  migration_distance,
  ...
]``` for each thread and then make decisions
#

actually I can assign a score to each thread

#

and the thread that scores the highest gets run

#

idk how to make this happen super duper fast tho

#

hmmm I'll need to benchmark the floating point implementation

#

this is going to be a very long term project in the kernel so I'll need to sketch a very solid blueprint before I go and do anything stupid

#

for now I'll focus on the elephants in the room and touch up on the neural network scheduler design plan here and there

eager yew
#

i really hope I can get the neural network scheduler done by september ngl

short lichen
#

That's crazy

eager yew
#

I just need to port code and train it

eager yew
short lichen
#

Estimated Moving Averages

eager yew
#

this is more fun though...

#

that's boring

#

boooooooo

eager yew
#

it's very cool

short lichen
#

Not really

eager yew
short lichen
#

I remember coming across this but never read it

#

No I mean

#

I've not looked into it

eager yew
#

ok that's it then I'm porting chatgpt to my kernel and asking it to make scheduling decisions

#

oh ok

eager yew
#

hello chat we are back to our scheduled programming

#

i fixed the scheduler to not be jank and all tests pass

#

now it properly switches my context ๐Ÿ˜ƒ

#

no more of the yield == int 0x20 nonsense

#

hooray

#

this was an extremely fast fix wow

#

took under 45 minutes

#

mister sky was once again correct that this was significantly easier

#

oh yeah the manual yield is faster so my things now run faster

#

new schedule routine ๐Ÿค‘ no more interrupt BS

void schedule(void) {
    uint64_t core_id = get_sch_core_id();
    struct scheduler *sched = local_schs[core_id];

    bool interrupts = spin_lock(&sched->lock);

    struct thread *curr = sched->current;
    struct thread *next = NULL;
    struct scheduler *victim = NULL;
    struct thread *prev = sched->current;

    /* skip */
    if (!sched->active)
        goto end;

    /* core 0 will recompute the steal threshold */
    maybe_recompute_threshold(core_id);

    /* re-insert the running thread to its new level */
    scheduler_save_thread(sched, curr);

    /* check if we can steal */
    if (!scheduler_can_steal_work(sched))
        goto regular_schedule;

    if (!try_begin_steal())
        goto regular_schedule;

    /* attempt a steal */
    begin_steal(sched);
    victim = scheduler_pick_victim(sched);

    if (!victim) {
        /* victim cannot be stolen from - early abort */
        stop_steal(sched, victim);
        goto regular_schedule;
    }

    struct thread *stolen = scheduler_steal_work(victim);

    /* done stealing work now. another core can steal from us */
    stop_steal(sched, victim);

    /* work was successfully stolen */
    if (stolen) {
        next = stolen;
        goto load_new_thread;
    }

regular_schedule:
    next = scheduler_pick_regular_thread(sched);

load_new_thread:
    load_thread(sched, next);

    update_core_current_thread(next);

    if (!next) {
        disable_timeslice();
        goto end;
    }

    if (all_threads_unrunnable(sched) && next->state == IDLE_THREAD)
        disable_timeslice();

end:
    /* do not change interrupt status */
    spin_unlock(&sched->lock, interrupts);

    if (prev)
        switch_context(&prev->regs, &next->regs);
    else
        load_context(&next->regs);
}```
short lichen
eager yew
#

I don't do fpu stuff

short lichen
#

Oh what

eager yew
#

that's for later

robust sorrel
eager yew
#

when i return control flow to the thread it goes to that interrupt handler

#

which restores all registers

#

๐Ÿ‘

eager yew
#

i feel like making a worker thread event pool rather than spawning a bunch of threads I'm ngl

#

feels simpler

#

I already have event pools/queues from my function deferring after some milliseconds so now i just need to port that over to a worker thread event pool

#

garbage collection

#

garbage collection gameplay

eager yew
#

longest symbol names I've written in a bit

eager yew
#

event pool gameplay

#
static bool event_pool_ran = false;
static void event_pool_fn(void *arg) {
    (void) arg;
    event_pool_ran = true;
    ADD_MESSAGE("event pool ran");
}

REGISTER_TEST(event_pool_test, SHOULD_NOT_FAIL, IS_UNIT_TEST) {
    event_pool_add(event_pool_fn, NULL);
    sleep_ms(50);
    TEST_ASSERT(event_pool_ran);

    SET_SUCCESS;
}```
#

I should setup testing in CI/CD ngl

#

I did not realize this was just a Deferred Procedure Call and just called it defer_func_t ๐Ÿ’”

#

whatever it's just a name

#

I will probably add a central thread registry to keep track of all threads regardless of their state so stuff like htop works

#

๐Ÿ’ฅ

eager yew
#

i am mildly concerned about the neural network scheduler idea performance wise

#

my current scheduling algorithm with rebalancing takes 5,000 clock cycles

#

I don't have too much leeway, any more than 20,000 clock cycles and ๐Ÿ’ฅ

#

I'll need to benchmark the NN

#

whew ok nevermind I am NOT cooked

#

the neural network runs in 5,000 clock cycles we're chilling (floating point)

#

fire

#

ya ok it's consistently 4-5,000 clock cycles this is baller

#

ok i made the NN code suck less

#

time to port to fixed point

eager yew
#

I am starting to understand why science code is often so bad looking

#

great ok I'll train it on XOR and stuff

#

chat, I have ported the neural network to fixed point

#

time to benchmark

#

woah hoa HOAH

Took 124 clock cycles
Input: [0, 0], Output (fixed): 3515, Predicted: 0, Expected: 0
Took 318 clock cycles
Input: [0, 65536], Output (fixed): 61385, Predicted: 1, Expected: 1
Took 110 clock cycles
Input: [65536, 0], Output (fixed): 61446, Predicted: 1, Expected: 1
Took 290 clock cycles
Input: [65536, 65536], Output (fixed): 4780, Predicted: 0, Expected: 0```
#

under 400 clock cycles!!!

eager yew
#

ok great the neural network is now pretty trivial to port, but I'll hold off on this until I get to userspace and have heavy workloads to test

#

oh yeah turns out the reason my kernel wouldnt boot with less than 5MB of RAM is because I forgot to -s the binary and it was building in all the debug symbols and mister bootloader didnt like that

#

it's 300kb without the debug symbols

#

time to boot on 3MB RAM

#

darn that doesnt work

#
#!/bin/bash

grep -rnE '\b(k[zm]alloc(_aligned)?)\s*\(' . | grep -v '/tests/' | while read -r line; do
    file=$(echo "$line" | cut -d: -f1)
    lineno=$(echo "$line" | cut -d: -f2)
    next5=$(sed -n "$((lineno+1)),$((lineno+5))p" "$file")
    if_line=$(echo "$next5" | grep -m 1 -E '^\s*if\s*\(.*\)')
    if ! echo "$if_line" | grep -Eq '!\s*\w+|==\s*NULL'; then
        echo "Possibly unchecked alloc at $file:$lineno"
    fi
done``` ah yes
#

delightful

eager yew
#

"Boy do I sure love adding allocation failure handling paths"

#

hooray it isnt exploding

#

all tests pass with 20 meggies of ram

#

all tests pass with 16 meggies of ram

#

I think this is good enough for now

#

I'll probably regularly test the kernel with low RAM usage from now on because I might end up porting this to older 32 bit systems and those aren't exactly famous for having bountiful memory

pearl kindle
eager yew
#

so every time you save a thread after it finishes running, it also saves a score

#

and the scores are per-queue level

#

since it's an MLFQ

#

the performance overhead is shockingly small and has only decreased after I ported it to fixed point arithmetic

eager yew
#

it pretty much always takes less than 1,500 clock cycles to run no matter how complex (within reason ofc) the network is

eager yew
#

that's just a sketch

#

I was originally going to use manual calculation heuristics using this data and then realized "wait a neural network would be 10 times better here"

#

and also much easier to scale and configure and debug and stuff (and also likely be faster)

pearl kindle
#

ill take a look once you implement it, seems interesting

eager yew
# pearl kindle ill take a look once you implement it, seems interesting

it's mostly just a different approach to what existing schedulers already do, and I'm primarily doing this out of laziness

for example, let's say right now I'm only tracking 3 thread local variables

if I wanna do manual calculation heuristics, sure that will work fine

but later on, when I wanna track 5, 7, 13, or even more, manual calculation heuristics become very very annoying to add/remove and still remain performant

so the neural network is used here due to laziness since I don't want to constantly be rewriting a bunch of heuristic rules and the NN will likely do similar work

obviously this is going to be held off until userspace when I can benchmark real software and get real results, and it will likely take a solid few days to integrate and train the neural network

I've only been working on this project for two months lol so it might be a bit, likely until september

#

but I've already ported the neural network to fixed point instead of floating point and I already have the GlobalAllocator for rust implemented in the kernel so it should perfectly drop in (I wrote the NN in rust)

#

the rest of the kernel is in C but this will be rust

#

pretty fun stuff ๐Ÿ˜€

pearl kindle
eager yew
# pearl kindle (i dont know the scheduling algorithm but) I dont really think a NN is faster th...

the neural network runs in under 1,500 clock cycles, with 100+ neurons (so speed isn't a huge concern) and the trouble with heuristics is that, sure they might be faster, but it becomes difficult to add/remove heuristics and test all of them, whereas with a neural network you can just say

"I want the input values of x, and y to produce an output of around Z" and you can train it offline to get neuron weights that produce exactly that result and just copy the neuron weights over

#

the current scheduler without the neural network runs in 6-7,000 clock cycles and as long as it stays under 10,000 to save the thread and pick a new thread I will be happy with it

#

also the neural network could learn and "nudge" the weights as the system runs, so if I can make training more efficient I can do that as well

#

and it's very easy to do this with a neural network since you have a learning rate which you can use to adjust how much you wanna "nudge" it

pearl kindle
#

wouldnโ€™t you need to run the nn on every single thread, if you want to select based on highest score? thatโ€˜d probably not scale well

eager yew
pearl kindle
#

oh right oops

eager yew
#

so when you add a thread back into the run queues after it ran, it would compute it, and if you wake a blocked thread, it would also compute it when you add it to the run queues

#

the only real overhead is the linear search but that shouldn't be too bad

#

๐Ÿ˜ƒ

molten night
#

How would you train it though, it sounds more like a RL problem then a traditional NN, and it has its overheaad and its much harder to train then traditional nn

eager yew
#

I guess I would probably need a form of a "starvation bonus" but that would probably not be too bad, I can just use a base score and a quick heuristic to find that out

pearl kindle
eager yew
eager yew
#
fn main() {
    let inputs = [
        [FIXED_ZERO, FIXED_ZERO],
        [FIXED_ZERO, FIXED_ONE],
        [FIXED_ONE, FIXED_ZERO],
        [FIXED_ONE, FIXED_ONE],
    ];
    let expected = [FIXED_ZERO, FIXED_ONE, FIXED_ONE, FIXED_ZERO];

    let mut net = NeuralNetwork::new(2, 1, 4, 1).unwrap(); // small network for XOR
    let learning_rate = FIXED_ONE / 4;

    for _ in 0..10_000 {
        for i in 0..inputs.len() {
            net.train(&inputs[i], &[expected[i]], learning_rate);
        }
    }

    for i in 0..inputs.len() {
        let tsc = unsafe { std::arch::x86_64::_rdtsc() };
        let output = net.run(&inputs[i])[0];
        println!("Took {} clock cycles", unsafe {
            std::arch::x86_64::_rdtsc() - tsc
        });
        let predicted = if output > FIXED_ONE / 2 { 1 } else { 0 };
        println!(
            "Input: {:?}, Output (fixed): {}, Predicted: {}, Expected: {}",
            &inputs[i],
            output,
            predicted,
            expected[i] / FIXED_ONE
        );
    }
}``` I mean this is how training works rn
#

you just give it inputs and expected outputs

#

and I can trivially dump the weights after it runs

molten night
#

You don't have the expected output, that's what you're trying to figure out

eager yew
molten night
eager yew
molten night
#

But you wouldn't see a better result without knowing what it did wrong, you can't really backprop "how long this task done" into your net

eager yew
#

I would define fixed cases that I know good scores for, and train the neural network to produce those, and I can just use traditional heuristics to get these good scores

#

and then afterwards I can have the neural network take over

molten night
eager yew
#

I dunno this is a long term thing

eager yew
#

it's saying "how well you did"

#

NN is supposed to make the decision

molten night
#

I wont pretand to know too much about ML, and hoping this idea would work, but yeah its not as simple as plugging the nn

eager yew
#

like if you were building a neural network to train to estimate things, the reward would be how close the estimated number is to the actual value and the neural network just trains itself until it can accurately predict the actual values in cases both covered and not covered by test cases

molten night
eager yew
molten night
#

Im looking forward to see how well this works in any case

eager yew
#

I should probably just use my neural network on something normal like image recognition lmao

#

this is indeed silly

eager yew
#

I will probably turn my DPC pool/worker thread event pool into a two-level MLFQ

#

so just high and normal

#

โœ…

eager yew
#

defer_free gameplay

#

ok so my stinky and lame ringbufffer workqueue might suck when it comes to lock contention

#

but I also will have the problem that some defers become inefficient

#

like if I wanna defer a free during an ISR and I use per-core workqueues, then that defer will just run after the ISR finishes (bad) instead of putting it onto another core which can run in parallel (good)

#

ok, so I think I can go with per-worker-thread event pools, and have some way of communicating between them. I can use very primitive stealing mechanisms

if other.event_count > self.event_count {
    if try_lock_worker_thread(other) {
        self.steal_work(other); 
    }
}```


without the need for complex heuristics like my scheduler

then, there can be three enqueues for work

a) `event_pool_add`
b) `event_pool_add_local`
c) `event_pool_add_remote`

`event_pool_add` will choose the least loaded worker thread to add work to and wake

`event_pool_add_local` adds to the caller's core's worker thread

and `event_pool_add_remote` will add to the least loaded core that is NOT the caller's core

I can simply force the thread spawning to guarantee it spawns one worker thread per core (trivial task) to verify that all this works accordingly, and for now, one worker thread per core will suffice

this way, for things like `defer_free`, I can call out to `event_pool_add_remote`, so that another core can potentially perform the free in parallel rather than `event_pool_add` which may add to the core itself
eager yew
#

looooooooooong

#

I wonder if there's a better way to signal to the reader that "hey, this technically does nothing but it implicitly means that X will happen"

#

whatever

eager yew
#

ok i am gonna change my worker thread system to not be super duper dumb

#

each event pool (per-core) gets one worker thread that is permanent (this way if I have an event pool add from an ISR and the threads for a pool are all non-existent I do not go spawning threads in an ISR)

#

then worker threads (except the permanent ones) are checked and reaped if they are inactive every 5 minutes

#

and for all the non-permanent threads I'll just spawn them as needed

#

๐Ÿง€

eager yew
#

once I go back and reimplement allocators (this WILL eventually happen) I will make them NUMA aware because I think it would be really really funny and I have everything to support that trl

eager yew
#

oh yeah, forgot to mention that "We have CONFIG_NO_HZ at home

#

the cores disable their timeslice if no work is to be done

#

so here only core 0 is being scheduled since everyone else goes "I ain't doin allat" since they're only running one thing

#

imma also make it disable if there is only one thing running

#

lol you can see the reaper thread waking in the logs when the idle thread runs and then the timeslice is disabled so it goes away

eager yew
#

holy smokes SOMEHOW all tests pass when I run with 1 core, I had the foresight in the back of my mind to write everything so it works fine on UP systems

#

the zero intel atom users will be so pleased with this

#

oh yeah I should do the C-state thing

#

and the primary reason for doing this, once again, is because I am the only person who plans to run this code on a known device (thinkpad t400) and I want the battery to not immediately ๐Ÿ’ฅ while the code is running

#

bazinga

eager yew
#

the hpet keeps bothering me and i cant figure out why thonk

eager yew
#

ok it was NOT the HPET, it was the PIT

#

I should uh probably make a generic timer abstraction lmao

#

great ok that means we now have CONFIG_NO_HZ at home

eager yew
#

I am bored, let's make our allocator lockless tonight

#

I'm thinking I'll have a current slab for each core, a global atomic way of allocating new slabs, and cross-core frees will add the object to be freed to a ringbuffer on the target core, and I can simply check this ringbuffer and flush it every time I want to free something before freeing said something

eager yew
#

i think i still need locks for my EMPTY, PARTIAL, FULL lists ๐Ÿ™

#

ts so tragic

#

I dont wanna make a lockless SLL rn

eager yew
#

I have an end goal for this OS I think

#

running fnaf

#

it's all a bunch of 2d sprites so the sad little igpu on my thinkpad won't have to do any heavy 3d work

eager yew
#

CI CD is currently being very very weird

#

I think the mkfs version over there is doing something strange idk

#

yeah their version of mkfs is 1.47.0 and mine is 1.47.2 I think that's doing a weird

#

๐Ÿ’ฅ

steel herald
#

Doubt

steel herald
eager yew
#

struct isn't wrong

#

odd

steel herald
#

the problem is probably somewhere else

#

like 2 patch versions are unlikely to change anything

eager yew
#
commit 713ecc74191d106a0c769b338644a7eb04203f5f (HEAD -> main, origin/main, origin/HEAD)
Author: gummi <[email protected]>
Date:   Sun Jul 20 19:48:03 2025 -0500

    [ext2]: reduce pointer dereferences``` truly incredible optimizations
eager yew
#

was not mkfs's problem

#

all tests pass now and the code is faster by a wee bit

eager yew
#

right ok now that I have once again confirmed that no one should ever let me do DevOps, it's back to on-demand worker thread spawn and reaping

eager yew
#

thought this looked cool so I'll save it here

#

ok I think I will do thread priorities properly

basically, the scheduler should go down the queues and for real time threads, it will disable the timeslice (if one is scheduled), and for timesharing ones, it will enable the timeslice, and RT threads will never decay their queue level, and timesharing ones will

lastly I will have a few background threads (I will call them background to differentiate them from the idle thread), that will only run once everything else is done

the highest queue level is THREAD_PRIO_RT, and then the next three are HIGH, MID, LOW (for timesharing) and the last one is BACKGROUND

this actually matches my bio scheduler funnily enough, which has URGENT, HIGH, MEDIUM, LOW, and background, how curious!

#

bing bop boom boom boom bop bam

#

rn my thread priorities barely exist

steel herald
eager yew
#

few fields became wrong

#

zoopisie

wraith pilot
#

Might be worth looking into that further

#

Sounds a bit sus that a second read is different

eager yew
#

it worked fine locally and maybe mkfs just did a very slight different thing

#

probably just forgot to add the byte offset ya I just double checked

#

๐Ÿคช

#

no reason to fix it since the first read (that actually properly calls the function to read the superblock) is sufficient, idk why I was doing it again

#

lol just had a small data race due to tickless, fixed that pretty ez tho

#

flashback to whatever the hell this was

#

I still have yet to see make/qemu/whatever that was segfault itself again

#

there's probably a data race in qemu's emulated device

#

ok ok now that all that dillydallying is done and my scheduler has RT threads (done properly) I can finally go back to doing the event pool on demand thread spawning and destruction

#
struct worker_thread {
    struct thread *thread;
    time_t last_active;
    bool should_exit;
    bool is_permanent;
    struct worker_thread *next;
};

struct event_pool {
    struct spinlock lock;
    struct condvar queue_cv;

    struct worker_task tasks[EVENT_POOL_CAPACITY];
    struct worker_thread threads[MAX_WORKERS]; // maybe don't do a static limit (?)
    uint64_t head; // producer index
    uint64_t tail; // consumer index

    uint64_t num_tasks;

    uint64_t num_workers;
    uint64_t idle_workers;
    uint64_t total_spawned;
    time_t last_spawn_attempt;
};``` ok so I have this thing going here, each core has its own pool, and its own set of threads. 
so basically

- one worker thread per event pool at the start

- when enqueuing work, check "Hey, do we have a worker that can run this task?", if not, and if the time since the last worker spawned for a pool is long enough, and if there are not too many worker threads on the pool, spawn another

- every worker will set up a timer event so that after some time it automatically checks itself to see if any work was done during that time period, and exit if not 

- this time duration for the timer event is dynamic. the first few workers will have longer time durations (30s to 1m), whereas the last few workers will have much shorter ones (3s to 10s), because the cases where I spawn a bunch of workers will hopefully not be super long lived, and saving memory is nice

- eventually, all workers but the permanent one will be gone
#

the trouble is now figuring out the max workers per pool. the static cap here is just a placeholder, but I might use it if i can't figure something out

#

I think a static one will be ok

#

ok so I guess the plan to start incorporating this will be

  1. start using this system to replace the current system (just one thread for now)
  2. start spawning new worker threads based off of the policy
  3. give each worker thread an inactivity_check_period where it'll check for activity after some time and cleanup if there is nothing to do, with this value being determined based off of the value of num_workers at thread spawn time

๐Ÿ’ฅ great ok now I hopefully won't do a massive stupid whilst implementing this

stuck nexus
#

i would do it as a periodic thing instead

eager yew
#

I just wanted the inactivity checks to be more dynamic since the first few worker threads spawned for an event pool will likely be useful for longer than the last few

#

oh yeah and if by periodic you meant it will repeatedly check once every inactivity_check_period, that's what I'm gonna do lol

eager yew
#

alright chat

#

the job is done

#

on demand worker thread spawning and reaping ๐Ÿ˜ƒ

#

code isn't super clean so I'll take a break and then go clean stuff up a bit

eager yew
#
void worker_main(void) {
    struct worker_thread *worker = get_this_worker_thread();
    struct event_pool *pool = get_event_pool_local();
    bool interrupts = are_interrupts_enabled();

    while (1) {
        bool idle = false;
        time_t start_idle = 0;

        interrupts = spin_lock(&pool->lock);

        if (idle_loop_should_exit(pool, worker, &idle, &start_idle)) {
            spin_unlock(&pool->lock, interrupts);
            worker_exit(pool, worker);
        }

        do_work_from_pool(pool, worker, interrupts, &idle);
    }
}``` ok this is probably readable enough for me
#

neat stuff

#

I guess I can make the worker thread stacks smaller

#

DPCs are pretty light

eager yew
#

non page aligned 64 byte stack trl

eager yew
#

got bored and made panic/oops prettier

eager yew
#

I just noticed that my tests have been consistently increasing in speed for some reason

#

are my features somehow making them faster

#

lol that's funny

#

like my filesystem integration test went from 12ms to 10ms to 8ms and now it is 6ms

eager yew
#

recursive priority inheriting mutexes are a doozy

#

if thread RT blocks on a mutex held by thread TS, and thread TS blocks on a mutex held by thread BG, then BG must somehow realize that RT, which is on an entirely separate mutex, is in the priority inheritance chain, and BG must figure out that it must boost to RT

oh brother

#

hmmmmmmmmm

eager yew
#

uh oh

stuck nexus
#

RT needs to be able to boost TS, and walk the chain to BG and boost that too

#

before RT blocks himself

eager yew
#

ohh yeah it goes the other way

stuck nexus
#

i have an implementation of this

#

that is well commented ish

#

in keturnstile.jkl

eager yew
#

alrighty great stuff yes

#

I think I'll need something like a blocker chain on threads to walk... hmmmmm

#

ah this looks like the mintia KiWaitBlock I think

stuck nexus
#

no thats diff

#

the thread blocking chain for locks is chained through KeThread and KiTurnstile structures

eager yew
#

ohh ok ok ๐Ÿ‘

#

ok so what I'm understanding is that each thread should keep track of the thread it is blocked on, so something like

struct thread {
    ...
    struct thread *blocker;
    ...
};```

So, priority propagation chains work something like this

**BG** acquires lock A, and lock B, releasing lock B. 

**TS** acquires lock B, but blocks on lock A. 

**TS** marks its `blocker` as **BG**, and boosts **BG**'s priority to *TS*, and stops since **BG** has no `blocker`.  

**RT** blocks on lock B.

**RT** marks its `blocker` as **TS**, and boosts **TS**'s priority to *RT*, and then sees that **TS**'s `blocker` is **BG** (now perceived as __TS__), so it boosts **BG** to *RT* as well, and then stops since **BG** has no `blocker`.
stuck nexus
#

i go more like thread^.BlockedOnTurnstile^.Owner^.BlockedOnTurnstile^.Owner^.BlockedOnTurnstile...

#

alternating between threads and turnstiles

eager yew
#

ohh ok yea, this was more primitive, I'm still tryna get the "Foggy Overview" in my head before I start refining things

stuck nexus
#

it might be possible for me to link threads directly but theres no reason to since i have to have the turnstile anyway for other reasons

#

so i may as well save space in the thread by putting that link in the turnstile

eager yew
#

oh wait funnily enough

struct mutex {
    struct thread *owner;
    struct thread_queue waiters;
    struct spinlock lock;
    bool initialized;
};```

So I kind of sort of already have a "turnstile" in my mutex (did not realize it was called that), so all this means is that I have to make my mutex have a separate turnstile object that a thread takes a pointer to when it acquires the mutex, and rather than needing the `blocker`, I can just walk `thread->turnstile->owner->turnstile...`
#

literallly copied you smh

stuck nexus
#

you dont get the pointer to the turnstile that way

#

u get it by looking it up in a hash table keyed by the address of the mutex

#

with per bucket locking

#

there may or may not already be a turnstile there, theres only one if the mutex is currently being contended on

#

if theres not then you need to initialize one

eager yew
#

why would this be necessary rather than embedding the turnstile in the mutex tho

stuck nexus
#

two reasons

#

one is that turnstiles are big

#

if you do this, your mutex can be the size of 1 pointer

#

very small

eager yew
#

true

stuck nexus
#

secondly turnstiles cant go in paged pool

#

but mutexes can

#

if this is done

#

paged pool being swappable kernel heap

#

it turns out you only really need one pointer worth of overhead for each mutex

#

which is good if you like embedding them in structures all the time

#

and all the extra info only needs to scale with number of threads

#

rather than with number of mutexes

eager yew
#

so it's literally just struct mutex{}; then

stuck nexus
#

because its only needed when theres contention

eager yew
#

neat!

stuck nexus
#

and the max number of mutexes that can be contended on at a time is like threads / 2 (because you need two threads to contend on one mutex)

#

you dont strictly speaking need any more instances of contention info (such as a queue of waiting threads or priority inheritance info) than that

#

in practice turnstile memory usage scales with # of threads rather than # of threads / 2

#

because the latter would require a global free list of turnstiles which would be inefficient for locking

#

its easier to give each thread a turnstile when its created

#

and let it just use that when it contends on a held mutex and needs to initialize a turnstile due to being the first thread to do so

#

important to note that turnstiles are not embedded in the thread though

#

you might wonder why if theres a one to one number of turnstiles and threads

#

basically its because a thread's turnstile can outlive it

#

because

#

when a turnstile is unblocked (all waiting threads are awoken)

#

you cant control the order in which the threads are scheduled and cease usage of the turnstile

#

so if the thread that initialized the turnstile left first, and took the turnstile with it, the others would come in and try to access the turnstile and die

eager yew
#

refcounting?

stuck nexus
#

so instead each thread that blocks on a turnstile will put its turnstile on a "free list" of turnstiles which is anchored on the turnstile theyre blocking on

#

when each thread leaves, it takes whatever turnstile is on the head of the list with it

eager yew
#

ohhh ok

stuck nexus
#

when the last thread leaves, and sees this list empty, it takes the main turnstile itself

#

so theres a case where the thread whose turnstile it was is first to be scheduled in after being unblocked, and takes another turnstile and leaves

#

and thats basically why you cant put the turnstile in the thread

#

because that thread might then immediately terminate or something and then its original turnstile has outlived it and become someone else's

#

the turnstiles get juggled around; their lifetime is not equivalent to that of the thread they were created for

eager yew
stuck nexus
#

basically yes

#

except you dont allocate the turnstile there

eager yew
#

sounds good

stuck nexus
#

you got one at thread creation time

eager yew
#

ahhh right yea

stuck nexus
#

if you allocated the turnstile there thered be deadlock

#

potentially

#

its very bad to put an allocation dependency in your blocking mutex's impl

eager yew
#

mildly confused

stuck nexus
#
  1. you are born with a turnstile, you are allocated one as part of thread creation
  2. at some point you go to grab a mutex and it is already held
  3. you look up the mutex in the hash table and theres no turnstile, so you must be the first guy to contend on it
  4. you link your turnstile into the hash chain and initialize it to represent that mutex, and then block on it
eager yew
#

ohhhhhhh thanks ๐Ÿ‘

eager yew
#

I'll need to refcount turnstiles to avoid deallocating them while theyre in use probably

#

and have some way of deferring a free of a turnstile once everyone is done with it and the thread it was borrowed from is freed๐Ÿ’ฅ

#

hmmmmm

stuck nexus
#

the "free list" of turnstiles that is inside the turnstile thats being used for the lock is your refcount

#

if its empty then the refcount is 1

#

otherwise its >1

#

thats all the info u need

eager yew
stuck nexus
#

i am starting to think you didnt read what i said above

eager yew
#

i shall read again

stuck nexus
#

explains what i mean by free list

eager yew
#

or do I just have that as a field in the thread

stuck nexus
#

my code for this really isnt THAT hard to read probably

eager yew
#

birth_turnstile or something ok

eager yew
#

ok yes first it is time to figure out how to bump a priority on a thread running on a different core and adjust things properly

#

so to go from TS->RT

first, adjust priority field in the struct

if thread is not running,

remove and reinsert to appropriate runqueue
no IPI

if thread is running (on another core)

send the IPI

disable the timeslice on that core, and do any other work needed

and there is no case where the thread could possibly be running on the current core because there's no way for the thread that's boosting the other thread to be running simultaneously

And for TS->Higher TS or from BG->TS there is no IPI since timeslices are not changed

vagrant cedar
#

Year of the charmOS desktop (I followed the post)

eager yew
#

hopefully we get userspace by september

#

filesystem stuff is mostly done I just need a few caches to make stuff faster and then implement my dual cache system for filesystem integrity guarantees

#

today is mutex improving tho

eager yew
#

oh boy

#

first fixing up my RBT impl

#

then making it into a macro

#

and then using it for turnstile queues since it's O(log n) to wake the highest priority waiter

eager yew
#

i found where freebsd puts their turnstile stuff so I'll read that too

#

grep -rine "target" has to be one of my most used commands probably

#

yippee

steel herald
#

sys sys

eager yew
#

ts ๐Ÿฅ€

#

lol their spinlock is mtx

#

silly โ€ผ๏ธ

eager yew
#

so this is useless unless i want CFS (I dont)

solid crescent
eager yew
#

I didn't see linux say anything about turnstiles which is interesting

solid crescent
#

solaris had the grand ambition that there would be basically no spinlocks at all outwith the scheduler itself (where what would be called 'dispatcher locks' are directly spinlocks)

eager yew
#

ya I saw that in a post about their turnstiles, everything uses sleeping mutexes except for the scheduler

#

neat plan

solid crescent
#

when freebsd, also netbsd, copied a lot of the ideas from solaris, they also copied an artefact of the incomplete realisation of that plan in solaris - in solaris they wanted to thread all drivers so that spinlocks become unnecessary, but they didn't complete this

#

so solaris mutexes are associated with an IPL and if it's low (vast majority of mutexes) then it's a sleeping (and only adaptively spinning) mutex, if it's higher it's a spinlock

eager yew
#

so their sleeping mutex and spinlocks are the same struct mtx and it checks the IPL to figure out what to do?

#

fascinating

solid crescent
#

yes, an (i think) quite inelegant thing

solid crescent
eager yew
#

why don't they just separate out the structs everywhere if they do have true exclusively spinning spinlocks? was it because of their idea with threading drivers?

solid crescent
#

i think to ease old drivers porting

#

solaris has a stable DDI

#

so you get an opaque "pil" ("ipl" with the "i" moved along) from certain APIs in the DDI and you initialise your mutex with this

eager yew
#

why don't other kernels like Linux take after Solaris as well? I read Linux's mutexes and saw nothing about a turnstile

#
linux [$] 
> grep -rine turnstile
include/drm/task_barrier.h:42:    struct semaphore enter_turnstile;
include/drm/task_barrier.h:43:    struct semaphore exit_turnstile;
include/drm/task_barrier.h:46:static inline void task_barrier_signal_turnstile(struct semaphore *turnstile,
include/drm/task_barrier.h:52:        up(turnstile);
include/drm/task_barrier.h:59:    sema_init(&tb->enter_turnstile, 0);
include/drm/task_barrier.h:60:    sema_init(&tb->exit_turnstile, 0);
include/drm/task_barrier.h:81:        task_barrier_signal_turnstile(&tb->enter_turnstile, tb->n);
include/drm/task_barrier.h:83:    down(&tb->enter_turnstile);
include/drm/task_barrier.h:95:        task_barrier_signal_turnstile(&tb->exit_turnstile, tb->n);
include/drm/task_barrier.h:97:    down(&tb->exit_turnstile);
``` how curious
solid crescent
#

linux was very siloed

#

they needed matt dillon (then of freebsd, later dragonfly founder) to help them whip their vmm into shape

#

this worked out to their advantage when they got some dynix people sent to work on them who introduced dynix's RCU mechanism

#

as to turnstiles, torvalds despises priority inheritance

eager yew
#

ah that's interesting

#

why though?

solid crescent
# eager yew why though?

"Friends don't let friends use priority inheritance".

Just don't do it. If you really need it, your system is broken anyway.

    Linus
eager yew
tulip solar
solid crescent
eager yew
#

I suppose I'll just stick with implementing priority inheritance in this hobby kernel's blocking mutexes, whilst trying to make most uses of mutexes only result in waiting threads taking the fast path of spinning for a little bit

I avoided RCU since it has that overhead of making a duplicate copy of the data, which I was not a fan of for things like the filesystem drivers

#

thanks ๐Ÿ‘

tulip solar
severe abyss
#

as i understand you build pointer sized locks with them

solid crescent
#

you could even have a zero-sized lock

#

since you could wait on any address or indeed any integer whatsoever with sleep()

#

turnstiles are distinctive in that they combine this mechanism for storing the waiters' queue out-of-line from the thing waited on, with a priority inheritance mechanism

solid crescent
#

by what way do you achieve them?

#

how are waiters represented? are they perhaps represented by structs allocated on the stack of the waiting thread, and ultimately linked onto a list headed by the lock-word itself?

#

i thought it might be, i implemented push locks once, i never did figure out how to do do priority inheritance with them though. windows did, some way or another

#

in windows the convoy avoidance solution was to move from waking only the next waiter to waking the whole herd of waiters at once

eager yew
#

don't they race to re acquire the lock?

#

pretty neat solution

steel herald
#

can you link your kernel

solid crescent
# eager yew don't they race to re acquire the lock?

the hope is that if you introduce a short period of spinning on the lock before committing to sleep on it, you avoid the cost of the whole sleeping & waking step, which would dominate if the actual time spent under the lock were short

steel herald
#

also, u do jira issues for your own kernel as a solo dev?

#

why did u decide to make it private?

#

ah

solid crescent
eager yew
#

I'm probably gonna fix up my idle thread so it's not just a halt loop

I'll probably make it more dynamic

so first it halt loops, and sets a timer interrupt for a short bit of time, like 100ms

if any work was done during that time, it resets back to the halt loop (I can use a simple state machine here), otherwise, it goes and tries to do scheduler thread work stealing

then, again if it succeeds, it resets back to going into the halt loop, but if it fails and no work is stolen, it searches for work elsewhere to migrate, such as the event pools, since these are more expensive and less impactful to go search and migrate work from

if it succeeds in a steal here, it resets to going back to the halt loop, but if it fails once again (no work migrated), it sets the processor up for a deeper sleep state (like a C level) and enters that interruptable sleep state

#

should be ok

eager yew
#

ok I dillydallyed tonight and did scheduler optimization instead of that idle thread thing

#

the slowness was partially from an O(n) operation which could've been O(1) and mainly from me constantly doing MMIO r/w to the LAPIC to enable/disable the timeslice

#

got rid of most of that

#

task pick and switch takes 500-600 clock cycles instead of 5,000-6,000

#

and also made the hpet use all the cores

#

because it's 2025

placid vessel
#

what does that mean

wraith pilot
#

It means they are using the hpet in 2025

placid vessel
wraith pilot
#

I imagine they're broadcasting interupts via the ioapic or making use of as many hpet comparators as they can.

#

I was memeing earlier

severe abyss
#

autoboost ?

eager yew
#

linuxhw is my lord and savior ๐Ÿ™ I can finally emulate C-states to test the idle thread

eager yew
#

oh yeah i was testing on BIOS this whole time. I tried uefi and everything worked perfectly fine so that's neat

eager yew
#

i am le guessing that I need to import custom ACPI tables to emulate C states

#

this no finds anythings

steel herald
#

do u expect VMs to expose c state configuration?

eager yew
#

probably not

steel herald
#

also u need to let the firmware know u are able to control it

#

then it loads extra tables with c states

#

its not that simple

eager yew
#

o ok

steel herald
#

you can dump your pcs tables to check what those look like

#

u might have to call _OSC a few times to negotiate that

eager yew
#

I'll see if I can get tables from linuxhw to work in QEMU

steel herald
#

nah that wont work

eager yew
#

but qemu seems to have a max of 64KB for acpitables passed with -acpitable

#

oh ok

steel herald
#

yeah exactly

#

u could make a smaller one for testing

eager yew
#

oh boy i get to learn ASL hooray

steel herald
#

the c state stuff is likely in a separate SSDT so that might work

#

if u grab linuxhw stuff

eager yew
#

so I just copy an SSDT with c state stuff?

#

sounds good

steel herald
#

well

#

just for testing u can try

eager yew
#

and I just use iasl to compile and -acpitable ok dok

#

is that how uACPI does its tests

steel herald
#

if u grab an existing one u just feed it directly

#

u can use acpixtract <thing>.bin

#

and it will give u all tables

eager yew
#
> acpixtract 1C0315195E84.bin

Intel ACPI Component Architecture
ACPI Binary Table Extraction Utility version 20241212
Copyright (c) 2000 - 2023 Intel Corporation

  DSDT -   62200 bytes written (0x0000F2F8) - dsdt.dat
  SSDT -     601 bytes written (0x00000259) - ssdt1.dat
  SSDT -    2123 bytes written (0x0000084B) - ssdt2.dat
  SSDT -    1183 bytes written (0x0000049F) - ssdt3.dat
  SSDT -    2545 bytes written (0x000009F1) - ssdt4.dat
  SSDT -     541 bytes written (0x0000021D) - ssdt5.dat
  SSDT -     281 bytes written (0x00000119) - ssdt6.dat
  SSDT -    1714 bytes written (0x000006B2) - ssdt7.dat
  SSDT -     771 bytes written (0x00000303) - ssdt8.dat
  SSDT -    1135 bytes written (0x0000046F) - ssdt9.dat``` very fancy
#

thanks

#
grep: ssdt6.dat: binary file matches
grep: ssdt7.dat: binary file matches``` in here it seems
#

great

steel herald
#

then u just need to find a table which usually has CPU somewhere in its name

steel herald
#

iasl thing.dat to disassemble

eager yew
#

looks great

#

so would I just plug this thing into qemu via -acpitable

#

or wait no

#

hmmmm

steel herald
#

well u can

#

but that returns an object that doesnt exist

eager yew
#

I guess I'll try it just to see if anything pops up in my search for the _CST

steel herald
steel herald
# eager yew or wait no

u can find the object its trying to reference and paste it in the file and then recompile it

eager yew
#

or what

steel herald
#

yes

#

it returns a union

eager yew
#

ooopz

#

ohh yes i am seeing it

eager yew
#

sad

steel herald
#

well yes u dont get to control cpu power states as a vm

eager yew
#

No _CST on _PR_ -> not foundhmmm that _PR_ wasn't there before so something happened

steel herald
#

pr is a predefined object that always exists

eager yew
#

ohh ok

#

so I suppose I'll decomp the /* External reference */s and copy them into this file and recompile it again

steel herald
#

u can just do
Device (\_PR.CPU0) { }

#

the main thing u have to find is the CPU0._CST table its trying to return

eager yew
#

ssdt 7 has a few things (I don't copy the text to discord since the indentation is super big and it would look bad)

steel herald
#

yeah u might be fucked here

eager yew
#

uh oh

steel herald
#

since its not a hardcoded table

#

i guess it constructs it by reading some proprietary stuff

#

from registers etc

eager yew
#

bruh this is a thinkpad table lmao

#

I'll look through linuxhw tables

steel herald
#

cant u just hardcode one yourself?

eager yew
#

do you have a better one i could use or nah

steel herald
#

for testing

eager yew