#The Flopperating System (tfos) - x86 32 bit Hobby OS

1 messages · Page 4 of 1

bold grove
#

and the kernel gets 1gb

#

(32 bit reemeber)

#

im kinda

#

losing my mind

#

but i finall got this part done

#
uintptr_t virt_to_phys(uintptr_t vaddr) {
    vaddr = _align_down(vaddr);

    uint32_t pd_idx = _pd_index(vaddr);
    uint32_t pt_idx = _pt_index(vaddr);

    if (!RESOLVE_RECURSIVE_PD[pd_idx].present) return 0;
    pte_t* pt = RESOLVE_RECURSIVE_PT(pd_idx);
    if (!pt[pt_idx].present) return 0;

    // resolve paddr from vaddr
    // - frame_addr << 12: converts frame number to paddr (upper 20 bits)
    // - vaddr & 0xFFF: preserves the page offset (lower 12 bits)
    return (pt[pt_idx].frame_addr << 12) | (vaddr & 0xFFF);
}


uintptr_t phys_to_virt(uintptr_t paddr) {
    paddr = _align_down(paddr);

    for (uint32_t pd_idx = 0; pd_idx < PAGE_DIRECTORY_SIZE; pd_idx++) {
        if (!RESOLVE_RECURSIVE_PD[pd_idx].present) continue;

        pte_t* pt = RESOLVE_RECURSIVE_PT(pd_idx);
        for (uint32_t pt_idx = 0; pt_idx < PAGE_TABLE_SIZE; pt_idx++) {
            if (!pt[pt_idx].present) continue;

            // check if entry maps to paddr 
            if ((pt[pt_idx].frame_addr << 12) == paddr) {
                // resolve vaddr from paddr
                // - pd_idx << 22: pd index in bits 31-22
                // - pt_idx << 12: pt index in bits 21-12
                // - paddr & 0xFFF: page offset in bits 11-0
                return (pd_idx << 22) | (pt_idx << 12) | (paddr & 0xFFF);
            }
        }
    }
    return 0;
}
#

i added comments so i dont completely have a stroke reading this next time

small hound
bold grove
#

My stupid laptop ain’t connecting to wifi now

