Compare commits

...

4 Commits

Author SHA1 Message Date
Bananymous ed82a18e2a Kernel: Fix deadlock in ext2 filesystem
If multiple threads were waiting for more block buffers without anyone
releasing them, they ended up in a deadlock.

Now we store 6 blocks for 8 threads. If a thread already has a block
buffer, it will not have to wait for a new one. Only if there are more
than 8 threads using blocks, will it block until there are free slots
for a thread available.
2026-01-10 00:30:30 +02:00
Bananymous 2961a49dc7 Kernel: Optimize futexes
Add support for processor local futexes. These work the exact same way
as global ones, but only lock a process specific lock and use a process
specific hash map.

Also reduce the time futex lock is held. There was no need to hold the
global lock while validating addresses in the process' address space.
2026-01-09 22:27:59 +02:00
Bananymous 5c9151d3e9 LibC: Add stubs for {init,set}state
Some port wanted these as it detected we had {,s}random
2026-01-09 22:08:32 +02:00
Bananymous 90deb9fb43 BAN: Make debug output thread safe
Now file lock is only acquired once per message, not once per character
2026-01-09 20:30:35 +02:00
8 changed files with 185 additions and 69 deletions

View File

@ -9,29 +9,35 @@
#include <BAN/Formatter.h>
#include <stdio.h>
#define __debug_putchar [](int c) { putc(c, stddbg); }
#define __debug_putchar [](int c) { putc_unlocked(c, stddbg); }
#define dprintln(...) \
do { \
flockfile(stddbg); \
BAN::Formatter::print(__debug_putchar, __VA_ARGS__); \
BAN::Formatter::print(__debug_putchar,"\n"); \
fflush(stddbg); \
funlockfile(stddbg); \
} while (false)
#define dwarnln(...) \
do { \
flockfile(stddbg); \
BAN::Formatter::print(__debug_putchar, "\e[33m"); \
BAN::Formatter::print(__debug_putchar, __VA_ARGS__); \
BAN::Formatter::print(__debug_putchar, "\e[m\n"); \
fflush(stddbg); \
funlockfile(stddbg); \
} while(false)
#define derrorln(...) \
do { \
flockfile(stddbg); \
BAN::Formatter::print(__debug_putchar, "\e[31m"); \
BAN::Formatter::print(__debug_putchar, __VA_ARGS__); \
BAN::Formatter::print(__debug_putchar, "\e[m\n"); \
fflush(stddbg); \
funlockfile(stddbg); \
} while(false)
#define dprintln_if(cond, ...) \

View File

@ -28,36 +28,28 @@ namespace Kernel
BAN_NON_COPYABLE(BlockBufferWrapper);
public:
BlockBufferWrapper(BAN::Span<uint8_t> buffer, bool* used, Mutex* mutex, ThreadBlocker* blocker)
BlockBufferWrapper(BAN::Span<uint8_t> buffer, void (*callback)(void*, const uint8_t*), void* argument)
: m_buffer(buffer)
, m_used(used)
, m_mutex(mutex)
, m_blocker(blocker)
{
ASSERT(m_used && *m_used);
}
, m_callback(callback)
, m_argument(argument)
{ }
BlockBufferWrapper(BlockBufferWrapper&& other) { *this = BAN::move(other); }
~BlockBufferWrapper()
{
if (m_used == nullptr)
if (m_callback == nullptr)
return;
m_mutex->lock();
*m_used = false;
m_blocker->unblock();
m_mutex->unlock();
m_callback(m_argument, m_buffer.data());
}
BlockBufferWrapper& operator=(BlockBufferWrapper&& other)
{
this->m_buffer = other.m_buffer;
this->m_used = other.m_used;
this->m_mutex = other.m_mutex;
this->m_blocker = other.m_blocker;
this->m_callback = other.m_callback;
this->m_argument = other.m_argument;
other.m_buffer = {};
other.m_used = nullptr;
other.m_mutex = nullptr;
other.m_blocker = nullptr;
other.m_callback = nullptr;
other.m_argument = nullptr;
return *this;
}
@ -75,9 +67,8 @@ namespace Kernel
private:
BAN::Span<uint8_t> m_buffer;
bool* m_used;
Mutex* m_mutex;
ThreadBlocker* m_blocker;
void (*m_callback)(void*, const uint8_t*);
void* m_argument;
};
public:
@ -130,6 +121,9 @@ namespace Kernel
BAN::ErrorOr<void> initialize(size_t block_size);
private:
void destroy_callback(const uint8_t* buffer_ptr);
private:
struct BlockBuffer
{
@ -137,10 +131,20 @@ namespace Kernel
bool used { false };
};
struct ThreadInfo
{
pid_t tid { 0 };
size_t buffers { 0 };
};
private:
static constexpr size_t max_threads = 8;
static constexpr size_t max_buffers_per_thread = 6;
Mutex m_buffer_mutex;
ThreadBlocker m_buffer_blocker;
BAN::Array<BlockBuffer, 16> m_buffers;
BAN::Array<BlockBuffer, max_threads * max_buffers_per_thread> m_buffers;
BAN::Array<ThreadInfo, max_threads> m_thread_infos;
};
private:

