#Ultra
1 messages · Page 13 of 1
well i should have clarified it earlier that i don't really mean multiprocessing when i say scheduling
what i mean by that is having the scheduling primitives
That is a VERY different thing
like threads + a way to switch between contexts
No what
for multiple threads u can just allocate a few global variables to test that
What are you talking about, how can you possibly run anything but asm without a stack
see thats what i mean
Even on one core
i forget a simple thing like this and everything falls apart
did u forget that threads need stacks?
perchance
lol
It happens to me. Somtimes I have a hard time getting a concept through my skull
same lol
i do and don't at the same time
Everyone has little brain farts. Dosnt make you less of a dev
wdym? No they don't, why would you want a stack? 
but it does feel terrible when you work on something, you think you have a good understanding of it and suddenly "threads don't need stacks"

i feel like an llm fr
i mean if u havent designed a scheduler in a while or context switch code its kinda easy to forget details
then JW screams at me
If you wanted to start a new kernel project or whatever I would love to see it
but yeah, you can just have a statically allocated stack per thread
ideally you need one stack, for the initial thread
My ISTs are statically allocated so I can trap faults ASAP and crash safely
that's very fair
@proper tulip do u have your kernel code somewhere btw
-# which 
the latest lol
that basically means no, i guess
is it in zig?
and even if i did it wouldn't be worth looking at
honestly i dont remember what i worked on last
probably the c++ thing where i actually tried implementing my own data structures
i still kinda want to make a zig kernel
Wasn’t that rustc?
Honestly same tbh
yeah
But zig and other languages than C look a bit more annoying for kernel code
why?
zig is probably the closest to C
Mostly context switching stuff and some of the crazy build scripts I’ve seen under zig
And the fact I’ve been programming only in C for the past few months and it’s been a while since I’ve touched anything else
oh yeah iirc zig doesnt have any fixed abi?
ok well, context switching in zig is kinda annoying
you basically gotta save all the gprs on every context switch
not just the callee saved regs
tbh thats a nanosecond diff
i guess
especially if you're coming from C
work on zag with me
not necessarily, you can work around it by making the function where context switch happens have a specific calling convention (sysv or win64 or whatever takes your fancy)
in any case come join the zig kernel gang
we have good uacpi bindings too
fn doContextSwitch() callconv(.naked) noreturn {
// ...
}
fn contextSwitch(from: *ThreadContext, to: *const ThreadContext) callconv(.sysv) noreturn {
asm volatile(
\\mov %[from], %%rdi
\\mov %[to], %%rsi
\\call %[doContextSwitch]
: // ...
);
@unreachable();
}```
i guess that would work?
yes
nice
idk I played around with it and figured it out and managed to do it
tbh i could try contributing to zag but i also might try doing my own thing
yep thatd work
port zag to amd64
im just lazy and do the all registers thing cause i cant be assed
so I dont have to do it 
lol
do you have anything other than um?
no
lol wtf
that'll be fun
bro i literally wrote all of it in like two days I cbf to port it😭
that means i'll probably have to come up with all the stuff around so it builds and iso and runs qemu too
no I do that already
i see, based
tbh ai oneshots that stuff
zig build system is goated for shit like this
i decided to not install copilot or any of the ai shit after the reinstall

i think i will become less dependent on it
the literal only external deps that need to be installed to build and run imaginarium from source are zig and qemu
literally fake kernel 💀
i can still use the web browser based interfaces for stuff but you know
it adds friction
yeah
so hopefully i become less inclined to do that over figuring it out myself
just use AI only for the boring parts
^
yeah but when i have all this tooling at my fingertips it's just sometimes too hard not to give in when i'm having a hard time doing something
so instead of slowly and painfully working through it i just ai it away and it's often worse than if i did it myself
i don't have anything against using it as a tool but i just used it for coding and that's bad
phenomenal script quality
ah ok that's related to clang jump tables
Wdym?
actually nvm, strings would be normal rodata so if something from init accesses them its fine
Yep
In which the NT kernel is light-years ahead of Linux.
Linux's internal allocator for big graphics buffers (TTM) stalls constantly (synchronously, in kwin_wayland) trying to allocate physically contiguous memory on a fragmented system. Now, graphics doesn't actually need the big
Another thing about Linux that sucks
The reliance on the stupid kvmalloc api that is spammed everywhere for no good reason
Or the api could be good, but they dont hint internally to alloc pages that it doesn't need to try that hard
i did try to fix a specific part in iptables that caused a giant kvmalloc allocation, but maintainers didnt want it
and whats funny is the allocation size completely depends on user input, so u can DOS the entire system by just uploading large tables in a loop
which would make alloc_pages reclaim all memory and activate compaction threads
which basically brings the system to its knees
all for a useless optimization that was never proven to speed anything up
even if an optimization is proven to speed something up, it's questionable whether it does that for common workloads or only the one that the maintainer cared about
the general idea was fine: let's grab a physically contiguous chuck if it's available, it's all in HHDM which is mapped as 1G pages usually so it should be way faster than vmalloc. But the consequences is every kvmalloc call now struggles potentially for literal minutes before falling back to vmalloc
which not only obliterates page cache but also literally freezes all other running threads to take their pages and migrate them to contiguous memory
well freeze is worst case if it happens to page fault on a in progress page, but it gets hundreds of thousands of IPIs for TLB invalidation per second which essentially means it's barely running at that point
imo relying on compaction for anything other than a background cleanup is stupid
this whole system needs a rewrite imo
contiguous allocation should be in niche places that really benefit from it
btw @sinful river @ornate jasper
i found a bug in my log ring, maybe u also have it
i wasnt able to trigger it via tests but its still possible in theory
it basically needs so many records being allocated at the same time that the tail is pushed all the way from start to finish
feel free
cant believe i finally managed to clean up the history of my giant pr
all thats left now
- verify clanker tests are decent
- add INIT_CODE/DATA everywhere
- verify all commits contain what they should
- verify all commits compile on their own
then i can finally merge it and move on to timer stuff
also find out how to fix that
check_free_after_init_references.py:6: error: Cannot find implementation or library stub for module named "elftools.elf.elffile" [import-not-found]
check_free_after_init_references.py:6: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
check_free_after_init_references.py:7: error: Cannot find implementation or library stub for module named "elftools.elf.relocation" [import-not-found]
Which function is that
it says at the top
I doubt I have copied the error as I barely referenced your code
ah ok good
I found a bug in your implementation
It doesn't support 32 bit


it's not a bug if there's an explicit BUILD_BUG_ON 
also like it technically does if your cpu can do 64-bit atomics
This is what I do
yeah i dont understand how this overflow logic works so i made it more explicit
Also you might want to look at my timer stuff it's actually good now I think
I use a seqlock
i dont think i need it since i dont support 32-bit, i can just use atomics there(?)
I need it because I'm modifying two 64 bit variables
why two?
I have one for offset one for initial count
shouldnt the initial count only be written once?
No I set it when it overflows too
oh
wtf no 486 and early pentium support
ahead of linux on that one
286 16bit protected mode support or I riot
cmpxchg8b is fine then?
gotta pester luk to work on lk-dos then
Lockless log ring for DOS applications
everything is lockless if there is only one cpu 
Well maybe atomicless? You may have multiple threads causing issues?
not on dos :P
What if i make a 8086 smp system tho
then you use the lock prefix :^)
to get atomicity
yes, it causes the LOCK# signal to be asserted for the duration of the instruction iirc
i do also have that bug in mine, thanks for the ping
(probably because i copied from yours
)
uLogRing when
uKernel when
microkernel 🥀
Not micro kernel
Just a kernel you can copy and paste
And post on Reddit

We already have uHDA and uACPI
Just complete it with uPaging
uPMM
Etc etc
Oh yeah there's thr lock prefix on that
I tought it was a 386 addition tbh
i think you can even do a 32-bit atomic load with sth like les/lds
not sure about store though
What is uHDA
uACPI but for Intel HD audio
I don't think so
hmm
Couldn’t you do an atomic load with an add register [mem]
And zeroing the register first?
Ahh wait 16bit
And when looking at the LDS/LES.... instructions it says they will #UD if LOCK is used
atomic load is just mov
no lock necessary as long as it's aligned
Why didn’t I see that 
I always forget that’s atomic tbh
hm well the original 8086 did not have #UD iirc so i wonder what happens there
No because threads don't run at the same time
Unless you consider cli a lock
Does it do a 6502 undefined thing where it does somthing random
Tell that to my old kernel with thread safety issues due to the lack of locks or atomics
Skill issue
i mean the 6502 undefined instructions weren't random, they were deterministic, just unintended since they weren't designed into the pla logic functions
well apparently 0x60-0x6f behaved like 0x70-0x7f? not sure how accurate that is and what about other opcodes
there is one opcode that is maybe useful, salc (al = carry ? ff : 00)
i love the explicit wrapping arithmetic operators in zig theyre so nice
you can in principle implement a lock just based on load/store and a full memory fence
not sure if the 8086 did have a full memory fence though
If there is no lock pin why not do an A20 style thing with an external device
// Return true if `lpos_current` has not yet reached `lpos_next`.
// Uses wrapping subtraction so that:
// - if lpos_current > lpos_next (another CPU already advanced past the target), returns false.
// - if lpos_current == lpos_next (already at target), the -% 1 wraps to usize max, returns false.
// - if the distance exceeds data_size (should never happen), returns false.
I just wrote this if it can be helpful
Probably 90% of my kernel is marked as INIT_{CODE,DATA} now 
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 1] .text PROGBITS ffffffff80000000 001000 021fdd 00 AX 0 0 16
[ 2] .rodata PROGBITS ffffffff80022000 023000 0081b8 00 AMS 0 0 16
[ 5] .data PROGBITS ffffffff80032000 033000 006380 00 WA 0 0 16
[ 6] .free_after_init PROGBITS ffffffff80038380 039380 007c80 00 WAX 0 0 16
0x7C80 bytes i guess
damn that's more than I'd think
well keep in mind the entire initial per-cpu section is in there
why is it marked init?
are you planning on discarding it in favor of something properly allocated later on?
i see
why?
probably so he can reclaim all of init data more easily
I'm thinking its so that you can avoid keeping an offsets array
that plus hhdm is mapped with gigabyte pages, and I have to allocate dynamic buffer for non-bsp CPUs anyway
it's just easier for uniformity
if you contiguously map all per-cpu data at a virtual base you can access any CPU's offset with simple arithmetic
that could be useful
so you dont need a cpu_offsets array
percpu_base + (percpu_end - percpu_start) * index is so based
that would get optimized into a shl
if your section is page aligned :^)
isnt indexing an array literally multiplication
yeah that's what I'm thinking
this would get folded into percpu_base + (i << 12) or something
depending on how big your percpu section is
the problem is the compiler cant know the size
but thats not know at compile time, can it really do an optimization like that?
yes it can
yeah
the symbols are link time
symbols are, code isnt
you know how big the percpu data section is at link time
but at instruction generation time it doesnt lol
mhm hear me out
lto :^)
lto works yes
not ideal i guess
maybe yeah
ksym pass -> percpu pass -> ksym pass 2
build with an undefined percpu size, build with known percpu size, build with kallsyms twice
if u do lto theres one more ksym pass
also can LTO even do that?
oh like that
well I suppose you could make per-cpu size a constant 
tbh accessing per-cpu values of other cpus its not a super common operation
how so?
what common cases are u thinking
but bro you must save like 64 bytes not allocating an array 
putting threads on other cpu's run queues
thats like once a second
on a scale of optimization we're talking about
tahts literally nothing
it would have to be like a hot path on context switch for it to matter
you can still just as easily align the percpu data to a page
and either make sure it doesn't get bigger than that
thats what i do atm anyway yeah
perfect
for example that kinda stuff
just slap an assert in the linker script :^)
why isnt there like a thread->cpu value?
yeah if u wanna do the math trick you have no choice but keep it fixed I think
that would actually work pretty well
wdym
you'd just have to bump it up every now and again
there is that
i mean, realistically it shouldn't get bigger than a page lol
why does that involve a per cpu read?
well see your dmesg
mine: percpu: Embedded 63 pages/cpu
the queues are per-cpu?
can u explain what that code does
percpu: Embedded 87 pages/cpu
yea its kind of a lot ig
also keep in mind linux supports per-module cpu locals + dynamic cpu locals
is ultra gonna have loadable kernel modules?
it tries to find a thread to preempt cur, but cur can be NULL in the case where you just wanna grab a thread and dont care about preemption
yeah ofc, i already have cmake support for it
baller
which is why I cant just do thread->cpu
so like if your runqueue is empty?
no like for stealing threads
yeah but why doesnt it check its own queue first?
stealing a thread from cpu is just select_thread(null, cpu, whatever)
yeah i doubt you can see the perf of a shift vs. mul for this in a userspace benchmark
yield also does that
because you dont care about preempting the current thread you just care about running something else
I suppose a cpu-local case could be useful but i doubt the performance really matters lol
what does yield pass for cpu?
the current cpu
so it reads the current cpu queue as if it was remote?
yes
oh ok
that is what I meant when I said I do remote local accesses
its to keep the code generic
in that case
thats fair i suppose
on arches other than x86 the remote vs local case costs the same
it's not like it's a huge cost either
for such a hot function maybe id still add a separate impl that checks cpu local but idk
it's not really hot
its literally like two indirections, there are more expensive things in there lol
fair
cant wait to daily drive ultra

How many rewrites
how do you plan to do cpulocal variables for modules?
Why not make uShittyXHCIThatOnlyWorksInQEMU
It has quite the ring to it
i should maybe try and get back to that at some point
is that still in a private repo?
Nah uShittyXHCIThatOnlyWorksInQEMU sounds better
but yeah its gonna take a ton of time also because im not really looking for contributors for this project, wanna focus on doing everything myself
since its mostly a learning project
@prime wraith
My project is accepting patches
just write it all down on a mysical scroll and have an owl deliver it to me
also i wanna be the first project to have hw accel on bare metal with own driver
at least on igpus
the trick is to learn and work on stuff that interests you and offload the boring stuff to others 
(@proper tulip)
probably with a generic pcpu_alloc
oops didnt want to actually ping 
@latent geode a simpler approach could be to reserve some space in the initial per-cpu area probably
i think linux does that also
dynamic per-cpu alloc sounds bad to me
wdym
linux has some insanely complex code to support it
I mean you could just allocate sizeof(type)*cpus and access with ptr[cpu] 
yes for most cases you can do that ^
I was about to suggest it
this is how magazines work
yeah thats how u would do it without pcpu_alloc
tho if you wanted to be more efficient youd need to align it to a cache line I think?
or you can allocate a per-cpu map from dynamic per CPU variable IDs to pointers
which means that everything is still local
yeah ofc
I don't think that the dynamic per CPU allocation adds any measurable benefit
Does Linux have it from pre-module days?
when u have expensive engineers sitting there doing nothing
u gotta make them do something
That'd explain why it exists
I was reading some messages about nvme drivers and having queues for each cpu and I think I'll just do this
including in modules
yeah
do that
maybe a nice little abstraction
^ I'd do this
what does that mean
(data*)map[var]
ah so you do it per variable
u just store some handle in the variable yeah
ah
or rather (data*)this_cpu_read(map[var])
whats the TLDR?
i think there is none but it was there very early
whats the skill issue part
wait lol
/*
* Use this to get to a cpu's version of the per-cpu object allocated using
* alloc_percpu. If you want to get "this cpu's version", maybe you want
* to use get_cpu_ptr...
*/
#define per_cpu_ptr(ptr, cpu) \
({ \
struct percpu_data *__p = (struct percpu_data *)~(unsigned long)(ptr); \
(__typeof__(ptr))__p->ptrs[(cpu)]; \
})
so their early per-cpu alloc just did an array of pointers
:P
and the per_cpu_ptr was only applicable to dynamic allocations
that is like korona proposed?
it's worse
since instead of a handle u keep literal void *alloc[num_cpus]
its like an array of arrays
korona's idea is u keep one single handle int alloc;
which is a key into a per-cpu map
ah right
/* Enough to cover all DEFINE_PER_CPUs in kernel, including modules. */
#ifndef PERCPU_ENOUGH_ROOM
#define PERCPU_ENOUGH_ROOM 32768
#endif
iretq idea 
@proper tulip see u could technically have written linux 2.6
hell yeah
youd need to dynamically resize that map right
or what
well
you could limit the number of variables
since its just a map of pointers
u could preallocate a pretty large one
yeah
its very very unlikely you'll have a lot of such dynamic allocations
Unlikely anyone is allocating sum 16384 objects
or u could add an extra indirection and dynamically resize it
thats also a way to do it
yeah but to reallocate it

you need the extra indirection nvm
you have to have something to allocate out of
yeah
u just described how to synchronize the indirection 
i guess for resize u could just rcu no?
with indirection that is
but imo just hardcode to a fixed capacity
say a page per cpu
how?
and then free the old table after synchrnoize
is it?
since it's CPU local data
it's not like every other CPU has a view on that object
rcu is useful when you have a chunk of memory thats viewed concurrently by multiple people
i was thinking u could allocate one table but just give every cpu a different offset
so its faster to write out an allocation
Holy shit
Always enable -Wmissing-prototypes early
I just spent half an hour fixing all of them
i had -Wstrict-prototypes this entire time instead of this one
im fucking dumb
thats why it accumulated
uACPI also had this problem
thank god it compiles with that on at least
what does it do?
it basically catches accidental global symbols
or if u forget to include a header
and use something anyway
nope
This is my current warning collection:
-Wall
-Wextra
-Werror
-Wstrict-prototypes
-Wmissing-prototypes
-Wimplicit-fallthrough
imo warnings shouldnt even exist
there are some people that are against Werror like mint
the argument is that what if the users uses a new toolchain that has more warnings
yeah
yes
but imo that's a C issue overall or something
nah ive had that exact problem with rust
warnings/errors should be the same across standards
warnings get randomly added between compiler releases
yeah that is a good point
I do think for development it's better to have it turned on tho
or do like me and download a new compiler every week
i try to somehow fight that with os-latest runners in github
and it catches new warnings that i dont have every now and again
also it doesnt help that GCC can generate different warnings at different optimization levels
this shit is why zig just straight up doesnt have warnings
yes but infy brought up a good point
this
but might not be an issue with zig because zig is so unstable you kinda have to pin to a version
yeah
That's impossible
Warnings are not even consistent across optimization levels
why would you do that?
The proper setup is to have -Werror in CI but not in your build system
Because if you have it in your build system, it just randomly breaks compilation for your users if they do something that you didn't expect
Like I mention above it catches accidental leaks of internal symbols as well as prototype mismatches due to missing header includes
So its a very useful warning
so what if you DONT want the symbol to be exposed to importers but you DO want it exposed to other compilation units?
holy
Lol
Sorry if it's a flat brain question, but can I build Hyper for multiple arches and bios and uefi at the same time?
seems unlikely
at the very least if you mean in the sense of one ISO containing all of them
Just cp the efi executable onto the drive
Im not sure what you're asking tbh
Oh ok
efi uses different filenames for different arches so you can def put them all on one disk too
you can't do bios for multiple arches on the same disk at once easily but idk why you'd want that when you're almost certainly going to have efi for all of them
what does bios for multiple arches even mean
I don't think a BIOS-style boot mechanism even exists for the architectures you're thinking about
Cuz BIOS just loads some garbage from disk
yeah lol
Without checking much of anything
its not even that
Whereas RISC-V, ARM and probably more use EFI
this style of barely documented intXX interface is a unique thing on x86
BIOS is inherently a x86 thing
yep
on other arches non efi mode would just mean you are the firmware
I had interpreted this as (bios and uefi at the same time) for multiple arches rather than bios and (uefi for multiple arches)
not with things like opensbi no?
opensbi doesnt really offer a disk reading helper no?
That's almost always U-boot so either Linux protocol or shitty U-boot EFI
Lol remember river
In which case the thing on disk is not firmware
OpenSBI doesn't do a lot
bios and efi at the same time is very easy
Certainly no disk I/O
using hybrid iso setups
Usually the most you get is earlycon and a timer
so u can have both uefi and bios boot with el torito
- u can treat the iso as a hard disk and as a cdrom and both will work
hybrid iso is a genius thing honestly
yep
maybe I should have put outer brackets here: ((bios and uefi at the same time) for multiple arches) rather than (bios and (uefi for multiple arches))
lol
because I know bios and uefi at the same time is easy, that message was about how i (probably mis-)interpreted the original question
This is kinda what I meant actually
Bios + amd64 UEFI + aarch64 UEFI
so the latter interpretation then
yeah that works fine
and yeah thats doable on a single disk, the x86_64 efi thing will be named BOOTX64.EFI and the aarch64 one will be BOOTAA64.EFI and the x86 bios stuff will be bios stuff
i do this for my bootloader tests and yeah works fine
Because with limine you can just ask to build everything
i can add an --everything flag if u want lol
The Hyper's build script has {i686,amd64,aarch64}, which kinda confused me
yeah
--arch {i686,amd64,aarch64}
architecture to build the tooolchain for
--platform {bios,uefi}
platform to build the toolchain for
There's also platform
technically u can build uefi in 32bit mode even though thats not something i support atm
Yeah
If the fw impl is sane, you can name them as you want. The fw would let you start your executables in several ways: UEFI Boot Manager "load from file" option, UEFI shell. This is in addition to the most normal - boot option.
Limine does suport it
yeah i know
Because of some cursed windows tablet
yeah its for some tablets
Though I have no such hardware
true, but the special names are standard if youre making a disk and wont have other bootloaders there
btw ee pc doesnt support 64-bit instructions at all right
Well, they are "standard" only for preisntallation scenario. But even then, the good fw would not require this from you. Bad ones oppositely would only load these "standard" named executables. Remember, these conventional names were made for launching the OS loader from a removable storage device in the installation case. Kind of "plug and play". When nothing else has been installed.
yeah, it depends on your use case. i use the standard names because i as a rule refuse to trust firmware if i can avoid it but you shouldnt need them
yeah
it's i686
It might give you funny consequence. Imagine you want to install your OS as the second to a machine. If you keep rely on the "standard names", your OS loader (be it a universal loader or your own) will have hard times with getting started. You just will have no means to start it, since without its own boot option and with the first OS having one, your loader will never be even considered by the fw.
yeah when i start thinking about installed vs portable installation media ill swap to my own names
Also, where does it even put the EFI files? I can't find them
Not only that, but most importantly - consider making your own boot option, by say your setup program.
yeah thats the end goal im just nowhere near there yet
heh, I've got such a helper boot option creator way before I had anything in my OS (except dreams). 😁
loader/uefi/hyper_uefi
it just doesnt rename them to the "official name"
why the hell does vmware prefix this with a tab ☠️
i thought i had a printf bug
\t APIC
i mean technically yes but why the fuck would u do that
i swear vmware is quirkier than most real hardware out there
maybe the devs just wanted to see how far they could push the specs and have some fun 
limine and hyper already have multiple workarounds for the vmware bios lmfao
now this shit as well
yeah they might just be trolling atp
With a fucking \t AND an extra space
They are just trolling
They got bored at the office and did this to laugh
maybe it was JW trying to troll us
“How cursed can we make the BIOS without breaking the operating systems and failing review”
Truee
thats beautiful, I wonder if there's a system that uses a vertical tab 
Isn't the signature supposed to be 4 bytes?
lmao no shit BadgerOS didn't boot in VMWare
Thats OEM/OEM table id
Probably lol
ahh I see
If it works on real hardware and QEMU I couldn’t give two shits about other VM platforms
My OS should boot on VMware but it doesn't do anything yet so
And if anyone complains talk is cheap
I can try if u have an ISO
fair
QEMU is the least-bad VM software anyway
Although you will need latest limine
Oh and il give intel SIMICS a pass because its needed for FRED rn 
QEMU will probably support it eventually too right
I can just install it myself 
It has it for QEMU KVM only if your host supports it
No TCG yet
Bochs “supports it” but its bochs so it fucking sucks
u have to register on their website for that which is annoying
Eventually I might but it's so useless right now it probably boots
This is why I want to get zen6 CPUs soon 
Zen6 has FRED?
It will yes
Intel and AMD actually worked together for once and didn’t fragment x86

that's great
at the same time they made workstation license free so I can live with having a broadcom account
no longer have to sail the high seas
What even is the difference between workstation and player
tbh getting keys for it was as easy as just searching for gists
pmoS=piss me off system?
yeah i cant unsee that
I added nested printf support to make uacpi logging integration nicer 
void uacpi_kernel_log(uacpi_log_level lvl, const uacpi_char *fmt, ...)
{
va_list vlist;
struct nested_printf npf;
va_start(vlist, fmt);
npf.fmt = fmt;
npf.vlist = &vlist;
print(
"%c%cacpi: %pV", LOG_LEVEL_PREFIX_CHAR, uacpi_log_level_to_syslog(lvl),
&npf
);
va_end(vlist);
}
fun fact i actually have nested printf for zig in my kernel's util module atm
What do u use it for?
the main variation i have of it is "aligned" which i use to print prefixed-hex numbers/numbers in parens with padding to width for tables
Why does that require nesting?
theres no format specifier to prefix eg a hex number with 0x in zig and if i do it pad-left with spaces then i get 0x AB instead of 0xAB
Oh
or for stuff in parens id get (1234 ) instead of (1234)
sadly i cant use my thing to do uacpi formatted logs because zig doesnt use printf syntax
(and also zig does log format parsing at comptime anyway theres no infrastructure to do formatting with a runtime format specifier)
couldnt you make bindings for somthing like nanoprintf in zig to get runtine printf etc?
yeah but like
why
would i need to?
Fair? It’s just if you want printf syntax for things that may want it and because I love printf and you can take it away from my cold dead hands
zig formatting is better than printf
zig format syntax is strictly better yeah
instead of %s i do {s} and the rest kinda follows
also because args are passed as an anytype tuple you can refer to args by index or make the args a struct with named fields and use names, tho i forget the exact syntax since i almost never need it
and for alignment you can do like {x:0>16} which pads-left the thing to 16 chars with 0 as an example
if you use the format specified {f} then it calls a format method on whatever the value is
the real benefit over printf is not having to memorize all the stupid bullshit size prefixes that have no reason to exist
does zig have string interpolation
not first class in the language no, that would take an allocator anyway
if the value is known at compile time there is comptimePrint tho
Wow, there's a bug in clang if you have code in multiple program headers the resulting .eh_frame_hdr is unsorted
basically everything in later program headers comes before
so entry 58 onwards is below 0..57
can't you do SORT() in the linker script
Sort of what? It's linker generated data
oh .eh_frame_hdr
😭
i guess im gonna put init_text in .text program header then idk
Oh ok that's what clanker said:
This continuation of the table perfectly isolates the exact bug. It is a **linker bug**, but not necessarily related to program headers. The data you provided clearly reveals an **unsigned sorting bug on signed relative offsets**.
Here is exactly what is happening under the hood.
### The Smoking Gun: raw_pc
The .eh_frame_hdr table doesn't store absolute addresses like 0xffffffff800390c0. Instead, to save space and remain position-independent, it often stores **relative offsets** (usually as signed 32-bit integers, denoted in DWARF as sdata4).
If we look at your raw_pc column, we can calculate the exact base address the table is relative to:
* **Entry 0:** pc = 0xffffffff800390c0, raw_pc = 59880
0xffffffff800390c0 - 59880 = 0xffffffff8002a6d8 (This is our Base Address)
* **Entry 57:** pc = 0xffffffff8003e160, raw_pc = 80520
0xffffffff8003e160 - 80520 = 0xffffffff8002a6d8
* **Entry 58:** pc = 0xffffffff80000000, raw_pc = -173784
0xffffffff8002a6d8 - 173784 = 0xffffffff80000000
### The Bug: Unsigned Sorting
Because your functions from Entry 0 to 57 are located *after* the .eh_frame_hdr base address in memory, their raw_pc offsets are **positive**.
Because your thunks from Entry 58 to 61 (<VIRTUAL_BASE>, <debug_thunk>, etc.) are located at 0xffffffff80000000, which is *before* the .eh_frame_hdr base address in memory, their raw_pc offsets are **negative**.
Whoever generated this table (likely lld or ld.bfd) sorted the entries using an **unsigned comparison** instead of a **signed comparison** on these 32-bit relative offsets:
1. **Entry 57 relative PC:** 80520 (Hex: 0x00013A88)
2. **Entry 58 relative PC:** -173784 (Hex representation: 0xFFFD5928)
i mean it makes sense that way lol
yeah moving .free_after_init before .eh_frame_hdr fixes it
cooking hypervisor detection
I'll detect all other hypervisors for fun i think, even though they have 0 paravirt
static bool bios_version_check(const char *prefix)
{
struct smbios_id bios_version;
if (!smbios_get_id(SMBIOS_ID_BIOS_VERSION, &bios_version))
return false;
return strstr(bios_version.str, prefix) == bios_version.str;
}
static u32 bochs_detect(void)
{
return bios_version_check("Bochs");
}
static u32 vbox_detect(void)
{
return bios_version_check("VirtualBox");
}
HYPERVISOR s_tcg = {
.name = "QEMU+TCG",
.cpuid_signature = "TCGTCGTCGTCG",
.max_function = DEFAULT_MAX_HYPERVISOR_FUNCTION,
};
HYPERVISOR s_bochs = {
.name = "Bochs",
.detect = bochs_detect,
};
HYPERVISOR s_vbox = {
.name = "VirtualBox",
.detect = vbox_detect,
};
Since it's very simple anyway
maybe i could abuse something when they're detected
like xchg bx,bx on bochs etc
what does xchg bx,bx do
isn't int1 supposed to be used for stuff like that?
like the sdm says Hardware vendors may use the INT1 instruction for hardware debug. For that reason, Intel recommends software vendors instead use the INT3 instruction for software breakpoints.
yeah i guess
but u know bochs
and int1 could? be used by the system it’s self no? For debug register things
I’d need to test on QEMU what happens
bochs in general is quite accurate, isn't it? or at least it used to be
Not for x86-64
Fails to even boot Linux astral or my kernel
does it fail due to cpu emulation or device emulation?
nah bochs is quite accurate
its a lot more accurate than tcg qemu
it just doesnt have much implemented
It was failing on Linux with IOAPIC things and mine and astrals due to FPU usage
and it depends a lot on how u configure it
Yet they somehow have FRED despite missing out on a lot of base x86-64 features
Not a bad thing just confusing
probably because its driven by random contributors rather than corporations
like qemu

IIRC I couldn’t even get my kernel to even recognize FRED under bochs
And I compiled with it all from source etc
strange, managarm boots in bochs (enough for me to try its xhci emulation)
did it ever get into SSE code?
since managarm has userspace drivers pretty sure that it does
strange
my kernel worked on bochs the last time I checked
(except that my PCI code is a mess)
(and that didn't work I think )
so like not bochs' skill issue 
@prime wraith i got thoes USBs in the mail
got an ISO for me to test on the eeepc?
ill get u one tmrw, since im in bed rn
kk
thx btw
@prime wraith
cool thanks
which eeepc is this?
i thought there would be just one mode, so looks like i have to do the mint workaround which is assume lower native res with backoff
cool
what are you trying guys?
The video modes on this eeepc
For bootloader workaround stuff
i was deciding how to handle the no EDID + tiny native res case in my bootloader
i have this too if you wanna try, lol
What would have to be done on this laptop in that case
assume even lower native res, pick mode accordingly, then backoff and try an even lower native res etc
i assume 1024x768 atm
So assume very low native res
Attempt to pick modes from the top bottom?
yeah the way the algo works atm:
- get EDID, if no EDID assume 1024x768
- Pick the highest <= native video mode && highest >= user_requested
This will have to be wrapped in a loop where we assume a lower native res each time
or just pick the highest lowest one anyway i guess and ignore user specified constraint
Wait, what did I miss?
Is it single core?
core duo
But not core 2 duo?
lemme go seee
So like p6 level
Because iirc core 2 duo is like a relatively sane x86_64
which is boring
centrino core 2 duo T5250
it has so many ports XD
lemme see if i can find its power supply
I think my parents have latitude e6400 somehwere
gg, it boots
well, no OS yet
wait, it has an IR port???
I've studied linux timekeeping code for like 5 hours today
ill try to maybe go for NO_HZ_FULL right away
or maybe NO_HZ_IDLE is good enough
these are the modes they have
why is it so janky?
under which conditions does NO_HZ_FULL fail to be tickless even when only one thread is running?
did linux use pit periodic mode in some of its early versions, or something?
also, why is this weird boot parameter needed?
probably because of the overheads they mention that it introduces
who knows
why does it introduce overhead?
alarm() or shit like that?
this sounds like a linux skill issue
no idea, I've only read the comment below it
it says there is one
what other options did u have?
wdym
u used to have only pit
oh
pit one shot?
it did use it
eh idk
okay reading the linux docs it seems that this is indeed a skill issue
what do they say
Like you could do RTC timekeeping + PIC one shot
doesnt it have like second resolution lol
At least one non-adaptive-tick CPU must remain online to handle timekeeping tasks in order to ensure that system calls like gettimeofday() returns accurate values on adaptive-tick CPUs. (This is not an issue for CONFIG_NO_HZ_IDLE=y because there are no running user processes to observe slight drifts in clock rate.) Note that this means that your system must have at least two CPUs in order for CONFIG_NO_HZ_FULL=y to do anything for you.
???
Finally, adaptive-ticks CPUs must have their RCU callbacks offloaded. This is covered in the “RCU IMPLICATIONS” section below.
Yeah, but it's precise
???
Like you estimate time with PIT + calibrate against RTC
Managarm doesn't have these issues and it always runs in the equivalent of NO_HZ_FULL on all CPUs
pit overflows every 50ms or so
how
Hm?
But this isn't an issue
I mean I can also just do that, I just haven't bothered disabling idle tick
This is like not an issue
For gettimeofday() equivalent it uses a clock source that's synchronized over all CPUs
I mean if you use ACPI PM or 32 bit HPET you have to poll it periodically
But I think I do it once a second
for RCU, idk how you even run into the problem that linux has
Jiffies?
Managarm runs RCU work in a thread context that is just woken up like any other thread
number of elapsed HZ since boot
its probably talking about updating jiffies
its like the fallback clocksource if you dont have anything high res + its used in code where u dont need super precise timestamps
solution: just don't have that 
u could rely on always having a high res timer ig
or, alternatively, just schedule ticks on one CPU even though it's in tickless mode
instead of preventing the user from setting it to tickless mode
isnt that what it means by non-adaptive-tick CPU
well yeah, but:
By default, no CPU will be an adaptive-ticks CPU. The “nohz_full=” boot parameter specifies the adaptive-ticks CPUs. For example, “nohz_full=1,6-8” says that CPUs 1, 6, 7, and 8 are to be adaptive-ticks CPUs. Note that you are prohibited from marking all of the CPUs as adaptive-tick CPUs: [...]
I think this is done for RT but instead of having nohz_full= you could simply have nohz_force=
and let the kernel decide for all non-forced CPUs
yeah
looks like ill need to make some sort of a timer wheel system
/proc/timer_list is pretty informative
Timer wheel?
The kernel internal API includes a flexible mechanism for requesting that events happen at some [...]
basically this
but recently it was changed to add support for hrtimers https://lwn.net/Articles/646950/
The kernel's 'timer wheel' data structure has served it well for some time; it has changed litt [...]
looks like someone made a nice impl https://25thandclement.com/~william/projects/timeout.c.html#source
reminds me that i got to figure out timer stuff someday
Just use a pairing heap
I got away with playing bad apple with pc speaker audio and decent frame timing with just 1ms timer IRQs incrementing a timer. You can do a half ass job and be fine for a while
My kernel is almost fully tickless idk what Linux is on about

Unironically
my kernel is fully ticking atm and frankly idk if i want to make it be tickless but even with my being shit at reasoning about this type of thing it still seems like a skill issue on linux's part lmao
the "timer wheel" is what's known as a calendar queue in the literature
but ofc linux had to make up its own terminology
My scheduler uses one
lmao
ah actually it seems that the name timer wheel also comes from literature, so that was an unjust comment
the two papers are from '87 and '88 respectively so they probably just discovered this independently
pvclock init... OK
also added msi ext dst capability detection for kvm/bhyve since it was easy
next ill design how that knowledge is going to tie into tsc calibration code
then i guess vmware support since that just straight up gives u the tsc freq
What's that
support for 32k cpus without having to add iommu support
extends the msi dest field
Huh
guest checks this feature bit before using extended destination ID bits in MSI address bits 11-5.
Linux code is truly insane sometimes:
static void ap_calibrate_delay(void)
{
/*
* Calibrate the delay loop and update loops_per_jiffy in cpu_data.
* identify_secondary_cpu() stored a value that is close but not as
* accurate as the value just calculated.
*
* As this is invoked after the TSC synchronization check,
* calibrate_delay_is_known() will skip the calibration routine
* when TSC is synchronized across sockets.
*/
calibrate_delay();
cpu_data(smp_processor_id()).loops_per_jiffy = loops_per_jiffy;
}
This code is executed in parallel by all APs if parallel bringup is enabled for x86.
- calls into generic
calibrate_delay(), which writes the result into a global variable calledloops_per_jiffy - stores it into a per-cpu
loops_per_jiffyvariable - This is not in any way synchronized with other APs, so the global variable is raced with all other APs
- The generic
calibrate_delay()already stores the calibration result into a per-cpu variable anywayper_cpu(cpu_loops_per_jiffy, this_cpu) = lpj;
So they have both: a race and duplicated variables to store the same information
how do i resist the urge to unfuck their code
the only reason this doesnt blow up because most of the time the TSC freq is known/constant so it just reuses the BSP frequency thus making calibrate_delay a no-op
static ALWAYS_INLINE u64 rdtsc(void)
{
u64 low, high;
if (this_cpu_has(X86_FEATURE_RDTSCP)) {
asm volatile("rdtscp" : "=a" (low), "=d" (high) :: "ecx");
} else {
asm volatile("lfence; rdtsc" : "=a" (low), "=d" (high));
}
return (high << 32) | low;
}
Bruh i reeeeeally dont wanna go on the alternatives tangent right now
but this is really ugly also
well not ugly but checking the feature bit every time its read is crazy
just do lfence; rdtsc unconditionally

i wonder what the cycle diff is
if the load + check + branch is cheaper than lfence
since it will be predicted every time probably
the cycle diff is not necessarily relevant, do benchmarks 
korona wont give me a straight answer 
idk it depends on how often your this_cpu_has is an L1/L2/L3 miss etc
you basically cannot know w/o benchmarking lol
fair fair
u32 pvclock_read_start(struct pvclock_vcpu_time_info *info)
{
u32 version;
do {
version = atomic_load_relaxed(&info->version);
} while (version & 1);
barrier_acquire();
return version;
}
u64 pvclock_cycles_to_ns(struct pvclock_vcpu_time_info *info, u64 tsc)
{
u64 host_ns, elapsed_ticks, elapsed_ns;
i8 shift;
u32 mul;
elapsed_ticks = tsc - atomic_load_relaxed(&info->host_tsc_timestamp);
shift = atomic_load_relaxed(&info->guest_tsc_shift);
if (shift < 0)
elapsed_ticks >>= -shift;
else
elapsed_ticks <<= shift;
mul = atomic_load_relaxed(&info->guest_tsc_mul);
elapsed_ns = ((u128)elapsed_ticks * mul) >> 32;
host_ns = atomic_load_relaxed(&info->host_ns_since_boot);
barrier_acquire();
return host_ns + elapsed_ns;
}
u64 pvclock_read_from(struct pvclock_vcpu_time_info *info)
{
u32 version;
u64 ns_since_boot;
do {
version = pvclock_read_start(info);
ns_since_boot = pvclock_cycles_to_ns(info, rdtsc());
} while (atomic_load_relaxed(&info->version) != version);
return ns_since_boot;
}
@carmine token is this correct atomics-wise?
lol seems to work nice
while (true) {
u64 new = pvclock_read() / (1000 * 1000 * 1000);
if (new != seconds) {
seconds = new;
pr_info("seconds since boot: %llu\n", seconds);
}
}
no
pvclock_read_start needs to load-acquire
theres an acquire barrier
oh in the tsc read?
in pvclock_read_start
in the code lol
oh
bruh just load acquire lmao
i mean the two are equivalent on x86
i.e. it does nothing except compiler barriers
yeah but technically it can be a billion loads
we only care about the last one being acquire
it doesnt matter its still a mov
on x86, acquire-load and relaxed-load are the same instruction
same with release-store and relaxed-store
ik ik
this code won't just run on x86 tho
pvclock only exists on x86 iirc
really?
well i don't see a problem with writing the code correctly even tho it will only run on x86
since it describes how to scale tsc to ns yeah
thats my thinking
keep up the same standard you would throughout the rest of the codebase
also the C memory model kinda forces you to anyway
acquire barriers are much more expensive than acquire loads on the one uarch i tested
which admittedly is the insane apple "yeah so LSE makes your code slower now lmao" firestorm uarch
im pretty sure to the point that doing an acquire-load is better than doing an acquire barrier if you only care about one thing? not sure
all of this stuff compiles to no-ops even at O0 and this code only runs on x86 
yeah i mean in general
yeah thats fair i guess
if u care about multiple variables its faster to barrier still?
uhh possibly
i think the apple m1 uarch is so fucked that no its not
what if you enable that reg that does the x86 memory model emulation lol
thats still a thing on modern m chips right
make an aarch64 kernel that relies on loads being acquire 
no
yeah but its a perf hit
is it the same perf hit if u made every load an acquire load with proper aarch64 instructions?
i have no idea
like if u forced the compiler to emit it like that
i think no im pretty sure its only the stores that are more expensive or smth
you can see relaxed vs acquire load is 0 difference
release vs relaxed store is like a 2x hit if contended
damn thats interesting
What if llvm decides to nuke it one day? 
we do a little trolling
I have 0 loc of kernel code ai generated but most of my tests are so I guess im staying here
Also might use it later anyway
For some algo stuff
Like rb trees
Idk, it's such a thin distinction
Like I had copilot on and was pressing tab on some of its suggestions, does it mean that a big chunk of my OS is ai generated?
it may be thin but i do think theres a distinction to be made there tbh
(I'm sorry for hijacking other threads)
but like what would my use of AI classify as
using autocomplete sometimes doesnt make the whole project vibecoded imo
like idk, I just feel bad today about all of this and the announcements
fair
id have preferred tags rather than separate forums esp since discord doesnt allow just moving a thread
No, my project is definitely not not in the vibecoded category, but I can't claim that 0% of code was written by ai