small hound
#
void schedule(void) {
    struct scheduler *sched = get_this_core_sched();

    bool interrupts = spin_lock(&sched->lock);

    struct thread *curr = sched->current;
    struct thread *next = NULL;
    requeue_current_thread_if_runnable(sched, curr);

    struct thread *stolen = scheduler_try_do_steal(sched);

    next = stolen ? stolen : scheduler_pick_regular_thread(sched);

    if (!next) {
        next = load_idle_thread(sched);
    } else {
        change_timeslice(sched, next);
    }

    load_thread(sched, next);
    update_core_current_thread(next);

    spin_unlock(&sched->lock, interrupts);

    if (curr && next)
        switch_context(&curr->regs, &next->regs);
    else
        load_context(&next->regs);
}``` for example, here I tried a bit to make the code document itself
#

do you think you understand what is going on

bold grove
#

Yeah perfectly

#

Shit let me true

#

Try*

small hound
#

ya you can try that

bold grove
#

Well

#

I’m restarting my bullshit fuckass router

#

idk

blissful grove
#

Not quite, that would be fine but is doing extra work. You only need to invalidate tlb entries when reducing permissions.

small hound
#

oh interesting, neat

bold grove
#

so

#

i did what you said @small hound

blissful grove
#

That's for x86 only btw

bold grove
#
uintptr_t virt_to_phys(uintptr_t vaddr) {
    vaddr = _align_down(vaddr);

    uint32_t pd_idx = _pd_index(vaddr);
    uint32_t pt_idx = _pt_index(vaddr);

    if (!RESOLVE_RECURSIVE_PD[pd_idx].present) return 0;
    pte_t* pt = RESOLVE_RECURSIVE_PT(pd_idx);
    if (!pt[pt_idx].present) return 0;

    uintptr_t paddr = _fetch_frame_paddr(pt[pt_idx].frame_addr);
    return _resolve_paddr(paddr, vaddr);
}

uintptr_t phys_to_virt(uintptr_t paddr) {
    paddr = _align_down(paddr);

    for (uint32_t pd_idx = 0; pd_idx < PAGE_DIRECTORY_SIZE; pd_idx++) {
        if (!RESOLVE_RECURSIVE_PD[pd_idx].present) continue;

        pte_t* pt = RESOLVE_RECURSIVE_PT(pd_idx);
        for (uint32_t pt_idx = 0; pt_idx < PAGE_TABLE_SIZE; pt_idx++) {
            if (!pt[pt_idx].present) continue;

            if (_fetch_frame_paddr(pt[pt_idx].frame_addr) == paddr) {
                return _resolve_vaddr(pd_idx, pt_idx, _fetch_page_offset(paddr));
            }
        }
    }
    return 0;
}
#

modularized it

#

and yes i always make subroutines have underscores

bold grove
blissful grove
#

Nah anything x86

bold grove
#

ah okay

#

i see

blissful grove
#

Long mode, protected mode whatever comes next

small hound
# bold grove ```c uintptr_t virt_to_phys(uintptr_t vaddr) { vaddr = _align_down(vaddr); ...

my thingy works like this (ignore the jank please, i didnt spend time making it pretty)

uintptr_t vmm_get_phys(uintptr_t virt) {

    struct page_table *current_table = kernel_pml4;
    for (uint64_t i = 0; i < 3; i++) {
        uint64_t level = virt >> (39 - (i * 9)) & 0x1FF;
        pte_t *entry = &current_table->entries[level];
        if (!ENTRY_PRESENT(*entry))
            return -1;

        current_table =
            (struct page_table *) ((*entry & PAGING_PHYS_MASK) + hhdm_offset);
    }

    uint64_t L1 = (virt >> 12) & 0x1FF;
    pte_t *entry = &current_table->entries[L1];
    if (!(ENTRY_PRESENT(*entry)))
        return (uintptr_t) -1;

    return (*entry & PAGING_PHYS_MASK) + (virt & 0xFFF);
}```
blissful grove
#

Long double mode meme

bold grove
#

wait

#

so in paging.c

#

i map the memory or whatever

#

i stat with identity map

#

then kernel space in upper 1g

#

and 3gb for the user

#

is this like

small hound
#

why do you fix the memory amounts lol

bold grove
#
void paging_init(void) {
    log("paging: Initializing paging...\n", LIGHT_GRAY);
    // zero page directory entries
    for (int i = 0; i < PAGE_DIRECTORY_SIZE; i++) {
        page_attrs_t attrs = {0};
        SET_PF((pte_t *)&pd[i], attrs);
    }

    // identity map first 4MB. nuke ts later
    for (int i = 0; i < PAGE_TABLE_SIZE; i++) {
        page_attrs_t attrs = {
            .present = 1,
            .rw = 1,
            .user = 0,
            .frame_addr = i
        };
        SET_PF(&pt0[i], attrs);
    }
    pd[0].present = 1;
    pd[0].rw = 1;
    pd[0].user = 0;
    pd[0].table_addr = ((uintptr_t)pt0) >> PAGE_SIZE_SHIFT;

    // map kernel at 3GB, starting from physical address 0x00100000
    for (int i = 0; i < PAGE_TABLE_SIZE; i++) {
        page_attrs_t attrs = {
            .present = 1,
            .rw = 1,
            .user = 0,
            .frame_addr = (K_PHYS_START >> PAGE_SIZE_SHIFT) + i
        };
        SET_PF(&pt1[i], attrs);
    }
    pd[K_PD_INDEX].present = 1;
    pd[K_PD_INDEX].rw = 1;
    pd[K_PD_INDEX].user = 0;
    pd[K_PD_INDEX].table_addr = ((uintptr_t)pt1) >> PAGE_SIZE_SHIFT;

    // map kernel heap 
    for (int i = 0; i < PAGE_TABLE_SIZE; i++) {
        page_attrs_t attrs = {
            .present = (i != PAGE_TABLE_SIZE - 1) ? 1 : 0,
            .rw = 1,
            .user = 0,
            .frame_addr = (K_HEAP_START >> PAGE_SIZE_SHIFT) + i
        };
        SET_PF(&pt2[i], attrs);
    }
    pd[K_HEAP_PD_INDEX].present = 1;
    pd[K_HEAP_PD_INDEX].rw = 1;
    pd[K_HEAP_PD_INDEX].user = 0;
    pd[K_HEAP_PD_INDEX].table_addr = ((uintptr_t)pt2) >> PAGE_SIZE_SHIFT;

    // map userspace (lower 3GB)
    for (int i = 1; i < K_PD_INDEX; i++) {
        pd[i].present = 1;
        pd[i].rw = 1;
        pd[i].user = 1;  
    }

    load_page_directory((uintptr_t)pd);
    enable_paging();
    log("paging: Paging initialized.\n", GREEN);

    // note: at some point after vmm_init, we need to remove the identity map
    
}
bold grove
#

4gb mem limit

small hound
#

that doesnt mean u have to make the memory amounts static tho koreablob

bold grove
#

it wont map past memory that doesnt exist

small hound
#

no i mean you can dynamically adjust how much memory you give kernel vs userspace

#

like you can shrink the amount of memory you give to the kernel and give that to user

#

for example once you have a page cache and a userspace program starts up and eats a lot of memory, you might start evicting things from your page cache, thereby making kernel space memory "smaller" and userspace memory "bigger"

bold grove
#

i see

#

yeah i do my page cache in pmm though

small hound
#

no need to make them fully static

small hound
#

page caches are for the virtual filesystem

#

why would the memory managers deal with that lol

bold grove
#

oh i was talking about

#

like page info

#

in the pmm

#

this

#
struct Page {
    uintptr_t address;
    uint32_t order;
    int is_free;
    struct Page* next;
};
small hound
#

this is the allocator i suppose

#

neat

bold grove
#

yeah

#

my pmm im also redoing

bold grove
#

@blissful grove is that wrong or am i tripping

small hound
#

that is not what that is called, no

bold grove
#

what is that called then

small hound
#

here, probably freelists?

bold grove
#

ohhhh

#

wait

#

i see

small hound
#

ya it's a list

bold grove
#

so

#

i should probably

#

change this naming scheme

small hound
#

call it freelist_entry probably

bold grove
#
static void _init_page_cache(struct Page* page, uintptr_t address, uint32_t order) {
    page->address = address;
    page->order = order;
    page->is_free = 1;
    page->next = buddy.free_list[order];
    buddy.free_list[order] = page;
}

static void create_page_cache(void) {
    for (uint32_t i = 0; i < buddy.total_pages; i++) {
        struct Page* page = &buddy.page_info[i];
        _init_page_cache(page, buddy.memory_start + (i * PAGE_SIZE), MAX_ORDER);
    }
}

small hound
#

yea that's just your allocator initialization

#

something like pmm_freelist_init

bold grove
#

mmmm

#

very nice

#
static void _add_page_to_free_list(struct Page* page, uintptr_t address, uint32_t order) {
    page->address = address;
    page->order = order;
    page->is_free = 1;
    page->next = buddy.free_list[order];
    buddy.free_list[order] = page;
}

static void _create_free_list(void) {
    for (uint32_t i = 0; i < buddy.total_pages; i++) {
        struct Page* page = &buddy.page_info[i];
        _init_page_cache(page, buddy.memory_start + (i * PAGE_SIZE), MAX_ORDER);
    }
}

static void buddy_init(uint64_t total_memory, uintptr_t memory_start) {
    buddy.total_pages = total_memory / PAGE_SIZE;
    buddy.memory_start = memory_start;
    buddy.memory_end = buddy.memory_start + buddy.total_pages * PAGE_SIZE;

    buddy.page_info = (struct Page*)buddy.memory_start;
    buddy.memory_start += buddy.total_pages * sizeof(struct Page);

    _create_free_list();
}

static uint64_t iterate_mb_mmap(multiboot_info_t* mb_info) {
    uintptr_t mmap_addr = (uintptr_t)mb_info->mmap_addr;
    uintptr_t mmap_end = mmap_addr + mb_info->mmap_length;
    multiboot_memory_map_t* mmap = (multiboot_memory_map_t*)mmap_addr;

    uint64_t total_memory = 0;

    // iterate through memory regions from mb
    while ((uintptr_t)mmap < mmap_end) {
        // add avaiable regions and skip reserved ones
        if (mmap->type == MULTIBOOT_MEMORY_AVAILABLE) {
            total_memory += mmap->len;
        }
        
        // move to next entry
        mmap = (multiboot_memory_map_t*)((uintptr_t)mmap + mmap->size + sizeof(mmap->size));
    }

    return total_memory;
}
#

lets gooo

#

man yll

#

maybe i should be sober for a while

#

i get shit done

#

this is the first night ive been sober in a few months honestly

small hound
#

stress test when

bold grove
#

oh this allocator design works great

#

unless youre talking about mental strength

#

ive already tested it

#

many times

small hound
#

is it SMP compatible and lockless

#

or will there be big lock contention

bold grove
#

no no its lockless and works with my scehduler

small hound
#

are you multicore

#

or are you single core

bold grove
#

yeah, but i limited it to 8 cores for now

#

im still sussint that shit out

#

smp is fucky on ia32

small hound
#

so did you test if all 8 cores can concurrently do allocations

#

does that work without locks

#

how did you make this without locks

#

send your lockless singly linked list operations

#

for the freelist entries

bold grove
#

like how im adding to the free list?

#

or

small hound
#

yea

bold grove
#

wait so

#

like in free_page

#

?

small hound
bold grove
#

i do that in my buddy split and buddy merge functions

small hound
#

how do they look

#
void list_push(lockless_list_t *list, node_t *new_node) {
    tagged_ptr_t old_head, new_head;

    do {
        old_head = atomic_load(&list->head);

        new_node->next = old_head.ptr;

        new_head.ptr = new_node;
        new_head.count = old_head.count + 1;

    } while (!atomic_compare_exchange_weak(&list->head, &old_head, new_head));
}
``` a lockless singly linked list should look something like this
bold grove
#

oh nah

#

i dont got the atomic shit

small hound
#

so it isn't lockless

#

so how is it SMP compatible/thread safe

bold grove
#

it cant do it "concurrently"

#

like my scheduler doesnt support that

small hound
small hound
bold grove
#

no , what i mean is that i cant run many things at once so technically i dont have a need for a lockless system

small hound
bold grove
#

create queues for each one and switch through them when i call schedule()

#

which yeah is basically single core

small hound
#

by cores do you mean threads

#

i am now very perplexed

bold grove
#

no

#

its hard to explain

small hound
bold grove
#

basically

#

i have a global integer of what core is currently being used

#

and as i schedule

#

i switc between them

#

so if schedule() gets called

#

core 1 will do it

#

and the next time schedule is called

small hound
#

why is it necessary to switch between cores 💥 ⁉️

bold grove
#

core 2 will do it

small hound
#

why not just run it all on core 0

bold grove
#

i mean i guess i could

small hound
#

that is what you should do

#

there is no need for needless task/thread migration

#

that just makes the cache unhappy and the performance is the same if you just run it all on one core

blissful grove
bold grove
#

so array of page structs is a PFN

#

ill take that into accont

#

i for some reason thought a page cache was that

blissful grove
#

well the pfndb refers to the whole array

#

nah they're different entities 🙂

bold grove
#

man

blissful grove
#

and dw about lockless stuff and migrating threads for now. Start simple and get it working with locks lol, then worry about that stuff

bold grove
#

people always say the buddy system is the hard one to implment

#

but its really not

blissful grove
#

yeah I think people are always put off by the little bit of theory at the start

bold grove
#

i should spinlock at the beginning and end of each pmm function (thats what ive seen)

blissful grove
#

but like all things it has pros and cons

small hound
bold grove
#

basically

blissful grove
bold grove
#

yeah i know it prevents other cores and operations from happening

#

so yeah it should be like that

blissful grove
#

only for things that need protecting

bold grove
#

isnt that like

#

kernel shit

#

like pmm,vmm, scheduler

#

etc

#

pretty much everytihng that doesnt run in the userspace?

blissful grove
#

yeah, so the easiest way to deal with locking is to have a big kernel lock

#

where you lock "the whole kernel" and dont allow any concurrent access

#

that does the job, but its not great

#

I'd suggest looking at locking per-subsystem

bold grove
#

i think i can probably do that for now

small hound
#

a good piece of advice is to "Do the finest grained locks you can easily do" - hyenasky

bold grove
#

i see many source codes

#

where they have like

#

a special scheduler mutex

#

and other ones for other subsystems

blissful grove
#

so look at your pmm, and ask yourself "what would happen if this code ran on two cpus at the same time", or if you got interrupt at a random point and maybe even got pre-empted.

#

as an example

small hound
small hound
bold grove
#

so basically

#

system doing its thing -> pmm operation call -> lock -> do the pmm operation -> unlock -> continue

blissful grove
#

pretty much

bold grove
#

and the system contnues as normal

#

okay i see

blissful grove
#

but only if the pmm operation needs to access some shared state

#

if the pmm operation is "convert this paddr to its struct page index", you dont really need to lock on that

#

or maybe you do

bold grove
#

like inline stuff?

#

if im accessing page tables or freeing or allocating its safe to say i should lock

small hound
#

you also might wanna get refcounting up and running right now too, since you'll soon be designing subsystems where you want to free things only once all references are gone

#

refcounting is really simple

blissful grove
bold grove
#

i get that but

#

what is an exampe of a shared state

#

like

#

something that is modified by many "subsystems"

small hound
#

a file

#

you don't have to think about it in terms of "subsystems"

#

let's just say two user programs wanna write to the same file at the same time and give certain offsets and lengths for their operations

#

you'd wanna do some form of locking there

blissful grove
bold grove
#

i see so files, memory, etc

#

i see

blissful grove
#

so your pmm buddy is an example. If you call pm_alloc() in multiple places (say threads, these could run on the same or different cpus) what would happen?

#

what if someone calls pm_free() at the same time as a call to pm_alloc() is already ongoing?

bold grove
#

it would cause some sort of fault

small hound
#

or just corrupted state

bold grove
#

ye ye

#

anything that is accessed in many places, needs to be locked before and afer the operation

#

so is pm free and alloc are called

#

at the same time

#

it will safely do the first operation

#

so

#

pm_alloc and pm_free called -> pm_alloc lock -> pm alloc operation -> pm_alloc unlock -> pm_free lock -> pm free operation -> pm_free unlock

#

and lets say

#

i need to access a file

#

if igot 2 requests or syscalls or whatever

#

that would be similar

#

ahh

#

i got it

#

but how do i determine the locking order?

small hound
#

now is about the time you wanna get a blocking mutex

bold grove
#

@blissful grove

small hound
bold grove
#

isnt a mutex just a lock?

small hound
#

mutex has an owner thread and waiters

bold grove
#

yeah mutual exclusion

small hound
#

but you also have priority inheritance in some implementations

blissful grove
#

yeah easy answer first is that a mutex is a blocking lock (if you fail to acquire it, you get suspened and will be run again when you can try to acquire it again) , compared to a spinlock that just spins

blissful grove
small hound
#

oh i thought you meant the order in which things acquire a lock

#

nvm

blissful grove
bold grove
#

but generally thats how it would be done?

blissful grove
#

yeah

small hound
# bold grove yeah mutual exclusion

except here, a blocking mutex will block the thread, mark it as a waiter on the mutex, and the unlock operation on the mutex will wake the waiter(s)

blissful grove
#

you have 1 resource (the pmm buddy), so it gets 1 lock

bold grove
#

and these can just be spinlocks right?

blissful grove
#

yeah they can be, you can get away with spinlocks for a long time (lol)

#

they're not always the best choice though

small hound
blissful grove
#

but yes, it would work

bold grove
#

for a pmm it should be enough though?

small hound
#

as long as it isn't super slow

#

if it somehow ends up taking a long time it might be less than ideal to spin

#

but yea it should be ok

bold grove
#

yeah i wont like map entire huge ass regions with a spin lock or something

small hound
#

for future reference once you implement wait-type locks

#

this is from NT, so i figured it might help u since i think you're familiar with NT

#

and their silly strange pushlocks

bold grove
#

nt my beloved

small hound
#

have you looked into their pushlocks

#

definitely an approach of all time to pointer sized locks

bold grove
#

i read up on their locking stuff once

#

but maybe i should again

small hound
bold grove
#

well i learned how mutual exclusion works and how theres an owner and waiters and that stuff but

#

i didnt learn about the push lokcs

blissful grove
#

dont worry about them for now lol

#

a spinlock is fine for your pmm

small hound
#

locking is cool

#

but yea dont worry about it until it becomes a genuine concern

bold grove
#

okay

#

so for multiprocessing then

#

i should create a queue for each processor

#

but how can they run "concurrently"

#

imkinda struggling with that

blissful grove
#

each cpu core is mostly independent, minus some MSRs they share

#

so they can all run code at the same time

small hound
#

I'm assuming it's just a simple round-robin

bold grove
#

yeah

small hound
#

then, start making a multicore round-robin, so make one scheduler per core

bold grove
#

it works on one core thats how i initially made it

small hound
#

once you get your multicore round-robin working, you can choose what direction you wanna go next. MLFQ is the next easy step up since that's just RR x how many queues you have. you can do CFS if you are high enough on drugs to wanna do that

#

and then you can implement work stealing/rebalancing

bold grove
#

i see

#

but when i run a thread

#

how does it know whih cpu to run on?

small hound
#

simple stuff

bold grove
#

okay i see

small hound
#

or just explicitly specify it if needed

blissful grove
#

you choose that when the thread is marked as ready to run, you would place it that cpu's work queue

bold grove
#

so for threads its just a circular doubly linked list for each core

#

mine is like that but just one

#

global one

small hound
#

yes

blissful grove
#

I wouldnt make it circular, its just a queue

small hound
#

faster

blissful grove
#

?

bold grove
#

how does that make it faster

small hound
#

wait nvm, yeah it doesnt need to be circular

#

I misinterpreted that as double vs singularly linked

#

you'll probably want it doubly linked to avoid that O(n) removal

bold grove
#

okay doubly linked list for each core got it

small hound
bold grove
#

well i had it in my old scheduler

#

my single core one

small hound
#

how did that work

bold grove
#

it was pretty primitive

#

well tasks with highest priorities were bumped from the bottom of the list

#

but the issue

#

is that i starved lower priorities

small hound
#

"We have MLFQ at home"

small hound
bold grove
#

yeah thats what i was told

small hound
#

and then boost them once enough time has passed

bold grove
#

but then i kinda gave up

small hound
#

MLFQ is better for this

#

RR is ok if you dont do thread priorities

bold grove
#

i only have experience with schedulers in like 16 bit real mode

#

that was most of the os experience i had

small hound
#

why were you scheduling in 16 bit real mode 😭

bold grove
#

becaue i made a multutasking operating system for 16 bit real mode

small hound
#

interesting

#

where are all of these operating systems now

bold grove
#

well this 16 bit one i never will post and nevre want to

#

my old old 64 bit kernel islost to time

#

like idk where it went

#

i suck at version control

#

a lot of the desig of my 16 bit kernel went into this one

#

i quit 16 bit because of

#

segmentation

#

Well guys

#

I got some stuff done tonight

#
  • Vmm rewrite works
  • Pmm rewrite works
  • Paging is good
  • I’m now using my allocator for Flanterm instead of the built in one
#

But now my scheduler is borked for some reason

#

More fun for tomorrow

#

I’m gonna keep myself to this

#

4 more days until GitHub repo drop

#

Goals for tomorrow:

  • fix sched
  • implement testing suite
#

Good night guys

#

Thank you for the help @blissful grove and @small hound

#

I promise you will have some fun code to look at soon

bold grove
#

I page fault

#

Whenever creating address spaces

#

In the lower 3g

#

Thats not my hands btw

#

😭😭

bold grove
#

Everything is fucked up bro

#

😭😭

#

Maybe more progress tonight if I’m up to it. Shits just going down

bold grove
#

i just want to take this time

violet anchor
#

this OS is familar

bold grove
#

to show how much of a fucking pain in the ass it is

bold grove
#

to set up pixel buffer color

#

void framebuffer_set_pixel_buffer(int x, int y, uint32_t color) {
    if (x < 0 || (uint32_t)x >= _fb_instance.width ||
        y < 0 || (uint32_t)y >= _fb_instance.height) return;

    uint8_t *_dest_fb = _fb_instance.screen;
    switch (_fb_instance.bpp) {
        case 8: {
            uint8_t *pixel = _dest_fb + _fb_instance.pitch * y + x;
            *pixel = color & 0xFF;
            break;
        }
        case 15: {
            uint16_t *pixel = (uint16_t *)(_dest_fb + _fb_instance.pitch * y + 2 * x);
            uint16_t r = (color >> 16) & 0x1F;
            uint16_t g = (color >> 8) & 0x1F;
            uint16_t b = color & 0x1F;
            *pixel = (r << 10) | (g << 5) | b;
            break;
        }
        case 16: {
            uint16_t *pixel = (uint16_t *)(_dest_fb + _fb_instance.pitch * y + 2 * x);
            uint16_t r = (color >> 16) & 0x1F;
            uint16_t g = (color >> 8) & 0x3F;
            uint16_t b = color & 0x1F;
            *pixel = (r << 11) | (g << 5) | b;
            break;
        }
        case 24: {
            uint8_t *pixel = _dest_fb + _fb_instance.pitch * y + 3 * x;
            pixel[0] = (color >> 0) & 0xFF;   // Blue
            pixel[1] = (color >> 8) & 0xFF;   // Green
            pixel[2] = (color >> 16) & 0xFF;  // Red
            break;
        }
        case 32: {
            uint32_t *pixel = (uint32_t *)(_dest_fb + _fb_instance.pitch * y + 4 * x);
            *pixel = color;
            break;
        }
    }
}

violet anchor
#

thats horrible, why multiboot2?

bold grove
#

its multiboot 1

violet anchor
#

even worse lol

bold grove
#

i just like 32 bit

#

and ive just stuck with it idk

violet anchor
#

no better bootloader?

bold grove
#

i dont wanna end up being another one of the limine x64 kernels that use flanterm

bold grove
violet anchor
#

lol

bold grove
#

once you get past the pain

#

its not so bad

violet anchor
#

just ... dont be a posix sheep

#

and youll be unique

bold grove
#

thats what i mean

#

im not conforming to that at all

#

my system calls im making

#

have nothing to d iwth unix

#

all the software im making for it

violet anchor
#

conforming, im deliberately pretending it doesnt exist.

bold grove
#

based

#

its obviusly like

#

reasonable to follow the standard

#

if you want shit to run

#

but no

#

i don want bash

#

i want flopsh

violet anchor
#

well technically it "does", but in the "world" where my OS would exist, posix never spread outside expensive mainframes.

bold grove
#

i havent reference the systemv abi ONCE

violet anchor
#

my shell is rocketsh, its written in BASIC 😄

bold grove
#

huh i wonder why

#

is your whole OS in basic?

violet anchor
#

apart from the kernel yes

#

thats the shell

#

the kernel has a multitasking basic interpreter in it

bold grove
#

userspace shell?

violet anchor
#

there is no userspace

bold grove
#

you made a tcp

#

impl

#

wjat the fuck

violet anchor
#

everything is ring 0, it acts like an old microcomputer, "mess around and find out", its intentional

#

poke it, peek it, its single user

bold grove
#

tcp in kernel space is

#

fucking awesome but horrible

#

at the same time lol

violet anchor
#

linux has tcp in kernel space... just in a module

bold grove
#

no no i know

#

but accessing the internet entirely from kernel space

#

is just funny

violet anchor
#

there is no "kernel space" in my OS

bold grove
#

you right

violet anchor
#

its just all the same

bold grove
#

everything is just

#

yeah

#

what does your gdt look lik then

#

is it a single address system too?

violet anchor
#

youre protected from completely messing up by the security on the interpreter, but it doesnt protect you from your intentions only your mistakes

#

if you decide you want to directly poke the PCI regs, you can

bold grove
#

i mean like single addres space

violet anchor
#

oh and its all identity mapped

bold grove
#

thats insane what

violet anchor
#

the interpreter doesnt need sepatate address spaces to mutlitask

bold grove
#

damn dude

violet anchor
#

i strongly believe in keep it simple

bold grove
#

kinda reminds me somehwt of temple os

violet anchor
#

yeah

bold grove
#

in the way that it doesnt really care about userspace

#

or like

violet anchor
#

except in 64 bit long mode, without religious undertones

bold grove
#

even has one at all

#

i thought temple was 64 bit long mode

violet anchor
#

iirc it was real mode

#

no youre right its long mode

bold grove
#

i just rewrote my slab allocator

#

it used to be like

#

400 lines

#

for nom reason

violet anchor
#

yeah so its basically the same memory model as templeos except i do multitasking

#

i just looked it up

violet anchor
bold grove
#

i cant paste the whole thing for now

#

but here is how

#

im creating a memory pool for the slab structs

#
// memory pool for allocating slab structs
// this prevents slab allocations for slab structs because that's retarded
// the pool is made up of a linked list of physical pages
// these are divided by the size of the slab_t struct, 
// we will start off with one physical page
// we will do a check in _slab_pool_alloc to see if we need to allocate a new page
// if we do, alloc a new page and append it to the linked list
typedef struct slab_pool_page {
    struct slab_pool_page* next;
    slab_t slab_array[]; // array of slab_t structs, size is determined by SLAB_METADATA_PER_PAGE
} slab_pool_page_t;

static slab_pool_page_t* slab_pool_head = NULL; // head of linked list
static uint32_t slab_meta_used = 0;
static slab_t* current_slab_array = NULL;

static void _slab_pool_init() {
    slab_pool_head = (slab_pool_page_t*)pmm_alloc_page();
    if (!slab_pool_head) return;

    slab_pool_head->next = NULL;
    current_slab_array = slab_pool_head->slab_array;
    slab_meta_used = 0;
}

static inline int _slab_pool_need_new_page() {
    return (!current_slab_array || slab_meta_used >= SLAB_METADATA_PER_PAGE);
}

static int _slab_pool_add_page() {
    slab_pool_page_t* new_page = (slab_pool_page_t*)pmm_alloc_page();
    if (!new_page) return 0;

    new_page->next = NULL;

    // Append to the linked list
    slab_pool_page_t* iter = slab_pool_head;
    while (iter->next) iter = iter->next;
    iter->next = new_page;

    current_slab_array = new_page->slab_array;
    slab_meta_used = 0;
    return 1;
}
static slab_t* _slab_pool_alloc() {
    if (_slab_pool_need_new_page()) {
        if (!_slab_pool_add_page()) return NULL;
    }
    return &current_slab_array[slab_meta_used++];
}
#

@violet anchor

#

fixed

bold grove
#

Guys

#

I’m pagefaulting

#

😔

#

Fucking everywhere

#

I suspect it’s an issue in my vmm

violet anchor
#

what has changed?

bold grove
#

I suspect

#

It has to do with how I’m mapping the kernel

#

I’m getting the size of the kernel via the linker

#

And I think somewhere I’m messing that up

#

I map the kernel, then the whole kernel space , then the userspace

bold grove
#

guys

#

this is a dumbass

#

thing i did

#

my damn vmm

#

so basically i have submethods for the pd and pt levels

#

(remeber 32 bit)

#

so i walk those tables here

#
int map_page(uintptr_t vaddr, uintptr_t paddr, page_attrs_t attrs) {
    vaddr = _align_down(vaddr);
    paddr = _align_down(paddr);
    if (vaddr == 0) return -1; 

    uint32_t indices[2] = {_pd_index(vaddr), _pt_index(vaddr)};
    pte_t* tables[2] = {RESOLVE_RECURSIVE_PD, NULL};
    for (int i = 0; i < 2; i++) { // walk page table levels
        switch (i) {
            case 0:
                _pd_lvl(tables, indices, attrs);
            case 1:
                _pt_lvl(tables, indices, paddr, attrs);
        }
    }

    __asm__ volatile("invlpg (%0)" :: "a"(vaddr));
    return 0;
}
#

so

#

looks fine and dandy

#

my dumbass

#

made the pd and pt functions have different

#

return types

#

so one of the return types of void and one is int

#
static int _pd_lvl(pte_t* tables[2], uint32_t indices[2], page_attrs_t attrs) {
    if (!tables[0][indices[0]].present) {
        pte_t* new_pt = (pte_t*)pmm_alloc_page();
        if (!new_pt) return -1;
        _zero_pt((pte_t*)((uintptr_t)new_pt));

        page_attrs_t new_attrs = {
            .present = attrs.present,
            .rw = attrs.rw,
            .user = attrs.user,
            .frame_addr = ((uintptr_t)new_pt) >> 12
        };
        SET_PF((pte_t*)&tables[0][indices[0]], new_attrs);
    }
    tables[1] = RESOLVE_RECURSIVE_PT(indices[0]);
    return 0;
}

static void _pt_lvl(pte_t* tables[2], uint32_t indices[2], uintptr_t paddr, page_attrs_t attrs) {
    if (tables[1][indices[1]].present) {
        pmm_free_page((void*)(tables[1][indices[1]].frame_addr << 12));
    }
    attrs.frame_addr = paddr >> 12;
    SET_PF(&tables[1][indices[1]], attrs);
}
#

💀 💀

bold grove
#

im rewriting my idt:)