View File

@ -346,6 +346,19 @@ namespace Kernel
vaddr_t m_shared_page_vaddr { 0 };
struct futex_t
{
ThreadBlocker blocker;
uint32_t waiters { 0 };
uint32_t to_wakeup { 0 };
};
static BAN::HashMap<paddr_t, BAN::UniqPtr<futex_t>> s_futexes;
static Mutex s_futex_lock;
BAN::HashMap<paddr_t, BAN::UniqPtr<futex_t>> m_futexes;
Mutex m_futex_lock;
BAN::Vector<Thread*> m_threads;
struct pthread_info_t

View File

@ -532,22 +532,88 @@ namespace Kernel
};
}
BAN::ErrorOr<Ext2FS::BlockBufferWrapper> Ext2FS::BlockBufferManager::get_buffer()
void Ext2FS::BlockBufferManager::destroy_callback(const uint8_t* buffer_ptr)
{
const auto tid = Thread::current_tid();
LockGuard _(m_buffer_mutex);
for (;;)
for (auto& buffer : m_buffers)
{
for (auto& buffer : m_buffers)
if (buffer.buffer.data() != buffer_ptr)
continue;
ASSERT(buffer.used);
buffer.used = false;
break;
}
for (auto& info : m_thread_infos)
{
if (info.tid != tid)
continue;
ASSERT(info.buffers > 0);
info.buffers--;
if (info.buffers != 0)
break;
info.tid = 0;
m_buffer_blocker.unblock();
break;
}
}
BAN::ErrorOr<Ext2FS::BlockBufferWrapper> Ext2FS::BlockBufferManager::get_buffer()
{
const auto tid = Thread::current_tid();
ASSERT(tid);
LockGuard _(m_buffer_mutex);
ThreadInfo* thread_info = nullptr;
for (auto& info : m_thread_infos)
{
if (info.tid != tid)
continue;
thread_info = &info;
break;
}
while (thread_info == nullptr)
{
for (auto& info : m_thread_infos)
{
if (buffer.used)
if (info.tid != 0)
continue;
buffer.used = true;
return Ext2FS::BlockBufferWrapper(buffer.buffer.span(), &buffer.used, &m_buffer_mutex, &m_buffer_blocker);
thread_info = &info;
break;
}
if (thread_info)
{
thread_info->tid = tid;
thread_info->buffers = 0;
break;
}
TRY(Thread::current().block_or_eintr_indefinite(m_buffer_blocker, &m_buffer_mutex));
}
ASSERT(thread_info->buffers < max_buffers_per_thread);
thread_info->buffers++;
for (auto& buffer : m_buffers)
{
if (buffer.used)
continue;
buffer.used = true;
return Ext2FS::BlockBufferWrapper {
buffer.buffer.span(),
[](void* self, const uint8_t* buffer) { static_cast<BlockBufferManager*>(self)->destroy_callback(buffer); },
this
};
}
ASSERT_NOT_REACHED();
}
BAN::ErrorOr<void> Ext2FS::BlockBufferManager::initialize(size_t block_size)

View File

