mirror of
https://github.com/Mathewnd/Astral.git
synced 2026-10-27 20:58:22 +00:00
207 lines
5.2 KiB
C
207 lines
5.2 KiB
C
#include <kernel/mm.h>
|
|
#include <kernel/event.h>
|
|
#include <kernel/scheduler.h>
|
|
#include <kernel/timekeeper.h>
|
|
#include <arch/cpu.h>
|
|
#include <logging.h>
|
|
#include <mutex.h>
|
|
#include <list.h>
|
|
#include <util.h>
|
|
|
|
#define WRITER_TICK_SECONDS 15
|
|
|
|
static mutex_t vnode_dirty_list_mutex;
|
|
static list_t vnode_dirty_list;
|
|
static bool writer_syncing;
|
|
static eventheader_t writer_wakeup_event;
|
|
static eventheader_t sync_complete_event;
|
|
|
|
// OK
|
|
void mm_cache_make_dirty(page_t *page) {
|
|
vnode_t *vnode = page->backing;
|
|
MUTEX_ACQUIRE(&vnode->dirty_list_mutex);
|
|
|
|
int flags = __atomic_load_n(&page->flags, __ATOMIC_RELAXED);
|
|
for (;;) {
|
|
if (flags & PAGE_FLAGS_TRUNCATED) {
|
|
MUTEX_RELEASE(&vnode->dirty_list_mutex);
|
|
return;
|
|
}
|
|
|
|
int new_flags = flags | PAGE_FLAGS_DIRTY;
|
|
if (__atomic_compare_exchange_n(&page->flags, &flags, new_flags, true, __ATOMIC_RELEASE, __ATOMIC_RELAXED))
|
|
break;
|
|
}
|
|
|
|
if (flags & (PAGE_FLAGS_DIRTY | PAGE_FLAGS_SYNCING)) {
|
|
MUTEX_RELEASE(&vnode->dirty_list_mutex);
|
|
return;
|
|
}
|
|
|
|
mm_hold_page(mm_get_page_address(page));
|
|
list_push_back(&vnode->dirty_pages, &page->dirty_list_node);
|
|
|
|
if (!vnode->dirty) {
|
|
vnode->dirty = true;
|
|
VOP_HOLD(vnode);
|
|
|
|
MUTEX_ACQUIRE(&vnode_dirty_list_mutex);
|
|
list_push_back(&vnode_dirty_list, &vnode->vnode_dirty_list_node);
|
|
MUTEX_RELEASE(&vnode_dirty_list_mutex);
|
|
}
|
|
|
|
MUTEX_RELEASE(&vnode->dirty_list_mutex);
|
|
|
|
vattr_t attr;
|
|
attr.mtime = timekeeper_time();
|
|
VOP_LOCK(vnode);
|
|
VOP_SETATTR(vnode, &attr, V_ATTR_MTIME, NULL);
|
|
VOP_UNLOCK(vnode);
|
|
}
|
|
|
|
// OK
|
|
static void mark_page_syncing(page_t *page) {
|
|
int flags = __atomic_load_n(&page->flags, __ATOMIC_RELAXED);
|
|
for (;;) {
|
|
__assert(flags & PAGE_FLAGS_DIRTY);
|
|
__assert((flags & PAGE_FLAGS_SYNCING) == 0);
|
|
|
|
int new_flags = (flags & ~PAGE_FLAGS_DIRTY) | PAGE_FLAGS_SYNCING;
|
|
if (__atomic_compare_exchange_n(&page->flags, &flags, new_flags, true, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
|
|
break;
|
|
}
|
|
}
|
|
|
|
// OK
|
|
// expects the per-vnode dirty list mutex to be held. returns true if the caller must release a vnode reference.
|
|
static bool try_undirty_vnode(vnode_t *vnode) {
|
|
if (vnode->dirty == false)
|
|
return false;
|
|
|
|
MUTEX_ACQUIRE(&vnode_dirty_list_mutex);
|
|
list_remove(&vnode_dirty_list, &vnode->vnode_dirty_list_node);
|
|
MUTEX_RELEASE(&vnode_dirty_list_mutex);
|
|
vnode->dirty = false;
|
|
return true;
|
|
}
|
|
|
|
// OK
|
|
int mm_cache_sync_vnode(vnode_t *vnode) {
|
|
int first_error = 0;
|
|
MUTEX_ACQUIRE(&vnode->writeback_mutex);
|
|
|
|
for (;;) {
|
|
MUTEX_ACQUIRE(&vnode->dirty_list_mutex);
|
|
list_node_t *page_node = list_pop_front(&vnode->dirty_pages);
|
|
if (page_node == NULL) {
|
|
bool was_dirty = try_undirty_vnode(vnode);
|
|
MUTEX_RELEASE(&vnode->dirty_list_mutex);
|
|
MUTEX_RELEASE(&vnode->writeback_mutex);
|
|
|
|
if (was_dirty) {
|
|
VOP_RELEASE(vnode);
|
|
}
|
|
return first_error;
|
|
}
|
|
|
|
page_t *page = container_of(page_node, page_t, dirty_list_node);
|
|
mark_page_syncing(page);
|
|
MUTEX_RELEASE(&vnode->dirty_list_mutex);
|
|
|
|
VOP_LOCK(vnode);
|
|
|
|
// TODO: remark as dirty on error
|
|
int error = 0;
|
|
if ((__atomic_load_n(&page->flags, __ATOMIC_RELAXED) & PAGE_FLAGS_TRUNCATED) == 0)
|
|
error = VOP_PUTPAGE(vnode, page->offset, page);
|
|
|
|
VOP_UNLOCK(vnode);
|
|
if (first_error == 0)
|
|
first_error = error;
|
|
|
|
int flags = __atomic_and_fetch(&page->flags, ~PAGE_FLAGS_SYNCING, __ATOMIC_ACQUIRE);
|
|
if (flags & PAGE_FLAGS_DIRTY) {
|
|
MUTEX_ACQUIRE(&vnode->dirty_list_mutex);
|
|
list_push_back(&vnode->dirty_pages, &page->dirty_list_node);
|
|
MUTEX_RELEASE(&vnode->dirty_list_mutex);
|
|
} else {
|
|
mm_release_page(mm_get_page_address(page));
|
|
}
|
|
}
|
|
}
|
|
|
|
// OK
|
|
int mm_cache_sync(void) {
|
|
MUTEX_ACQUIRE(&vnode_dirty_list_mutex);
|
|
|
|
if (vnode_dirty_list.head == NULL && !writer_syncing) {
|
|
MUTEX_RELEASE(&vnode_dirty_list_mutex);
|
|
return 0;
|
|
}
|
|
|
|
eventlistener_t listener;
|
|
EVENT_INITLISTENER(&listener);
|
|
EVENT_ATTACH(&listener, &sync_complete_event);
|
|
|
|
EVENT_SIGNAL(&writer_wakeup_event);
|
|
MUTEX_RELEASE(&vnode_dirty_list_mutex);
|
|
|
|
EVENT_WAIT(&listener, 0);
|
|
EVENT_DETACHALL(&listener);
|
|
return 0;
|
|
}
|
|
|
|
// OK
|
|
static void writer_tick(context_t *, dpcarg_t) {
|
|
EVENT_SIGNAL(&writer_wakeup_event);
|
|
}
|
|
|
|
// OK
|
|
static void writer(void) {
|
|
long ipl = interrupt_raiseipl(IPL_DPC);
|
|
timerentry_t timer_entry;
|
|
timer_insert(current_cpu()->timer, &timer_entry, writer_tick, NULL, (uintmax_t)WRITER_TICK_SECONDS * 1000000, true);
|
|
interrupt_loweripl(ipl);
|
|
|
|
for (;;) {
|
|
eventlistener_t listener;
|
|
EVENT_INITLISTENER(&listener);
|
|
|
|
MUTEX_ACQUIRE(&vnode_dirty_list_mutex);
|
|
list_node_t *list_node = vnode_dirty_list.head;
|
|
if (list_node == NULL) {
|
|
writer_syncing = false;
|
|
EVENT_ATTACH(&listener, &writer_wakeup_event);
|
|
EVENT_SIGNAL(&sync_complete_event);
|
|
MUTEX_RELEASE(&vnode_dirty_list_mutex);
|
|
|
|
EVENT_WAIT(&listener, 0);
|
|
EVENT_DETACHALL(&listener);
|
|
continue;
|
|
}
|
|
|
|
vnode_t *vnode = container_of(list_node, vnode_t, vnode_dirty_list_node);
|
|
VOP_HOLD(vnode);
|
|
|
|
writer_syncing = true;
|
|
MUTEX_RELEASE(&vnode_dirty_list_mutex);
|
|
|
|
mm_cache_sync_vnode(vnode);
|
|
|
|
VOP_RELEASE(vnode);
|
|
}
|
|
}
|
|
|
|
// OK
|
|
void mm_cache_init_writer(void) {
|
|
MUTEX_INIT(&vnode_dirty_list_mutex);
|
|
list_init(&vnode_dirty_list);
|
|
writer_syncing = false;
|
|
EVENT_INITHEADER(&writer_wakeup_event);
|
|
EVENT_INITHEADER(&sync_complete_event);
|
|
|
|
thread_t *writer_thread = sched_newthread(writer, PAGE_SIZE * 2, 0, NULL, NULL);
|
|
__assert(writer_thread);
|
|
sched_queue(writer_thread);
|
|
}
|