#

and just my interrupts in general

bold grove
#

im so fucking close guys

#

to the commit

small hound
#

top ten things that would sound terrible out of context

bold grove
#

I rewrote my idt

#

I didn’t name it that though

#

I just have one folder called interrupts

#

And interrupts.c, interrupts,h, and interrupt_asm.asm

small hound
#

might as well call it interrupts_c.c and interrupts_h.h to make the naming consistent trl

small hound
#

forgot the _c

bold grove
#

💀

#

i know that naming is unconventional but

bold grove
#

i made a function to clone a pagemap

#
int clone_pagemap(uintptr_t src_base, uintptr_t dst_base, size_t size) {
    size_t pages = _align_up(size) / PAGE_SIZE;
    for (size_t i = 0; i < pages; i++) {
        uintptr_t src_vaddr = src_base + i * PAGE_SIZE;
        uintptr_t dst_vaddr = dst_base + i * PAGE_SIZE;

      
        uint32_t src_pd_idx = _pd_index(src_vaddr);
        uint32_t src_pt_idx = _pt_index(src_vaddr);

        if (!RESOLVE_RECURSIVE_PD[src_pd_idx].present)
            continue; 

        pte_t* src_pt = RESOLVE_RECURSIVE_PT(src_pd_idx);
        if (!src_pt[src_pt_idx].present)
            continue; 

        uintptr_t src_paddr = src_pt[src_pt_idx].frame_addr << 12;
        page_attrs_t attrs = {
            .present = src_pt[src_pt_idx].present,
            .rw = src_pt[src_pt_idx].rw,
            .user = src_pt[src_pt_idx].user
        };

        void* dst_frame = pmm_alloc_page();
        if (!dst_frame)
            return -1;

        void* src_frame = (void*)src_paddr;
        flop_memcpy(dst_frame, src_frame, PAGE_SIZE);

        attrs.frame_addr = ((uintptr_t)dst_frame) >> 12;

        if (map_page(dst_vaddr, (uintptr_t)dst_frame, attrs) < 0) {
            // Rollback previous mappings
            for (size_t j = 0; j < i; j++) {
                uintptr_t undo_vaddr = dst_base + j * PAGE_SIZE;
                unmap_page(undo_vaddr);
            }
            pmm_free_page(dst_frame);
            return -1;
        }
    }
    return 0;
}
paper cloak
#

