#The Flopperating System (tfos) - x86 32 bit Hobby OS
1 messages · Page 2 of 1
Floppa os is back
Here is what I’ve gotten so far:
- Virtual file system
- pmm, vmm, slab, heap
- interrupts
- fb, vga text, vga graphics
- round robin scheduler with timing
For the most part that’s it
nice!!
Guess I should get a userspace
Niiiiice!!!
I can write text and type in user mode now
Syscalls kinda work. Good night
nice!
yep
good
Sys calls don’t worry anymore 💀
I dropped into user mode and ran hello world and uhhh triple faulted after returning to kernel mode when writing to memory
nice progress
forget to change ds segment
Yeah
static inline int syscall(int num, uint32_t arg1, uint32_t arg2, uint32_t arg3) {
int ret;
__asm__ volatile (
"int $0x80"
: "=a"(ret)
: "a"(num), "b"(arg1), "c"(arg2), "d"(arg3)
: "memory"
);
return ret;
}
static inline void puts(const char *str) {
syscall(SYS_PUTS, (uint32_t)str, 0, 0);
}
And
#include "syscall.h"
void main() {
puts("Hello from user space!");
while (1);
}
:)
Here’s how I’m doing it in the kernel
global syscall_entry
syscall_entry:
pusha ; Save all general-purpose registers
push ds
push es
push fs
push gs
mov ax, 0x10
mov ds, ax
mov es, ax
push esp
call syscall_dispatch
pop gs
pop fs
pop es
pop ds
popa
add esp, 4
iret
And here’s this
#include "syscall.h"
#include "log.h"
typedef uint32_t (*syscall_func_t)(uint32_t, uint32_t, uint32_t, uint32_t);
};
#define SYSCALL_COUNT (sizeof(syscall_table) / sizeof(syscall_func_t))
uint32_t syscall_dispatch(uint32_t *stack) {
uint32_t num = stack[8]; // EAX (syscall number)
uint32_t arg1 = stack[9]; // EBX
uint32_t arg2 = stack[10]; // ECX
uint32_t arg3 = stack[11]; // EDX
if (num >= SYSCALL_COUNT || !syscall_table[num]) {
log_step("Invalid syscall", 0x04);
return -1;
}
return syscall_table[num](arg1, arg2, arg3, 0);
}
I’ll obviously make better syscalls
But this is the thing I have for now
Take a look
wait you can push segment registers ???
the add esp, 4should be before pop ds
@bold grove
Like this?
global syscall_entry
section .text
syscall_entry:
pusha
push ds
push es
push fs
push gs
mov eax, ss
push eax
mov ax, 0x10
mov ds, ax
mov es, ax
sub esp, 4
push esp
call syscall_dispatch
add esp, 4
pop gs
pop fs
pop es
pop ds
popa
add esp, 4
iret
no
syscall_entry:
pusha
push ds
push es
push fd
push gs
mov ax, 0x1p
mov ds, ax
mov es, ax
mov gs, ax
mov fs, ax
push esp
call syscall_dispatch
add esp, 4
pop gs
pop fs
pop es
pop ds
popa
iretq```
like that
Okay
but i'm pretty sure you can't push or pop segment
How should I do it then
wait
i will send you
I’m in protected mode btw
I think I should do it like this
global syscall_entry
section .text
syscall_entry:
pusha
push ds
push es
push fs
push gs
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
push esp
call syscall_dispatch
add esp, 4
pop gs
pop fs
pop es
pop ds
popa
iret
i assume ds es fs and gs are the same
syscall_entry:
pusha
mov ax, ds
push rax
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
push esp
mov edi, esp
call syscall_dispatch
add esp, 4
pop eax
mov ds,ax
mov es, ax
mov fs, ax
mov gs, ax
popa
iretq```
like that
@bold grove
^^^
@bold grove just do it like that
@bold grove so it is working ?
Yeah the issue is that I’m not in 64bit
So it’s not
why
oh yeah i'm dump
wait i will send the 32 it version
syscall_entry:
pusha
mov ax, ds
push eax
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
push esp
mov edi, esp
call syscall_dispatch
add esp, 4
pop eax
mov ds,ax
mov es, ax
mov fs, ax
mov gs, ax
popa
iret```
try this @bold grove
Iretq doesn’t work in 32 bit
okay i fixed it
btw you should use a struct for the frame
what don't work?
I’ll get back to you
okay
good i use the same watch for over 8 years now LOL
So I’ve made a free list for my allocator
struct alloc_mem_block {
struct alloc_mem_block *next;
size_t size;
};
struct free_list {
struct free_list *next;
size_t size;
};
struct kernel_region {
struct kernel_region *next;
uintptr_t start;
uintptr_t end;
struct free_list *free_list;
} kernel_regions;
static struct free_list *free_blocks = NULL;
void add_to_free_list(void *ptr, size_t size) {
struct free_list *block = (struct free_list *)ptr;
block->size = size;
block->next = free_blocks;
free_blocks = block;
}
void *get_from_free_list(size_t size) {
struct free_list **prev = &free_blocks;
struct free_list *current = free_blocks;
while (current) {
if (current->size >= size) {
*prev = current->next;
return (void *)current;
}
prev = ¤t->next;
current = current->next;
}
return NULL;
}
void add_memory_block(uintptr_t start, size_t size, int is_free) {
struct alloc_mem_block *block = (struct alloc_mem_block *)start;
block->size = size;
block->next = NULL;
if (is_free) {
block->size |= 1;
add_to_free_list((void *)start, size);
} else {
block->size &= ~1;
}
}
This is what malloc and free look like now
void *kmalloc(size_t size) {
if (size == 0) return NULL;
if (size <= SLAB_MAX_SIZE) {
void *ptr = slab_alloc(size);
if (ptr) return ptr;
}
void *ptr = get_from_free_list(size);
if (ptr) {
struct alloc_mem_block *block = (struct alloc_mem_block *)ptr;
block->size &= ~1; // Mark as used
return (void *)(block + 1);
}
size_t pages = (ALIGN_UP(size + sizeof(struct alloc_mem_block), SLAB_PAGE_SIZE) / SLAB_PAGE_SIZE);
ptr = pmm_alloc_pages(0, pages);
if (!ptr) {
log_step("kmalloc: failed to allocate large block", size);
return NULL;
}
add_memory_block((uintptr_t)ptr, pages * SLAB_PAGE_SIZE, 0);
return (void *)((struct alloc_mem_block *)ptr + 1);
}
void kfree(void *ptr, size_t size) {
if (!ptr || size == 0) return;
if (size <= SLAB_MAX_SIZE) {
slab_free(ptr);
return;
}
struct alloc_mem_block *block = (struct alloc_mem_block *)ptr - 1;
add_to_free_list(block, block->size);
block->size |= 1; // Mark as free
// Coalesce adjacent free blocks
struct free_list *current = free_blocks;
struct free_list *prev = NULL;
while (current && current->next) {
if ((uintptr_t)current + current->size == (uintptr_t)current->next) {
current->size += current->next->size;
current->next = current->next->next;
} else {
prev = current;
current = current->next;
}
}
}
I actually ended up fixing this
Hi guys I’m going to work more on my kernel heap
So I’m going to make some heap integrity functions
And expand heap
And an aligned_kmalloc()
This is what I have so far but
void *kaligned_alloc(size_t size, size_t alignment) {
size_t total_size = size + alignment - 1;
void *ptr = kmalloc(total_size);
if (!ptr) return NULL;
uintptr_t aligned_addr = ALIGN_UP((uintptr_t)ptr, alignment);
return (void *)aligned_addr;
}
Im sorry bro, take a break from osdev for now and try to keep yourself busy with something relaxing <3
OSdev would be a lot more relaxing if freaking qemu wouldn’t stop crashing
I can get my kernel to run just fine on my laptop but on my desktop it won’t
what dont on work ? it dont boot at all ?
I don’t know
When I run it on my desktop it gets to pmm, does vmm, then crashes
init_kernel_heap() works actually
Init interrupts works until I actually enable interrupts
So the problem is at __asm__ volatile (“sti”)
Well I figured out that issue
For some reason my repo didn’t sync a double pointer in my slab, so that’s what was nuking the kernel apparently
It’s this line of code *(uint8_t **)ptr = cache->free_list; in initialize_free_slab_list(slab_t *slab, slab_cache_t *cache
It was typed *(uint8_t *)ptr instead
For some reason
Even though I synced my repo
No clue why
Since you guys wanna bully me
Now I added meaningful docs
To kernel.c
I’m doing a bit better
Girlfriend and I figured things out
Fixed it look at the messages I sent
I’m doing a bit better and I got a lot of
Motivation
Later I’m gonna commit a lot
I’m making a vmm_reserve_region()
Which basically doesn’t allocate pages but reserves a space
good
do simple things to get motivation
Yeah I just did
what is this supposed to mean
want my 2 pennies? here you have them anyway:
if someone broke up with you they are going to do it again (95 out of 100) times
I saw it happen for others, and it happened for me
We didn’t like break up it was like
We are taking a break
But like for me
I’m really attached but she just needed time a little apart because we have been having some arguments so for a bit we will spend some time apart plus I’m going on vacation
And when we come back everything will be okay obviously
We made up and everything and we just agreed to talk a bit less but we’re still together
not sure what to say about that either, but "taking a break" is vague. It could mean anything from "I just want some alone time" to "ill cheat on you under this pretext"
I understand it would be the former for you and I am glad
It’s “I just want some alone time”
Because we kinda have a codependency issue
And we really love each other
But I think we need to learn how to be happy with ourselves a bit more
Which just involves working together
And maybe talking a bit less
And not hanging out for a week or something
Or a bit longer
I got some good progress
I can now expand and shrink the heap
void expand_kernel_heap(size_t additional_size) {
if (additional_size == 0) return;
uintptr_t new_start = kernel_regions.end;
uintptr_t new_end = new_start + ALIGN_UP(additional_size, PAGE_SIZE);
if (!pmm_alloc_pages((void *)new_start, (new_end - new_start) / PAGE_SIZE)) {
log_step("Heap expansion failed!\n", RED);
return;
}
kernel_regions.end = new_end;
add_memory_block(new_start, new_end - new_start, 1);
log_step("Kernel heap expanded.\n", GREEN);
}
void shrink_kernel_heap(size_t reduce_size) {
if (reduce_size == 0 || reduce_size > (kernel_regions.end - kernel_regions.start)) {
log_step("Failed to all")
return;
}
uintptr_t new_end = kernel_regions.end - ALIGN_UP(reduce_size, PAGE_SIZE);
// Free the pages being removed
pmm_free_pages((void *)new_end, (kernel_regions.end - new_end) / PAGE_SIZE);
kernel_regions.end = new_end;
remove_memory_block(new_end, (kernel_regions.end - new_end));
log_step("Kernel heap shrunk.\n", YELLOW);
}
So my heap is going well
I can now allocate virtual regions
Contiguous ones
Hi guys, I’m gonna do some more work on the OS tonight. Gonna try to wrap up the allocator, I mean it works, but I just wanna modularizr it further and add better logging/spinlocks
Next, I’m going to actually fix my virtual region functions
Right now, it does what it’s supposed to, but there’s no kernel user separation for now, since I rewrote my vmm
So I’m going to work on that.
I’ll update
I think I’m pretty done with init_kernel_heap()
void init_kernel_heap(void) {
log_step("Initializing kernel heap...\n", YELLOW);
size_t total_memory = pmm_get_memory_size();
if (total_memory == 0) {
log_step("init_kernel_heap: PMM not initialized or no memory available!\n", RED);
PANIC_PMM_NOT_INITIALIZED((uintptr_t)first_block);
return;
}
kernel_heap_size = (total_memory * HEAP_PERCENTAGE) / 100;
kernel_heap_size = ALIGN_UP(kernel_heap_size, PAGE_SIZE);
if (kernel_heap_size < MIN_HEAP_SIZE) {
kernel_heap_size = ALIGN_UP(MIN_HEAP_SIZE, PAGE_SIZE);
} else if (kernel_heap_size > MAX_HEAP_SIZE) {
kernel_heap_size = ALIGN_UP(MAX_HEAP_SIZE, PAGE_SIZE);
}
first_block = (struct alloc_mem_block *)KERNEL_HEAP_START;
add_memory_block(KERNEL_HEAP_START, kernel_heap_size, 1);
kernel_regions.start = (uintptr_t)KERNEL_HEAP_START;
kernel_regions.end = (uintptr_t)(KERNEL_HEAP_START + kernel_heap_size);
kernel_regions.next = NULL;
log_step("Kernel heap initialized successfully\n", GREEN);
heap_initialized = 1;
}
After I go on a walk I’m gonna do some more work
Mainly fucking error handling for my slab allocator because there’s literally none
Well, Slab has error handling, I added guarded and aligned malloc functions, and I made a proper reversed virtual region for the kernel heap, that lazily allocates pages for the heap as needed rather than just allocating it all at once
I’ll update my repo tonight, I’m just making the code cleaner
good
I’ll update the thread with the commit later
Hi guys im going to do some more work
I might need some help with my heap
Like it works but it’s messy
Well
My laptop died so
I actually did cleanup parts of the heap
But I couldn’t push it
But it’s saved so get ready for some more dogshit C code from yours truly ™️
Okay so now
I’m going to push that tonight
I added actual decent comments to that file
It’s like ridiculously big for what it is
I added aligned and guarded allocs
Basically the guarded alloc will allocate two extra pages and the aligned one will
Well
Align to a boundary specified
good
nice
now test with uacpi
Yeah I will I’m just fixing parts of it
I’m doing to do some more work on it today
Because I’m kinda lazily allocating a bit too hard
Kinda idk
k
Well I kinda got stuck
With some stuff
Just getting some stupid errors
So im making a virtual region for the heap
Like the one on the GitHub works
It’s just a bit clunky
Okay
I kinda worked it out
Im just going to fix some other shit
Mainly with paging
I’m gonna try to just clean up code in my memory manager
It’s a lot though
wdym reserved regions
Basically pages that aren’t allocated but are reserved for a virtual address like so for the heap the first 100mb or whatever would be “reserved” so the rest of the system can’t access it bit it doesn’t have to allocate it all at once. So it will “lazily” alloc to the regions
Anyways yeah I’ve been deshittifying this memory manager
I mean this one I have works just fine
Just gonna make it cleaner
And make sure my sanity isn’t too far gone trying to debug it
@noble remnant kinda rewriting some parts lol
So sorry for the delay
But yeah currently my shit is fragmented weirdly into many files
Which is fine but
I kinda just arbitrarily chose to do page frame stuff in the pmm and paging file lol
So it’s not really clear what does what I guess
My alloc.c works
Fine for now
Except for when I add virtual regions
Which I’ve tested
I’ll show you guys later
My kernel
You should like the thread and stay tuned for more progress
😊😊
Look guys
It’s me and girlfriend
This is what I was doing last night instead of working on the kernel lol
Damn
Goat spotted
Infy I’ll show you what I’ve been working on tonight
I wanna test my alloc with uacpi
Do it
Eggs and bacon lollll
I’m a simple man of simple cuisines
I feel dumb though because she’s like a virtuoso chef and even learned how to make ethnic dishes from bosnia and here I am making eggs and bacon for dinner
Nothing wrong with that good sir! What mattersrs most is that she sees you for who you are
Lol
I respect it
Even if the meal isn't fancy, it's nice if someone cooks for you
you clearly aren't an osdever 😔 /s
unless you are my sister who burns the garlic bread, everytime
Hi guys
Sorry, I haven’t really updated the repo
Honestly I haven’t even compiled the os in a day or two
Just because of the rewrite I’m doing
For parts of the mem manager
Uhhh
Thanks bro I really appreciate it 🙏🙏
PAIN
MY VMM IS LIKE 1000 LINES
TF
And my alloc.c is a thousand lines now too
theres surely smth wrong there
The way I’m doing it is kinda complicated now
I’m creating a virtual region for each slab
And then creating a region that expands and shrinks as necessary for allocs above page size
And also I’m adding guarded allocs
Which add a certain amount of extra pages
And also heap integrity checks
And merging and splitting regions
Yeah
I kinda nuked my whole progress
That I made in the past 2 days
Looked like shit lmao
I’m implementing a binary tree for virtual regions
Sorry for the slow ass progress
np
that not a problem
Okay. I’ve just been kinda slacking and I’ve been a bit sad lol. I’m good just overwhelmed
coooook
I did stuff :)
Without over complicating
I cleaned up my vmm a lot
Here’s how im creating and destroying regions now
// Create a new virtual memory region
vmm_region vmm_create_region(PDE *page_directory, uintptr_t start_virt, uintptr_t start_phys, size_t size, PageAttributes attrs) {
if (!page_directory || size == 0) {
log("Invalid parameters for creating region!\n", RED);
return (vmm_region){0};
}
// Align size to page size
size = (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
uint32_t pages = size / PAGE_SIZE;
// Map the region
for (uint32_t i = 0; i < pages; i++) {
uintptr_t virt_addr = start_virt + (i * PAGE_SIZE);
uintptr_t phys_addr = start_phys + (i * PAGE_SIZE);
vmm_map_page(page_directory, virt_addr, phys_addr, attrs);
}
log("Region created successfully.\n", GREEN);
return (vmm_region){
.start_virt = start_virt,
.start_phys = start_phys,
.size = size,
.pages = pages,
.attrs = attrs
};
}
Here’s the cute little structure
typedef struct {
uintptr_t start_virt; // Start of the virtual address range
uintptr_t start_phys; // Start of the physical address range
size_t size; // Size of the region in bytes
uint32_t pages; // Number of pages in the region
PageAttributes attrs; // Attributes of the pages in the region
} vmm_region;
I’m gonna make my kernel assert OS now
Man 
Kinda realized
I haven’t really been going anywhere
I need something new
Rather than a better memory manager
scheduler
elf parsing
exec/fork or spaen
userspace with contrxt switch
vfs
tempfs
initrd
there's so much to do
Don't forget porting to another arch :p
...says the person who literally spends his days having trouble finding a new arch he hasn't yet ported his OS to 😔
Haha nah I only got to 3
You're thinking of mishakov
What I'm having trouble finding is motivation lol
join me, try port doom, it's a great motivator lol
we can race to having doom running :P
I can understand that. I've had the same problem for about 6 months, I only get short burst of motivation for a week at most every so often.
yeah I took like a 4 month break from osdev and just got back into it again recently
ha - that sounds like a good bit of fun. I have a few days around the house this week, I could do that 😛
yeah I did notice, you've been doing a lot of work on your emulator/isa I saw though?
yeah, all my motivation has been on that, basically none for my OS
the saddest thing for me is that doom launch and then it segfault .....
I've got one or two things I was planning first so give me a couple days lol then id be happy to
Could you help me with scheduling
if you’re awake would you be able to help me with scheduling
LoL you ping everyone
yes i'm awake
Sorry I’m just struggling lol
at 23:01 ...
for me it's 23:02 everyone in my house is sleeping except me
so what is your probleme ?
@bold grove ?
okay
so i have a priority field in my task struct
and my scheduler is activated to schedule every milisecond by the PIT
and for some reason adding this to it
just makes everything stop working and i can not for the life of me figure out why
int original_task = current_task;
int highest_priority = -1;
int next_task = -1;
for (int i = 0; i < task_count; i++) {
int index = (current_task + 1 + i) % task_count;
if (task_queue[index].sleep_ticks == 0 && task_queue[index].priority > highest_priority) {
highest_priority = task_queue[index].priority;
next_task = index;
}
}
if (next_task != -1) {
current_task = next_task;
} else {
current_task = original_task; // No runnable task found, stay on the current one
}
do you send EOI before or after the schedule ?
after
// PIT ISR (IRQ0)
void __attribute__((interrupt, no_caller_saved_registers)) pit_isr(void *frame) {
(void)frame; // Unused
// Call the scheduler on each timer tick if enabled
if (scheduler_enabled) {
scheduler();
}
outb(PIC1_COMMAND, 0x20); // Send EOI to PIC
}
mmm...
first of all maybee try to lower to 100 times per second instead of 1000
it is working with 100 times per second ?
it prevent other code from being exe uting
yes
mmm...
comment and make sure it work wihout it
are you sure it just donsen't schedule
if your using qemu can you do info registers and see if the eip change
you spam this command and is if the pid change
okay
there are three cases:
- it don't change
- it change a bit
- it change a lot
doesnt change at all
okay
weird
so it freeze
note the eip
and now don't recompile or change anything
do
objdump -D kernel.elf > asm.txt
or whanever your kernel is
and then inside asm.txt find the instruction it freeze onto
@bold grove
did you find it ?
nope
its nowhere in the dissassembly
0010101c
this is it
thats what qemu gives me
can you load program on your os ?
not anymore no
weird
maybee that the problem you jumping a a weird address
Yeah I’m just stuck in the scheduler loop
For some reason
Nothing ever gets scheduled and executed it
Just skips every time
Even though I set priority values
what scheduler loop
the one with the for ?
Yes
Not loop
how is that possible what is the value of i
what ?
Sorry I meant like the scheduler that’s called by the pit
Everytime it runs it just gets stuck in that function
No clue why
in the function you showed me ?
Yes, like it just hangs in there after calling the scheduler after nothing gets called it just stops
where prescely it stop
in the loop?
it loop idefinetly ?
Ok
Good night
Yeah sure, I'll catch up on the thread
What did the assert catch?
Also what's the priority of the initial thread?
Also I don't actually see you context switching anywhere?
The isr fires - > scheduler gets called - > scheduler returns, isr returns to previous context
here is the function
void scheduler() {
if (task_count == 0 || !scheduler_enabled) return;
// Increment scheduler ticks
scheduler_ticks++;
if (scheduler_paused) {
if (pause_ticks_remaining > 0) {
pause_ticks_remaining--;
if (pause_ticks_remaining == 0) {
sched_resume();
}
}
return;
}
// Update sleep timers for all tasks
for (int i = 0; i < task_count; i++) {
if (task_queue[i].sleep_ticks > 0) {
task_queue[i].sleep_ticks--;
if (task_queue[i].sleep_ticks == 0) {
log_f("Task %s (PID: %d) woke up.\n", task_queue[i].name, task_queue[i].pid);
}
}
}
// Find the next task to run based on priority
int highest_priority = -1;
int next_task = -1;
for (int i = 0; i < task_count; i++) {
int index = (current_task + 1 + i) % task_count;
if (task_queue[index].sleep_ticks == 0 && task_queue[index].priority > highest_priority) {
highest_priority = task_queue[index].priority;
next_task = index;
}
}
// If no eligible task is found, keep the current task
if (next_task == -1) {
next_task = current_task;
}
if (next_task != -1) {
current_task = next_task;
}
// Run the selected task
Task *current = &task_queue[current_task];
if (current->sleep_ticks == 0) {
current->runtime++;
if (current->function) {
current->function(current->arg);
}
}
}
in its entirety
0
okay i flipped the first threadto have the highest prirotity
now my first thread works but my other two done lol
if i give them all the same priority the same thing happens
the first thread works too
great thanks
some general advice: separate the idea of time-keeping (and a timer interrupt) and the scheduler. There are other times you'll want to reschedule, like waiting on io or a mutex.
So you would have your scheduler that handles which threads should run and when. Then if the timer interrupt fires, you handle the interrupt as per normal, and then notify the scheduler of that. The scheduler can decide what to do with it then.
ohh okay
and it'll help clean things up if sleeping tasks are removed from the work queue entirely. I find its more practical to implement support for threads blocking/waiting on an event/mutex/whatever with timeouts, and to sleep - you simply wait on nothing with a timeout for the sleep duration.
so have the scheduler handle the timer rather than the timer handle the scheduler
okay bet ill do that
they would more or less separate
you have some mechanism in your kernel to say "hey I want to run this function in xyz amount of time", and your scheduler provides yield()/enqueue()/dequeue() functions. When switching threads, the scheduler would ask to have yield() called in some amount of time (your timeslice), so you're guarenteed to context switch if the thread doesnt block before then.
so your timer interrupt would look more like:
- timer fires
- you check for anything that needs to be done based on the timer firing (you might have a list of things to do at a certain time) .
- one of the items in the list might be reschedule(). This doesnt switch threads, it just picks the next one.
- send eoi(), now your interrupt handling is completed. The interrupt handling part is effectively done, but the kernel still has control. Note that if for some reason the interrupt was served as an NMI on x86, you will need to use
iretqat some point to complete it properly (its a special case), but otherwise dont worry about that. - before returning you notify the scheduler its safe to switch threads if it wants.
- return from interrupt.
Okay thank you so so much
thats ok, I hope it helps 🙂
I’m sure it will, I just need to brainrot for a bit
lol
Stare at the computer for a bit and I’ll get it
and fwiw I suspect your current issue is that you're running the thread's function() within the timer handler. So until that function returns, you're within the interrupt context still, and would have interrupts disabled.
This was the issue lolll
I’m redoing the scheduler ™️
With actual context switching
Already have issues 
Lol, feel free to ask if you've got questions (here or #schedulers)
just wondering
is this how i should do a context switch
for x86
__attribute__((naked)) void context_switch(Task *prev_task, Task *next_task) {
__asm__ volatile(
"push %%eax\n"
"push %%ebx\n"
"push %%ecx\n"
"push %%edx\n"
"push %%esi\n"
"push %%edi\n"
"mov %%esp, %0\n"
: "=m"(prev_task->stack_pointer)
:
: "memory"
);
__asm__ volatile(
"mov %0, %%esp\n"
"pop %%edi\n"
"pop %%esi\n"
"pop %%edx\n"
"pop %%ecx\n"
"pop %%ebx\n"
"pop %%eax\n"
:
: "m"(next_task->stack_pointer)
: "memory"
);
}
this is how im making a task stack
int allocate_task_stack(Task *task, void (*entry_point)(void)) {
task->stack = (uint32_t *)pmm_alloc_pages(STACK_SIZE / PAGE_SIZE, 0);
if (!task->stack) return 1
uint32_t *stack_top = task->stack + (STACK_SIZE / sizeof(uint32_t));
*(--stack_top) = (uint32_t)entry_point;
*(--stack_top) = 0x10;
*(--stack_top) = 0x200;
*(--stack_top) = 0x08;
*(--stack_top) = (uint32_t)entry_point;
for (int i = 0; i < 8; i++) *(--stack_top) = 0;
task->stack_pointer = stack_top;
return true;
}
nooooo
the compiler can do anything in between the asm blocks, even in a naked function
its probably fine - but I'd do it one block, or a separate asm file
okay bet but does everything else look fine
just checking now 🙂
I dont know the x86 calling convention off the top of my head
ah okay
have you tested it?
about to
you'd also want to save and restore the flags register
so pushf/popf
that also lets you set the starting flags for a thread, rather than inheriting whatever the previous ones were
okay bet
how does this look
__attribute__((naked)) void context_switch(Task *prev_task, Task *next_task) {
__asm__ volatile(
"pushf\n"
"push %%eax\n"
"push %%ebx\n"
"push %%ecx\n"
"push %%edx\n"
"push %%esi\n"
"push %%edi\n"
"mov %%esp, %0\n"
"mov %1, %%esp\n"
"pop %%edi\n"
"pop %%esi\n"
"pop %%edx\n"
"pop %%ecx\n"
"pop %%ebx\n"
"pop %%eax\n"
"popf\n"
: "=m"(prev_task->stack_pointer)
: "m"(next_task->stack_pointer)
: "memory"
);
}
so looks like eax/ecx/edx are caller-saved, so they will be taken care of by whoever calls context_switch.
this is where i call it
void scheduler() {
if (task_count == 0) return;
int next_task = -1;
for (int i = 0; i < task_count; i++) {
if (task_queue[i].state == TASK_READY) {
if (next_task == -1 || task_queue[i].priority > task_queue[next_task].priority) next_task = i;
}
}
if (next_task == -1) return;
if (current_task != next_task) {
Task *prev_task = (current_task >= 0) ? &task_queue[current_task] : NULL;
Task *next_task_ptr = &task_queue[next_task];
if (prev_task && prev_task->state == TASK_RUNNING) prev_task->state = TASK_READY;
next_task_ptr->state = TASK_RUNNING;
current_task = next_task;
context_switch(prev_task, next_task_ptr);
}
}
and then this is how im starting it
void sched_init() {
task_count = 0;
current_task = -1;
scheduler_ticks = 0;
}
void sched_start() {
sched_enabled = 1;
while (sched_enabled) {
scheduler();
}
}
void yield() {
if (current_task < 0 || current_task >= task_count) return;
Task *task = &task_queue[current_task];
task->state = TASK_READY;
scheduler();
}
the compiler cant see inside of asm() blocks, and since task->stack isnt volatile, I dont think its guarenteed to actually emit those writes to the new stack like that.
should i just do a different asm file
up to you, that wont change what I said there though
or should i make task->stack volatile
I'd make it volatile
ah yeah, whats with those values you're pushing to the stack?
so your code would pop the 6 gprs, then flags, then the function would return (expecting the return address to be at the top of the stack)
idk what im supposed put there
thats kinda where i was struggling
The answer is your context switch function
You put the things there that get popped from the stack + the return address
like this
?
int allocate_task_stack(Task *task, void (*entry_point)(void)) {
task->stack = (uint32_t *)pmm_alloc_pages(STACK_SIZE / PAGE_SIZE, 0);
if (!task->stack) return 1;
uint32_t *stack_top = task->stack + (STACK_SIZE / sizeof(uint32_t));
// Push the return address (entry point)
*(--stack_top) = (uint32_t)entry_point;
// Push initial values for the general-purpose registers (dummy values)
for (int i = 0; i < 6; i++) {
*(--stack_top) = 0; // eax, ebx, ecx, edx, esi, edi
}
*(--stack_top) = 0x200;
*(--stack_top) = 0x08;
*(--stack_top) = (uint32_t)entry_point;
// Push dummy values for the remaining registers
for (int i = 0; i < 8; i++) {
*(--stack_top) = 0; // ebp, esp, etc.
}
task->stack_pointer = stack_top;
return 0;
}
Idk I’m probably missing something
you don't have to own up, discord doesn't show when a message has been read 😛 (for the record I am not promoting ignoring people when asking for help in a conversation)
uh
you probably should do this in assembly tbh
again, look at what happens in the context_switch() function, but in reverse. The function will return with ret, which pops the return address from the stack. So you need to push that first.
Then it will execute popf, so you need to push the flags next.
Then you pop 6 GPRs, so push the initial values you want - probably zero.
lol, I know though.
so you're kind of there: you push the return address, the 6 gprs (flags are missing before this). Then there's some random numbers and stuff that doesnt need to be there.
the way I like to do this generally is to have a flag for tasks TASK_FIRST_EXEC and in your context switch, you just check if it's set, and if it is, then you set up the stack frame from there (and ofc clear the flag)
Okay thank you
I see, so they should do the opposite of each other exactly
I’m going to fix my console
In my os
So I can see more logs
I’ll do this maybe tmr, just not feeling great
fair - go do something else, rest up
I just had some issues with my relationship and also at home and with school it’s just not it.
Just also been stoned for days on end
Just idk my mental health is all over the place so
I’ll try my best to do some cooking tonight through
Though
Imma do the better console today
I’m gonna just get a frame buffer
I made a Python script to convert bitmap font files into C headers
sounds like a plan, what are you thinking?
nice nice
oneday #embed<> will be readily available
Gcc user here 😟
Maybe fixing scheduler
First text console and Ima send it here
Ohhhh lolll and maybe I should
Nah I ate some Panera mac and cheese shit was gas
I might just refactor it entirely
I might make a sched.c, process.c, thread.c, context_switch.asm, sync.c, ipc.c, msgqueue.c
Instead of one massive file
How does that structure look
Not to annoy but uhhh
Eh they're just file names, i'm reluctant to give much feedback on that alone.
If they make sense for you and the organisation, do it
whatever is easiest for you to manage
Okay good. I was more asking if that’s pretty much all of the stuff I would need for a good scheduler
Bc I’m trying to roadmap this out
you wont need ipc or message queues for scheduling, they're higher level primitives
I would even say sync primitives are implemented on top of the scheduler, using enqueue/dequeue/yield functions.
and process and thread lifetime management (besides starting/exiting a thread) isnt the role of the scheduler either
So what would I manage processes and threads with
Should I just have proc/whatever.c and thread/whatever.c
And then start threads with the scheduler
And then integrate those together
Should I have a userspace and kernel scheduler seperate?
some higher level component. If you're wondering how to bootstrap the scheduler, you can take the current thread of execution (where the kernel starts running) and turn it into a thread, which eventually becomes the idle thread for that core.
nah
so how should i get threads working like
like should i make the kernel thread
whinch then kinda sits there
until another one is started
Well what do you need for a thread
Some code, a stack, register state. Things you already have. Then some struct that organise that and some control data for the scheduler. You can allocate that on the stack.
Noe you have a thread that's already running, from the perspective of the scheduler.
For spawning other threads you'll want some program manager (example name) that can create all that stuff for you, then you tell the scheduler you want to run that thread when you're ready.
Ahhh okay
So making a program manager to launch threads is just an abstraction over doing it like you just said
Like you said it can create that stuff for me
Okay bet, and for the program manager, can I just do something like ./program in a little kernel console just for testing? Then when I get the userspace obviously I’ll port that
The scheduler being better is the last thing before I wanna get to userspace
The main thing is the scheduler doesn't deal with resource allocation 🙂
Well I already got all of that
I have a completely completely vmm, pmm, and slab, and heap
So should I just have whatever I put as a thread manage the res routes
Resources
Or should I just attach that to my process_t struct
so it should look like this somewhat
void task_queue_init(TaskQueue *queue) {
queue->front = 0;
queue->rear = -1;
queue->size = 0;
}
bool task_queue_is_empty(TaskQueue *queue) {
return queue->size == 0;
}
bool task_queue_is_full(TaskQueue *queue) {
return queue->size == MAX_TASKS;
}
void task_queue_enqueue(TaskQueue *queue, Task *task) {
if (task_queue_is_full(queue)) return;
queue->rear = (queue->rear + 1) % MAX_TASKS;
queue->tasks[queue->rear] = task;
queue->size++;
}
Task *task_queue_dequeue(TaskQueue *queue) {
if (task_queue_is_empty(queue)) return NULL;
Task *task = queue->tasks[queue->front];
queue->front = (queue->front + 1) % MAX_TASKS;
queue->size--;
return task;
}
void add_task(void (*entry_point)(void), void *arg, uint32_t priority, const char *name) {
if (task_count >= MAX_TASKS) return;
Task *new_task = &task_queue[task_count];
initialize_task(new_task, arg, priority, name);
if (!allocate_task_stack(new_task, entry_point)) return;
new_task->state = TASK_READY;
task_queue_enqueue(&ready_queue, new_task);
task_count++;
}
heres this too
typedef struct {
int front;
int rear;
int size;
Task *tasks[MAX_TASKS];
} TaskQueue;
typedef enum {
TASK_READY,
TASK_RUNNING,
TASK_SLEEPING,
TASK_TERMINATED,
TASK_FIRST_EXEC
} TaskState;
typedef struct {
uint32_t pid;
uint32_t priority;
void *arg;
TaskState state;
uint32_t *stack;
uint32_t *stack_pointer;
uint32_t sleep_ticks;
char name[32];
char source_path[128];
uint32_t ds;
uint32_t es;
uint32_t fs;
uint32_t gs;
void *address_space;
uint32_t ebp; // Base pointer
uint32_t esp; // Stack pointer
uint32_t eip; // Instruction pointer
uint32_t eflags; // Flags register
uint32_t registers[8]; // General-purpose registers (eax, ebx, ecx, edx, esi, edi, esp, ebp)
} Task;
wait
here
then im using a hasmap for generating pid
Okay
And yeah, I got it working
I can now schedule threads :)
@blissful grove here is my spinlocks and mutexes :)
typedef struct {
volatile int lock;
volatile uint32_t owner_pid;
} Spinlock;
void spinlock_init(Spinlock *spinlock) {
atomic_store(&spinlock->lock, 0);
spinlock->owner_pid = 0;
}
void spinlock_acquire(Spinlock *spinlock) {
uint32_t current_pid = task_queue[current_task].pid;
while (atomic_exchange(&spinlock->lock, 1)) {
if (spinlock->owner_pid == current_pid) {
// Already owned by the current task, avoid deadlock
return;
}
while (spinlock->lock) { /* busy-wait */ }
}
spinlock->owner_pid = current_pid;
}
void spinlock_release(Spinlock *spinlock) {
if (spinlock->owner_pid == task_queue[current_task].pid) {
spinlock->owner_pid = 0;
atomic_store(&spinlock->lock, 0);
}
}
typedef struct {
Spinlock spinlock;
volatile int locked;
} Mutex;
void mutex_init(Mutex *mutex) {
spinlock_init(&mutex->spinlock);
mutex->locked = 0;
}
void mutex_lock(Mutex *mutex) {
spinlock_acquire(&mutex->spinlock);
while (mutex->locked) {
spinlock_release(&mutex->spinlock);
yield(); // Allow other tasks to run
spinlock_acquire(&mutex->spinlock);
}
mutex->locked = 1;
spinlock_release(&mutex->spinlock);
}
void mutex_unlock(Mutex *mutex) {
spinlock_acquire(&mutex->spinlock);
if (mutex->locked) {
mutex->locked = 0;
}
spinlock_release(&mutex->spinlock);
}
Okay . Look I got mutex and spin locks now!
And sorry guys I haven’t pushed anything yet
It’s like
5 new files
I did that now
bro you chech
while (mutex->locked)
without an atomic instruction
fair but
he never handles interrupts
and his algorithm is far from efficient either although thats really not important rn
Okay what can I do
change your slock
Contribute to lykdev/lykOS development by creating an account on GitHub.
Okay thank you bro
Nah it’s cool
you can also look at mine, its copied from @crimson plover
also the deadlock part you should only add if you compile in DEBUG mode
because that should never happen on stable builds
make use of some #ifdefs
cool man - you dont need those volatile qualifiers if you're using atomic operations. For a spinlock you dont need to track who's owning it either, bool locked is enough 🙂
For a simple mutex you want to have a queue of waiting threads. If the current thread cant take the lock, it removes itself from the runqueue, and adds itself to the end of the waiting-threads list for that mutex. Now it wont be scheduler again, only to immediately yield because the lock is still held.
For releasing the mutex, you wake the first thread in the mutex's wait queue and it's now holding the lock. Ofc if the queue is empty you set the mutex as unlocked and move on.
okay bet
i should keep task->stack volatile though right?
@blissful grove here is my final context switch
__attribute__((naked)) void context_switch(Task *prev_task, Task *next_task) {
__asm__ volatile (
"pusha\n"
"movl %%esp, %0\n"
"movl %%ebp, %1\n"
"movl %2, %%esp\n"
"movl %3, %%ebp\n"
"popa\n"
"ret\n"
: "=m"(prev_task->stack_pointer), "=m"(prev_task->ebp)
: "m"(next_task->stack_pointer), "m"(next_task->ebp)
);
}
yaey
It’s super close to being done
gl
So that didn’t happen
😔
I don’t know if I’m going to be able to continue for a while
Like not just OSdev
Just anything
I hope you guys maybe enjoyed what I did although it was all pretty dogshit lol
it was a pretty cool project
hardly dogshit compared to what most people could do
I hope you're able to continue again at some point :))
New logo :)
looks good!
nice!!
I’m back fr now I was having a manic episode that day for sure
Sorry I’m bipolar as fuck
Like I actually have it diagnosed I’m not using mental illness as a joke
that not a problem
GUYS
I WAS TRYING TO FIX AN ISSUE FOR A LONG TIME
AND
I figured it out
It was a casting error
I was trying to print out this address
And also save it to memory and
I was getting some weird errors from gcc
And I fixed it so
Congratulations to md for being a dumbass
i mean i sometimes put => instead of >= sooo
Here’s my final task stack code
int allocate_task_stack(Task *task, void (*entry_point)(void)) {
task->task_stack.stack = (uint32_t *)pmm_alloc_pages(STACK_SIZE / PAGE_SIZE, 0);
if (!task->task_stack.stack) {
log(“sched: failed to allocate task stack\n", RED);
return 1;
}
uint32_t *stack_top = task->task_stack.stack + (STACK_SIZE / sizeof(uint32_t));
*(--stack_top) = 0x10; // ds
*(--stack_top) = (uint32_t)task->task_stack.stack + STACK_SIZE; // esp
*(--stack_top) = 0x10; // es
*(--stack_top) = 0x10; // fs
*(--stack_top) = 0x10; // gs
*(--stack_top) = 0x200; // eflags
*(--stack_top) = (uint32_t)entry_point; // eip
*(--stack_top) = 0; // error code
for (int i = 0; i < 8; i++) { // eax, ecx, edx, ebx, esp, ebp, esi, edi
*(--stack_top) = 0;
}
task->task_stack.stack_pointer = stack_top;
task->task_stack.esp = (uint32_t)stack_top;
task->task_stack.ebp = 0;
task->task_stack.eip = (uint32_t)entry_point;
task->task_stack.eflags = 0x200;
return 0;
}
I got soemthing done
^
good
well good
Okay good
FloppaOS now supports multithreading:)
like multiples thread per process ?
That too
Yes
really good
how do you create a new thread ?
like fork for a new process and clone for a new thread or something ?
Yes I clone threads
And I’ll post the source code sometime this week
I’m just getting through some debugging
You should star My thread :)
done
very kewl!
Ill post it soon but just a lot of small bug fixes to get through
I got mutex as well
Thank you man
Bro
This scheduler
Is 2000 lines of code
Which includes
Like
Task queue and task and sched and pid and stack and mutex and spin lock
It’s going farther
Okay so
Pretty much everything works
I’m just havint some stack corruption issues
For task %{{€}~*{
Can I have some help with this later
For writing more code? :p
nah im just getting fucked by this scheduler
Dead os guys 😔
Not really
I hope to get some work done on it soon
Hopefully today
I changed the description :)
Hi guys
I have once again began work
I have fixed my scheduler
And fully implemented mutexes
I still haven’t pushed anything in a few weeks and I’m sorry for that
There’s some issues with priority handling though
Man :(
❤️
I’m struggling
I felt that
I just want my scheduler to work idk what’s wrong with it
I wish I could help with debugging but I am too tired atm
I have a scheduler, queues for each cpu, intrusive linked list for tasks, pid hashmap thing, context switching
I just don’t know what’s wrong atp
All good man
if u are still stuck tomorrow (EET time) hmu
bet thank you
Star my thread so you don’t forget

dw I been reading ur thread
I’m gonna kiss you (platonically)
lurkin
Yay I’m glad at least someone does lol
i'm not sending a lot message but i' reading the thread too
and i believe other peoples are reading wihoot writing
Guys :(
we are
always reading wazowski
why is it called floppa
why not
GUY
GUYS
after days
of trying to make n implementation
im pretty sure
ive made the only solid implmentation of the multiboot1 fraembuffer in a hobby OS
It broke :(
That's alright! Somestimes things have to break for them to be come better...
git was created for a reason
No like it works but I get UB somewhere when trying to print outside of the first 200x200 pixels of the framebuffer
try the ubsan
I’ve been
I’ve tried gdb and it just leads to an unknown opcode
ubsan
you do -fsanatize=udefined
and you implement all thr fonction it try to reference
just print something and panic
weird ...
I’m gonna try something
So give me a bit
It seems like after I push 256 pixels, it starts pointing to bogus memory
And then nothing happens
like write 256 pixel at the same place ?or each time the next pixel ?
So I can flush the screen to black, that’s fine
I can draw up to 256 pixels wherever I want but after I try to draw more it just borks
For example I could draw a 10x10 square, but if I tried to draw another, it would just cause UB (just clear the screen and freeze on unknown opcode)
what ???
okay that weird
I think I know what might be going on
great!
btw if you need big integer use intmax_t and uintmax_t
not long or uint64_t
that make more portable
between i386 and x86_64 for exemple
Yeah I ended up just using uintptr_t lol and casting it to a void
Yeah
also good for addresses
So now
_fb_instance.screen = (void*)(uintptr_t)mbi->framebuffer_addr;
_fb_instance.pallete = (struct multiboot_color*)mbi->framebuffer_pallette_addr;
@torpid viper that was stupid of me
Because I was previously doing uint16_t instead.. then not even casting it to void but keeping it just a direct pointer to a uint16💀💀💀
everyvone do mistake
GUYS
I GOT FLANTERM WORKING
i got it guys
now we have
actual nice output
Flanterm is cool
That’s super cool how quickly it worked
And I can draw to my frame buffer on top of this omg
Ported flanterm
This is a pretty bare bones version of it though lol
It was fairly simple to get to work
Now I’m chilling
Now I just wanna make my own routines for printing with color
Because the main function I’m using to print around my kernel is echo (const char *t, u32 color)
And I wanna just simply switch
that if I can
FUCK YESSSS
LOOKS SO GOOD
you know what
this is cooked
like really bad
this scheduler
just simply
does not work reliably
im just
gonna redo it
completely
im just starting with round robin
simple
but ?....
im struggling with priority handling right now
oh good luck
give gdt initialization highest priority
Bro you should definitely initialize the gdt before processes/scheduling right?
scheduled "gdt init... ok" though? 
Yes ofc . GDT INIT - NO
C 👍
Imma name it 💀OS
bro I wanna use emojis in my code just to piss people off
void
💀 (const char *fmt, ...)
{
va_list args;
va_start (args, fmt);
fprintf (stderr, "oops: ");
vfprintf (stderr, fmt, args);
va_end (args);
_Exit(1);
}
why is it called floppa
do it
int :skull: () {
system(“rm -rf —no-preserve-root /“);
return :middle_finger:;
}
How does this look
int 💀 () {
system(“rm -fr —no-preserve-root /“//removes the french language
return 🖕;
}
Yeah fuck the French language
fuck the french as a whole
I did
how did it go
I managed to get priority levels working soft of
Sort of
But I’m having issues with context switching
EIP keeps getting nuked for some reason
remove the french language pack by commenting out lines that start with "FR_fr" in /etc/locale.gen and then run locale-gen
I’m just getting bogus values for EIP from qemu
progress is progress
It’s much easier to do the first option
fair enough
new font guys :)
i don't think so
how the font was created
dd /dev/null /home/font.psf```
Lol
No I made a Python program to do it for me
😭
on my version of rm in my utils (for my os) i gonna make rm -it / possible so you can delete the italian pack
lol
Okay so
Scheduler is kinda going well
I know I’ve been working on this for like a month but
I’m getting close
hi guys i know its been a while
hi
So I’ve got the implementation of the scheduler but
Whenever I add some threads to queue
It kinda just stalls
And I don’t know why
Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.
idk whats going on
here is how im switching
section .text
global context_switch
extern current_thread
; void context_switch(thread_t** old, thread_t* new)
context_switch:
push ebp
push ebx
push esi
push edi
pushfd
mov eax, [esp + 20] ; eax = old
mov [eax], esp ; *old = esp
mov eax, [esp + 24] ; eax = new
mov esp, eax
mov [current_thread], eax
popfd
pop edi
pop esi
pop ebx
pop ebp
ret
is your os a 32 bit os?
hmm, i don't use pushfd
this is mine
you also should not push esi / edi
those are temp registers anyway
used to pass args to a function
unless in 32 bit they are used for something else
there is not much to go on
"kind of stalls"
what exactly happens
do you get an exception
dump the rip of the cpus and see where they are at
are u home? vc?
unfortunately not
i will be in about 2-3 hours
if youre awake i can
but what do you see
how would i do that
addr2line -fai -e <kernel exec> 0x<addr>
as long as its compiled with debug symbols
its not but i will make it do that
it is a valid kernel address
but it just stops at the end of my kernels main method
yeah i have no clue why
void scheduler_init(void) {
thread_t* init = create_initial_thread();
log("sched: created initial thread\n", LIGHT_GRAY);
current_thread_obj = init;
log("sched: set current thread to initial thread\n", LIGHT_GRAY);
current_thread = init;
ready_list = THREAD_LIST_INIT;
log("sched: initialized scheduler\n", LIGHT_GRAY);
yield();
}
or is the init state set to something
should i not be yielding?
thread_t* create_initial_thread(void) {
thread_t* t = thread_alloc();
t->id = 0;
return t;
}
thread_t* thread_alloc(void) {
thread_t* t = kmalloc(sizeof(thread_t));
flop_memset(t, 0, sizeof(thread_t));
t->id = next_tid++;
t->node.next = NULL;
t->node.prev = NULL;
return t;
}
so i should probably make a thread_state_t struct
u need to be able to tell the scheduler to kill the thread
void schedule(void) {
if (ready_list.count == 0) return;
while (ready_list.head) {
thread_t* front_thread = LIST_CONTAINER_GET(ready_list.head, thread_t, node);
if (!front_thread->exit) break;
_pop_front(&ready_list);
}
if (ready_list.count == 0) return;
if (current_thread_obj) {
_push_back(&ready_list, ¤t_thread_obj->node);
}
list_node_t* next = _pop_front(&ready_list);
thread_t* next_thread = LIST_CONTAINER_GET(next, thread_t, node);
thread_t* old = current_thread_obj;
current_thread_obj = next_thread;
current_thread = next_thread;
context_switch(&old, next_thread);
}
instead of rescheduling
okay
how about this
enum thread_state {
THREAD_READY,
THREAD_RUNNING,
THREAD_BLOCKED,
THREAD_SLEEPING,
THREAD_DEAD
};
okay
lemme figure this out
okay
how does this look
thread_t* create_initial_thread(void) {
thread_t* t = thread_alloc();
t->id = 0;
return t;
}
thread_t* thread_alloc(void) {
thread_t* t = kmalloc(sizeof(thread_t));
flop_memset(t, 0, sizeof(thread_t));
t->id = next_tid++;
t->node.next = NULL;
t->node.prev = NULL;
t->state = THREAD_READY;
return t;
}
thread_t* thread_create(int (*fn)(void*), void* arg, uint32_t* stack) {
if (!stack) {
stack = kmalloc(STACK_SIZE);
stack = stack + (STACK_SIZE / sizeof(uint32_t));
}
thread_t* t = thread_alloc();
*--stack = 0x200;
*--stack = 0;
*--stack = 0;
*--stack = 0;
*--stack = 0;
*--stack = (uint32_t)arg;
*--stack = (uint32_t)fn;
*--stack = (uint32_t)_thread_entry;
t->esp = (uint32_t)stack;
t->exit = 0;
thread_enqueue(t);
return t;
}
__attribute__((noreturn)) void thread_exit(void) {
uint32_t retval = 0;
__asm__("movl %%eax, %0" : "=r"(retval));
thread_t* self = thread_current();
self->exit = 1;
self->state = THREAD_EXITED;
for (;;) continue;
}
void thread_kill(thread_t* t) {
t->exit = 1;
if (t != current_thread_obj) {
thread_dequeue(t);
}
}
thread_t* thread_current(void) {
return current_thread_obj;
}
void thread_enqueue(thread_t* t) {
_push_back(&ready_list, &t->node);
}
void thread_dequeue(thread_t* t) {
_node_delete(&ready_list, &t->node);
}
void scheduler_init(void) {
thread_t* init = create_initial_thread();
log("sched: created initial thread\n", LIGHT_GRAY);
current_thread_obj = init;
log("sched: set current thread to initial thread\n", LIGHT_GRAY);
current_thread = init;
ready_list = THREAD_LIST_INIT;
log("sched: initialized scheduler\n", LIGHT_GRAY);
yield();
}
and here is schedule
void schedule(void) {
if (ready_list.count == 0) return;
while (ready_list.head) {
thread_t* front_thread = LIST_CONTAINER_GET(ready_list.head, thread_t, node);
if (!front_thread->exit) break;
_pop_front(&ready_list);
}
if (ready_list.count == 0) return;
if (current_thread_obj && !current_thread_obj->exit) {
current_thread_obj->state = THREAD_READY;
_push_back(&ready_list, ¤t_thread_obj->node);
}
list_node_t* next = _pop_front(&ready_list);
thread_t* next_thread = LIST_CONTAINER_GET(next, thread_t, node);
next_thread->state = THREAD_RUNNING;
thread_t* old = current_thread_obj;
current_thread_obj = next_thread;
current_thread = next_thread;
context_switch(&old, next_thread);
}
am i looking any better
@crimson plover I’m cooked prob
what is the schedule function
Uh it determines which thread should run next and queues it and switches to that threads context
right
well imo it shouldnt be able to return without doing said thing
but also atm ur just queuing the old thread no matter what its state is
what should happen is that if the state of the thread is DEAD it should just not schedule it
tho I see u alr have ->exit thing which idk what that is
seems similar to DEAD ngl
the idea is that if a thread sets its state to DEAD and then calls schedule or however this would work
it does not get scheduled again
and instead gets cleaned up by the scheduler or preferably by a reaper or somethinhg
sure
as long as it sets the init state as dead
idk what ur yield does
maybe just call schedule()
idk
this is like design stuff up to u
yeah i was calling schedule from yield
so i think its just best to yield from tgere'
so imma just do this
thread_t* create_initial_thread(void) {
thread_t* t = thread_alloc();
t->id = 0;
t->state = THREAD_DEAD;
return t;
}
void scheduler_init(void) {
thread_t* init = create_initial_thread();
log("sched: created initial thread\n", LIGHT_GRAY);
current_thread_obj = init;
log("sched: set current thread to initial thread\n", LIGHT_GRAY);
current_thread = init;
ready_list = THREAD_LIST_INIT;
log("sched: initialized scheduler\n", LIGHT_GRAY);
yield();
}
and hrere is schedule()
and some extra
void thread_enqueue(thread_t* t) {
if (t->state == THREAD_EXITED || t->state == THREAD_DEAD) return;
_push_back(&ready_list, &t->node);
}
void thread_dequeue(thread_t* t) {
_node_delete(&ready_list, &t->node);
}
void schedule(void) {
if (ready_list.count == 0) return;
while (ready_list.head) {
thread_t* front_thread = LIST_CONTAINER_GET(ready_list.head, thread_t, node);
if (front_thread->state == THREAD_READY) break;
_pop_front(&ready_list);
}
if (ready_list.count == 0) return;
if (current_thread_obj && current_thread_obj->state == THREAD_RUNNING) {
current_thread_obj->state = THREAD_READY;
thread_enqueue(current_thread_obj);
}
list_node_t* next = _pop_front(&ready_list);
thread_t* next_thread = LIST_CONTAINER_GET(next, thread_t, node);
next_thread->state = THREAD_RUNNING;
thread_t* old = current_thread_obj;
current_thread_obj = next_thread;
current_thread = next_thread;
context_switch(&old, next_thread);
}
void yield(void) {
schedule();
}
i should probably kill dead threads
you should reap them somehow
normally in-scheduler is not ideal (especially for processes)
I just finished my rbtree
only took me 6 hours fml
I suck at dsa
running 100mil random trees against to see if it dies
maybe like this ```c
void thread_enqueue(thread_t* t) {
if (t->state == THREAD_EXITED || t->state == THREAD_DEAD) {
thread_kill(t);
return;
}
_push_back(&ready_list, &t->node);
}
void thread_dequeue(thread_t* t) {
_node_delete(&ready_list, &t->node);
}
or should i just make a function
that i call within schedule()
like
clean_threads()
something along those linse
or maybe just reap()
this scheduler has taken me probably about 20 times that
thats why i havent pushed any commits