[kernel] First steps at removing channel objects

This commit does a number of things to start the transition of channels
from kernel to user space:

- Remove channel objects / syscalls from the kernel
- Add mutex type in libj6
- Add condition type in libj6
- Add a `ring` type flag for VMA syscalls to create ring buffers
- Implement a rudimentary shared memory channel using all of the above
This commit is contained in:
Justin C. Miller
2023-03-16 19:32:52 -07:00
parent ed95574c24
commit 9fa588566f
20 changed files with 323 additions and 251 deletions

View File

@@ -0,0 +1,150 @@
// The kernel depends on libj6 for some shared code,
// but should not include the user-specific code.
#ifndef __j6kernel
#include <string.h>
#include <arch/memory.h>
#include <j6/channel.hh>
#include <j6/condition.hh>
#include <j6/errors.h>
#include <j6/flags.h>
#include <j6/mutex.hh>
#include <j6/syscalls.h>
#include <j6/syslog.hh>
#include <util/spinlock.h>
namespace j6 {
static uintptr_t channel_addr = 0x6000'0000;
static util::spinlock addr_spinlock;
struct channel::header
{
size_t size;
size_t read_index;
size_t write_index;
mutex mutex;
condition read_waiting;
condition write_waiting;
uint8_t data[0];
inline const void* read_at() const { return &data[read_index & (size - 1)]; }
inline void* write_at() { return &data[write_index & (size - 1)]; }
inline size_t read_avail() const { return write_index - read_index; }
inline size_t write_avail() const { return size - read_avail(); }
inline void consume(size_t n) { read_index += n; }
inline void commit(size_t n) { write_index += n; }
};
channel *
channel::create(size_t size)
{
j6_status_t result;
j6_handle_t vma = j6_handle_invalid;
if (size < arch::frame_size || (size & (size - 1)) != 0) {
syslog("Bad channel size: %lx", size);
return nullptr;
}
util::scoped_lock lock {addr_spinlock};
uintptr_t addr = channel_addr;
channel_addr += size;
lock.release();
result = j6_vma_create_map(&vma, size, addr, j6_vm_flag_write|j6_vm_flag_ring);
if (result != j6_status_ok) {
syslog("Failed to create channel VMA. Error: %lx", result);
return nullptr;
}
header *h = reinterpret_cast<header*>(addr);
memset(h, 0, sizeof(*h));
h->size = size;
return new channel {vma, h};
}
channel *
channel::open(j6_handle_t vma)
{
j6_status_t result;
util::scoped_lock lock {addr_spinlock};
uintptr_t addr = channel_addr;
result = j6_vma_map(vma, 0, addr);
if (result != j6_status_ok) {
syslog("Failed to map channel VMA. Error: %lx", result);
return nullptr;
}
header *h = reinterpret_cast<header*>(addr);
channel_addr += h->size;
lock.release();
return new channel {vma, h};
}
channel::channel(j6_handle_t vma, header *h) :
m_vma {vma},
m_size {h->size},
m_header {h}
{
}
j6_status_t
channel::send(const void *buffer, size_t len, bool block)
{
if (len > m_header->size)
return j6_err_insufficient;
j6::scoped_lock lock {m_header->mutex};
while (m_header->write_avail() < len) {
if (!block)
return j6_status_would_block;
lock.release();
m_header->write_waiting.wait();
lock.acquire();
}
memcpy(m_header->write_at(), buffer, len);
m_header->commit(len);
m_header->read_waiting.wake();
return j6_status_ok;
}
j6_status_t
channel::receive(void *buffer, size_t *size, bool block)
{
j6::scoped_lock lock {m_header->mutex};
while (!m_header->read_avail()) {
if (!block)
return j6_status_would_block;
lock.release();
m_header->read_waiting.wait();
lock.acquire();
}
size_t avail = m_header->read_avail();
size_t read = *size > avail ? avail : *size;
memcpy(buffer, m_header->read_at(), read);
m_header->consume(read);
m_header->write_waiting.wake();
*size = read;
return j6_status_ok;
}
} // namespace j6
#endif // __j6kernel

View File

@@ -0,0 +1,37 @@
// The kernel depends on libj6 for some shared code,
// but should not include the user-specific code.
#ifndef __j6kernel
#include <j6/condition.hh>
#include <j6/errors.h>
#include <j6/syscalls.h>
#include <j6/syslog.hh>
namespace j6 {
void
condition::wait()
{
j6::syslog("Waiting on condition %lx", this);
uint32_t v = __atomic_add_fetch(&m_state, 1, __ATOMIC_ACQ_REL);
j6_status_t s = j6_futex_wait(&m_state, v, 0);
while (s == j6_status_futex_changed) {
v = m_state;
if (v == 0) break;
s = j6_futex_wait(&m_state, v, 0);
}
j6::syslog("Woke on condition %lx", this);
}
void
condition::wake()
{
uint32_t v = __atomic_exchange_n(&m_state, 0, __ATOMIC_ACQ_REL);
if (v)
j6_futex_wake(&m_state, 0);
}
} // namespace j6
#endif // __j6kernel