dunno why you need size

bold grove
#

You’re right

paper cloak
#

also any reason why you cant just memcpy from dst_frame to src_frame why do you need to call map

violet anchor
#

flopperating system doesn't copy memory, it flops it.

bold grove
paper cloak
bold grove
#

@paper cloak

#

is this the correct way of doing it?

#

wait hold up

viscid zephyr
bold grove
#

@small hound

small hound
#

huh

bold grove
#

is this a decent way of yielding per core

#
void sched_yield_core(uint32_t core_id) {
    thread_t* old = core_current_thread[core_id];
    thread_t* t = old ? old->next : NULL;
    thread_list_t* list = &core_thread_lists[core_id];
    if (!list->head) return;
    if (!t) t = list->head;
    thread_t* start = t;
    do {
        if (t->state == THREAD_READY)
            break;
        if (t->state == THREAD_SLEEPING && global_tick_count >= t->wake_tick) {
            t->state = THREAD_READY;
            break;
        }
        t = t->next;
    } while (t != start);
    if (t->state != THREAD_READY && core_idle_thread[core_id])
        t = core_idle_thread[core_id];
    if (t != old) {
        core_current_thread[core_id] = t;
        ctx_switch(&old->ctx, &t->ctx);
    }
}
small hound
#

uh what are wake ticks? is your sleep using scheduler ticks?

