#Nyaux
1 messages ยท Page 34 of 1
so for scheduler designs
and smp
hmmmm
lets start by giving each core some per cpu data
now
should that be in arch/
okay each per cpu data should contain
the runqueue
of threads
or like
idk what do yall think
what other things should i store other then the cur thread and whatever
cause like
what about other archiectures
if i wanted to store something in per cpu data right
like a lapic id
but thats only for x86
how should this be abstracted
how I do it is just have arch specific headers and then I add the arch include directory to the compiler search path conditionally based on the arch
wha
i have an idea
struct per_cpu_data {
thread_t *cur;
#if defined(x86_64)
// arch spefic shit
#endif
}
src/arch/x86/include/arch/arch_cpu.hpp: ```cpp
struct ArchCpu {
uint32_t lapic_id;
};
src/arch/aarch64/include/arch/arch_cpu.hpp: ```cpp
struct ArchCpu {
uint32_t affinity;
};
``` and then I add either `src/arch/x86/include` or `src/arch/aarch64/include` to the compiler include search dir with `-I` based on the arch, then everywhere else I can just do `#include "arch/arch_cpu.hpp"` and its going to include the arch specific header
how could u do that
the build system should know what arch you compile for, you don't want to compile all files for all architectures anyway
you can use a variable
im js gonna do this
i dont know how to write makefiles
i stole it from limine c template lol
ARCH ?= x86_64
CFLAGS = -I src/arch/$(ARCH)/include
``` or whatever
you could also do #if defined(__x86_64__) #include "arch/x86_64/..." #elif defined(...) ... #endif
oh good point
this is easier
well adding the include paths is easiest imo
also re. the per-cpu data
i'd do ```c
struct generic_percpu_data {
struct arch_specific_percpu_data arch;
/* ... generic stuff ... */
};
so that arch-specific stuff is at fixed offsets if you need to access it from assembly or whatever (e.g. a self pointer, syscall stack pointer etc)
wdym
oh yeah good point
i might do it like that then
? what's unclear
nothing sorry
im gonna have to store the user and kernel stack in the thread struct itself
it can be
struct generic_percpu_data {
struct arch_specific_percpu_data arch;
thread_t *cur;
};
typedef struct {
uint64_t kernel_stack_ptr;
uint64_t user_stack_ptr;
struct arch_specific_thread_data arch;
pagemap_t *cur;
process_t *cur_process
thread_t *next;
// etc etc
} thread_t;
arch_specific_thread_data can js store the stackframe for the thread or wtv
and how does your syscall handler then load the stack pointer
that would be kinda complicated
but it might go something like this
hold on
syscall_entry:
mov rsp, [gs:0]
; this loaded the kernel stack ptr
i made them both pointers
the arch structs
to which i can just allocate on ze heap
tell me if theres anything wrong with this
why the double-indirection
then what would u do to be better
i'd just put the kernel syscall stack pointer in the arch specific struct
and update it when resuming a thread
why tho
so that the layout of thread_t isn't depended upon by the assembly code
no
so no pointer okay
and ud have the first thing in arch_specific_percpu_data be the kernel_stack_ptr
?
so would this work?
well you're discarding the user stack pointer
the seocnd thing would be the user_stack_ptr
wait
yea
yeah but you have to save the user's stack pointer on entry in the syscall handler
well swapgs before but yeah
yea ofc
and im guessing ill have to update to the current threads kernel stack ptr and user stack ptr as well?
when u schedule
yeah
okay
I kinda do this, but I also include arch-specific headers
Why do you need to update user stack though
to the current threads user stack?
Yes (?)
why wouldnt i
It's part of the user registers
You don't update rax when switching tasks
Why would you update rsp
Just push it onto the kernel's stack and that's it
No need to be saving it in the CPU-local kernel data
oh u mean in rsp in the stackframe of the thread
i have the same structure
Then
When you have an interrupt, the CPU pushes RIP and stack address
With syscall, I think the old RIP is saved to r11
Or something like than
ohhh okay
Just store it in the same place as when you have interrupts
now im very confused
And when you return from syscall, take the value from the stack and put it into the rsp
yea i know about this
so if old rip is saved in r11
i have to?
what exactly
Push it onto the kernel's stack
okay
Ok I'm not sure about r11
Oh that's not how sysenter works
i should have just the value right?
No
oh
Ugh ```x86asm
syscall_entry:
swapgs
Userspace passes %rip in %r9. %rsp gets saved to %rcx
movq %rsp, %r10
movq %gs:0, %rsp
Construct what would be pushed by CPU
pushq $USER_SS
pushq %r9
pushq $USER_CS
pushq %r11
pushq %rcx
Save registers
...
movq %rsp, %rdi
call syscall_stuff_in_c
Restore registers
...
swapgs
movq (%rsp), %rcx
movq 16(%rsp), %r11
movq 24(%rsp), %rsp
sysretq
This way you can also be compatible with syscalls by int
Idk if this works, I've made it up just now
(my kernel does some cursed stuff)
is this mov dest, src or the other way around
Yes
that's it
okay when a syscall happens, rcx has rip, r11 has rflags
Hmm
This is sysenter then
yea im supporting syscall
not the intel only sysenter
My kernel supports both 
we must save the rsp immediately to somewhere in gs
then
You don't have to
then what can i do then
You have a lot of scratch registers, you can โจ trash โจ one of them
Although you can definetely do that
this is better
But you don't need to be switching it with processes
yea because its just a tempory thing
only syscall is really gonna be using it
to store and read really quickly
theres no need to have it switched yea
anyways after u save the rsp switch to a kernel stack then push rcx and rflags in a way that would be similar to when a cpu recieves an interrupt what the stack would look like. push all registers to build a interrupt stack frame, then do syscall stuff. then pop all registers, pop rcx and rflags as expected as well as the other stuff. swapgs done
Yes
okay i understand now
okay i know what to do but first i must shower
then scheduler
typedef struct {
pagemap_t *cur_map;
} process_t;
typedef struct {
struct StackFrame *frame;
} arch_thread_stuff;
typedef struct {
process_t *proc;
arch_thread_stuff arch;
uint64_t kernel_stack_ptr;
thread_t *next;
} thread_t;
typedef struct {
uint64_t syscall_stack_ptr_tmp;
uint64_t kernel_stack_ptr; // this has to get updated when switching threads. its just indirection, so its easier for the cpu to get the kernel stack for syscall
// other things like lapic id etcetc
} arch_cpu_data;
typedef struct {
arch_cpu_data arch;
thread_t *run_queue;
} per_cpu_data;
let me know if theres anything else i should change
before i start working on this
give me feedback
like does it make sense
user_stack_ptr?
Looks fine idk
okay good
Have fun with smp 
yea each cpu has its seperate run queues
what problem am i gonna run into
lol
Load balancing
yea good point but no load balancing for now
might also need to put a lock on process
but
lets just get some barebones scheduler working
So all processes run on the same core?
So a giant race condition
When you access queues of other CPUs
oh no i wont be doing that
js in main on the bsp
before i start scheduling
like FOR NOW
til i figure out how load balancing works
cause i know nothing
also
t
just replying to them so i dont forget
threads*
no this is before smp is active
Doesn't matter
so no other cores are online atm
But then
then what
When they're active
all cpus js access their respective queue
when they are active
and if i get a syscall to open another program
when we get to that point
we will add load balancing
because from what ive heard load balancing is very compelx
anyways time to work on scheduler
Also, this can just be the bottom of the kernel stack
why not top
I can't count
cause stack grows down
it will point to the end of the memory region
i.e
bottom of stack*
ez
okay this should be it
OH COME ON
this is what i do
clang is mad
it forced me to make it pointers
@tawdry mirage that is not pro

so extra heap allocation ig!!
what
nothing is forcing you to do anything
clang did
except your skill issues
๐ก
if you typedef struct arch_thread arch_thread_t
then
wait what am I talking about
ignore this
without a pointer it thinks its sizeless
it's a definitely a skill issue on your part
how
I do like exactly what you're doing rn
structure comes from here
it works for me on clang and gcc
no you can
but you need to:
typedef struct blah {
} blah_t;```
instead of using an anon struct
ohhhh
i.e., one with no name
makes sense
yea that solved
well the typedef is pointless
๐ญ
but eh
anyways no pointers needed
fair
// so instead of
typedef struct blah_t {} blah_t;
// you do:
typedef struct blah {} blah_t;
yea i get the convention
It's not a struct
its a typedef yea
I already explained to him his skill issue
i turned everything to regular non typedef structs because its still complaining and that didnt solve anything
๐
including all the structures
wtf clang

