#Zag
1 messages · Page 3 of 1
I have become everything 13 year-old me would hate
infy what is your solution to the chicken-and-egg pfndb problem
Boot allocator, same as linuxes memblock
right now I do this
Sure I have that but less hacky
but this is tricky because hardware might have lots of small memory entries, and I need to make pfndb entries while mapping them so those pages may get reserved
how do you handle the case where you may be setting up pfndb entries from a memory map entry but also allocating from it (for mapping)
Its fine to have reserved pages in pfndb, in fact Linux does it also
ah
As long as you set the reserved flag etc
yeah, the pfn is just allocated in that case
Pfndb is orthogonal to buddy allocator
For buddy u just walk the memory map after building the pfndb
And build buddies
if you make your pfn freelist doubly linked you can just remove them as you allocate them
its what i do
Or freelist or whatever
and then i use page number for the links with maxint for terminator instead of pointers
to save bits
how do you know which pages the initial allocator pulled tho
like how does it mark them reserved or something if the struct page is not allocated
i create a copy of the memmap entry
and shrink it
as i allocate early pages
and then you iterate over those pfn entries and set them reserved?
well in my case i just only add the leftover ones to the free list
i use the original entry to mark them as normal memory and the shrunken copy to only add to the freelist the still-free ones
Tbh im not sure why you have a problem here
yeah Ithink it's fine actually
Do u not build a freelist after building a pfndb?
worst case i over-allocate
yes
i just thought about it
the worst case is I allocate space for a pfndb entry but then that page is used for mapping it
and that's a small memory waste
Thats the way
IMO all usable memory must be covered
Especially if you use 2m pages
You will have e.g. 128 meg blocks
i dont do that but probably should lmao
I don't think the pages used for the pfndb are considered usable
Usable means reported as such by e820
yeah
yes
but I dont see why you would have pfndb entries for them
also linux larp
Convenience, plus its a fragile micro optimization
That is overshadowed if you use 2m pages anyway
maybe one of these days ill use 2m pages for anything 
U should I think
I think I cna have something like "large page" and "huge page"
per-architecture
and the large page is like 2mb and huge is 1gb on x86 for example
Riscv allows shortcutting all page table levels
same sizes for aarch64 if your base granule size is set to 4k btw, which limine does
So 2 types won't cut it
and you can have arch_largest_page_size
well what's a good architecture-agnostic way of doing it then
and yeah i think aarch64 also allows shortcutting at the 512gb level
Just per page table level shortcut
my plan for imaginarium is to stop at 2m and just not worry about even bigger pages
And the size is implied
ok well I can have "page level" then or something
which is like large page, huge page, and super page
E.g. in Linux u always have 5 levels of arch agnostic page tables, and each arch defines the values of like how much virtual memory a level covers etc
well I would have 4
If u dont want to support la57 etc sure
i actually have 5 levels, the top level has two entries and the second level has 256 entries/table on x86
because on aarch64 theres two completely separate page table base registers one for lowmem one for highmem and this way i can encode that while still supporting x86 on the same abstraction
actually I think the only issue I need to fix is the largest usable entry thing
I dont think the mapping thing is a problem
for that I can just drain free entries one by one
tbh i should do that too
imma do a quick&dirty thing for now then I will figure out a proper abstraction for the page tables
my solution was to just make the page tables recursive tbh
that doesnt work on riscv
whats tricky is making the abstraction powerful enough so I can do cool things but portable enough so it works on any pagetable format hopefully
fwiw NT also relies heavily on being able to do recursive paging
yeah ik thats why i found out about recursive paging in the first place
There are no more buddies. There is only LLFree.
4 KiB sucking is part of why Babaoğlu-Joy page clustering is such a good idea.
There's CASing round the bitmaps in my LLFree.
Telix does LLFree without having a pfndb.
Half of the point of doing Telix is demonstrating that lining up 2 MiB pages is much easier when you only have to line up 32 Babaoğlu-Joy -style allocation units of 64 KiB each instead of 512 allocation units of 4 KiB each.
aarch64 also has a 64 KiB page size it does by marking 16 ptes in a row. Saner architectures have page sizes at every power of 4 or 8 starting from the smallest page size.
Frankly it could well be worth using allocation units of 128 KiB so there's always a properly aligned 64 KiB page no matter the virtual offset of the mappings.
Can you link a paper on that
Yes, it was the technique used during Babaoğlu's & Joy's port of Unix to the VAX released as 3BSD in 1979:
https://cseweb.ucsd.edu/classes/sp03/cse121/unixVM.pdf
The paper is extremely sparse on detail, sadly.
As a first approximation, when you're using a pfndb, you don't need to really care that much about there being a 1:N page:pte relation because each pte holds a refcount to the page structure. From there most of the rest is just adjusting offsets into the page around the rest of the operations so long as you're not too aggressively trying to combat internal fragmentation.
yeah the contiguous pte thing is great
With Babaoğlu-Joy page clustering, the 4 KiB ABİ can be kept while never having 64 KiB allocation units be fragmented & so there's never any danger of allocation failures due to external fragmentation thwarting 64 KiB. In fact, on MIPS, some cores have 1 KiB pages and from there page sizes at every power of 4. With that, 64 KiB allocation units could have promotions depending on mapping up from 1 KiB → 4 KiB → 16 KiB → 64 KiB all within an allocation unit before external fragmentation ever becomes a factor, which is only promoting from 64 KiB → 256 KiB → 1 MiB → 4 MiB → 16 MiB → 64 MiB → 256 MiB and I'm unsure where the largest page size is.
The 1 KiB ABI is vaguely cost-free given pagetable (entirely software) structures being able to be condensed when larger mappings are used.
@weary jetty looking for feedback on this:
For the page frame allocator, you have one global treiber stack of "batches" of 64 pages. Every CPU has its own local set of batches (4 at most) which it allocates from, when they reach the limit of batches, they evict one (the coldest) to the global batch list. On memory pressure, all CPUs are asked to dump their batches to the global queue (which is doable since its lockfree so no issue there)
i think this is fairly straightforward but might be good to get feedback on this
I dont plan on supporting allocations larger than one page size for now
might be interesting to benchmark this with different batch sizes and a lock on the global list
This sounds like stock magazining.
yeah pretty much 
Even if your global allocator structures are lockfree, magazining is still worth doing, because exclusive ownership of the cachelines is still involved even if spinlocks etc. aren't.
I'm curious to see if here the critical section is so small that lockfree doesnt matter
I guess I do get the progress guarantee tho
It matters when there's enough busywait to be observable. The hardware cacheline acquisition doesn't look anywhere near as bad as busywait on an actual CPU does.
also wondering how I could avoid cache misses if I store the linkage in the physical memory itself
I could allocate out-of-line metadata but not sure which and how
mhm could just store the PFN
Hardware cacheline acquisition can still be pretty bad. I debugged a livelock in '04 where a sampling profiler was livelocking. The sampling profiler had an array for code positions with atomic counters in each array position. The atomic counters were stuck in a cacheline bounce so long that another timer tick would come in before it finished.
quick benchmark shows that lockfree is slightly faster
Faster than what?
having a lock over the global batch list
both are way faster than the base freelist without magazines tho
The exclusive cacheline grab is still there to be amortised with magazining.
Typo?
no
do you know what qspinlocks are? I assume you do
might exhibit better cache behavior in this case
You might be describing MCS locks.
qspinlocks are a hyper-optimized version of mcs locks yeah
theres a bit more to it because of the optimization
There shouldn't have been much room to optimise much more. I'll have to look.
it's optimized for size
they keep a 2-bit index of per-cpu MCS nodes
yeah dont they fit the whole thing in 32 bits somehow with qspinlocks?
yes
That kind of defeats the point of trying to avoid the sharing of cachelines.
https://rdmsr.github.io/writing/qspinlocks/ for info about qspinlocks btw
no? all CPUs still wait on their own cache lines
You don't even need SMP for false sharing, but anyway.
mhm is a 32-bit PFN enough
what if it's running on a supercomputer with like 200TB of ram
i did 36 bit but 32 is probably fine in practice
its just most of the bitfields are 36 so that was best for me
lmao
do you align(1) it
should I align(1) it
no?
that was replying to the did i
idk about the should
i basically never store it outside of a bitfield or an enregistered local
i crammed by pfndb entry into 4 64-bit bitfields with a whole lot of union hell too lmao
because for my allocator design I will use PFNs instead of pointers for the chaining
for free pages I need one next pointer, one next batch pointer and one count (byte) field
256 bits is huge
the entry
wait
yeah ok
thats not much
well I think since this is a free state I can cram as much as I want basically
so I could make them like 64 bits
do you use hhdm?
yes
ok so you can save 63 bits over my setup at least
why
why not pfn of pte
because i cant go from pfn to pte that way
i can go from pte to pfn by just reading it
why not
ah right
well ok for now I'll keep them 32 bits but might change in the future
also now zls has just given up
doesnt even launch
my four qwords of the entry are
- pte pointer with lower bit used for locking the entry
- basic metadata (status enum, refcount)
- union of backward link pfn, index in vmem working set for once i make that, and pointer to a waitable io event used when using io to resolve a page fault
- union of forward link pfn and share count (number of usermode processes sharing the page)
i save 16 bytes over windows by using pfns for the linked list instead of pointers and by not deciding to put a whole intrusive avl tree node in there for some fuckin reason idk why nt does that
it continues to make me upset that the nt/windows pfn struct doesnt evenly divide into a page
they surely have a reason
yeah i just dont know what it is 
I could probs use pointers for the list and it wouldnt matter
would be like 20 bytes but thats just for the "free state"
should I do the ultimate meme and use a packed struct for the pagetables 
entries
i use a packed struct for the entries and its pretty nice
so i would recommend that
(i actually use a packed union - one of the union fields is the "valid pte" packed struct and the others are for stuff stored in the ptes that have the present bit set to false)
Why not
just never felt a need for one?
Well u did since u have recursive paging, but u decided to go with that instead ig?
yeah i went with recursive paging instead of an hhdm for getting access to the page tables easily
also when ive had to deal with an hhdm (when i used to use limine) i found it annoying to manage, likely because of having to fight it to convert to my own page tables anyway plus the stuff that limine doesnt put in the hhdm that i needed more than any actual issue with hhdms conceptually, but it put me off on the concept a bit in general
oh yeah @languid canyon what was the MTRR thing again
I see
like I cant map reserved mem over 4 gib or something
You can
Allegedly there is an AMD cpu where mapping random phys addresses as WC caching blows up the cpu
ah I see
Which is a typical hhdm pattern
Usually its not an issue because mtrrs override WC
Well and also cpu doesn't randomly dereference memory on its own
wonder if phys_to_virt makes sense to put in mm
or in platform code
it should be in mm
48 bits is 256 TiB. I'm foggy on whether PiB RAM SSI SMP systems exist yet. Another consideration on pfns is software e.g. Babaoğlu-Joy page size. That trims bits off the low end. With that a 36-bit pfn can be for 64 KiB pages even if the hardware is only doing 4 KiB, for 52-bit i.e. 4 PiB RAM.
If willing to do e.g. 256 KiB pages, the RAM capacity goes up further still even with a 36-bit pfn.
03:38 MESZ here… just a middle-of-the-night trip to the loo, so if ppl take a while to get back to me, I might already be back to sleep.
yeah that makes sense, I'm not looking to abstract over page sizes though. I'm assuming/sticking to 4kib
and if the hardware page size is different idc 
It's an extra 2 bits so 54-bit physical addressing, 16 PiB RAM capacity is enabled with 256 KiB Babaoğlu-Joy page sizes. 1 MiB Babaoğlu-Joy page sizes & it's 56-bit physical addressing for 64 PiB RAM. It gets rough quick but it can buy a couple of bits.
The Babaoğlu-Joy technique isn't that far out. It was trickier for me because I was working both without a pfndb/mem_map and while handling superpaging and with shared pagetables on radix tree hardware pagetable architectures (well, I haven't gone back & done pagetable-free implementations for inverted pagetable & software refill TLB architecture yet). I made it harder for myself in most ways possible.
those numbers are waaay too big to actually worry about in a hobby os imo
but also i dont see how any software technique can expand physical address space beyond what the hardware supports
shrink below the hardware width for software optimization sure but if the hardware only supports 48 bit physical addresses on 4k pages i dont see how you can make it any bigger
That's not what I said it did. I was saying that the software technique could handle the cases of larger physical addressing spaces. Not that it changed hardware magically.
ah ok, my misunderstanding then
Boxen were being made in 2003 with 52-bit physical addressing, so there's real potential for manufacturers to make boxen with larger physical address spaces.
yeah
No the idea is instead of making pfn bigger you make the pages bigged
youd need to change some achitectural stuff at that point compared to current arches, or at least use something the 128-bit ptes that aarch64 allows (but doesnt require atm) but yeah
With that mentality I would just write the shittiest implementations
lol fair
I think it's fun to try to design something fairly scalable
yeah it def can be
I should buy an old xeon server to test scalability
Well, „page” in the sense of kernel allocation unit whilst handling smaller units for memory mapping.
yeah
id call one granule and the other page but then arm comes around and throws the naming backwards from what id instinctively do
Ugh I'm going so slow
Well I've finished the mapping pfndb part I think
And that's all for tonight
I remember IA64 handling a lot of bits of physical addressing & don't remember much else in as much detail. It's likely that many architectures are being cheap with bits of physical addressing.
faster than i was for this stuff
I do think I've done quite a lot for like two weeks
yeah you have
But rn I just need more free time to finish this lol
This long weekend I can hopefully get fireworks on amd64
lack of free time, the bane of any hobby osdever's life
If I were dodging bullets about nomenclature, I'd just say AllocationUnit or allocation_unit for the allocation unit & then TLBMappingGranularity or tlb_mapping_granularity for the TLB mapping granularity or something on that order. Maybe I should rename stuff to be super unambiguous about it all.
And I'm not even doing much school stuff on nights these days
theres actually a debate about that naming going on in zig rn about the std, since it has to describe the memory mapping/page allocator granularity for alignment and stuff in the type system but theyve now added os constants for a system (the psp) that doesnt have an mmu or virtual memory and setting the granularity of the psp os's mmap as the page size return value raised some eyebrows
I'm hoping ppl will say „Wow. Your kernel is awesome. Have a new Blue Card sponsorship and a job.” Perhaps I had better do the Windows system call personality as a higher priority.
can always do windows system call emulation in usermode with a custom ntdll and programs wont be able to tell the difference
very unlikely unless you heavily advertise it honestly
The unlikely can be made to happen. Trick is, the consequences if they don't are existential, so there's no reason to bet on much of anything else.
In a manner of speaking, that's what it looks like in a microkernel.
How and where to advertise might be a question for me to consider, too. If answers are about, I'm interested.
fair though i imagine theres a goodly bit more to the implementation than the real ntdll which is like 90% thin syscall wrappers
the other 10% is split between rtl utility functions and adapters for emulating old removed syscalls using newer ones
I think I can mostly replace inner layers & not worry too much about higher-level details with WINE regression tests that can focus on particular components at particular levels.
Then it mostly comes down to speed.
From there things can march toward replacing more of WINE with bespoke stuff that runs very slightly faster.
pretty much everything goes through ntdll.dll yeah. idk how much you know about how windows syscall stuff actually works
Not a whole lot. Are there also gdi & user libraries?
so the tldr is that theres a set of dlls that are dynamically loaded and linked into every process unconditionally, and the bottom-most is ntdll.dll which is thin syscall wrappers. each higher level thing builds on that, often with the help of IPC stuff for some of the super high level stuff like UI things
so when you use the normal createfile function in kernel32.dll, that fowards to kernelbase.dll which forwards to NtCreateFile in ntdll.dll which invokes a syscall that calls NtCreateFile in the kernel which does arg validation and then calls ZwCreateFile in the kernel which does the actual thing
My microkernel IPC stuff might be closer in semantics to NT than Linux.
By a long longshot, even.
if you use networking with advapi.dll or whatever the thing is called that opens a device object using the open function in ntdll and then does ioctls on it
if you use security functions that does an IPC to csrss.exe which does the security things
the actual ipc mechanisms are windows specific mailbox and named pipe stuff
also sorry security does ipc to lsass.exe
not to csrss.exe
csrss.exe is what you do ipc to if you want to create windows or do ui things
Telix file operations largely look like sendmsg() where message descriptor structures describe a particular file operation. Getting results like recvmsg() with some trickiness surrounding port sets etc. NT has M:N threading & real asynchronous IO, so what it's done for those matches what's already in Telix.
Semantically NT is a closer match to Telix than Linux.
mhm
Is there a decisively identified system call API/ABI for NT or is it sort of endlessly expanding?
the base principle is a requirement for perfect backwards compatibility
but only for the functions exposed by the dlls
So it's endlessly expanding?
the actual internal syscall abi is opaque
they will often change functions to implement old syscalls using new syscalls that internally replace them
so internally not necessarily
but externally yes
Okay, so it's not endlessly expanding but it's also mysterious from not being directly observable.
yes
also most functions are built around taking options structs with a length and/or revision field if they expect to add params later
the main example of a syscall getting replaced with a wrapper around a new syscall is the mapviewoffile series
which has FOUR versions
because it took them three tries to get the interface to satisfaction and then they had to add a numa variant
CXL variant shortly incoming.
probably lmao
it is known that windows supports int 0x20 for interrupts on x86_64 but i believe they use the syscall instruction if available
but when people talk about syscall api for windows theyre generally talking about ntdll
(and friends)
zig's std is down to one (1) call into any dll other than ntdll on windows actually
this is because kernel32.createprocess isnt a thin wrapper and is very notoriously fragile to try and bypass using ntdll
It's notable that file & network IO look very similar (actually identical) in Telix. Asynchronous IO follows as it's network -like all the way through the kernel/microkernel servers.
oh and the dlls are preloaded into memory during early os init and matched by name to prevent a MITM attack from replacing the dlls
Like the Linux vdso things?
(and hash verified on load ofc)
ntdll and friends are literally the same as vdsos yeah
windows just used vdsos for all syscalls since the start
PECOFF instead of ELF but yeah.
well yeah i meant conceptually
if you have windows and install winobj you can actually check the list because windows uses the \KnownDlls object directory to find them, wherein they are a bunch of section (memory mapped file) objects
theres also KnownDlls32 which has the x86_32 versions for backcompat with 32-bit programs on x86. i wouldnt be surprised if they use the same folder for aarch32 stuff when running on aarch64
I very rarely use it.
extremely fair lol
heres what half of the dir looks like on my system, cant show the other half in the same shot for screen and font size reasons
What was his name? Culver?
but yeah ig the tldr of all my ramblings about windows syscalls here is that the design is "what if we used exported symbols from vdsos instead of actually giving an abi"
„Get a byte, get a byte, get a byte byte byte!”
lol
The guy said it in an interview. Designed VMS & NT.
mhm
Dave Cutler
Not sure if it's got the quote https://youtu.be/9K3eMzF6x28
Dave Cutler explains why Microsoft selected Windows as the primary personality for NT.
It's the right guy but the quote isn't in that interview.
2 hrs long, but could potentially have the quote:
https://youtu.be/D_ulAJmYTUo
Series: Windows NT Overview
Title: NT Kernel & Executive Architecture
Speaker: Dave Cutler
Date: 1991/03
Source: https://www.youtube.com/watch?v=UVKKhF9pGpI
Web: https://www.computerhistory.org/collections/catalog/102717746/
Dave Cutler, our lord and savior
ok locking in time
I think for now the pmap interface might just be a generic range-based thing, i.e pmap_map(vaddr_t start, paddr_t start_phys, size_t size, ...) and it will choose on its own the underlying page size based on alignment and size. Eventually I am thinking I can have a generic pmap implementation for radix-tree architectures and a different one for e.g. user-mode linux or ppc.
@long pendant Could you provide any insight on this?
I know you and will talked a bit about that and how that ties in with working set aging or something but I'm not sure what that's about
What do you mean by pmap?
actually you might be able to provide insight too, I'm talking about the architecture-dependent mmu abstraction
pmap is the mach terminology (inherited to the BSDs)
Yes it's good to have non-radix-tree options. I love B+ trees.
not sure this is relevant
The API is great for mapping. I'm all for it.
what do you mean?
Virtually mapping an extent of physical memory is great for things.
yeah like a range-based API?
100% yes.
Virtual ranges aren't so atypical. Physical ranges are the rare part.
yes that is true, I suppose I could ask for an array of physical pages or something, but this might not be fun to work with for e.g identity mapping
something like
pmap_map(vaddr_t start, paddr_t phys[], size_t n, ...)
struct pv_vec {
phys_pfn_t phys_pfn;
virt_pfn_t virt_pfn;
pfn_diff_t nr_pfn;
};
bool pfn_sg_map(struct pv_vec *vec, pvec_nr_t nr);
it was that if you age by scanning PTEs then at that point there are really two sensible options
either the aging logic has to sit in pmap or you can provide a 'cursor' interface for reading/modifying PTEs whose method "next pte" is just "if incrementing the pte address gives an address within the same page table, just do so and return"
I'm using memory objects as the repositories for physical page data. There are obvious complications round COW sharing groups, but it keeps it out of reserved memory & able to have more done with it e.g. extent-based tracking for power of 2 -sized and -aligned physical memory extents. Using WSCLOCK the way I am seems unlikely to respond very directly or satisfactorily to memory pressure.
Plausibly, I need a long-term scheduler (swapper) to swap out whole processes for when there's too much pressure.
I think the vector one might make more sense for general purpose usage
Does that forbid you from disposing of the page tables?
You can swap out pagetables (or discard where fully reconstructible) once things have been cleaned out, usually mostly the swap ptes.
If you have a virtwin() that may need preservation too.
AIX had nutty stuff where all kinds of things needed to be pinned & released for the sake of swapping the kernel, or if not swapping outright, allowing eviction of kernel mappings from the inverted pagetable.
Not really, if you are aging a page then it's mapped currently and it's atypical to dispose of page tables that contain valid page mappings at this time
okay that makes sense
I should've added refcounts to the swap ptes.
well at least my paging code works, now to design a nice API 
working is good
oh yeah the new API works
now a radix-tree architecture only needs:
pub const Impl = struct {
pub const Pte = struct { ... };
// level 0 = smallest page, last = root
pub const levels = [_]Level{
...
};
pub fn activate(root_pa: b.PAddr) void { ... }
pub fn make_table_pte(pa: b.PAddr) Pte { ... }
pub fn make_leaf_pte(pa: b.PAddr, flags: mm.MapFlags, level: usize) Pte { ... }
};
I am thinking I can provide some kind of iterator interface as well
something like map_range(va: usize, pa_iterator: anytype) and basically the iterator would return an address for a given offset or something
so for mapping a range of allocated memory the iterator would return alloc_page()
one thing i would recommend doing earlier than later is figuring out how you want to handle alternate ptes (ones with valid false used for storing metadata like where a swapped-out page is)
yeah
if the valid/present bit in a pte is false the hardware ignores the rest though
so you can store whatever the hell you want there
yes
are you making that per-impl too?
idk maybe
and if so what will the api be for storing stuff there?
not sure
and if not how will you handle it?
idrc about that for now
yeah but itll be hard to kludge it in later if you dont think about it at least a little now
oh my god yes this is the way
nice
pmap only needs to implement
pub fn map_from(self: *Self, va_start: b.VAddr, size: usize, source: anytype) void
and I can implement any policy on top of that
@rocky yoke I think this is similar to managarm's MemoryView right?
I know you mentioned something about how adding arbitrary page sizes to this would work but I forgot
you would call something like next() or something on it which would return a physical offset to map to
so yeah
the problem now is figuring out how to tie in larger page sizes to this, it's a shame I cant find the conversation but I swear you mentioned having a plan for managarm for that
Ah. The plan would be to not only return the physical address from your next() but also the page size (or log2(page size))
but then the next() would be architecture-dependent no?
mhm well I guess this is not really an issue, I can return arch_largest_page_size() or something for identity mapping sources
theres even a zig std function for max and min page sizes that you can (and probably should) override
im not sure where else its used
(this is fine since this is the only place where I would use large pages for now)
It doesn't have to be. The pmap code could still decide to map a 2M page as 512 4k pages
Or similar, a 64K page as 16 4k pages
ah so the page size can be completely arbitrary
yeah. You need some care when interacting with your pfn db but aside from that, it should be possible
but does that mean that e.g for a large identity mapping the "page size" (or rather view/source size) could be possibly like 4+ GiB and it's up to the hardware to decide how to do it
so in the case of an identity-mapping MemoryView/Source it would be something like:
next() -> (pa, size of mapping)
``` and in the mapping function you map and `-= size of mapping` until 0
/// Map a virtual address range to physical addresses allocated on demand.
pub fn map_range_allocating(self: *Self, va: b.VAddr, size: usize, flags: mm.MapFlags) void {
const src = AllocatingSource{
.flags = flags,
};
self.map_from(va, size, src);
}
this kinda stuff is really powerful
really is
always the best option
its just like 100 lines more
the way this works right now is that each CPU can hoard 1 MiB of memory maximum
which should be fine for now
more stuff to check off 
@past dome rate this OOM handling code 
better than mintia trust
ugh well imma do a sidequest to implement vmem
I dont think we're getting SMP this weekend 💀
Do u already have timer calibration?
no
that is platform code and that requires memory allocation
I have all the timekeeping infrastructure already tho
Why does it require allocation?
well idk maybe it doesnt but I do it along with other device initialization code (which includes ACPI and friends)
perhaps I can do it in early init I just need to check my code rq
Well the pm timer is just an io port, same goes for pit etc, tsc is an instruction, idk
yeah no I could totally do it
mhm
now you're making me wonder if i should do memory allocation or that kind of driver work
well I might need allocations for mapping regions, like the RSDP or HPET or whatever
unless I can just assume theyre in the hhdm but I dont think that's sane
Latest limine revisions guarantee that
yes but then I would need to remap it after switching to my own page tables and stuff
And hpet is hardcoded at FD000000 or whatever
id rather set it up like 100ms later when the physical memory stuff is initialized at least
well theres a table for that anyway so if you have acpi tables mapped then its fine
no need to hardcode that
I mean in general it will end up mapped
mhm
If you map the lower 4G
No early log timestamps is kinda meh tho
I think linux very recently added early timestamps
You could say that since its basically x86 abi at this point, moving it will likely break a lot of things
Linux does tsc calibration very early way before memory allocation is available
Even before those patches
Lol
timer (or at least tsc) cal actually a feature i wish limine had, but ig itd only be sane on efi
but efi boot services has a stall function
Yeah thats an interesting feature
i use it to calibrate tsc in the bootloader
and no I'm not using uACPI early table bullshit 😎
Meanie
smh
What are you using then
(I'd rather not import 30k lines of code for finding a timer)
i will use uacpi in the future
but for now I see no point in importing it if I'm just gonna use it for something I'd write like 100 lines for
Only 600 loc out of those is compiled in barebones mode
zuacpi even supports barebones mode if you want to use my bindings for it
@granite kiln thoughts?
keep pinging random people, I realized it brings people into the thread 
Lol
lmao
idk if you realized that too
but like if you ping someone they start showing up on the sidebar on the right
which means its like they're following it lol
Its kind of a strange system honestly
I will figure this out later
for now I can just define like 2 structs and I'll be fine
I dont have enough infrastructure to warrant a uACPI integration this early
oh god
(note that tsc calibration in bootloader is mostly useful for x86 efi - it takes way too much extra work to do it on bios imo unless theres some bios interrupt for stall i dont know about and afaik most other arches provide sane ways to get the frequency without calibration)
Barebones mode needs map/unmap + log
i was actually adding tsc calibration in BIOS for internal use rn in Limine
I cant rely on map/unmap because I will be using it before mm is initialized
Its actually an insane feature I think
U can have very early log timestamps
No op with hhdm
getting tsc frequency from the bootloader slaps, speaking as i now have it on my bootloader
and if limine is already doing the cal...
I think it's cool the bootloader is doing stuff for you but then you kinda end up relying on it
its fine if its your own
(again on efi its ez because you just have a stall function on boot services)
Does bios not have like a stall function
i have no idea
i dont think so bro
I mean it has thousands of functions so its possible
is acpi a PC only thing
Its available on every arch
I was going to put it in platform/pc/acpi
Other than super old ones
but I think it'll just be platform/acpi
Ofc lol
Its first class on itanium and x86
Other arches implement reduced hardware acpi
Like arm and riscv
yeah
it's also kinda first class on arm considering there are proper devices that have acpi
and board support is sometimes sketchy
unless first class = non-reduced hw acpi
Very few but yeah
omfg ZLS please stop crashing 😭
too much to ask
I cant believe the maintainers are getting sponsored to work on zls and it still sucks

youre the one trying to murder it with your project structure
i just realized zls can't figure out the actual return type of a function if it depends on a comptime parameter
which is very disappointing
yeah that is one big downside
well maybe if zig allowed for my shenanigans it'd be another story 
at least it gives you completions for all the possible types, but it's very disappointing
i wonder what cursed stuff you're doing
not much
iirc a bunch of modules that are all mutually dependant and in a shared dir?
I just have a base module because I cant have modules with aliases that are independent
basically I want to be able to do @import("ke") but I cant do that since ke might depend on stuff which depend on it (and modules cant have cyclic dependencies), so it is simpler to have a base module from which you import ke, so @import("base").ke
i still think you should just use @import("root") for that
tl;dr its a way to avoid relative includes
since @import("root") always points to the main file of the root module of the compilation
yes but that breaks on um since the root file is the entry point since it exports main
the root file doesnt have to export main
yes it does
you can use
comptime {
_ = @import("something");
}
to force a file to include in compilation
and main can be anywhere
oh wait for um
yes
mhm
well i know theres some plans eventually to have a compiler server of some sort built into zig and im sure thatll help with lsp stuff its just super low priority for them because theres definitely bigger issues lol
yeah I'm sure
it's not that bad either, Zed still handles it better than emacs somewhat
i use clion lmao
and zed has the vibecoding builtin so I have completions
(theres an lsp plugin for clion and then a zig plugin called zigbrains that builds on top of the lsp plugin with zig features)
I use emacs but for some reason their LSP support didnt like zls or something so I use zed for zig (kate also seems to handle it better, so it's def an emacs thing)
mhm
im a neovim gal if im on cli for editing so i cant help on emacs things lmao
and no the jetbrains stuff isnt slow at all ime
yeah
that
plus im a windows gal anyway i got started doing dev work with a version of visual studio 2005 that i managed to extract from the school computers onto a flash drive to bring home, i feel most comfortable in my IDEs lmao
i am trying clion to see if it handles it better
rip
XD
report it to the zls people ig
how do i explain this lol
nah what I'll do is I'll wait until the project gains more traction and then I will use my aura to get them to fix it
lol
though for real AI inline completion is kinda helping a lot making up for zls
it's wrong a lot of times because zig is newer but it kinda replaces lsp
I'm still doing Linux binary compatibility.
btw be careful with fadt stuff
revision cannot be trusted basically, only length matters
mhm how do you propose I do the ACPI GAS reads architecture-agnostic?
also is there anything but memory and port io really
uacpi_map_gas 
pci config space sometimes
in theory any address space is allowed
oh that reminds me i recently learned theres a standard non-ecam mechanism for pci config space access on arm
its not guaranteed to be supported tho
yeah the view selector thing
bruh I just learned the zig naming convention for enum members is snake_case
I swear I thought it was PascalCase
oh i wasnt even talking about that, theres a hypercall/smc standard for it too
oh thats even worse
errors are PascalCase and theres a lot of arguing about this lmao
I've been using PascalCase
its far more important that youre consistent within your project tbf
yeah true
not that you match up with what std does always
yeah i dont even bother with non-ecam pci config space on arm atm lol
SMC = Sequential Monte Carlo?
secure monitor call
it supports pci config for reboot
as its the only place where it was seen thus far
I mean for GAS
generic helper only supports those
I dont see it
ah ok but that's ad-hoc
yep
I think I will only support mem and IO for now we'll see later
other address spaces only matter for really niche gasses
What are you doing with ACPI here?
I'm just setting up early table traversal and timer initialization
Just early boot ACPI table scanning?
yeah a gas may be arbitrarily large, just like operation region registers
or I can panic if size > 8 
you can lol
this is literally just for early timer stuff
for the pm timer linux assumes io space anyway
note that u cant use the same revision check as linux because linux uses the sanitized acpica version of fadt which guarantees correct revision
or tbh u can just implement sanitization yourself
its just a table that matches revision to length
why cant I rely on it
because firmware often sets bogus revision
why the fuck does the field exist then???
firmware devs smh
its a part of the standard acpi sdt header
ok well I copied the uacpi logic for the revision 
Fair
@fallen bobcat i got kinda inspired by your pmap code and i found one bug in yours while writing mine - the advance function in radix pmap cursor struct stops at the first level that was invalidated, you have to do the loop backwards to find the last that was invalidated, or not stop after the first one that was invalidated
bruh the hpet is not mapped
I will take a look, thanks
which project are you working on?
the 1293812938th attempt at writing a kernel
stop getting inspired and start contributing 
this time (maybe (hopefully)) successfully
why does limine not map the first 4 gib anymore
great question, it kinda annoyed me when i tried to access the lapic
because this is kinda a PITA
and now I cant setup timers without allocations
made me go on a whole ass tangent of implementing page table manipulation and now i'll do the virtual memory allocator
ah I think the fix is just removing the break
not sure why I added that
yeah, in my version i just wrote the loop backwards
which language
so it finds the largest level that changed and just breaks
but not that big of a deal
zig
I think I did
makes more sense to do it the other way
nah, i've always leaned towards zig
my last working kernel was also written in zig, like 3 years ago atp
back when zig the language wasn't so shit
wouldn't call myself an expert but i did find a few things that could be written better
can't tell off the top of my head, i'd have to take a look again
i can do it in a bit
or take a look in general if you'd like, not necessarily just what i found from a quick look at pmap related code
aaaaa infy you got me sidetracked 😠
but in general the code looks pretty clean, you seem to have grasped zig pretty quick
it's not a complicated language, so that may be why
well anything that can be made better id like to know
yeah it's not, i had literally only written like 50 lines of zig before this project lol
Did the lang become worse over time?
I thought newer limine guaranteed that all ACPI stuff is mapped
the HPET table is mapped
the HPET itself isn't "ACPI stuff", just like the IO APIC and local APICs for example
Oh ok
though admittedly it's annoying to deal with them not being mapped
I mean yeah its kind of pointless to have the table mapped if you cant use the hpet anyway etc
well no
the idea to have the ACPI tables mapped wasn't about the HPET or the APICs or other similar MMIO devices that could be exposed via ACPI
that's just generic MMIO which we do not map by default in the HHDM
the idea to have the tables mapped was mostly something done in response to complaints by @rocky yoke
yeah
nah i get it, im just saying it could be extended a bit further
for example on systems where you need the tables to discover serial
mapping the MMIO to the hddm doesn't make sense though
yeah
surely it does
hpet is x86 specific anyway and u have mtrrs
plus limine could just specify the correct caching mode
the framebuffer is already mapped to the hhdm isnt it
the framebuffer is basically the only exception in terms of mmio
because early output
anyway its fine if u wanna keep it that way
i just dont see anything fundamentally wrong about mapping hpet and lapic etc
the fundamentally wrong part is: what is "etc"?
ioapic 
is that it?
probably
what about on !x86?
seems like a slippery slope
yeah
eventually the request will be "map ALL mmio"
its not a slippery slope if its convenient for korona 
which is not really feasible
well mapping ACPI isn't the same as mapping MMIO
same with the SMBIOS tables
no, the tables need to be mapped in some cases, otherwise the system is unusable
and mapping hpet is not the same as mapping all mmio
why not? is it not MMIO? like sure, it's on the system bus, not PCI, but why does that matter?
that distinction may not even exist on !x86
anyway it's not really worth fighting about, i dont exactly gain anything from this even if u did do it
so w/e
i am not fighting
i am trying to understand your rationale
so if i am convinced about it, it is something that could be changed
you already map stuff pointed to by other fields in other tables like DSDT in the fadt
this is just another field in another table
0 diff imo
whats so special about mmio that makes it unmappable
for one you would have to special case the HPET and APIC pages, because you cannot say "tables and all memory that they reference, MMIO or not", that's way too like uh, broad?
not to mention that for that you also need to know how to interpret every ACPI table, which is not forwards compatible
like for other arches, whats there to map from acpi tables other than the interrupt controllers
that's a pre-existing problem no? Since e.g. fadt can add new fields and limine wont map them
Limine explicitly lists what ACPI tables are mapped so it's not really a concern right now, it's about going "every table pointing to some memory that isn't in the tables will see that memory mapped to the HHDM" that is the issue
again, unless you special case HPET and APIC tables as "Limine knows about these and will map them"
well as you mentioned you can easily limit the scope in the spec just like you did already
yeah
or maybe the tsc frequency request could be an even better solution idk
which, sure, i guess it's convenient, but is there actually a case where it is strictly necessary to have the HPET/APIC mapped before having page tables code and allocator code?
hpet for tsc calib, apic not really i guess since limine provides all the info like ids via other means
yeah but do you need the TSC calibration either?
it's nice for timestamps
sure, but i am talking about, as i said, strictly necessary things, like sure, having the TSC frequency would be nice, but that's perhaps better served by its own request as you said
what was the original reason that managarm needed all tables pre mapped?
but beyond the timestamps, which are useful but not strictly necessary, what else?
fun fact, i don't actually remember it that well
korona mentioned tables to have serial output
ah
which i guess is more essential, for debugging and whatnot, than anything else discussed here
but we can cc @rocky yoke about it. what else was needed from the tables?
technically you may need early delays for device stuff as well
like timeouts or polling etc
true
and detecting CPUs early is also useful to determine the kernel memory layout etc
plus Limine already guarantees that DTBs are mapped on !ACPI
so having ACPI tables mapped mirrors that
☠️
tbh hpet is just one alternative, u ofc have pit and the pm timer also which dont require mapping
yeah
well technically the PM timer is GAS but w/e
GAS?
that is true
but in any case having a time stamp counter frequency request sounds useful, also on non-x86
yep
x86 is the only arch where it needs to be calibrated though
itanium? 
the big ones for acpi to have early are srat, madt, and dbg2 yeah
and ig dbgp which is the older shittier version of dbg2
I actually have an (x86) laptop where the serial port is only discoverable by DBG2 and not in the aml
(probably because the serial port is also only accessible by the mobo and doesn't have an external connector on that laptop but still)
oooh I have a realized I can use the hhdm in the slab allocator for small objects, therefore segment allocation in vmem can be done through a slab cache

this solves a bunch of circular dependencies
yup
vmem will only be used by large slabs
thats effectively how i do it to but more complex and ive not gotten through to true vmem stuff yet
damn
yeah github rounds up to whatever unit its using
tbh i kinda dont like zig allocators
it's annoying to pass them around when i just wanna test something
oh well there's std.debug.gpa or whatever
just make a global for shit like that
std.heap.DebugAllocator slaps for debugging
no I mean I'm writing it in userspace and for allocating segments I just wanna use malloc
ah
the std.debug allocator is meant for debug info/unwind related allocations and is an arena
i wouldnt use it for anything else even testing
yeah ig but also its nice swapping off the (temporary) testing related allocator for things
oh yeah if its structs storing an allocator in the struct is good
also I think I will use rbtrees instead of hashtables
kinda dont wanna mess with resizing hash tables
if you wanna use avl trees instead of rb trees i got one in zig with unit tests you can steal if needed
alrighty, just figured id offer in case you didnt lol
thats extremely fair and im the same way so idk why i keep thinking others wouldnt be lmao
link instead of blindly copy pasting, I follow the same logic but it's not 1:1
like the linux code
every few months i get this itch to write an acpi library in pure zig instead of using uacpi for mostly that same sort of reason and then i squash that urge because holy shit that sounds like agony
we could do something collaborative
like i wouldnt mind working on an acpi library but I dont want to spend a shitton of time doing it, so maybe doing it as a collaborative thing would be better
yeah same
yeah
I have been thinking about this btw
but maybe in the future
idk
but zig has good c integration and uacpi just works out of the box so theres so little reason to work on my own acpi lib
yeah idk
if i did do it id def steal infy's microcode setup for aml
at least in concept
Would be fun to see
i legit would never have thought of implementing microcode to run aml ops but once you see it its so obvious
the only reason for this is NIH
infy you can flex cuz you wrote uacpi and hyper
I cant
(for the bootloader I might do an efistub as an option eventually though)
I spent like 3 years on that tho 💀
True
Tbh thats a decent idea
Just support efi only
Make a simple protocol for your kernel
Its nice I think
U can even use the simple fs protocol
yep
So no need for fat drivers
i was like 80% through a fat driver when i decided to ditch non-efi booting and then i just dumped the fat code in a folder for later and use simple fs protocol for everything
its actually an eventually™ goal of mine to get an implementation of zig's std.Io for uefi using it so you can just use the std
at least for the threaded std.Io in single_threaded mode
which is the only case that makes any sense on efi
efistub or real bootloader tho
Also I have a great bootloader name
Zap
btw this is why i got fat up so fast for my kernel @languid canyon, i literally had 80% of a discarded fat driver for the bootloader i could adapt instead of having to do it from scratch lol
oh that name slaps
Yes I have written bootloaders before
I wrote the brutal one
Zag is still funnier tho
doesnt strike me as a good kernel name but def a good loader name
Zag booted by Zap written in Zig
I wouldn't do a stub thats just not my thing
Makes sense
Why dont you use it?
It's in C and I wrote it like 3 years ago
I will probably reuse code tho
And it's very tied to brutal obviously
Didn't know you were related to brutal os
The authors speak French and so do I so I know them
Ooh
B+ trees are the way to go.
It's really about cacheline and page touches.
The goal is to have an intrusive alternative to a hashmap
Well, not rely on allocations
If you're allocation-constrained, then you're largely doomed to suffer the costs of the cache & TLB misses.
I've got some intrusive things due to allocation constraints like for turnstiles.
Multiple and more than I'd like.
In general I like to minimize memory usage too unless the cache behavior is awful
I think the non-intrusive data structures are mostly non-deterministic slight improvements on overall memory usage.
If you could expand on this that would be great 🥺
You don't have to
oh hm
But ideally I try keeping the code high quality
few things i found is places where you could use @intCast insteadf of @truncate
I use truncate where I'd write a mask manually and intcast
i'd @intCast when putting the physical address into a pte
same
to make sure the user didn't pass in a bullshit physical address
intcast there is a noop on its own and just asserts on safe modes
whereas truncate doesnt assert and still has to do things on unsafe modes
truncate masks it no?
ah unless you mean like intCast is = to trusting the user
And masking it is hiding the problem
basically
truncate is fine if you do something like
const high: u32 = @truncate(u64_value >> 32);
there is nothing to assert, obviously
I think vmem works 
show code
it's a simplified implementation because I dont use a bunch of the stuff
like importing arenas
and phase and nocross
.rs lol
yes no zig highlighting
@lavish meteor I beat you to the punch

ok to be fair I mostly rewrote the code I already had but made it simpler and better
one possible improvement would be having a bunch of rbtrees instead of one (indexed by hash) but this is better than the fixed 16 entry hashtable + linked lists I had lol
whats the tldr of vmem
generic resource allocator where u have a bunch of power-of-two freelists
mhm actually I might want quantum caching in the future too
fyi i think zig enums already use the smallest possible int type that can fit all the tags, so you don't need to specify one yourself
ok thanks
also whats neat is I implemented next fit so I can allocate pids using that too
im def interested in taking a look at this, i need a general "resource number" allocator at some point (plus vmem in general) and still just kinda dont have a clue how to do it
well it's called vmem but it's a generic resource allocator
yeah
over any interval of integers
just use an rbtree 
annoying missing zig feature is no self referential globals
wdym by self-referential?
const nil = Node{
.left = &nil,
.right = &nil,
.parent = .init(&nil, 0),
};
I cant do this
you can
so I have to have an ugly init function to make it work
no
im p sure theres a way
it literally worked for me last time i did it
there were some type resolution changes kinda recently that fixed a bunch of circular reference issues that could be related tbf
give it a type
const nil: Node = .{
.left = &nil,
.right = &nil,
.parent =.init(&nil, 0),
};```
wtf does it change
in the other case you're refering to something that hasn't been evaluated yet
so it has no type
that is kinda weird
in the other case it doesn't have a value yet but it has a type
(also thing: T = .{ is the preferred syntax and theyre trying to get rid of the other version partly because of issues like this)
the semi-technical thing is that the Node{...} expression has to be fully evaluated
i prefer having an explicit type anyway so i never really ran into it :^)
before it has a type
mainy because the types are used a lot for stuff like casts
so you can do const x: u32 = @truncate(...); or whatever without using @as
whereas if you do thing: Node = ... that has type Node when the initializer is being evaluated
np