#

you should probably use a separate external timer like the hpet for that instead of tying it to the scheduler

bold grove
#

should i just forgo the ticks for now

#

yeah it works

small hound
#

since that would mean no tasks left

small hound
#

when would the current thread be null

#
    struct scheduler *sched = get_this_core_sched();
    bool interrupts = spin_lock(&sched->lock);

    struct thread *curr = sched->current;
    struct thread *next = NULL;

    save_current_thread(sched, curr);

    /* Checks if we can steal, finds a victim, and tries to steal.
     * NULL is returned if any step was unsuccessful */
    struct thread *stolen = scheduler_try_do_steal(sched);

    next = stolen ? stolen : scheduler_pick_regular_thread(sched);

    if (!next) {
        /* Nothing available via steal or in our queues? */
        next = load_idle_thread(sched);
    } else {
        /* Depending on what was loaded, we may or may not
         * need to adjust the timeslice. RT threads do not
         * have timeslices, so the timeslice needs to be
         * disabled if an RT thread is chosen to run */
        change_timeslice(sched, next);
    }

    load_thread(sched, next);
    spin_unlock(&sched->lock, interrupts);

    context_switch(curr, next);
}``` my thingy looks like this, maybe you can add work stealing and locks and load the idle thread if the head is null
#

other than that it ok

bold grove
#

im rewriting it

bold grove
#

@small hound

#

is this how i could do that

#
if (!list->head || !list->head->next) {
        thread_t* idle = core_idle_thread[core_id];
        if (idle && old != idle) {
            core_current_thread[core_id] = idle;
            ctx_switch(&old->ctx, &idle->ctx);
        }
        return;
}
#

i have an idle thread for each core

#

and this would be the whole function

#
void sched_yield_core(uint32_t core_id) {
    thread_t* old = core_current_thread[core_id];
    thread_list_t* list = &core_thread_lists[core_id];

    if (!old) {
        old = core_idle_thread[core_id];
        core_current_thread[core_id] = old;
    }

    if (!list->head || !list->head->next) {
        thread_t* idle = core_idle_thread[core_id];
        if (idle && old != idle) {
            core_current_thread[core_id] = idle;
            ctx_switch(&old->ctx, &idle->ctx);
        }
        return;
    }

    thread_t* t = old->next ? old->next : list->head;
    thread_t* start = t;

    do {
        if (t->state == THREAD_READY)
            break;
        t = t->next ? t->next : list->head;
    } while (t != start);

    if (t->state != THREAD_READY)
        t = core_idle_thread[core_id];

    if (t && t != old) {
        core_current_thread[core_id] = t;
        ctx_switch(&old->ctx, &t->ctx);
    }
}
bold grove
#

We got multi core scheduling boys

violet anchor
#
uint32_t get_core_id() {
    uint32_t core_id;
    __asm__ volatile ("movl %%gs:0, %0" : "=r"(core_id));
    return core_id;
}

what fills gs?

#

if you're using the lapic id as your core id and relying on it to be sequentially numbered between 0 and MAX_CORES-1, that is an incorrect assumption

#

real hardware can give any 32 bit number

#

you might get 0, 2, 4 ,8 ,10, 12... or 1, 5, 9...

bold grove
#

My kernel is

Hopes and prayers init - ok 
Multibootfuckshit init - ok 

Page allocator init - maybe  

Err: Page fault 
Get fucked 
red dirge
#

Very nice

#

Is this a 16-bit os?

bold grove
#

Nah

#

32bit

red dirge
#

does this have acpi support?

bold grove
#

Kinda lol

red dirge
bold grove
#

Yeah

red dirge
#

thats what my os has lol

bold grove
#

And it works on hypervisors

red dirge
#

whats the github link?

red dirge
bold grove
red dirge
bold grove
#

Thank you so much

#

I’ll do the sand

#

Same*

red dirge
#

rn im adding build numbers

#

that i can acsses in cDOS

#

so i can see if my computer is running the latest OS

red dirge
#

nice now i have a nice working version thingy

#

only tool like a hour to do that lol

bold grove
#

we're adding spinlocks gang

#

i finally read up on all that bullshit

small hound
#

features galore

bold grove
bold grove
#

well it wasnt just you but

#

im doing the queue per cpu

small hound
#

💥 incredible

bold grove
#

so

#
static void sched_reschedule_core(uint32_t core_id) {
    if (core_id >= global_scheduler.core_count) return;
    
    thread_t* old = global_scheduler.current_threads[core_id];
    thread_list_t* ready = &global_scheduler.ready_queues[core_id];
    
    if (!old) {
        old = global_scheduler.idle_threads[core_id];
        global_scheduler.current_threads[core_id] = old;
    }
    
    if (!ready->head) {
        if (old != global_scheduler.idle_threads[core_id]) {
            global_scheduler.current_threads[core_id] = global_scheduler.idle_threads[core_id];
            ctx_switch(&old->ctx, &global_scheduler.idle_threads[core_id]->ctx);
        }
        return;
    }
    
    thread_t* next = ready->head;
    thread_t* start = next;
    
    do {
        if (next->state == THREAD_READY) break;
        next = next->next;
    } while (next != start);
    
    if (next->state != THREAD_READY) {
        next = global_scheduler.idle_threads[core_id];
    }
    
    if (next && next != old) {
        global_scheduler.current_threads[core_id] = next;
        old->state = THREAD_READY;
        next->state = THREAD_RUNNING;
        next->last_run = global_ticks;
        ctx_switch(&old->ctx, &next->ctx);
    }
}

void schedule() {
    uint32_t core_id = sched_get_core_id();
    sched_reschedule_core(core_id);
}
#

im also doing load rebalancing

#
void sched_balance_load() {
    spinlock(&global_scheduler.sched_lock);
    for (uint32_t i = 0; i < global_scheduler.core_count; ++i) {
        if (global_scheduler.ready_queues[i].count > 1) {
            thread_list_t* list = &global_scheduler.ready_queues[i];
            thread_t* current = list->head;
            thread_t* next = current->next;
            while (current != list->tail) {
                if (current->state == THREAD_READY && next->state == THREAD_READY) {
                    // Move next to a less loaded core      
                    uint32_t target_core = (i + 1) % global_scheduler.core_count;
                    while (global_scheduler.ready_queues[target_core].count >= global_scheduler.ready_queues[i].count) {
                        target_core = (target_core + 1) % global_scheduler.core_count;
                    }
                    sched_remove_thread(next);
                    next->core_id = target_core;
                    sched_add_thread(next);
                }
                current = next;
                next = next->next;
            }
        }
    }       
    spinlock_unlock(&global_scheduler.sched_lock, true);
}
bold grove
paper cloak
#

yes?

bold grove
#

I just have a global lock for my scheduler

#

It’s only for use in the scheduler

#

i have a different lock for my pmm, etc

small hound
#

BKL

small hound
#

funny way to do that

bold grove
#

Big kernel lock

small hound
#

yes

bold grove
bold grove
#

Like

#

I’m making a pmm lock, etc

small hound
#

yes

bold grove
#

Okay, just making sure

small hound
#

even more fine grained than that would be better

bold grove
#

Like

#

A lock for pmm free and pmm alloc

#

Separate?

small hound
#

no that would break something

#

they both operate on the same structure right

#

that would break something

bold grove
#

Oh I see

#

Any shared state

#

Should have a lock

#

Right?

small hound
#

that's the general idea yeah

bold grove
#

Okay

#

So I guess for my pmm

#

I will have a lock for freeing and allocating pages

small hound
bold grove
#

Yeah

#

So within critical sections

#

Spinlocking is generally the idea

#

So I should probably make a list of “shared states”

small hound
bold grove
#

Yeah

small hound
#

are you doing lock profiling?

bold grove
#

No just a list in my notes lol

small hound
#

oh alright

bold grove
#

Like not a programmatic list lol

#

But I will be making locks for a bunch of subsystems

small hound
#

do you mean integrating them

#

yea but sounds good 👍

#

for many things you will probably want to avoid the "Big Function Lock" approach but it'll be OK for now

#

for example one fairly performant memory allocation technique that jemalloc uses is per-thread arenas

bold grove
small hound
bold grove
#

That makes sense

#

Is a big function lock

#

Just

#

Something you call at the end and beginning of a critical section

#

Like a pmm allocation or something

small hound
#

just any lock that is really coarse grained and just used to wrap around a function yea

#

you've heard about the linux BKL I'm guessing right

#

suboptimal locking decision

bold grove
#

Yeah that’s how I know about the whole acronym I heard of it a few times

#

I feel like that just fucks up lock contention lol

#

Having a giant ass lock

small hound
#

yup it's typically why you wanna avoid doing that

bold grove
#

Wait so having one big kernel lock means only one processor can execute kernel code at once right?

bold grove
#

That comes with a huge ass performance hit I’m sure

viscid zephyr
#

it was not as big as it started out though

#

also usually bkl's are released across long waits like file IO

#

and re-acquired when the wait completes

#

which mitigates it a little

bold grove
#

Dang it’s like

#

A plague

#

Within the kernel lmao

#

That took 20 years to remove

bold grove
#

And they probably just

#

Decided it was bad lol

small hound
#

so it's not that it got extremely complex, just extremely slow

#

people didn't like that

#

I believe (you can double check by reading the sources) that early linux (like pre-2.0) was not smp aware

small hound
#

pretty much

bold grove
#

Crazy

bold grove
#

Alright so I’ve implemented smp

bold grove
#

@small hound should I spinlock within vfs and tmpfs operations

#

Like read and stuff

bold grove
viscid zephyr
#

when

#

what do you do while holding this spinlock

#

what does it guard

bold grove
#

I have a tmpfs_lock

viscid zephyr
#

what it for

bold grove
viscid zephyr
#

lol

bold grove
#

Like writing to two inodes at the same time would cause issues right?

viscid zephyr
#

no you should allow concurrent reads and writes

#

you would just say that concurrent writes to overlapping regions of a file will have undefined results wrt which data ends up in the file

#

your locking should guard fs metadata

#

to stop it from becoming inconsistent

#

but not necessarily file data

#

that service is often offered but its done with explicit "file range locking" which you have to ask for

#

otherwise its concurrent by default

small hound
# bold grove If I have a tmpfs that’s in memory, don’t I have to prevent two processes from a...

you can have a tree structure of byte ranges and lock the byte ranges when they are being read or written

so if you write to range 0-50, you'd see nothing is there doing any write, and insert that range into the tree and acquire the lock

then if someone else writes to 11-77, they'd see that 0-50 exists and is locked which overlaps with their write, and then after 0-50 finishes you'd insert the 11-77 node

but if you write to 88-100, that write would proceed and wouldn't get blocked

this is only for writes that explicitly call for their regions to be locked

#

very nice and fine grained 😃

#

locking also isn't done at the filesystem node level and it's done at the page cache page level

#

oh yeah and going back to what will said, you would make this concurrent by default, only protecting against file metadata being concurrently modified, and serialization would be explicitly asked for by the user

like on linux you have the fcntl utility and work say something like

struct flock fl = {
    .l_type = F_WRLCK,
    .l_whence = SEEK_SET,
    .l_start = start_offset,
    .l_len = length
};
fcntl(fd, F_SETLKW, &fl);
bold grove
#

Can I just make like 20 byte ranges?

small hound
#

is that your favorite number

bold grove
small hound
#

i'm assumed you'd do that with a bitmap

#

so bigger file -> lots of space wasted

#

better to just use a tree

bold grove
#

What type of tree

#

And how would I implement that

small hound
#

💥 a tree that holds ranges of places in the file, like a red black tree would work fine

for example

struct range {
    size_t start;
    size_t end;
    struct rbnode tree_node;
    spinlock_t lock;
};``` and the `.data` in the `tree_node` is sorted by the `start`
small hound
# bold grove And how would I implement that
user makes lock request