-# This message is flagged as misinformation
I MEAN THERE IS AN ISSUE
IM NOT SAYING ITS NOT MY FAULT
๐ญ
try gcc
same issue
glhf

I'm not sure but it looks like for some reason the type is not defined. Have you checked if you did the include guards correctly?
yes
oh
this should work
always make sure your language server isn't being dumb
and saying it's not included
language servers make mistakes
no i mean
to make sure it's not telling you false stuff
this should be included
I'd add an #error and try compiling if I didn't trust the language server
assert the fact it is:
#ifdef __x86_64__
#error hi
#include "..."
#endif
it does
try compiling
then check that file that you include
gl with this
its just this
wish you the best
thanks
your kernel is cursed btw
whats something i dont know\

oh
this makes sense
both depend on each other
that cant work
cursed but idc
would a doubly linked list be better for the run queue
because what if the last thread as a next that is NULL
how am i gonna go back to the start
its either
i store the first thread in the queue on a seperate field in per cpu data or
idk
what do yall think
also @finite summit solved ze skill issue 
cirucular depends
idk if this makes sense (I don't do osdev) but you could also store a pointer to the start instead of NULL and keep track of the last thread so that you can add a new thread using it
ill just store head and tail
so that going to the next one after the last one just comes back to the start (I assume that's what you wanted?)
btw you should store the current thread in gs, not the cpu local
there's a race condition I can't remember
yea per cpu struct is gonna be stored in gs
a race condition
can't remember
you'll prolly end up with a very hard to diagnose bug
I'll to find the thread
yea
basically the problem he's trying to describe is:
- loads cpu pointer from gs_base
- we now return that value
- task switch to a new CPU (uh oh)
- we use this stale value to get the current thread of a different cpu
returning that value?
sorry
what??/
๐คฆโโ๏ธ
that was literally me
cpu_local* get_cpu_ptr() { return (cpu_local*)gs_base; }```
if we then use that like this:
thread* get_curr_thread() { return get_cpu_ptr()->thread; }
#osdev-beginner-0 message
between get_cpu_ptr() and it's dereference, it is possible for a task switch
causing you to move to a different cpu
and read the current thread of the cpu you were on before that
why would 2 cpus share a thread
basically you'll avoid headaches if u store the current thread in gs
they don't
you switch
in between the dereference
so now the old cpu contains the current thread value of a different thread
mhm
actually here #osdev-beginner-0 message
pseudocode:
get_cpu_ptr:
mov rax, gs_base
ret ;right here if we switch to a different cpu, we still return the gs_base for the old cpu. despite being on a new one
get_thread_ptr:
call get_cpu_ptr
mov rax, [rax+curr_thread_offset]
ret```
so ur saying
somehow when calling that function
u suddenly switch to a different cpu running that code?
and ur returning the gs base of the old cpu
but how can this be possible exactly
and how can it be solved after u explain how it can be possible
a timer irq can happen anywhere, remember
yes
so on that ret we can receive a timer irq
yielding
and we then end up on a new cpu
while we continue to read from another cpu's gs_base pointer
You could also lower IPL anywhere u want to get thread data
but that's painful and a worse solution imo
the solution: disable IRQs while reading from the cpu local pointer
but this code isnt being scheduled, this should not be happening. no data should be going between 2 cpus and cant u disable interrupts while task switching
what is up with the last part
disabling is pretty overkill
yeah
just lower IPL past sched
he doesn't have that
no 1. i will disable interrupts while scheduling
we'll it's relatively simple to implement but fair enough
no 2. i dont understand how this makes sense
but even though i dont understand the problem
i have a solution to it and im js not gonna worry about it
say ur in a syscall, u want to get thread information. u call the fn, u get preempted, ur fucked
by far the simplest solution (as mint stated) is to eliminate the race condition
even if it was on the same cpu
wdym same
oooo scary
oh wait
yea
thats bad
if ur doing a syscall
well not "by" but
no that's normal and fine in most kernels
u can also decide syscalls aren't preemptable but there are drawbacks
i dont understand this first issue
but
its fine
we will just roll with it
it won't be if ur syscalls can't get preempted
this race condition can only happen in-kernel and if u can't get preempted in-kernel the race condition doesn't exist
its fine just disable interrupts for syscall and scheduling
done
anyways
im gonna go write code
fair enough
OKAY so we've switched threads changed contexts switch to the processes pagemap
yea i think the scheduling function is done
very simple
void sched(void *frame) {
struct per_cpu_data *cpu = arch_get_per_cpu_data();
if (cpu->run_queue == NULL) {
kprintf("Nothing to Schedule.\n");
return;
}
arch_save_ctx(frame, cpu->run_queue);
if (cpu->run_queue->next == NULL) {
cpu->run_queue = cpu->start_of_queue;
} else {
cpu->run_queue = cpu->run_queue->next;
}
arch_load_ctx(frame, cpu->run_queue);
// for reading operations such as switching the pagemap
// it is fine not to lock, otherwise we would have deadlocks and major slowdowns
// for writing anything however, the process MUST be locked
arch_switch_pagemap(cpu->run_queue->proc->cur_map);
}
but its good
bro
ok fixed that
well good thing i have an assertion
damn
๐
happening when trying to access it huh
even tho i wrote to it?
yea
and qemu says its in there???
i set runqueue to null
any time i try to access it, it dies
oh
so 2e93018 can fit
but not the rest
is there something wrong with this function?
the function is right
oh it was the rdmsr function
solved it
okay address was something
then it turned to 0

i think its because i save gs in my interrupt ctx
lets remove that
yep that was the issue lmao
it works tho
!!
HERE WE GO CHAT
so close
SO CLOSE
e
oh here
but i gave it the map?
weird
0x0 is the error code
what is going on 
mappings look like this
whats the cr2
this really looked like a completely valid address
thats the cr2
and not switching the page map just create more faults
looks valid to me
yea a 0xd
when iretqing
error code?
eh could be a number of things
yea no idea

could it be that the page tables are fucked again

code on github
so the current thread is the start of the run queue?
yes
does ur kmalloc zero things out?
I dont see any glaring issues but your code style makes it a bit difficult to interpret
struct StackFrame arch_create_frame(bool usermode, uint64_t entry_func, uint64_t stack) {
if (usermode) {
struct StackFrame meow = {
.rip = entry_func,
.rsp = stack,
.cs = 0x40 | (3), // USER CODE
.ss = 0x38 | (3), // USER DATA
.rbp = stack,
.rflags = 0x202
};
return meow;
} else {
struct StackFrame meow = {
.rip = entry_func,
.rsp = stack,
.cs = 0x28, // USER CODE
.ss = 0x30, // USER DATA
.rbp = stack,
.rflags = 0x202
};
return meow;
}
}
that is my create frame function
yea
since ur context switching on interrupts..
yea?
probably not the most flexible way to do it but fair enough
ok but
still dunno whats going on
i have a feeling the page tables are fucked again somehow
I'd have to run it and debug to really figure out and I dont really feel like making a nix flake
nix flake?
I run nixos
๐
I suppose I could do one as long as u dont use some niche software thats not packaged
i dont at all
thanks!
yea many people run into that issue, what u need to do is js mkdir .git
in that cc-runtime folder
@silver yarrow
gotcha
yep
almost got it building
coolcool
need to figure out what the fucking clang package is again
ill include both in the flake
truly
whats the flag to use gcc
u can jst go into the kernel makefile and change cc=clang to cc=gcc or wtv
based
kernel/gnumakefile
indeed!
clang and gcc are having different behavior with the page faulting (gcc says the page fault handler is a problem in the stacktrace, clang js says none 0x0 a bunch) so theres def some UB going on, dw about that tho js use gcc and im sure ull find the uB and then itll be the same on both gcc and clang
lol
yea clang
yeah I got it build btw
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { nixpkgs, flake-utils, ... } @ inputs: flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
inherit (pkgs) lib stdenv mkShell;
in {
devShells.default = mkShell {
shellHook = "export DEVSHELL_PS1_PREFIX='Nyax'";
nativeBuildInputs = with pkgs; [
libisoburn # xorisso
nasm
mtools
curl
gnumake
gdb
clang_19
clang-tools_19
qemu_full
];
};
}
);
}
here is the flake for any future nix users
yea!!!
so funny thing
on my machine
it goes insane
throws tons of exceptions
or wait a sec
those are just timer interrupts
oh it does the same for u?
yea
yes
fyi there's a faster way to do this but its ok for now
ik
what header is kprintf in
#include <term/term.h>
what is it
gs:0
it's dumb how lea rax, gs:0 just loads zero into rax as the effective address
I guess it makes sense
but I'm not a fan of it
oh yeah right
@surreal path afaict something in the struct thread is getting corrupted maybe
cpu local *
that shouldnt happen
ugh actually not quite sure
this works
u only add one thread to each core right
yes
so that if should technically be a null operation
like the whole moving the queue forward
wdym
that should work js fine?
that is interesting that this if statement is causing corruption
???
this assertion fails
afaict it shouldnt
if u only add one thread the next should always be null no?
question is.. why is it not
this line fixes it
no need for this no more
fair
now question is
why isnt it printing hello world