View File

@@ -4,6 +4,8 @@ j6 = module("j6",
kind = "lib",
deps = [ "util" ],
sources = [
"channel.cpp",
"condition.cpp",
"init.cpp",
"init.s",
"mutex.cpp",
@@ -15,6 +17,8 @@ j6 = module("j6",
],
public_headers = [
"j6/cap_flags.h.cog",
"j6/channel.hh",
"j6/condition.hh",
"j6/errors.h",
"j6/flags.h",
"j6/init.h",

View File

@@ -0,0 +1,51 @@
#pragma once
/// \file channel.hh
/// High level channel interface
// The kernel depends on libj6 for some shared code,
// but should not include the user-specific code.
#ifndef __j6kernel
#include <stddef.h>
#include <j6/types.h>
namespace j6 {
class channel
{
public:
/// Create a new channel of the given size.
static channel * create(size_t size);
/// Open an existing channel for which we have a VMA handle
static channel * open(j6_handle_t vma);
/// Send data into the channel.
/// \arg buffer The buffer from which to read data
/// \arg len The number of bytes to read from `buffer`
/// \arg block If true, block this thread if there aren't `len` bytes of space available
j6_status_t send(const void *buffer, size_t len, bool block = true);
/// Read data out of the channel.
/// \arg buffer The buffer to receive channel data
/// \arg size [in] The size of `buffer` [out] the amount read into `buffer`
/// \arg block If true, block this thread if there is no data to read yet
j6_status_t receive(void *buffer, size_t *size, bool block = true);
/// Get the VMA handle for sharing with other processes
j6_handle_t handle() const { return m_vma; }
private:
struct header;
channel(j6_handle_t vma, header *h);
j6_handle_t m_vma;
size_t m_size;
header *m_header;
};
} // namespace j6
#endif // __j6kernel

View File

@@ -0,0 +1,28 @@
#pragma once
/// \file condition.hh
/// High level condition interface based on futexes
// The kernel depends on libj6 for some shared code,
// but should not include the user-specific code.
#ifndef __j6kernel
#include <stddef.h>
#include <stdint.h>
namespace j6 {
class condition
{
public:
condition() : m_state {0} {}
void wait();
void wake();
private:
uint32_t m_state;
};
} // namespace j6
#endif // __j6kernel

View File

@@ -15,6 +15,7 @@
#define j6_status_destroyed 0x1001
#define j6_status_exists 0x1002
#define j6_status_would_block 0x1003
#define j6_status_futex_changed 0x1004
#define j6_err_nyi j6_err(0x0001)
#define j6_err_unexpected j6_err(0x0002)

View File

@@ -9,10 +9,8 @@ enum j6_vm_flags {
j6_vm_flag_MAX
};
enum j6_channel_flags {
j6_channel_block = 0x01,
};
enum j6_flags {
j6_flag_block = 0x01,
enum j6_mailbox_flags {
j6_mailbox_block = 0x01,
j6_flags_COUNT // custom per-type flags should start here
};

View File

@@ -33,6 +33,9 @@ public:
m_mutex.unlock();
}
inline void acquire() { m_mutex.lock(); }
inline void release() { m_mutex.unlock(); }
private:
mutex &m_mutex;
};

View File

@@ -3,7 +3,7 @@ OBJECT_TYPE( none, 0x00 )
OBJECT_TYPE( system, 0x01 )
OBJECT_TYPE( event, 0x02 )
OBJECT_TYPE( channel, 0x03 )
OBJECT_TYPE( endpoint, 0x04 )
OBJECT_TYPE( mailbox, 0x05 )

View File

@@ -1,8 +1,10 @@
VM_FLAG( none, 0x00000000)
VM_FLAG( write, 0x00000001)
VM_FLAG( exec, 0x00000002)
VM_FLAG( contiguous, 0x00000020)
VM_FLAG( large_pages, 0x00000100)
VM_FLAG( huge_pages, 0x00000200)
VM_FLAG( write_combine, 0x00001000)
VM_FLAG( mmio, 0x00010000)
VM_FLAG( none, 0x00000000 )
VM_FLAG( write, 0x00000001 )
VM_FLAG( exec, 0x00000002 )
VM_FLAG( contiguous, 0x00000020 )
VM_FLAG( large_pages, 0x00000100 )
VM_FLAG( huge_pages, 0x00000200 )
VM_FLAG( write_combine, 0x00001000 )
VM_FLAG( ring, 0x00002000 )
VM_FLAG( mmio, 0x00010000 )