lookup begins

1. check if there are overlaps
2. if no overlap -> insert the node to represent this range
     
   else, if there is an overlap, and
     if it is non-blocking -> fail with EAGAIN
     if it is blocking -> attach to wait-queue of overlapping node, sleep until woken, and retry the lookup

then on unlock (operation has finished)
1. remove node
2. wake all waiters on the node


#

shrimple as that

bold grove
bold grove
small hound
#

wait did you not know how rbts worked lol

#

you should go learn that first then 👍

bold grove
small hound
#

no problem then

#

you might find it suboptimal to go and implement a rbt from scratch, linux already has one you can go nab

bold grove
#

Yeah?

#

Idk I don’t wanna copy code

#

I did implement it

#

In the class and I’m working on it now

small hound
#

ok then sounds great just use that then and make sure it works properly 👍

bold grove
#

Yeah I’ll show you when it’s done and after I test it

paper cloak
#

I literally just translated the pseudocode to C

bold grove
bold grove
bold grove
#

Just to make sure I’m not missing anything

paper cloak
#

spinlock is literally spinning on a cmpxchg

bold grove
#
#define SPINLOCK_INIT {ATOMIC_VAR_INIT(0)}
typedef struct spinlock {
    atomic_uint state;
} spinlock_t;

static inline void spinlock_init(spinlock_t* lock) {
    atomic_store(&lock->state, 0);
}