i load the context into the frame
it should work
mmm
lol
mmm
yea this makes sense
we overwrite the ctx with arch save ctx
question is
how do i fix this with one thread
wdym?
as in
we are overwriting the threads contex
xt
well maybe
instead of setting the runqueue
i need to set start of queue only
yea
it did not work
I have a dummy thread with its state set to destroy
that is "active" when entering the schedule
and on the first switch it gets nuked
because its state is destroy
arch load context isnt even being called
ever?
oh waiut
my if statement
well
we got closer
happeniung when memcpying
the sched rip is just the start
for some reason
yea not fun

cr2 is ffff800003a7df30
i pushed my changes to git
init thread
how????????????????????
@silver yarrow please ๐ญ
this is just a mockup
u need to impl it properly
oh I also renamed some ur vars when debugging earlier
i dont have an init thread
but
i js do that if run queue is null
check
wait
i realized
nvm
LETS FUCKING GOOOO

@silver yarrow LOOK
@finite summit scheduler ๐
real!
now i have a kentry
thats being scheduled
cool indeed
ok i did the scheduler now chat
and now we have a kentry
okay so what is next
let us check the mint to do osdev list from curated resources
Wait you are in GMT right
yes
its 2:10am
*it's

good point
but before i go to sleep
lets say what we will do tmrw
so
we will add an apic "driver" to nyaux
im thinking for this time ill do x2apic