@ -38,14 +38,8 @@ namespace Kernel
static BAN::Vector<Process*> s_processes;
static RecursiveSpinLock s_process_lock;
struct futex_t
{
ThreadBlocker blocker;
uint32_t waiters { 0 };
uint32_t to_wakeup { 0 };
};
static BAN::HashMap<paddr_t, BAN::UniqPtr<futex_t>> s_futexes;
static Mutex s_futex_lock;
BAN::HashMap<paddr_t, BAN::UniqPtr<Process::futex_t>> Process::s_futexes;
Mutex Process::s_futex_lock;
static void for_each_process(const BAN::Function<BAN::Iteration(Process&)>& callback)
{
@ -3107,18 +3101,52 @@ namespace Kernel
return BAN::Error::from_errno(EINVAL);
const bool is_realtime = (op & FUTEX_REALTIME);
const bool is_private = (op & FUTEX_PRIVATE);
op &= ~(FUTEX_PRIVATE | FUTEX_REALTIME);
// TODO: possibly optimize private futexes?
LockGuard _(s_futex_lock);
auto* buffer_region = TRY(validate_and_pin_pointer_access(addr, sizeof(uint32_t), false));
BAN::ScopeGuard pin_guard([&] { if (buffer_region) buffer_region->unpin(); });
const paddr_t paddr = m_page_table->physical_address_of(vaddr & PAGE_ADDR_MASK) | (vaddr & ~PAGE_ADDR_MASK);
ASSERT(paddr != 0);
switch (op)
{
case FUTEX_WAIT:
break;
case FUTEX_WAKE:
abstime = nullptr;
break;
default:
return BAN::Error::from_errno(ENOSYS);
}
const uint64_t wake_time_ns =
TRY([abstime, is_realtime, this]() -> BAN::ErrorOr<uint64_t>
{
if (abstime == nullptr)
return BAN::numeric_limits<uint64_t>::max();
const uint64_t abs_ns =
TRY([abstime, this]() -> BAN::ErrorOr<uint64_t>
{
LockGuard _(m_process_lock);
TRY(validate_pointer_access(abstime, sizeof(*abstime), false));
return abstime->tv_sec * 1'000'000'000 + abstime->tv_nsec;
}());
if (!is_realtime)
return abs_ns;
const auto realtime = SystemTimer::get().real_time();
const uint64_t real_ns = realtime.tv_sec * 1'000'000'000 + realtime.tv_nsec;
if (abs_ns <= real_ns)
return BAN::Error::from_errno(ETIMEDOUT);
return SystemTimer::get().ns_since_boot() + (abs_ns - real_ns);
}());
auto& futex_lock = is_private ? m_futex_lock : s_futex_lock;
auto& futexes = is_private ? m_futexes : s_futexes;
LockGuard _(futex_lock);
switch (op)
{
case FUTEX_WAIT:
@ -3126,36 +3154,20 @@ namespace Kernel
if (BAN::atomic_load(*addr) != val)
return BAN::Error::from_errno(EAGAIN);
const uint64_t wake_time_ns =
TRY([abstime, is_realtime, this]() -> BAN::ErrorOr<uint64_t>
{
if (abstime == nullptr)
return BAN::numeric_limits<uint64_t>::max();
TRY(validate_pointer_access(abstime, sizeof(*abstime), false));
const uint64_t abs_ns = abstime->tv_sec * 1'000'000'000 + abstime->tv_nsec;
if (!is_realtime)
return abs_ns;
const auto realtime = SystemTimer::get().real_time();
const uint64_t real_ns = realtime.tv_sec * 1'000'000'000 + realtime.tv_nsec;
if (abs_ns <= real_ns)
return BAN::Error::from_errno(ETIMEDOUT);
return SystemTimer::get().ns_since_boot() + (abs_ns - real_ns);
}());
auto it = s_futexes.find(paddr);
if (it == s_futexes.end())
it = TRY(s_futexes.emplace(paddr, TRY(BAN::UniqPtr<futex_t>::create())));
auto it = futexes.find(paddr);
if (it == futexes.end())
it = TRY(futexes.emplace(paddr, TRY(BAN::UniqPtr<futex_t>::create())));
futex_t* const futex = it->value.ptr();
futex->waiters++;
BAN::ScopeGuard _([futex, paddr] {
BAN::ScopeGuard _([&futexes, futex, paddr] {
if (--futex->waiters == 0)
s_futexes.remove(paddr);
futexes.remove(paddr);
});
for (;;)
{
TRY(Thread::current().block_or_eintr_or_waketime_ns(futex->blocker, wake_time_ns, true, &s_futex_lock));
TRY(Thread::current().block_or_eintr_or_waketime_ns(futex->blocker, wake_time_ns, true, &futex_lock));
if (BAN::atomic_load(*addr) == val || futex->to_wakeup == 0)
continue;
futex->to_wakeup--;
@ -3164,8 +3176,8 @@ namespace Kernel
}
case FUTEX_WAKE:
{
auto it = s_futexes.find(paddr);
if (it == s_futexes.end())
auto it = futexes.find(paddr);
if (it == futexes.end())
return 0;
futex_t* const futex = it->value.ptr();
@ -3178,8 +3190,6 @@ namespace Kernel
futex->blocker.unblock();
return 0;
}
default:
return BAN::Error::from_errno(ENOSYS);
}
ASSERT_NOT_REACHED();

View File

@ -884,9 +884,11 @@ int pthread_mutex_unlock(pthread_mutex_t* mutex)
mutex->lock_depth--;
if (mutex->lock_depth == 0)
{
const int op = FUTEX_WAKE | (mutex->attr.shared ? 0 : FUTEX_PRIVATE);
BAN::atomic_store(mutex->futex, 0, BAN::memory_order_release);
if (BAN::atomic_load(mutex->waiters))
futex(FUTEX_WAKE, &mutex->futex, 1, nullptr);
futex(op, &mutex->futex, 1, nullptr);
}
return 0;

View File

@ -28,8 +28,9 @@ int sem_getvalue(sem_t* __restrict sem, int* __restrict sval)
int sem_post(sem_t* sem)
{
const auto old = BAN::atomic_fetch_add(sem->value, 1);
const int op = FUTEX_WAKE | (sem->shared ? 0 : FUTEX_PRIVATE);
if (old == 0)
futex(FUTEX_WAKE, &sem->value, 1, nullptr);
futex(op, &sem->value, 1, nullptr);
return 0;
}

View File

@ -888,3 +888,17 @@ void srandom(unsigned seed)
{
s_random_state.seed(seed);
}
char* initstate(unsigned seed, char* state, size_t size)
{
(void)seed;
(void)state;
(void)size;
ASSERT_NOT_REACHED();
}
char* setstate(char* state)
{
(void)state;
ASSERT_NOT_REACHED();
}