static inline bool spinlock(spinlock_t* lock) {
    bool interrupts_enabled = IA32_INT_ENABLED();
    __asm__ volatile ("cli");
    while (__atomic_test_and_set(&lock->state, __ATOMIC_ACQUIRE)) {
        IA32_CPU_RELAX();
    }
    return interrupts_enabled;
}

static inline void spinlock_unlock(spinlock_t* lock, bool restore_interrupts) {
    __atomic_clear(&lock->state, __ATOMIC_RELEASE);
    if (restore_interrupts) {
        __asm__ volatile ("sti");
    }
}

static inline void spinlock_noint(spinlock_t* lock) {
    while (__atomic_test_and_set(&lock->state, __ATOMIC_ACQUIRE)) {
        IA32_CPU_RELAX();
    }
}

static inline void spinlock_unlock_noint(spinlock_t* lock) {
    __atomic_clear(&lock->state, __ATOMIC_RELEASE);
}
#endif // SPINLOCK_H
paper cloak
#

yea thats probably fine, tho disabling interrupts is not something you really wanna do ideally

bold grove
paper cloak
#

no

#

you should disable preemption

#

not interrupts entirely

bold grove
#

Ahhh

#

I see

#

Speaking of that

paper cloak
#

there are many ways of doing it

bold grove
#

I need preemption lmao

paper cloak
#

linux has preempt_enable() and preempt_disable()

#

the best way imo is to do the NT/VMS thing which is having interrupt priority levels

bold grove
#

Oh I see. Which interrupts would be of “low” and “high” priority

paper cloak
#

depends

#

usually the order is something like all interrupts enabled -> APCs disabled -> DPCs disabled (preemption) -> device interrupts disabled -> all interrupts disabled

bold grove
#

I see, so I should just have some sort of way to describe how important an interrupt is

paper cloak
#

so to disable preemption you raise your ipl to dpc/dispatch/whatever you wanna call it level

bold grove
#

Should I just have an enum of some sort

paper cloak
#

and to enable it you lower it back again

#

and with this its pretty easy to do DPCs which are like linux softirqs

bold grove
paper cloak
#

yeah

#

disable preemption on lock

#

restore it on unlock

small hound
# paper cloak not interrupts entirely

do note that if you have some function that can be called both manually and via some device interrupt, you'd wanna disable interrupts to avoid a deadlock upon lock acquisition

like take the following scenario

function is called manually -> lock is acquired (interrupts still enabled) -> interrupt fires -> function is called again -> deadlock

so like linux has spin_lock_irqsave for this

paper cloak
#

yeah

#

usually u can just have your spinlock function take a desired ipl tho

small hound
#

VMS propaganda!!!!!!

#

yea but that works too if u have that infra

bold grove
#

@paper cloak what do you think about diagrams in code

#

I want to start going in the educational direction

#

If that makes sense

bold grove
bold grove
#

Here is my cool little thing I did for recursive mapping

#

/* 
    * Recursive mapping:
    *
    * PDE 1023 points to the Page Directory itself:
    *
    *   +-------------------------+ 0xFFFFF000
    *   | Page Directory (PD)     | <--- PDE[1023] points here 
    *   +-------------------------+
    *              ^
    *              ^
    *   +-------------------------+ 0xFFC00000
    *   | Page Tables (PTs) area  |  Covers PDE[0..1022]
    *   | +---------------------+ |
    *   | | PT 0                | | <-- PDE[0]
    *   | +---------------------+ |
    *   | | PT 1                | | <-- PDE[1]
    *   | +---------------------+ |
    *   | |        ...            | <- PDE[...]
    *   | +---------------------+ |
    *   | | PT 1022             | | <-- PDE[1022]
    *   | +---------------------+ |
    *   +-------------------------+
    *
    * virt address bit layout (32 bits):
    *
    *   [10 bits PDE][10 bits PT][12 bits offset]
    *
    * get pt:
    *
    *   0xFFC00000 + (PDE_index << 12) + (PT_index * 4)
    *
    * get pd:
    *
    *   0xFFFFF000 + (PDE_index * 4)
    */
paper cloak
#

So Id say it's good

small hound
#

check out graphviz

bold grove
#

Not super hardcore but

#

I want most of the files to have soemthing to learn from

#

Not just code

bold grove
bold grove
#

Okay I need to redo my heap allocator gang 🥀

bold grove
#

This is going bad

#

I’m thinking about making kernel allocations maxed at below page size

paper cloak
#

Why

small hound
bold grove
#

You think that’s good enough?

small hound
bold grove
#

Quitting alcohol = 300% productivity increase

bold grove
#

Hi guys I’m gonna redo lots of the allocator and vmm in the next few days I’ll show updates and stuff

bold grove
#

Okay I’ve worked out my spinlock impl

bold grove
#

Okay I’ve been working on address space protection more

#

And aslr

#

Gonna randomize the kernel loading address

#

I kinda have weird names for it

#

For spinlocks

#

Idk

bold grove
#

The naming scheme is

#

spinlock(&lock) which calls try_spinlock

#

spinlock_unlock releases the lock

#

A lot of people do spinlock_acquire and release but idk I like this more

#

It’s not entirely clear to someone who doesn’t know what a lock is what it means to “acquire” one

#

So I think for the educational purposes it’s better like this

small hound
#

acquire would make more sense for things that have both a refcount and a lock, you can use it there

bold grove
#

I don’t have a refcount rn

small hound
violet anchor
#

do you need to?

#

usually all you need to do is store the current rflags when entering the lock and restore it when leaving

#

while disabling interrupts while spinning

bold grove
small hound
#

seems a bit whimsical to restore all the other flags

small hound
# bold grove That’s exactly what I do

you can consider doing lock profiling by making the spinlock_init put the lock in some global list to track all of them, and then you can print out their stats after you run tests or whatever

#

helps you find locks that are highly contended without checking each one manually

violet anchor
bold grove
#

On the agenda for this week

✅ rework spinlock
✅diagrams in vmm and paging
❌aslr/security features
❌diagrams in the rest of mem/

small hound
#

why diagramming

bold grove
small hound
bold grove
#

I want people to be able to learn something from it

small hound
#

uhhhhhhhh

#

ok best of luck then