ive never done that before so might be more fun
Do both
why both
Gn
done
what's nyax ```
shellHook = "export DEVSHELL_PS1_PREFIX='Nyax'";
i solved it dw
I think you're doing it wrong
In theory you're supposed to be switching kernel stacks
You're supposed to be switching kernel stacks
No
You have a page fault, that must block the thread and fetch the data from disk
What do you do
switch to kernel stack
No
then do what u need
There's no one kernel stack (unless you're microkernel doing weird stuff, e.g. pmOS)
You have kernel stack per process
why
Like when creating a thread, you create a kernel stack
why would you need different kernel stacks for different threads
kthread is still a kthread, rsp is where its stack is stored
for syscall premption
But also preemption in general
it makes it easier
literarly what i only use kernel stack for
Like to handle stuff in interrupts
and interrupts yeah
But you're not doing that
You just change the pointer to current thread
oh I haven't touched this subject for a long time, my old kernel has per thread kstacks too
if i allocated one or not
my bad
It would
My kernel doesn't, but it was a deliberate design choice, and it's not preemptive because of that
and per thread page fault stack
why
it can be the same stack as the syscall stack no?
So you can preempt
Yes
yea
In syscalls, from timer interrupts, in pf handlers
I remember almost nothing
Idk maybe the kernel which I've seen in os class in uni was weird
But I thought that was how it was generally done
cringe but fine ill do it
ugh
Cringe is what you do at the moment
ill write it in asm
i know what to do
Which pushes the saved registers onto the stack, saves the stack pointer, switches to a different thread, pops those same registers and returns
yea i know what to do
When you create a thread, you push the return address of a function, which prepares it for the first time onto the kernel stack
So when you switch to that thread, that is the first thing that gets called
So you switch (kernel) contexts
Idk
bru
im giving a kernel stack per core
want me to do it per thread
will that make u happy
Yes
k
If you do that, then you don't need a separate shack per syscall
im doing it chill
yea thats not correct
this should be not 0
oh now he mad
when i put iretq
cringe
so why is bro mad
This should stay the same
dw abt it
i js need to figure out why bro is mad
forgot the colon
but still bros mad
wha
sir
thisa
Why
dw abt it im js trying to test if it works first off
how is this a segment override
ugh
You're only supposed to be saving general registers here
i did already
And do swapgs if you're coming from userspace
yes i did
stop assuming i didnt lmao
sooo
why is it saying its a segment override..
invalid segment override

