#The Flopperating System (tfos) - x86 32 bit Hobby OS
1 messages · Page 4 of 1
(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
or just make small functions that self document :^)
My stupid laptop ain’t connecting to wifi now
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
ya you can try that
Not quite, that would be fine but is doing extra work. You only need to invalidate tlb entries when reducing permissions.
oh interesting, neat
oh okay
so
i did what you said @small hound
That's for x86 only btw
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
so like ia32?
Nah anything x86
Long mode, protected mode whatever comes next
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 = ¤t_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 = ¤t_table->entries[L1];
if (!(ENTRY_PRESENT(*entry)))
return (uintptr_t) -1;
return (*entry & PAGING_PHYS_MASK) + (virt & 0xFFF);
}```
Long double mode 
long long mode 
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
why do you fix the memory amounts lol
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
}
that doesnt mean u have to make the memory amounts static tho 
it wont map past memory that doesnt exist
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"
no need to make them fully static
what? 💥
page caches are for the virtual filesystem
why would the memory managers deal with that lol
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;
};
i thought page info in the pmm is also called a page cache no?
@blissful grove is that wrong or am i tripping
that is not what that is called, no
what is that called then
here, probably freelists?
ya it's a list
call it freelist_entry probably
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);
}
}
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
stress test when
oh this allocator design works great
unless youre talking about mental strength
ive already tested it
many times
no no its lockless and works with my scehduler
yeah, but i limited it to 8 cores for now
im still sussint that shit out
smp is fucky on ia32
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
yea
no, when you append to the list or remove from it
i do that in my buddy split and buddy merge functions
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
what did you mean here
how does this have to do with ur scheduler
no , what i mean is that i cant run many things at once so technically i dont have a need for a lockless system
i cant run many things at once
so what do you do with 7 of your cores here
create queues for each one and switch through them when i call schedule()
which yeah is basically single core
how is it single core if you make queues for each core and switch through them
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
why is it necessary to switch between cores 💥 ⁉️
core 2 will do it
why not just run it all on core 0
i mean i guess i could
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
nah - a page cache is for caching on-disk blocks in memory for faster access and use by mmap(). If you're talking about an array of metadata you store for each page, thats usually referred to as struct page or the pfndb (page frame number(?) database)
oh yeahhhh thats what NT called the PFN
so array of page structs is a PFN
ill take that into accont
i for some reason thought a page cache was that
man
and dw about lockless stuff and migrating threads for now. Start simple and get it working with locks lol, then worry about that stuff
bet
yeah I think people are always put off by the little bit of theory at the start
i should spinlock at the beginning and end of each pmm function (thats what ive seen)
but like all things it has pros and cons
it depends on how you wanna do it, a basic one is not hard lol ya
basically
I'm not going to answer that directly, because you should understand what a lock does and why - then you can answer that.
yeah i know it prevents other cores and operations from happening
so yeah it should be like that
only for things that need protecting
isnt that like
kernel shit
like pmm,vmm, scheduler
etc
pretty much everytihng that doesnt run in the userspace?
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
i think i can probably do that for now
a good piece of advice is to "Do the finest grained locks you can easily do" - hyenasky
oh yeah
i see many source codes
where they have like
a special scheduler mutex
and other ones for other subsystems
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
these are probably wrappers over the other locks
instant fault
static inline void bcache_ent_lock(struct bcache_entry *ent) {
mutex_lock(&ent->lock);
}
static inline void bcache_ent_unlock(struct bcache_entry *ent) {
mutex_unlock(&ent->lock);
}``` lol like this
so basically
system doing its thing -> pmm operation call -> lock -> do the pmm operation -> unlock -> continue
pretty much
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
like inline stuff?
if im accessing page tables or freeing or allocating its safe to say i should lock
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
if someone else could also access those, then yes.
i get that but
what is an exampe of a shared state
like
something that is modified by many "subsystems"
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
if it can be accessed more than once at the same time, its shared
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?
it would cause some sort of fault
or just corrupted state
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?
now is about the time you wanna get a blocking mutex
@blissful grove
oh this is a great great question
isnt a mutex just a lock?
keyword: blocking
mutex has an owner thread and waiters
yeah mutual exclusion
so basically, the first guy that hits the lock will acquire it
but you also have priority inheritance in some implementations
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
you think about it carefully
where if a high priority thread sees a low priority thread on a mutex, it boosts the low prio thread and unboosts it once the lock is unlocked
oh i thought you meant the order in which things acquire a lock
nvm
although having just looked at your example thats overcomplicated. You could just have a single lock for the pmm, that both alloc and free use.
but generally thats how it would be done?
yeah
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)
you have 1 resource (the pmm buddy), so it gets 1 lock
and these can just be spinlocks right?
yeah they can be, you can get away with spinlocks for a long time (lol)
they're not always the best choice though
the amount of waiters you wake differs based on implementation. you can wake one like a normal person, or you can wake all waiters and have them race to acquire the lock again
but yes, it would work
for a pmm it should be enough though?
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
yeah i wont like map entire huge ass regions with a spin lock or something
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
nt my beloved
have you looked into their pushlocks
definitely an approach of all time to pointer sized locks
what did you gain from that reading
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
that's mostly a general purpose view on locking, and there's a lot more to locking than just simple owner/waiter systems
locking is cool
but yea dont worry about it until it becomes a genuine concern
okay
so for multiprocessing then
i should create a queue for each processor
but how can they run "concurrently"
imkinda struggling with that
each cpu core is mostly independent, minus some MSRs they share
so they can all run code at the same time
I would suggest first making your current scheduler not migrate things between cores and test it on one core
I'm assuming it's just a simple round-robin
yeah
then, start making a multicore round-robin, so make one scheduler per core
it works on one core thats how i initially made it
you dont need to worry about rebalancing for now
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
pick the cpu with the least threads in the scheduler runqueue
simple stuff
okay i see
or just explicitly specify it if needed
you choose that when the thread is marked as ready to run, you would place it that cpu's work queue
so for threads its just a circular doubly linked list for each core
mine is like that but just one
global one
yes
I wouldnt make it circular, its just a queue
?
how does that make it faster
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
okay doubly linked list for each core got it
for now you dont have to worry about thread priorities but don't forget to eventually do that too
how did that work
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
"We have MLFQ at home"
the typical solution to this is to decay thread priorities over time
yeah thats what i was told
and then boost them once enough time has passed
but then i kinda gave up
i only have experience with schedulers in like 16 bit real mode
that was most of the os experience i had
why were you scheduling in 16 bit real mode 😭
becaue i made a multutasking operating system for 16 bit real mode
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
Famous last words 💀
I page fault
Whenever creating address spaces
In the lower 3g
Thats not my hands btw
😭😭
Everything is fucked up bro
😭😭
Maybe more progress tonight if I’m up to it. Shits just going down
i just want to take this time
this OS is familar
to show how much of a fucking pain in the ass it is
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;
}
}
}
thats horrible, why multiboot2?
its multiboot 1
even worse lol
no better bootloader?
i dont wanna end up being another one of the limine x64 kernels that use flanterm
no, i kinda enjoy multiboot 1 honestly
lol
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
conforming, im deliberately pretending it doesnt exist.
based
its obviusly like
reasonable to follow the standard
if you want shit to run
but no
i don want bash
i want flopsh
well technically it "does", but in the "world" where my OS would exist, posix never spread outside expensive mainframes.
i havent reference the systemv abi ONCE
my shell is rocketsh, its written in BASIC 😄
apart from the kernel yes
thats the shell
the kernel has a multitasking basic interpreter in it
userspace shell?
there is no userspace
everything is ring 0, it acts like an old microcomputer, "mess around and find out", its intentional
poke it, peek it, its single user
linux has tcp in kernel space... just in a module
there is no "kernel space" in my OS
you right
its just all the same
everything is just
yeah
what does your gdt look lik then
is it a single address system too?
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
i mean like single addres space
oh and its all identity mapped
thats insane what
the interpreter doesnt need sepatate address spaces to mutlitask
damn dude
i strongly believe in keep it simple
kinda reminds me somehwt of temple os
yeah
except in 64 bit long mode, without religious undertones
yeah so its basically the same memory model as templeos except i do multitasking
i just looked it up
whats it look like now?
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 ¤t_slab_array[slab_meta_used++];
}
@violet anchor
fixed
what has changed?
Don’t know
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
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);
}
💀 💀
top ten things that would sound terrible out of context
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
might as well call it interrupts_c.c and interrupts_h.h to make the naming consistent 
Okay vro
interrupt_descriptor_table.c
forgot the _c
interrupt_service_routine_c.c
💀
i know that naming is unconventional but
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;
}
this looks cool
dunno why you need size
You’re right
also any reason why you cant just memcpy from dst_frame to src_frame why do you need to call map
flopperating system doesn't copy memory, it flops it.
I’m not copying memory I’m cloning mappings
Where do you think these mappings are stored lol
don't copy that floppa
@small hound
huh
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);
}
}
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
have you tested this code
if the list head is null you probably wanna load the idle thread
since that would mean no tasks left
some of the logic here just seems odd, like the initialization of the variable "t"
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
im rewriting it
okay imma load tge idle thread of the head is null \
@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);
}
}
We got multi core scheduling boys
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...
My kernel is
Hopes and prayers init - ok
Multibootfuckshit init - ok
Page allocator init - maybe
Err: Page fault
Get fucked
Kinda lol
just like off and reboot?
Yeah
thats what my os has lol
And it works on hypervisors
whats the github link?
nice
i followed you and stared all your repos :)
Thanks!
rn im adding build numbers
that i can acsses in cDOS
so i can see if my computer is running the latest OS
features galore
yessir
im doing a little bit of what you suggested a while ago
well it wasnt just you but
im doing the queue per cpu
💥 incredible
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);
}
aaa global lock
Is that a bad thing
yes?
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
BKL
lol what's with the global_scheduler variables
funny way to do that
Big kernel lock
yes
Idk just how I named them for now
Isn’t it better to have different locks
Like
I’m making a pmm lock, etc
yes
Okay, just making sure
even more fine grained than that would be better
no that would break something
they both operate on the same structure right
that would break something
that's the general idea yeah
there are nuances and caveats and edge cases but those don't matter tooo much for now
Yeah
So within critical sections
Spinlocking is generally the idea
So I should probably make a list of “shared states”
yea, you really don't wanna sleep/block during an ISR or something
Yeah
wait why a list?
are you doing lock profiling?
No just a list in my notes lol
oh alright
Like not a programmatic list lol
But I will be making locks for a bunch of subsystems
wdym making locks lol
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
Yes integrating
since these are per-thread they don't need anything like a Big Function Lock
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
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
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
yup it's typically why you wanna avoid doing that
How long did the bkl exist in Linux?
Wait so having one big kernel lock means only one processor can execute kernel code at once right?
basically yes
I can see why that’s not optimal lol
That comes with a huge ass performance hit I’m sure
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
I’m assuming the lock contention issues made it extremely complex at some point
And they probably just
Decided it was bad lol
well the reason for the BKL existing in the first place was more of a simplicity hack
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
Dang so single core only??
pretty much
Crazy
Alright so I’ve implemented smp
Should I spinlock within filesystem operations
uh
when
what do you do while holding this spinlock
what does it guard
what it for
I have it so two things don’t access it at once
that is way too vague
lol
If I have a tmpfs that’s in memory, don’t I have to prevent two processes from accessing it at once
Like writing to two inodes at the same time would cause issues right?
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
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);
I see, so basically not accessing portions that are being used by other processes
Can I just make like 20 byte ranges?
why specifically 20
is that your favorite number
Idk I just threw some arbitrary number
well the thing with hardcoding it with a number like that is it becomes overly fine grained and you waste space
i'm assumed you'd do that with a bitmap
so bigger file -> lots of space wasted
better to just use a tree
💥 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`
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
Two hours later I’m getting there
I basically implanted this diagram
wait did you not know how rbts worked lol
you should go learn that first then 👍
I took a class on data structures last year in college so there was a lot of material and I just had to relearn some of the stuff about it
no problem then
you might find it suboptimal to go and implement a rbt from scratch, linux already has one you can go nab
Yeah?
Idk I don’t wanna copy code
I did implement it
In the class and I’m working on it now
ok then sounds great just use that then and make sure it works properly 👍
Yeah I’ll show you when it’s done and after I test it
a good resource is CLRS
I literally just translated the pseudocode to C

Oh yeah
Translate pseudo code to c? I’ve been ready for this my whole life
Hey would you be able to take a look at my spinlock impl
Just to make sure I’m not missing anything
spinlock is literally spinning on a cmpxchg
#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
yea thats probably fine, tho disabling interrupts is not something you really wanna do ideally
Do you think I should aim to use the noint one instead?
there are many ways of doing it
I need preemption lmao
linux has preempt_enable() and preempt_disable()
the best way imo is to do the NT/VMS thing which is having interrupt priority levels
Oh I see. Which interrupts would be of “low” and “high” priority
depends
usually the order is something like all interrupts enabled -> APCs disabled -> DPCs disabled (preemption) -> device interrupts disabled -> all interrupts disabled
I see, so I should just have some sort of way to describe how important an interrupt is
so to disable preemption you raise your ipl to dpc/dispatch/whatever you wanna call it level
Should I just have an enum of some sort
and to enable it you lower it back again
and with this its pretty easy to do DPCs which are like linux softirqs
So at the beginning of the spin lock -> disable preemption -> spin -> enable preemption -> continue?
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 what do you think about diagrams in code
I want to start going in the educational direction
If that makes sense
Maybe your input could help too
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)
*/
I have a few of my own
So Id say it's good
pretty good 👍
check out graphviz
Okay so I’m gonna start going the educational route for this
Not super hardcore but
I want most of the files to have soemthing to learn from
Not just code
Okay thank you, imma start heading down the education path for this os soon
Okay I need to redo my heap allocator gang 🥀
This is going bad
I’m thinking about making kernel allocations maxed at below page size
Why
just make a separate allocator for allocating above one page 💥
That’s what I’ve been doing for a while
You think that’s good enough?
no you should ideally make your other allocator not break when you allocate above one page lol
sometimes you have allocations where the size is not known at compile time
Quitting alcohol = 300% productivity increase
Hi guys I’m gonna redo lots of the allocator and vmm in the next few days I’ll show updates and stuff
Okay I’ve worked out my spinlock impl
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
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
acquire would make more sense for things that have both a refcount and a lock, you can use it there
I don’t have a refcount rn
Should I refcount every lock
if you need to
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
That’s exactly what I do
well not all rflags lol, iflag is the one that you'd care about
seems a bit whimsical to restore all the other flags
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
yeah I suppose you could | it in
On the agenda for this week
✅ rework spinlock
✅diagrams in vmm and paging
❌aslr/security features
❌diagrams in the rest of mem/
why diagramming
Because education
why education though
Because I want to create an educational os
I want people to be able to learn something from it
I’m not going for an “OSdev” tutorial
I’m not doing the whole “here’s the GDT code🤓🤓” thing
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
uhhhhhhhh
and yet still doing 32-bit x86 🤦♂️
not how that works but ok
Yeah because it’s what I know
I wouldn’t want to do something I don’t know very well
it's still vastly inferior
Yeah I know
and shouldnt be taught anymore
I’m not teaching the Architecture specifics in this devlog
I want it to apply to x64, arm, longarch
Not necessarily just ia32
ok so why are you making page table diagrams for x86
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)
I’m not trying to reinvent the wheel I just want to help people if I can
Maybe I should just do long modes
also this is true for all licenses except public domain and equivalent
I guess you’re right
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
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
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
page cache
vfs when
Today
nice would like to see code and compare to my Eldrich nightmare that is walk_to_node_internal
Bet
I’ve made vfs_open so far
And vfs_transverse
Isn’t that for swapping
I need block storage first
But that’s going on the list
Probably
didn't r4 tell u about the page cache a while back
Yeah I just remembered
Just saying I made this a long ass time ago when I didn’t know much so
you can trivially make this like 5x better by just doing an array of pointers to pages
It will obviously be improved
I’m studying an actual vfs impl
FreeBSD
So
It won’t be that bad soon
no way flopfs is vibe coded too?
Yeah but
peak
Yet again
HELLA LONG AGO
I’m getting rid of it rn lol
It’s not even in the makefile
how do you say stuff like this when tmpfs doesn't even dynamically allocate its memory for nodes lol
I was talking about the system call handler
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
This is yet again wayyyyy before
like if core 0 is writing to port 4 and core 3 is reading from port 80 those dont need a lock
I just did like this before I had synchronization
yea but like if you're going to make an "educational OS" the code should be correct ykwim
is this all getting rewritten
Yeah
Absolutely lol
I’ve spent months on mem/ and other stuff
Haven’t really done anything with drivers or fs
why is alloc not threadsafe
allocating zero pages
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
no
No. (Order, pages)
it is better than what you had before
not good though
don't you have some tree implementation? you can use a tree here to store tmpfs pages
also the file_data is not store at the tmpfs level, tmpfs essentially lives in the page cache
Oh so the file data should be a pointer to a page in the page cache?
Or something like that
Ohhhh, I see
Within tmpfs_read for example
Right?
So these structs should only contain metadata
oh wait i thought you were talking about the contiguous "file_data" pointer you had
no you do want a pointer to page cache structures lol nvm
Ohhh okay
but the node itself isn't where "the data is stored"
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
not a page, but the file page cache
why would you need that
you just keep one backpointer like
struct address_space *i_mapping;
yea
Okay. Isn’t that like a lot of overhead tho?
for example
why would it be
Creating an entire virtual address space for each file?
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;```
it's a semaphore
because linux is a multitasking smp compatible kernel
Oh this is Linux
Ahh
I see
Is it just acceptable for me to spinlock
Within tmpfs operations
Instead of doing all of that
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
How is it suboptimal to spinlock when everyone here does it lol
Like every kernel is
Spinlock; do shit; spinlock_unlock
no it's not that it's suboptimal to spinlock, but it is suboptimal when you're doing things like a pmm_lock
Well I know I should have locks for certain shared states
and people reading an "educational OS" can get easily misled by that
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
sounds good
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”
Yeah it’s a placeholder for now I’m gonna start that impl
the primary issue with the big array of pointers is that the memory usage explodes