bold grove
#

I’m not going for an “OSdev” tutorial

bold grove
#

I’m more trying to show the process/teach others how to learn and implement their own thing

#

My code is all gpl anyways, it can’t be copied

small hound
paper cloak
small hound
bold grove
#

I wouldn’t want to do something I don’t know very well

paper cloak
#

it's still vastly inferior

bold grove
#

Yeah I know

paper cloak
#

and shouldnt be taught anymore

bold grove
#

I’m not teaching the Architecture specifics in this devlog

#

I want it to apply to x64, arm, longarch

#

Not necessarily just ia32

paper cloak
#

ok so why are you making page table diagrams for x86

small hound
#

ok sooooo we already have books, articles, papers, code documentation, Real Kernel Code, so there's no need to reinvent the wheel here unless you're documenting something fully unique and novel (which isn't happening I fear)

bold grove
#

I’m not trying to reinvent the wheel I just want to help people if I can

#

Maybe I should just do long modes

paper cloak
bold grove
#

I think I’ll just diagram in this os and then eventually make something in x64 and make it educational

#

I do wanna write a devlog about this os though

bold grove
#

Hi guys

#

I’m gonna work on some new stuff today

#

Particularly userspace

#

Syscalls

#

Idk what to call it so

#

Imma just make a folder sys/

#

And have a syscalls.c

#

Maybe c++

#

I need to make a handler first

#

Okay we have sys_read and sys_write now

bold grove
#

Okay so imma
Make an actual vfs

#

Instead of using my tmpfs for fucking everything

#

Okay started on that vfs

#

Big commit coming today probably

small hound
#

page cache

violet anchor
bold grove
violet anchor
#

nice would like to see code and compare to my Eldrich nightmare that is walk_to_node_internal

bold grove
#

Bet

bold grove
#

And vfs_transverse

bold grove
#

I need block storage first

#

But that’s going on the list

#

Probably

small hound
bold grove
#

Yeah I just remembered

small hound
#

HE DID THE THING

#

NO WAY IT'S REAL

#

pog

bold grove
#

Bruh

#

It’s not that bad lol

small hound
#

crazy statement ⁉️

#

#define TMP_MAX_FILE_SIZE 4096

#

oh this is so peak

bold grove
#

Just saying I made this a long ass time ago when I didn’t know much so

small hound
#

you can trivially make this like 5x better by just doing an array of pointers to pages

bold grove
#

It will obviously be improved

#

I’m studying an actual vfs impl

#

FreeBSD

#

So

#

It won’t be that bad soon

small hound
#

no way flopfs is vibe coded too?

bold grove
#

Yeah but

small hound
bold grove
#

Yet again

#

HELLA LONG AGO

#

I’m getting rid of it rn lol

#

It’s not even in the makefile

bold grove
#

I made that in like November

small hound
bold grove
#

I was talking about the system call handler

small hound
#

uhhhhhhhhh ok

#

I can't believe the filesystem design gif is real now

bold grove
#

Aight

#

But yeah

#

I made that shit

#

WAYY before I had much knowledge

small hound
#

we have spinlock at home

#

why is that even necessary

#

you should be handling locking at the level of the driver that is using these instructions

bold grove
#

This is yet again wayyyyy before

small hound
#

like if core 0 is writing to port 4 and core 3 is reading from port 80 those dont need a lock

bold grove
#

I just did like this before I had synchronization

small hound
#

is this all getting rewritten

bold grove
#

Yeah

#

Absolutely lol

#

I’ve spent months on mem/ and other stuff

#

Haven’t really done anything with drivers or fs

small hound
#

why is alloc not threadsafe

bold grove
#

Haven’t migrated it yet

#

@small hound

#

Does this looks better

small hound
#

allocating zero pages

bold grove
#
typedef enum {
    TMP_NODE_FILE,
    TMP_NODE_DIR
} TmpNodeType;

typedef struct TmpNode {
    TmpNodeType type;
    char *name;
    struct TmpNode *parent;
    struct TmpNode **children;
    uint32_t child_count;
    void *file_data;
    uint32_t size;
    TmpPerms perms;
} TmpNode;

typedef struct {
    TmpNode *root;
} TmpFileSystem;

At least for the structs

small hound
#

no

bold grove
small hound
#

not good though

#

don't you have some tree implementation? you can use a tree here to store tmpfs pages

bold grove
#

I got rid of the static sizes

#

Yeah I do

#

Not in my os but I do

small hound
#

also the file_data is not store at the tmpfs level, tmpfs essentially lives in the page cache

bold grove
#

Oh so the file data should be a pointer to a page in the page cache?

#

Or something like that

small hound
#

no it shouldn't exist at all

#

you use a radix tree to do the lookup

bold grove
#

Ohhhh, I see

#

Within tmpfs_read for example

#

Right?

#

So these structs should only contain metadata

small hound
#

no you do want a pointer to page cache structures lol nvm

bold grove
#

Ohhh okay

small hound
#

but the node itself isn't where "the data is stored"

bold grove
#

Each tmpnode should have a pointer to the page in the page cache, got it

#

Yeah it’s just a pointer to where it is

small hound
bold grove
#

Should I just create an array of uintptr_t’s to pages

#

In each tmpnode

small hound
#

why would you need that

#

you just keep one backpointer like
struct address_space *i_mapping;

bold grove
#

Ah okay

#

So it should be an address space for each node

small hound
#

yea

bold grove
#

Okay. Isn’t that like a lot of overhead tho?

small hound
#

for example

small hound
bold grove
#

Creating an entire virtual address space for each file?

small hound
#

uhhh it's not really a virtual address space

#
struct address_space {
    struct inode        *host;
    struct xarray        i_pages;
    struct rw_semaphore    invalidate_lock;
    gfp_t            gfp_mask;
    atomic_t        i_mmap_writable;
#ifdef CONFIG_READ_ONLY_THP_FOR_FS
    /* number of thp, only for non-shmem files */
    atomic_t        nr_thps;
#endif
    struct rb_root_cached    i_mmap;
    unsigned long        nrpages;
    pgoff_t            writeback_index;
    const struct address_space_operations *a_ops;
    unsigned long        flags;
    errseq_t        wb_err;
    spinlock_t        i_private_lock;
    struct list_head    i_private_list;
    struct rw_semaphore    i_mmap_rwsem;
    void *            i_private_data;
} __attribute__((aligned(sizeof(long)))) __randomize_layout;```
bold grove
#

Ohhhh

#

I see now

#

What does i_mmap_rwsem mean

small hound
#

it's a semaphore

bold grove
#

Why is there so many locks

#

I mean I know fine grain locks

small hound
#

because linux is a multitasking smp compatible kernel

bold grove
#

Oh this is Linux

#

Ahh

#

I see

#

Is it just acceptable for me to spinlock

#

Within tmpfs operations

#

Instead of doing all of that

small hound
#

sure but for an ""educational OS"" you'd wanna tell people that almost everything you're doing is suboptimal

#

and then tell them to go look at real kernel code or something

bold grove
#

How is it suboptimal to spinlock when everyone here does it lol

#

Like every kernel is

#

Spinlock; do shit; spinlock_unlock

small hound
#

no it's not that it's suboptimal to spinlock, but it is suboptimal when you're doing things like a pmm_lock

bold grove
#

Well I know I should have locks for certain shared states

small hound
#

and people reading an "educational OS" can get easily misled by that

bold grove
#

I guess

#

I guess I won’t do the education thing

#

I don’t think I’m really good enough for it or any of this stuff

small hound
bold grove
#

I’m just gonna study file system implementations

#

Just kinda demotivated for now

small hound
#

sounds good

bold grove
#

typedef struct {
    uintptr_t *pages;   
    uint32_t   num; 
    uint64_t   size;   
    uint32_t   flags;
    spinlock_t lock;
} address_space_t;
``` @small hound is this good enough for the “address space”
small hound
#

you'd wanna use a tree to store the pages

#

radix tree is what most do

bold grove
#

Yeah it’s a placeholder for now I’m gonna start that impl

small hound
bold grove
#

Yeah I see that lol

#

I should probably make a file in lib/ called radix.c

#

I already have a radix implementation