i dont understnad
any master nasm users know whats going on
What's the full code
here
what line even is it
isr_stub_%1:
; Push a dummy error code if a 2nd parameter has been passed to the macro
%if %0 == 2
push %2 ; Push dummy error code
%endif
test byte [rsp + 16], 0x3; Are lower 2 bits(priv lvl) of CS selector 0?
jz .skipswapgs1 ; If they are 0 (kernel mode) then skip swap
swapgs ; Otherwise swapgs in user mode
.skipswapgs1:
push rbp
push rax
push rbx
push rcx
push rdx
push rsi
push rdi
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
; push gs ; Save previous state of segment registers
; push fs
push %1 ; Push the interrupt number
call arch_get_per_cpu_data
test rax, rax
jz .idk
mov rsp, [rax:8]
.idk:
mov rdi, rsp ; put value of stack pointer into paramter 1 of c interrupt handler
mov rax, [idt_handlers + %1 * 8]
; Get the registered handler to call
cld ; Required by the 64-bit System V ABI
call rax
mov rsp, rax
add rsp, 8 ; skip int number
; Restore previous state of segment registers
; pop fs
; pop gs
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rdi
pop rsi
pop rdx
pop rcx
pop rbx
pop rax
pop rbp
test byte [rsp + 16], 0x3; Are lower 2 bits(priv lvl) of CS selector 0?
jz .skipswapgs2 ; If they are 0 (kernel mode) then skip swap
swapgs ; Otherwise swapgs in user mode
.skipswapgs2:
add rsp, 8 ; Skip error code
iretq
%endmacro
line mov rsp, [rax:8]
it kinda worked
i just need to extern arch_get_cpu_data
well it died
to be honest makes sense
i need to check if the field is not null too
well
it is not working so far
wait it might be working
i js need to check if its from cpu 0
hold on
nvm
it died so hard
yea
something bad happened
i may be accessing the wrong field?
somehow??
i shouldnt be?
arch cpu data is just this
if im accessing [rax + 8]
and rax has the ptr to per cpu data structure
and im deferencing it
and adding 8 bytes
shouldnt i js get kernel stack ptr??
like normal?
??
how do you get the value into rax in the first place
call arch_get_per_cpu_data
test rax, rax
jz .idk
mov rcx, [rax + 8]
test rcx, rcx
jz .idk
mov rsp, [rax+8]
these instructions
that is arch_get_per_cpu_data
What are you doing
wdym
good point
well, it's the simplest way to grab the value out of the kernel stack
i dont think he is
i think this is supposed to be in his syscall handler
wtf
Then you're pushing registers onto the userspace stack
bru
ok so switch stacks immeditely?
bro
u told me
switch stacks when task switching
and interrupts
i said
"ok"
bro
๐ญ
๐
yea thanks for confusing me lmao
/j
Yes
You're not task switching when entering kernel
your stack switching on interrupts is done by an IST
thanks
glad ist exists
yea so not gonna worry abt that for now
This is what you need to change
The other stuff was fine
You need to be changing kernel stack in this function
into the ist right?
What