#MINTIA (not vibecoded)
1 messages ยท Page 14 of 1
which is not the same as the modified page writer
they have both
for reasons
at least last i checked
is it landy wang
ik he's mm but i know people start wearing multiple hats when theyre around that long
i see
what does the lazy writer do that the mpw doesn't?
do you know if they changed how mm scans working sets ?
hes someone id like to meet one day if AI-fication doesnt take all the jobs first
much has come down to us about landy wang's mm work lol
we actually figured that out
some time ago
based on changed structures in recent versions
or @warm pine did
he might like to talk to you about that
afaik he figured this out just from looking at changed structure definitions
lol
nice to see you confirm his hypothesis
i think he decided on doing it the same way in his hobby kernel keyronex when he gets there
theyre from the adjacent private server i mentioned
which is my project's server
It took some time reflecting
But the names were very clueing
Then the algorithm is implied by the structure itself
Since you only have page tables linked onto your queue, it is those you must be scanning- presumably all their contained PTEs and you can age them
surely there has to be some other mechanism to skip unmapped areas though
iterating 512 times per page table that might only have a few valid PTEs sounds bad
i guess you could look up the first mapped region that lies within the page table and start there and scan by region
but now your lock for your mapping structures is subordinate to a memory-making lock (the working set lock)
and now you cant put them in paged pool and stuff
:(
It doesn't seem too bad to me. Nice access pattern
Looking up map entries less nice
it would be unacceptable for me i think but i have like 20mhz to deal with so its not applicable i guess
And one assumes the typical case is you usually have more than a few valid ptes
the high priority working set trimmer thread iterating over just one page table for each of like say 10 processes could cause a noticeable UI stutter
at 20mhz
if that was 1024 iterations each
Yes I think it makes more sense when both processing and memory have scaled by a few orders of magnitude, and I think that would be reflected in the patterns of valid ptes tending to appear in longer sequences
And on the software side you expect local blocks of data accessed frequently to have also scaled up commensurately, with isolated 4k blocks probably much less common
there has to be a way to accelerate it
if i used a bitmap that would introduce a 1024 / 8 = 128 byte overhead for each page table but would let me skip groups of 32 ptes at a time
oh
i was already planning on having bitmaps though
for commit tracking
so i could probably reuse that
since only a committed area can have valid PTEs
mm nvm thats not applicable
that was specifically for tracking page table commitment
like charging commit for the maximal number of page tables that may need to be created to map an area
and instead of a bitmap i was going to do something a bit different
for each level of the page table hierarchy except the leaf level i was going to have a distinct avl tree
per process
which are used to look up arrays of counts of mappings that overlap each lower-level page table
when it transitions from 0->1 the commit for that page table is charged
1->0 its uncharged
and they left too
shame
yeah, wanted to reread some messages because i didn't fully understand them yesterday but theyre not there anymore
feels unfortunate that industry people aren't comfortable discussing their work with aspiring people
damn, we'll never know what cache manager problems and design limitations they were referring to
shame, seemingly they just came to see if any of us knew that paper about linux
the other NT guy we had in here was also quite skittish and would often delete messages and leave so i get the impression these people are terrified of HR
this is an interesting case since here you see how the changes in the scale of software and hardware alike (which came together) call for different solutions
i was in a hurry to give a response at the time but now that i am home i can be clearer about what i meant
there's a ms agent sitting here, and if they see any ms developers talking about their work, make them leave
which is a valid reason
hr is scary
Yeah
Not saying it isnt
they also might be afraid of who would be attracted to a legit NT kernel guy who is willingly conversant in some public internet space
Skids galore if the wrong people heard
oh ya
basically i am taking as a given that: in 1989, it was a common pattern to have individual 4k pages sparsely populated in the address space, while in 2025, you are much more like to have at least dozens of 4k pages living next to each other and acting like a unit, either all of them being active or none of them
kelower talked quite a lot and didn't seem to delete anything, so this guy probably just wanted to find out some paper, didn't find the answer, and left
I thought of a possible solution which is that when a page table is created for the first time, I can look up all of the map entries that overlap the region mapped by the page table and create smaller "mirror map entries" in nonpaged pool which just represent extents of PTEs that may exist in the page table. These would be threaded along a list anchored on the PFN entry for the page table
that's one half of why i don't think scanning a whole 512 PTEs at once is so bad; the other half is simply that scanning 512 PTEs is extremely fast on a modern machine, the access pattern involved is an ideal case, this is part of why there are so many anti-linked-list people nowadays
So then I only need to scan the PTEs that are represented by the mirror map entries
If I don't have room in the PFN entry for the list head i can also just do like a separate map entry tree with small nonpaged map entries that function similarly, or split the normal map entries into a nonpaged and paged part and just use those.
reasonable compromise i think, it's fine to look at the map entries in a path where other costs dominate anyway, then get yourself a nice look aside structure you can consult much more efficiently in the scanning path
Or just keep the map entries nonpaged to begin with
They aren't that big
they were only 52 bytes apiece in old mintia
or 64 bytes of pool
counting the pool header
i know why you didn't like it in principle in the past, since it brings in a non paged factor which scales in a way that really pure virtual memory (which scales independently and doesn't produce a scaled cost in physical memory) should not, but i think there's no getting away from some impurity there
yeah
i am doing disposable page tables this time though
rather than pageable page tables
so when the last valid PTE is trimmed from a page table it will just instantly go away
and page tables arent tracked in the working set
the info for where anon pages was swapped out will go in a vm object or amap like in UVM
i want to do likewise
not for the fun of it though i do prefer it in abstract/aesthetic ways
its more portable for one
i also think its probably more memory efficient
and more efficient in general
you cant have a fully abstracted pmap layer without the page tables being disposable
if you rely on them being a certain way then suddenly ppc gets more difficult and stuff
for the practical reason that i want to support aging the working set, and on arches like risc-v, i really don't want to hold a spinlock to do this
i want to be able to do a sequence of atomic operations instead that should ultimately get me to setting the A-bit
and the approach i have in mind involves deferring the reuse of hardware page table pages for any other sort of page, in such a way as reuse can't happen until after - any page fault handler that began before we got rid of the parent PTE referring to that page - has completed
or rather that the "lockless A-bit/D-bit setting" part of a page fault has completed first
this is the other part of it, i dislike that i would have to do trad style page tables in order for a port to ppc to be even possible
i have completely discarded that portability aspect already though lol
so im not doing it for that reason but rather for efficiency
i think having the page tables be disposable and having the "prototype page tables" for vm objects and amaps go in a special pinnable paged pool instead will be much more efficient
in practice it might be that doing these is useful anyway for reasons of performance, and this would also get in the way of doing the new style approach to working set aging, but if you say working set aging is in the pmap layer (which is a division i think is reasonable) then you can feel better about it all
ill probably do the cool pte scanning working set
it's too cool not to do
i dont have an A bit but there are two alternatives:
- simulating it w/ the page fault handler by toggling the valid bit off and using one of the available bits in the valid PTE as your A bit
- using presence of the virtual page in the TLB as your "A bit"
both have been used on A-bit-less RISCs in the past
i want to support aging in the working set for improved replacement dynamics and this is clearly a superior approach to that, than the traditional WSL where you have the WSL then you have to look out-of-line, all-over-the-place to see the actual PTE
also VMS used option #1 on VAX which didnt have an A bit
and probably also on Alpha which lacked one as well
mach also
this might legit be simpler than the older working set array
because you dont need to manage this other structure
that needs to be able to grow and so on
its folded into page table management
that you already have
you also dont need to explicitly insert virtual pages into the working set
just by creating the valid PTE you have accomplished that
my question is how do you "age" PTEs afterwards
where do you store the age of the PTE
you can store it in available bits in the PTE itself
on 64-bit its okay because theres like 16 spare bits (if you don't use the PTE key feature)
it makes life a lot easier, especially not having to shrink it, which is just not a very pleasant operation
this is what NT does
3 is reasonable, 7 ages is not too bad
but there may be other bits you want to use too
xr17032 has like 7 available bits and fox32 has like 9
and those are the only 32 bit architectures im supporting
ever
the other planned ISAs are Aphelion and AMD64 which are both 64 bit
okay
on RISC-V i think i only have 2 bits available so i have even fewer ages, but still you do get a few ages out of that, and that should be helpful
worth noting that there is an A bit on x86 32
but there are 3 spare bits and surely you need some for something
in boron one of the pte bits tells me whether a page is mapped from physical RAM or is MMIO
probably unnecessary
you can get that info from the map entry instead
in my projected design i only need spare bits for the age
one of my (extremely rough) concepts for a mintia book cover is a reference to this BoC album cover lol
there are probably other optimisations possible if you have more bits free but none that i see as really essential
same
multics used page aging and they found they only needed like two page ages
the benefits dropped off DRASTICALLY after that
and at about 8 ages it started performing worse
this is about that
one nicety i don't have a full vision of how to do yet - parallelised working set aging - probably once you already divide a working set into a list head per, say, average age of PTE within the page tables headed by that list, you can then individually lock these heads and do some magic to allow you to unlock the list head you were working on before and re-queue the page to another head (when i last thought about this, i expected some complication here, but now that i'm thinking of it again, i wonder if i was just blinkered by something stupid at the time)
also possibly one of the earliest descriptions of clock
k is the number of ages
and with that parallellised aging i think you alter the considerations a bit and more ages = more better up to some limit more like how many cores do you have? and how big is the program?
smth i dislike about using the valid bit for page aging though is the number of tlb shootdowns it requires
probably you would just not do any shootdowns (they arent "functionally" necessary) while doing aging and then at the end you do a big shootdown that makes everybody dump their entire TLB
the parallelized aging works by having each cpu be able to work on a different age list right
and theyre individually locked on that basis
that's what i've inferred
also are you excited for the potentially imminent new boards of canada album
it seems believable as kelower said something to this effect once
for a sec i was worried about what happens if you need to move a page table to a different age list but its not really an issue
you can unlink it from the one you have locked
after youre done aging the PTEs within
and add it to a list of page tables that need to be re-linked into different age lists
anchored on your kernel stack or smth
and then when youre done you can go through and lock each of the age lists and insert the page tables into the new one they need to go into
something like that
and believe it or not but i had never knowingly listened to boards of canada, you are the second person to recommend me them
they are scottish apparently so they must be good
they grew up in canada though
yes exactly that
hence the name
alternative you can just trylock the new age list it needs to go into (while holding the one youre running along) and if you fail to get it immediately you can just go "nvm it can stay on this old one until some later point in time"
because its not functionally necessary for it to be on the correct one
they're most well known for a soup of smoked haddock, it's got an unfortunate name
they have an extremely potent aesthetic that has inspired that of my project in numerous ways
i found out just recently that they once promoted an album by doing an ARG that involved a fake DCL command prompt on OpenVMS which was very awesome to learn lol
hm i thought itd embed it
i had in mind something like after each table is aged, unhook it, release that list lock, then re-queue it on the right table, but it is better to batch this process
i probably thought of doing it liket hat because i just barely made my thread migration work exactly like that https://github.com/xrarch/mintia2/blob/047b6acd995e3ea8833e1241bd64d2c2e912122b/OS/Executive/Ke/KeDispatch.jkl#L1745-L1900
that it's perfectly fine to tolerate the tables being on the "wrong" list makes life easier
i take destination+source ready queue locks (in address order), take as many threads off the source queue as necessary to reach balance, unlock the source queue, enqueue to the destination, then unlock the destination
the wrinkle is that i need to take each thread spinlock before enqueuing it to the destination
which is out of order with respect to its ready queue spinlock
so i just trylock the thread while holding the destination queue spinlock and re-enqueue as many threads as i can that way
and any threads that i couldnt immediately enqueue, i put in an array and do it individually at the end
so im trying to maximize the odds that im actually fixing an imbalance and not making it worse
due to something changing in the middle
this actually did make the fireworks test run visibly more smoothly with lots of processors which was delightful !
i bet there are rwlocks involved in their multithreaded page aging as well
the working set trimmers probably take some stuff shared that is taken exclusive in the page fault handler or other places
shared locking scares me because it is opaque to priority inheritance but its not actually an issue here because the working set trimmers are the only shared lockers and are already high priority system threads
when i think about implementing this i think of probably having a big page table tree lock
per process
which is taken shared by the working set trimmers
exclusive in page fault codepath
they also take the age list locks exclusive ofc
while holding this paging tree lock shared
i guess itd be cooler if i could figure out a way to have per-page-table locking
oh well im still pretty far from doing any serious vmm work cuz i need my IO system working first according to the roadmap i set myself like 1.5 yrs ago
Development is organized into base levels; initial development is done on UP and MP XR/station and at the end of each base level anything architecture-specific is brought up to fox32 and tests are written.
Executive base levels:
0 - Initial development
1 - Full loader, full scheduler and core functionality, pushlocks (Ke)
2 - Pool management (partial Mm), full object manager (Ob) and partial namespace (Ns)
3 - Object-level process & job management (Ps), IO manager (Io)
4 - Finished XR/station and fox32 system drivers
5 - Pagefiles, memory mapping, page fault handling, etc (full Mm)
6 - File viewcache (Fv) and filesystem drivers for AisixFS, FAT12/16/32, finished namespace
7 - Duplex driver (Du), executive pty console (Ec), and first userspace execution
8 - IPC
Userspace base levels:
0 - Initial development (executive base level 7)
1 - Basic uexec.dll functionality (dynamic linking, apc/signal dispatch, etc), SystemInit.exe starts
- Executive base level 8 -
2 - SystemInit performs basic system initialization (Si), Authority management server performs session control & logon (Ams)
3 - Mintia Command Language exists as a generalized DLL and parses and executes programs (Mcl), has a shell wrapper cmd.exe
4 - Trivial tools such as cat, ps, ls, kill, etc
5 - Management tools such as users, groups, etc
6 - Port toolchain, self-hosting system
im working on BL 3 (so im on BL 2)
Ps is done (has been for months) and Io probably would have been as well if i didnt spontaneously decide it has to be plug n play which i wasnt sure how to do and has required a lot of thinking
original idea was to just straight rewrite old mintia's io system, maybe would have been better
@warm pine OH
i just figured it out
you dont have to hold the age list lock that long
you can take it to pop a page table off the list
then unlock it
and crunch on that page table in parallel
and re-insert it afterwards
i wonder where the other operations we need to deal with tables on the list come in
say in taking a page fault, you find the table you want to work on
you can probably do something like this:
DeferredAgeList: AgeList[NUM_AGES]
take Process.PageTableLock shared
for Age = 0 < NUM_AGES:
while true:
take Process.AgeLists[Age].Lock exclusive
PageTable = pop Process.AgeLists[Age]
release Process.AgeLists[Age].Lock
if PageTable == null:
break
NewAge = age/trim PageTable
push PageTable to DeferredAgeList[NewAge]
end
end
for Age = 0 < NUM_AGES:
take Process.AgeLists[Age].Lock exclusive
while DeferredAgeList[Age] isn't empty:
push (pop DeferredAgeList[Age]) to Process.AgeLists[Age]
release Process.AgeLists[Age].Lock
end
release process.PageTableLock
this way batches the operations which is nice, you can limit how much work you do in each run to some appropriate amount
yeah you also might batch the popping
like grab 2 page tables at a time instead of 1
or something
probably dont want to take too many more than that
4 at most
this is probably also the only time the per-process PageTableLock is ever taken shared anywhere in the kernel
in my idea for how it would work
it would be taken exclusive in the page fault handler to hold the page table structure in place while a transition PTE is set up
this is exactly the area that is playing on my mind now, how aging and general page fault handling intersect
then unlocked across IO, page waits, whatever
so itd be basically analogous to the working set lock of old mintia
the aging is part of the general case of trimming/aging so that's one major family of operations. then there are page faults, which are a bit like another family of operations, and we can probably see things like unmapping a region as being in the same family as page faults, in terms of how it interacts with the page aging process
and figuring out how the two intersect is the more challenging thing
i feel like i have things pretty much figured out
id like to start working on it now but i really need the IO system first
i have been extremely stupid and started working on a language and have been stuck in the doldrums of that before i can start working in earnest on new kernel plans
oh yeah i totally forgot to respond to your dms about that
it's frustrating as now my interest is cycling back to kernel
now u know why i was constantly bitching about being bored while working on jackal
also i might end up just allocating a bitmap of valid PTEs, for each page table page
for 1024 entries it ends up being 1024/8=128 bytes
which increases the memory overhead of the page tables by about 3%
which isnt actually that bad?
Where do you point to that 128 byte block
for 512 entries (on 64 bit) its just 64 bytes
worst case number of scanning iterations for both before finding PTEs to age is 128/4=32 and 64/8=8 respectively
which makes it acceptable
our plans are getting too many brain folds for our own good
this shit is gonna be unimplementable
we should have smelt something dangerous when we started looking at the famous unimplemented or notoriously late kernels of history
multics, mica, ozix
yeah lol
very arrogant of us to presume we can succeed where the giants failed
well we have 20/20 hindsight on an extra like 30-60 years of kernel design that they didnt
so we actually might be able to
but there's more practical experience to draw on, and now the modern kernels are now in most ways more complex than these would've been
yeah, exactly this
yeah we have an overwhelming advantage compared to people who were trailblazing
its not comparable
thats one reason multics is so amazing to me
it's easy to forget sometimes that early cultures had to develop over a very long time to gain the colour concepts they did
it did a ton of shit there was literally zero hindsight on and succeeded on at LEAST 40% of it lol
they start off with a dark/light distinction, red usually appears next (blood), green (fresh vegetation), etc
and no one today appreciates that they name the colours with words that developed through a millenia-long dialectical process where certain needs were practical drivers to repeatedly segment and name parts of the colour space
i like how the word "Asia" comes from the hittite word "assuwa" which referred to specifically a small segment of northwestern anatolia
their backyard basically
what gets me is when you read some stuff which sounds profoundly modern from the 60s, and then youread in the next paragraph some pragmatic consideration about punched card input or whatever
the greeks directly west of anatolia knew of the east as "assuwa" and eventually it became the western word for the entire massive eastern landmass all the way out to japan
which is very silly
behold: the entirety of asia
(as that word referred to in about 1000 BC)
yeah its funny
i was so shocked to see they even had lazy symbol resolution with dynamic linking
it's an interesting word even today because of how culturally bound its meaning is
except they did it with a "linkage exception" rather than a direct call to a dynamic linker
and the dynamic linker was in the "kernel"
i know in america when you hear that someone is an "asian" i think the usual implication is they are from china or some other country in the sinosphere
yeah we imagine china, japan, korea
while here it means first and foremost the subcontinent
maybe southeast asia too if the person is cultured
the middle east being "west asia" would break the brains of most americans lol
and on twitter i often saw american chuds talking about how it's george orwell 1984 oceania that, say, some grooming gang was described as "asian" here
"this is proof that they are trying to trick people by making them think they are chinese or koreans" that sort of thing
the exclusive solo debate phase
there was an NT kernel person and he deleted all his messages and left
mysterious
(Ik someone must've deleted their messages, but now it looks hilarious)
hey do you think CS degrees are still worthwhile or should i be doing a CE degree or something
or perhaps i should do a CS bachelors and CE master's or something
i have a friend who did that
also @hybrid condor the NT people have this cool algorithm (extremely rough pseudocode, their impl does basically the same thing but probably not at all in that exact way) for doing parallelized page aging
i personally didn't think a pure CS degree was worth it for me
i chose a computer engineering faculty
Here are two stories. I studied CS and started working while I was still a student. Being honest, my education didn't prepare me for my low-level work. Even though it was a really highly rated program, I don't think it was worth it, except for the good connections I made. My friend studied EE/CE and to this day believes that the knowledge and thinking he gained then still guide him. I think courses like microprocessor architecture or microcircuit design can be useful.
okay cool
Interesting that the account who deleted their messages and left has existed since the 19th of July this year (9 days ago). Almost as if it was created exclusively for talking NT internals here.
its still semi reasonable for me to pivot to CE so i might do that
no they had a specific question about a linux paper and were tempted into talking about their NT experience by the presence of a lot of in depth NT talk
which apparently they regretted
lmao
i could also abandon my math minor and do a CS+CE double major or something
or CE major math minor
thatd be funny
CE+math double major would also be funny
also doable if im willing to do an extra semester or two and take on the associated extra loan debt
@hybrid condor what do you think. would a CE+Math double major make me a double cool kid or a double loser
im genuinely interested in both of those topics
the more i think about it the more im pretty sure CS is not the correct major and i made a small oopsie
there would be quite little wasted time if i switched rn because most of what ive done lies in the CE/CS overlap
and would work at least as electives for one or the other
because i think math people are smart and its beneficial for abstract thinking to get really good at math
im also interested in math
I need enlightenment to remember what VM looks like in NT
also i wont just be a one trick pony
bryan cantrill is a great exponent of it, he likes to employ people who read maths
bit of diversity in my degree would be good
CS+math is too theoretical and doesnt reflect my low level interests so i feel like CE+math is better
So I was talking to our VM guy the other day, and he goes, seeing FreeBSD's VM after ours is like listening to Canadian when you know French lol
lmao
essentially it started with there's two levels of replacement, the working set level and the global level. a process has a working set, this was historically represented as a FIFO queue of in-core pages within the process; the kernel tries to balance the size of working sets based on the fault patterns of each process. after a page is evicted from every working set to which it belongs, it goes onto either a dirty or a clean queue; dirty pages are gradually written back and make it to the clean queue, and a need for a page can be satisfied from the clean queue. this is slightly simplified but that's the thrust of it
I think it's okay, I myself was kinda interested in studying math, but I think it only makes sense if you like it, otherwise it's pretty useless way
and the actual form of the working set queue was actually more like a sort of ring buffer, not a list. but anyway, you have in this what is called the segmented-FIFO policy
also this is a hot take or even blasphemy to STEM people but like i sincerely believe humanities classes are harder than STEM classes lol
5 upper level STEM courses looks way less scary to me than 5 humanities courses
there are always debates about whether STEM or humanities is harder on like twitter and its mostly STEM majors calling humanities majors perma-broke children playing with crayons and humanities majors calling STEM majors illiterate morons
i lowkey side with the latter
both can be true :^)
but later in NT they started doing aging of the pages within the working set instead, so instead of just trimming working sets to maintain balance, they scanned the working set queue, doing the typical techniques you'll be familiar with - reset the access bit, increment the age if it wasn't set. so the first level of page replacement, the working set level, is now no longer FIFO, but already a closer approximation to LRU
which part of the mm does this?
the really smart people are so curious they know a lot about both
thats how i know im not "really" smart
because i dont know much about humanities
i feel insecure if a humanities friend breaks out philosophy quotes and stuff
kinda
but the traditional working set queue list structure was a poor fit for this, as you have basically a ring buffer full of virtual page numbers + other state you fancy putting there, and when aging, you have to then go look at PTEs spread all over the place
working set trimming
this is how it works now
so (here is what i have guessed, but it seems at this point almost certain this is basically what actually does happen), instead of having this working set list, which was a fantastic data structure for the FIFO replacement approach, you instead thread the pfn db entries (vm_page_t to you) onto as many list heads per process as you have ages
and you do aging by iterating such a list, for each page-table page, you age all PTEs within it, and you use spare bits in the PTE itself to store the age
as part of the working set manager periodic task? i know it does trimming, but i didn't know it ages in parallel
it sounds like there are now multiple working set trimming threads yes
potentially as many as there are cores or some other ratio
i wish there were less of a wall between the two
needs more RE'ing to determine exactly how it is
historically philosophers treated mathematics and mathematicians were philosophers
Impressive
Did you figure this out based on changes in data structures?
also we're not sure if the "age" of the page table (which age list it is threaded onto) is based on the average or oldest age of any pte in the table page
my gut feeling is that its the average
and the average is recalculated during each aging pass
someone who worked on NT spoke of working set aging "now being parallelised" and then when i had a look to see whether there were any clues, the data structures were revealing
https://www.vergiliusproject.com/kernels/x64/windows-11/24h2/_MI_WORKING_SET_PAGE_TABLE_AGE_LINKS this one is the smoking gun
//0x100 bytes (sizeof)
struct _MMWSL_INSTANCE
{
struct _MMPTE* PteResumePoint[3]; //0x0
ULONG LastAccessClearingRemainder; //0x18
ULONG LastAgingRemainder; //0x1c
ULONGLONG LockedEntries; //0x20
struct _MI_WORKING_SET_PAGE_TABLE_AGE_LINKS ActivePageTableLinks[8]; //0x40
};
yeah there are 8 of those
as many as there are ages with the 3 available bits on amd64
the name alone basically tells you everything, and when there are as many of these in each working set structure, as there are ages, then there's no doubt left
MMWSL = memory manager working set list
not to mention it was confirmed by the other NT guy who was in here briefly
without prompting
that one was striking
What's that link?
he was very cryptic about it, i couldn't tell if it was trying to talk simply to people he presumed wouldn't have the foggiest idea what he meant, or whether he was deliberately trying to not say much
that website has structure definitions for various windows kernel versions
from public debug symbols
so it should be clean to look at
i want to learn all of it but im stupid and low energy so learning takes me a long time
its not leaked or anything like that btw, this information is opened by microsoft devs themselves
i feel like a few yrs ago i had a reputation for being really speedy with finishing things, and now im known as being fairly glacial, i think i got majorly depressed since then
jinx
i can blame school but i had school back then too so its probably just the depression, i seem to require like 10x as much "doing literally nothing" time inbetween things im required to do (like school) and this has pushed my mintia time down to nearly zero
as i used to
i was always really razzle-dazzled by this mathematician, lawvere, who was alive until just a year or two ago, who put a great deal of effort into study hegel's dialectical philosophy and he attempted to put it on a rigorous mathematical footing
i feel like being a stem or humanities person (exclusively) and talking condescendingly about the ppl in the other camp is probably a sign of having low self awareness and being insecure
and if such people were as smart as they thought they were theyd respect and be curious about all of it
Just recently, they've actually uncovered a lot more MM data structures, including those related to working sets and aging
struct _MMWSL_INSTANCE {
_MMPTE* PteResumePoint[3];
ULONG LastAccessClearingRemainder;
ULONG LastAgingRemainder;
ULONGLONG LockedEntries;
_MI_WORKING_SET_HARD_LIMIT* HardLimit;
ULONGLONG TrimmedPageCount;
ULONG AttachedThreads;
LONGLONG AddedLeafPages;
_KGATE* ExitOutswapGate;
_MI_WORKING_SET_PAGE_TABLE_AGE_LINKS ActivePageTableLinks[8];
};
struct _MI_WORKING_SET_HARD_LIMIT {
_LIST_ENTRY Links;
_MMSUPPORT_INSTANCE* Vm;
_MI_AGING_FACTORS AgingFactors;
};
struct _MI_AGING_FACTORS {
ULONGLONG AvailablePages[8];
ULONGLONG AverageAvailValue;
ULONG AvailableDeviationPosition;
};
struct _MI_WORKING_SET_CONTROL {
_ETHREAD* WorkingSetThread[2];
_LIST_ENTRY WorkingSetExpansionHead[3];
_RTL_AVL_TREE HardLimitWalkers;
_MI_WORKING_SET_CONFIGURATION Config;
_MI_WORKING_SET_AGING_CONTROL AgeControl;
_MI_WORKING_SET_TRIM_CONTROL TrimControl;
_KEVENT Events[5];
volatile LONG PartitionListLock;
volatile CHAR WorkingSetInitialized;
LONG LowPriorityPassNeeded;
};
struct _MI_WORKING_SET_AGING_CONTROL {
UCHAR Active;
UCHAR PeriodicAccessClearing;
ULONGLONG TrimWhileAgingLowThreshold;
ULONGLONG TrimWhileAgingHighThreshold;
_RTL_AVL_TREE AgeListWalkers;
ULONGLONG AgedPages;
ULONGLONG AgingTime;
ULONGLONG MaximumPtesAgePerSecond;
};
okay so there are two working set trimmer threads
always
_RTL_AVL_TREE AgeListWalkers; is also scary
struct _MI_WORKING_SET_TRIM_CONTROL {
USHORT TrimStamp;
ULONGLONG AvailableDeviationAverage;
ULONGLONG AvailableDeviation[8];
ULONG NumPasses[5];
ULONG Delays;
UCHAR HistogramIndex;
_MI_WORKING_SET_TRIM_HISTOGRAM Histogram[16];
ULONGLONG LowPriorityWsThreshold;
ULONGLONG LastAvailable;
ULONGLONG MinAvailableGoal;
ULONGLONG IdealAvailableGoal;
_MEMORY_PARTITION_TRIM_WHILE_AGING_STATE TrimWhileAgingState;
volatile LONG ManyLowPriorityAgesExist;
UCHAR TrimActive;
};
struct _MI_WORKING_SET_TRIM_HISTOGRAM {
ULONGLONG StartTime;
ULONGLONG ElapsedTime;
ULONGLONG StartAvailablePages[2];
ULONGLONG EndAvailablePages[2];
ULONGLONG TrimGoal;
ULONGLONG PagesTrimmed;
_MI_WS_CLAIM_INFORMATION Claim;
USHORT NumPasses;
USHORT TrimStamp;
MI_TRIM_REASON TrimReason;
};
I found them interesting, so I'll post them here.
they are interesting
@warm pine what do u reckon this is
in _MI_WORKING_SET_AGING_CONTROL
which is in _MI_WORKING_SET_CONTROL
which seems to be global (to a partition object)
@craggy spire can you confirm whether _MI_WORKING_SET_CONTROL is ultimately part of _MI_PARTITION
well an "age list walker" seems clear enough, why you need an AVL tree of them i've got no idea yet
they could've done just [8] of them but they didn't
Yes, this one is per-partition
called it
if we knew the key by which they're entered to the tree then we'd have an answer
so they have a (semi)global AVL tree of "age list walkers"
can you share MI_TRIM_REASON ?
@craggy spire you have a mission
enum MI_TRIM_REASON : LONG {
TrimReasonNone = 0x0000,
TrimReasonLowPages = 0x0001,
TrimReasonSlowStandby = 0x0002,
TrimReasonFastStandby = 0x0003,
TrimReasonForce = 0x0004,
TrimReasonForceSwaps = 0x0005,
TrimReasonExternal = 0x0006,
AgeReasonLowAvailable = 0x0007,
AgeReasonSlowStandby = 0x0008,
AgeReasonFastStandby = 0x0009,
AgeReasonOwedStandby = 0x000a,
AgeReasonLowClaim = 0x000b,
AgeReasonRequiredMinimum = 0x000c,
AgeReasonHardLimit = 0x000d,
TrimReasonHardLimit = 0x000e,
AgeReasonClearAccessed = 0x000f,
ThreadFaultClusterAging = 0x0010,
TrimReasonMaximum = 0x0011,
};
i think something similar happened to me
i wonder if its dopamine addiction or some shit
several times with old mintia i wrote 10-15k lines of debugged code in a single month
ive yet to do that once with mintia2
they can also age some "fault clusters"!
ThreadFaultClusterAging
i feel like the world needs updated version of what makes it page
particular incidents i remember are like november 2021 when i first implemented the fancy vmm functionality (including page swapping) and december 2022 when i knocked out a like 500 item long todo list of improvements for "mintia 0.1"
god only knows what that's to do with, i can only guess that pages, other than the one that you actual faulted on, that are brought in by a clustered pagein, get some kind of special treatment
yeah probably
they might age those more aggressively
when they bring it in they might leave a bit in the PTE that says "brought in by a clustered fault" which is cleared if the A bit is ever seen accessed, but until it is, they age it by like 2 at a time or something
total speculation but would make some sense
i would've probably just brought them in with an initial nonzero age, i wonder what sort of thing they're doing instead
yeah you could do that as well
presumably there is a good reason for not doing it that way and instead doing whatever this is - i would guess something like "so that you can more quickly age out clustered pages by this mechanism, instead of having to trawl through everything in a working set to get to them"
it may be that they found evidence, that having cluster-associated pages treated the same as pages you actually faulted on, is pessimal. that sounds like a reasonable thing to conclude
wouldn't this reduce the number of available ages?
only for pages brought in due to clustering
and only until theyre actually seen accessed
but fadanoid's idea is better
you could just set the initial age to 4 (out of 7)
or something
same effect
@warm pine do people usually reset the age to 0 if they see the A bit set or do they just decrement it or something
classically most algorithms reset to 0!
since they are trying to approximate LRU
so any access = a recent use
true
but this is also why LRU-approximating isn't a god
since pure LRU is not necessarily ideal
my intuition would be you should just decrement it
or halve the age
or something
because just cuz a page was accessed once doesnt mean it will be again any time soon
that's my intuition as well, linear aging, but exponential youthing
ok ill do that if i remember this conversation happened in like 7 months
when i implement this shit
i havent been keeping notes, i used to keep really good notes about old mintia
i had some breakthrough about the IO system that ive been frustrated i forgot about cuz i didnt write it down
cant remember what it was
oh, here is an idea for you btw
i was just taking a look at linux "mglru" to see whether they do reset-to-0 youthing or something else
they are using bloom filters somewhere to decide on whether to scan particular PTEs
if you want to cut down on looking at definitely-zero PTEs but without the cost of a full bitmap for every PTE that's an option
i had to google bloom filter ngl but yeah that makes perfect sense
lol
im pretty sure ive literally done that before but didnt know what it was called
jesus
"core memory"
stone age wikipedia article
i can't resist saying "in-core" to refer to pages in memory
it's just too short and useful
ill probably use a counting bloom filter
that is superior to the bitmap yeah
i can do it simply by having each group of 16 ptes represented by a 4 bit counter or something
i scan them if the count is nonzero
and i can quickly skip big swathes by loading many of these counters at once and checking for zero
that works!
wait this works out to the exact same number of bytes lol
nvm it doesnt
its only 32 bytes because 1024/16 = 64 counters, then divide by 2 (cuz two counters per byte) and its 32 bytes
i did the math wrong
i remember "incore" was a term i was a little mystified by when i first started studying old unix sources when i was like 14 cuz i hadnt heard the term core memory
and it looked like a single word
incore
not in-core
same with "bread" i remember being confused what "bread" was
TERRIBLE name
even after i knew what it did i still couldnt figure out why it was named after a random food item
before i finally read it as "b-read" one day
the worst part is, its not like the unix ppl didnt have uppercase to work with
they did!
it could have been bRead
all along
probably how ill allocate these is in chunks of like 8 or 16 of them at a time and then put them on a per-process free list
which is guarded by the page table lock
so i dont need to take anything else cuz ill already be holding that when i made a new page table
i think this is even faster than the bitmap
or stop being lazy and say buffer_read
i can load 4 bytes at a time which is 4*2=8 counters which is 8*16=128 ptes checked and skipped at once
nah they couldnt have done this
did early C compilers have a character limit?
no
however
the object file format only had 6 character symbols
anything beyond that would just be silently truncated
ah so it would just be called buffer
and it could confuse with buffer_write etc which would also start with buffer
whatd they end up doing for youthing
yeah lol
damn
they should have used XLO instead of a.out
it goes youngest again
inchresting
ill still experiment with halving the age rather than resetting to 0
i have a gut feeling thatll end up being superior
they also do something with pages accessed through FDs (since they don't have a unified approach to page cache access) which i haven't tracked down yet but i suspect those also get rejuvenated to newborns
actually it looks more complicated, there is a PID controller involved
tossing out ALL the age information if it was accessed ONCE seems a little bit silly
zfs went so far as to have their own cache of blocks which has a much more sophisticated policy than this, even knowing that it would require a huge effort to integrate with the memory manager (unfortunately that integration happened only after solaris went proprietary again!)
probably there should also be a "things are very bad." escape hatch flag where if a low memory situation has persisted for longer than X amount of time, despite some aging going on, you just start trimming everything in sight regardless of age
at least starting with the worst average age page tables first
idk how long X should be
probably a few seconds
maybe like 5-10 sec and it stays in "trim everything" mode for 1 second before resetting back to normal aging activity
in fact i think this is functionally necessary to guarantee memory being made eventually
you could conceivably have a bunch of very rude processes accessing a ton of pages in a loop just to stop anything from aging out ever
need to have a way to trim stuff anyway
mintia2 will return to page table scanning after 4 years
for about a month before i implemented working sets that was how i did swapout in old mintia
it also had a synchronous IO system and had an identity mapping and other stuff
it changed sooooo much
now i wonder if i can even build some random old version of old mintia
itll be fun cobbling together an archaic toolchain to try to do that i think
ok lets do it
ill do january 2, 2022 mintia
can this tree refer to walkers as partitions which participate in aging?
idk if its parallelized between multiple partitions
there probably has to be something like that if they can share memory
feels weird seeing this again
i think nanoshell was in its very beginnings back when you were at this stage
then for some reason i sent myself back to the stone age and yearn for mintia2 to be this functional again lol
omg... you can... enter commands...... and it spawns processes.... and they do stuff....
i will need to do several things before i can do that including writing a pty driver
(it'll probably be located directly in the kernel though)
lol current old mintia can still mount this old disk image but it cant run any of the programs
even the ISA changed a lot so im not surprised
what are you missing from that?
mintia2 has no io system or vmm lol
ah
or userspace
just call it a microkernel and put off vmm for later(tm) 
i know exactly what to do, the issue is more mental energy
also the mintia2 plans are way more ambitious
when its done itll be substantially larger and more sophisticated than old mintia
by like 2-3x
which is intimidating because it took me 3 years the first time
you didnt have as much experience in those first 3 years
thats true but mintia2 also has a lot of new stuff
so the sophistication should cancel out with your amount of experience in an ideal world
new to me
i have a suggestion
the vmm will be wayyyy different and the IO system is gonna do plug n play and fancy dirent cache stuff
work on mintia2 instead of talking here
does that count
most of my commits lately have just been from staring at code i already wrote nad going "hm thats slightly inefficient"
the actual next thing i need to do is initializing the iokit-like IO catalog
and probably fleshing that stuff out by just writing a driver
and implementing whatever i need
for driver matching and stuff
this would have been very exciting to me in like late 2022 but rn it sounds dreadfully boring
nowadays its almost good enough just to have designed it in my head and actually implementing it feels boring now
because i know itll work
theres no like excitement
or risk
i did this all before and just doing it with an extra 2 dimensions isnt novel enough to get my brain interested
sadly this has all added up to the result that like
i thought itd take me like a year to write the kernel lol
its been a year and change and i have basically the scheduler done, object manager, Ps, some other tidbits
and thats it
i have at least chiselled away at these things and improved them in the interim
in the same amount of time, old mintia got to an interactive command line with a pty
and (bad, but functioning) page swapping
i will note though
they are almost the same number of lines of code
lmfao
mintia2 Ke is already substantially more sloc than the final version of old mintia
how much simpler was the old mintia's scheduler?
mintia2 Ke:
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Jackal 34 4693 0 11376
-------------------------------------------------------------------------------
SUM: 34 4693 0 11376
-------------------------------------------------------------------------------
mintia Ke:
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Dragonfruit 20 1779 0 5139
C/C++ Header 1 29 1 37
-------------------------------------------------------------------------------
SUM: 21 1808 1 5176
-------------------------------------------------------------------------------
and this was old mintia Ke after the same amount of time working on it as i have mintia2:
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Dragonfruit 16 876 0 2374
-------------------------------------------------------------------------------
SUM: 16 876 0 2374
-------------------------------------------------------------------------------
so, Ke is nearly 5x larger
very very much simpler
it was uniprocessor only and was simple round robin within priorities
my new scheduler is as big as a lot of ppls entire kernel
yeah i'm aware
i've read through some of the code
is your scheduler an original idea or is it based on ULE or something
so i guess i shouldnt be too hard on myself because im roughly on the same track i was on with old mintia in terms of overall debugged code output even though the outwardly visible feature set seems so much smaller
the policies are from ULE mostly
the fact that mintia2 ke is like 75% of the size of boron's entire kernel (19475 loc) is absolutely insane
but the implementation is mine
and my ke is 4992 loc
is the time shared and interactive stuff a ULE thing?
yeah it is
i see, thanks
i used ".h" as the extension for dragonfruit header files for some godforsaken reason
so its recognizing it as that
well yeah thats not the include directory
hey what was that tool that allowed you to see the growth of the locs in your git repo
i forget
it wasnt a tool it was a custom script made by a friend
he didnt give it to me he just used it on my repo
as a test
and showed me the graph
no i could swear i had it too once
years ago
this is the whole OS (old mintia)
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Dragonfruit 316 28407 0 79260
Assembly 13 2734 84 6822
C/C++ Header 149 1747 289 4405
make 75 185 0 297
Jackal 1 23 0 70
-------------------------------------------------------------------------------
SUM: 554 33096 373 90854
-------------------------------------------------------------------------------
god damn
and not incremental improvements
even with old mintia i would have long periods of just like staring at the code and fixing random things
and i actually credited my habit of doing that for its long term stability
and the fact it didnt collapse under its own weight at any point
so its probably a good thing
yeah but you have to make progress
another thing i dislike is that old mintia felt like a fun toy and mintia2 feels like a job
like it actually looks like kernel code now because its pseudo-pascal and not evil rpn vomit
somehow this makes a world of difference in how i feel about working on it
like negatively
aint as fun anymore
also old mintia was like fine to stick with what was okay in 1989 like not having smp and whatever
but now i want it to run on amd64 one day and actually run well
and that breaks the fun larp a bit
someone who followed my project since like 2018 stopped paying attention to it and they said "it just got too professional for my tastes tbh" and that felt very upsetting
because i kind of agree
no but seriously its a problem
it has changed a lot and im unsure i still like what it changed into
as much as i liked it before
i miss dragonfruit and the shitty lua toolchain
and the "limnstation" name which was incredibly bad but at least had more character than "xrstation" does
oh well its probably just rose tinted goggles and nostalgia
too late now in any case
i liked limnstation more but almost compeltely forgot about it
i may have gotten too taken in by the corporate aesthetic of like mid 80s DEC
and all of the zaniness left my project
it is no longer zany
what hath the NT hyperfixation wrought upon LIMNstation...
even the boot firmware
look at the old
vs the new
WHERE DID THE ZANY GO
well its still a little zany cuz you can actually still get to the old menu from the new one lol
the new firmware chain-loads the old firmware to boot aisix and old mintia
you can set a boot argument for aisix that causes its bootloader to drop into a prompt through the old firmware console
and from there you can get to the old firmware prompt and if you type autoboot the old boot menu will appear
thats pretty zany i guess
did i miss a mintia rewrite or what is this about
wdym
in early 2023 i decided to do a new lang and rewrite mintia in it after like 3 yrs of working on old mintia
thats the only rewrite
and only one likely to ever occur
i am having nostalgia about the old mintia
and howt he project has changed in the last like 5 years
I miss the old mintia, straight from the 'Go mintia
Chop up the soul mintia, set on his goals mintia
I hate the new mintia, the bad mood mintia
The always rude mintia, spaz in the news mintia
I miss the sweet mintia, chop up the beats mintia
I got to say, at that time, I'd like to meet mintia
behold the mysterious "brelse" food item ๐ป
I saw that it uses a similar sentinels pattern to other "walkers" in the MM
There were so many changes... But I haven't looked into anything in depth for some time since I've been pretty busy. I just see new things and make some notes
The MM calls MiInsertListSentinel which apparently allows multiple list heads to be inserted under the same key
It looks like it uses the partition ID as the key for sentinel
Weird
By the way, some guy I know studied this behavior more closely and found traces of Autoboost: https://pastebin.com/WEU6QpUv
Yikes, that's scary
Yes, "it uses the partition ID as the key for sentinel" and that's wrong
Working set expansion list goes into MiInsertListSentinel
This seems like a race condition in autoboost where the priority inheritance is not always being removed when the inheritor releases the lock
alternatively, inheritance is being applied to an inheritor who already released the lock
the latter is scarier because it may imply a UAF of the inheritor thread
_LIST_ENTRY WorkingSetExpansionHead[3]; from _MI_WORKING_SET_CONTROL ?
All cases of priority not dropping with Autoboost that I've seen were caused by incorrect handling of releasing locks between threads
Typically this leads to either BSOD or priority issues
including these?
As for these I don't know
When I test in a virtual machine, all priorities seem to revert to their default values, even if they were boosted to real time range. For example:
0xffffa8066ad03143 [Type: void *]
5: kd> g
THREAD PRIORITY CHANGE: 0n12 -> 0n16
# Child-SP RetAddr Call Site
00 ffffb80d`3b2bbed0 fffff806`ebd14566 nt!KiUpdateThreadPriority+0x63
01 ffffb80d`3b2bbf40 fffff806`ebdf7956 nt!KiSetPriorityThread+0x3a6
02 ffffb80d`3b2bc030 fffff806`ebdf699d nt!AutoBoost::KiAbpApplyTargetPriority+0x176
03 ffffb80d`3b2bc0e0 fffff806`ebc13b90 nt!AutoBoost::KiAbpProcessThreadEntries+0x58d
04 ffffb80d`3b2bc170 fffff806`ebc12083 nt!KiAbProcessPreContextSwitch+0x1d0
05 ffffb80d`3b2bc230 fffff806`ebd0dd5b nt!KiSwapThread+0x53
06 ffffb80d`3b2bc340 fffff806`ebd31159 nt!KiCommitThreadWait+0x1eb
07 ffffb80d`3b2bc3e0 fffff806`ebd2ec03 nt!KeWaitForSingleObject+0x4c9
08 ffffb80d`3b2bc4c0 fffff806`ebd2e722 nt!ExpWaitForFastResource+0xb3
09 ffffb80d`3b2bc570 fffff806`ebd2e377 nt!ExpAcquireFastResourceExclusiveSlow+0x242
0a ffffb80d`3b2bc760 fffff806`82731b90 nt!ExAcquireFastResourceExclusive+0x217
0b ffffb80d`3b2bc7c0 fffff806`82730a9f win32kbase!<lambda_63b61c2369133a205197eda5bd671ee7>::<lambda_invoker_cdecl>+0xc0
...
THREAD PRIORITY CHANGE: 0n16 -> 0n10
# Child-SP RetAddr Call Site
00 ffffb80d`3c9676c0 fffff806`ebd1fba0 nt!KiSetPriorityThread+0x246
01 ffffb80d`3c9677b0 fffff806`ebd1edba nt!AutoBoost::KiAbpUnboostThread+0x12c
02 ffffb80d`3c967810 fffff806`827305f5 nt!ExReleaseFastResource+0x39a
...
So, apparently, this is related to NVIDIA software.
possible nvidia driver doing something naughty like releasing a lock from a different context than the one it was taken in
which was asymptomatic until now
doesnt this trigger https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/bug-check-0x162--kernel-auto-boost-invalid-lock-release
youd think so
if they dont bsod this means they do something wrong in their user space component
but how can it get that high real time priority
afaik user threads are limited to 15
actually i can show you something that might be even more funny
windows internals says that the base priority is never lower than the dynamic priority
i wonder what is the case when you want the dynamic priority be lower than the base one
@twilit smelt do you know the meaning of the โdeferred readinessโ thread state in NT?
It's when a thread is on the per processor "deferred ready queue" and is enqueued for real at some later time
I think this is done when the thread is readied under spinlocks it can't actually be put on the ready queues under (out of order or something)
So they wait until later to do it for real, possibly as a soft irq at DISPATCH_LEVEL or something with the same effect
But I don't remember it's been a while so that could be totally wrong
And what are threads in โstandbyโ state?
Those are threads that have been chosen to preempt the running thread on some processor but it hasn't yet actually switched to it
It gets put in a per processor "next thread" field and the processor switches to it asap but until then it remains there in standby state
Only one such thread per processor?
Yeah and if a thread is selected that preempts the next thread then the current next thread is put back on the normal ready queue and replaced
Is how it worked last I checked
Got it
This is just to eliminate some extra work afaik and possibly also has locking reasons
Basically if another processor noticed this thread will preempt your current thread (and decided to enqueue it directly to you) then why should you have to look at your ready queues? It can just directly tell you what to switch to
@warm pine "OpenVMS Galaxy" was an almost identical project to Cellular IRIX it sounds like
"galaxy" comes from VMS's 70s codename "star"
because it was basically running multiple VMS kernel instances on a single machine (so, multiple stars. ie a galaxy. corny) and they could dynamically partition the hardware between them and so on
it was used on big NUMA Alphas
it was done under Compaq and HP
between like '99 and '03
My suspicion was NVIDIA querying thread priority at wrong moment when it's high, then temporarily adjusting it for its own nefarious reasons, then "restoring" it, but by then it was too late and the system had already lowered the priority back. Apparently, they are not aware of Autoboost.
Interesting
I've never seen this in practice, but I discussed it with a kernel dev as a hypothetical scenario
One of his hypothetical scenarios was reverting back priority boosts in the case of priority inheritance. As an alternative to lowering the priority immediately on release or doing lazy lowering, you can temporarily lower the priority even below the base priority in order to keep the low priority thread's effective priority comparatively consistent over time
do you guys think its sane to have a kernel primitive which atomically pins the current thread to the processor it is currently running on, which is to say, it just prevents it from being able to be migrated, and then returns the ID of that processor
and another which unpins the current thread
and this can be used to safely access certain per-cpu structures without disabling preemption
the overhead is taking the thread spinlock to set thread^.Pinned = TRUE and then taking it again to set thread^.Pinned = FALSE basically
the use case i have in mind for this is page frame zeroing
do you have existing thread flags
currently i have one virtual page which is allocated for each cpu where they can map arbitrary page frames for tasks like page zeroing
i have to disable preemption for the duration of using this (i.e. the whole time im zeroing the page frame) and the benefit is that i dont need to do a tlb shootdown, i only need to flush the page on the current core
you can use a byte where each bit is the state of a thread flag if you want to avoid making another byte for the boolean Pinned
i dislike that preemption is disabled for the entire course of page zeroing
so instead id like to atomically pin the thread to the current core and each core has like 4-8 of these virtual pages instead of just 1
each has its own blocking mutex
the thread trylocks each of them and when it gets one it can map it and flush the tlb on the current core only
if it trylocks them all and cant get any of them, it increments a per-core counter and does a blocking lock on whatever mutex that lands on
this might be a wise idea
i already have a Pinned boolean
the reason i dont use bit flags for most of these things in the thread structure is that the locking can get complicated (fine grained locking)
and i dont want to have to use atomics to update them
instinctively it sounds scary to allow preemption while doing this if these virtual pages are a limited resource per-core but then i remembered i have priority inheritance
so actually itd be fine
priority inheritance was such a good investment
now i dont have to fear as much if i make something high-traffic, preemptible
the main thing that was bothering me is that the page zeroing thread is idle priority so, basically anything should be able to preempt it asap
but how its implemented currently i have preemption disabled for each page frame that is zeroed
which is 1000+ cycles
yucky
i also dont want to make these virtual pages for quick mapping of arbitrary page frames a global resource because for one there will be more cache line contention over them
for two ill then need to do a big tlb shootdown sometimes
so it needs to be both per-cpu (for functional, not just heuristic reasons) and also preemptible which seems to necessitate this mechanism
why cant you just zero it directly
do you think im some kind of pleb with an hhdm
oh right 
but dont you have an identity map at least
on 64 bit i could just zero it directly though yes
no
oh that wouldnt work for user threads
also i could have a smaller hhdm which is around specifically to optimize operations like this on 32 bit
and i can use that if the page frame just so happens to lie within the mapped range
so like the low 256-512mb of the phys addr space or something
dont think thats worth it
probably is worth it considering that covers the entire range of possible memory on both fox32 and xrstation
which only support up to 64mb and 256mb respectively
yeah also i dont need to create page tables to map it for regions that dont actually exist
this is my one simple trick for the m68k port of keyronex
i don't need to support a full 4gib of ram
thats really neat because you have tons of free virtual memory
it is perfectly possible to have, but no actual m68k platform has ever allowed this
im reconsidering because the cost of the page tables is 1/1024 all of physical memory. cuz 4096 bytes of page table maps 4mb of physical memory so 4mb / 4096 = 1024
im unsure this would be thaaaaat much faster in practice because the time is most likely to be dominated by the page zeroing itself anyway
yea but you can use it for other stuff as well
and the cost would be yet another thing that consumes a constant percentage of all of phys mem
on top of pfn db
i dont think i have anything else i'd need it for
old mintia only used arbitrary page frame mappings for two things
- page zeroing
- PTE writeback upon page frame reclamation (to indicate the page frame was no longer in memory) when the page was tracked in a process page table, and process attachment wasn't available because at IPLDPC
i still do #1 but i wont do #2
a page's location in the pagefile will never be tracked in process page tables like that
cuz disposable page tables
then how will you track it?
Not in the pagetable
You have an additional structure to do it
Something like an amap
Well each page can go in vastly different places in the page file
objects and amaps
will have page arrays
page tables wont work like they used to, when the last pte is invalidated in a page table itll immediately be destroyed
rather than still containing needed info (like where the pages were put on disk) and just becoming pageable
thats kinda sorta what an amap is yeah
I see
this also makes fork a lot saner to implement
cow fork
because you dont need to convert private pages that are tracked in page tables
into shared pages
you can just share the amap structure
this doesnt lead to the mach object chaining problem because the uvm guy figured out more cleverer ways to deal with the amaps
so its only ever a two-level lookup structure (level 1 is amaps followed by level 2 which is objects, the page fault handler looks them up in that order)
rather than an arbitrary N-level structure like in mach
where object chains just keep getting deeper as you fork
how exactly does that work
read the uvm thesis it goes in depth
how is this different from cpu affinity allowing only a specific cpu
its not, its just optimized for the specific case of "pin me to whatever cpu im running on right now and tell me which one that is"
the codepath for that can be a little shorter than that for general affinity setting
I do this when unwaiting a thread because
I actually don't know why, but the effect is that a thread wakes up on the same core it went to sleep on
Ah, no, I remember why this is important. Apparently I had issues when the wait timer (that handles wait-object timeouts) was bugged because I was cancelling the timer from a different core
ah, yes, and since I use an RB tree instead of a calendar queue or whatever, I end up needing a pointer to the queue that the timer belongs to to remove it...
i just loop locking whatever queue is specified in the timer and checking once i have the lock that its still in that same queue
if not i retry
once i catch it in that same queue its guaranteed not to be able to move anywhere
Ah yeah I can store a backpointer within the timer
If you have one virtual page per CPU, how does this work with multiple tasks zeroing a page pinned to the same processor?
CPU pinning itself is definitely sane and something I believe Linux has.
@twilit smelt I'm kinda curious: what is your view on the MCS spinlock and similar techniques?
There's a single per-cpu page zeroing thread
That puts zeroed pages on a queue
Afaik
I don't get the question
In the new idea I had, there would be multiple such pages per cpu and they'd be guarded by blocking mutex
I was describing how it is currently which is a single virtual page per CPU where the user of it blocks preemption while they're using it
I disliked that because disabling preemption for the whole course of zeroing a page is bleh
Isn't zeroing a page pretty fast tho
Ah, ok.
Sounds like pushlocks
Linux reinventing another NT idea
Except pushlocks are cooler cuz they're blocking locks
And they do what the MCS spinlock does basically
wel for me it's like 1000+ cycles in the case where everything is in cache already (it won't be)
so that's not good
It's probably the like second or third worst case longest preemption blocked area in the kernel
First by far if everything misses in the cache
Why not have a number of virtual pages (N) that are dynamically managed in some pool and is shared across CPUs?
Mapping a page:
- acquire a page index
- disable migration
- map the page
- flush TLB
If it's shared across cpus then you need to do a TLB shootdown IPI
True I guess that would work
Since no other cpus need to see the mapping you made
Cuz migration is disabled
Makes sense
I know that your architecture has a number of 'fixed' TLB entries that are never overwritten by the TLB miss handler. If you have one such TLBe to spare, it is possible to get even smarter and use that so you have one virtual page shared by all tasks (that TLBe can be reloaded similarily to your equivalent to cr3 on task switches) and no locking/etc. for temporarily mapping a page.
(the CPUs never see each other's mapped pages because they map by writing directly into the fixed TLB entry)
the disadvantage of this is of course:
- permanently consumes one TLB entry
- not portable
Ya that is also an interesting option
I could make page zeroing be architecture specific
The main issue I see with that though is that writing the new TLB entry each time I switch threads could be costly
But I only need to do it if the thread actually wants it
do you not need to anyway in the common case, for the page tables?
I don't know that that's always the common case
MCS dates back to the early 90s
pssh doubt it
they used a time machine to steal the NT idea
typical unixoids
Why do you need a per-CPU page zeroing thread
Slow imaginary computer
It ain't per cpu
There's just one
Soaks up idle time zeroing pages so you don't have to do it when you have work later
They used to do it on x86 until it started to have a net negative effect
is it smart to do even with nowadays amounts of compute?
i wanted to do it aswell, it seems like a smart optimization
I read a study that said no
We showed the dfbsd thing where the guy is like "idle page zeroing bad now because cache" to our NT friend and he said "our internal studies disagree completely"
Well idk but the cost of page zeroing is almost nothing these days and u get it in cache which is a bonus
i don't think destroying the caches of hot cpus by zeroing out pages is smart
NT guy told us it's still good
Yeah it's done at idle priority
On Linux they measured it as a net negative
only do it on a hot cpu if needed
Hm, I don't think we do that in the background
Interesting
They at least THINK it's still good
Zeroing out pages is only done at idle priority so...
The other time is when a page is allocated from the free non-zero list
Maybe they cook it with some special sauce
that's what i meant by "only do it when needed"
yes
they might use NT writes for it
(non temporal)
They have a pretty complex infrastructure for zeroing with calibration, various measurements, backoffs...
also probably you should limit it to only zero out about 500kb/sec maximum so you don't take up a lot of bus bandwidth
or be fancier like this
I was told that they spent a long time improving it
As for my use case it's trivially just better. Like it makes a huge (positive) difference
And shows better results
Even if I do it stupid
Probably cuz I'm working with something comparable to the stuff classical NT ran on back in the day
what do they calibrate and measure
Yeah just dumb page zeroing on an idle thread will have a terrible impact on caches
If I disable page zeroing it's like 20% longer to execute some commands to completion, which is visibly apparent with human eyes
So for me it's a no brainer
Kelower worked on this, and I think talked about it somewhere, didn't he?
On a slow computer it definitely outweighs trashing caches
Slow being like really slow
Non temporal writes!
Perhaps NT's special sauce is NT. writes.
I can map a page non cached and get a similar effect
zero through a noncached mapping
Nt store is smart actually idk how I didn't think of that
would i be right in assuming xnu?
But iirc its an extension on x86
Only issue is I'd have to do a dcache shootdown before releasing the zeroed pages for allocation
Why, if its not in the cache?
Because there can be stale dcache data for the pages I zeroes
zeroed
Idk if NT writes are cache coherent on x86 but they aren't for me
Shootdown before zeroing or after?
After
To make sure they actually read zeroes if they allocate and use this page
And not some random stale dcache junk
I could batch it though
Just once every second or so wouldn't be noticeable but also my caches are tiny. If they were big it'd be a much larger tragedy to do this shootdown
why shootdown eagerly instead of just flushing the dcache when you pop a page off the empty queue?
So probably on a modern machine it's not worth it if NT writes aren't automatically invalidating of dcache lines and stuff, if they are then it's a good idea
Yeah maybe
When used excessively they also cause performance issues. I know in NT they have, quote, "some crazy tricks" to not degrade
That would make page faults very slow tho
Since u have to wait for all cores to pop it
oh smp, right
They soak up bus bandwidth but that's why you throttle how many pages you zero
You don't need to zero as quickly as possible
You can do a leisurely few hundred kb per second or so
More if you can tell the other cores are also mostly idle
If they are then you can go full tilt I guess
Sounds like its only beneficial on modern systems if you're extremely careful with it
A sequence number would suffice
That's why Linux opted not to do it
Each core can do its own dcache invalidation if the global sequence number doesn't match the one stashed in its prb when it tries to grab a page from the zero list
Global sequence number is incremented each time a batch of zero pages is released to the list
I can invalidate page frames but that wouldn't be practical here
Linux loves seqnums, they're literally everywhere
You mean in case a large batch has been zeroed?
Yeah
Perhaps
Calc sucks
good luck
wdym? for synchronization?
I'm doing a math minor so I'm stuck with it
All sorts of things
But yes mostly as a growing id for synchronization
seqlock? am i looking into right direction?
maybe they measured different things
they said they solved cache issues with non temporal writes
or something like that
something which I believe is relatively common is that you have two studies for X and Y really similar things that show completely different results, and without knowing both in great detail it is easy to (mistakenly) think that the studies study the same thing.
this effect is amplified because there are many tiny tiny details that can differ, 1% difference here 1% difference there and suddenly you end up with a 20% difference
For example the log ring uses seqnums to synchronize which log messages have been fed to a console and which have not, this is also how /dev/kmsg polling works
Each console stores the last sequence number it has output
Not currently
how did the exam go
It went great! I'm a 4.3 gpa student! Recruiters hmu!
(Could have been better)
Turns out it was a bad idea to take a 1 month version of the calculus 3 class in the middle of rail construction and mental health crisis