Start working on a VFS implementation #22
@ -28,7 +28,7 @@ set(CMAKE_FIND_ROOT_PATH ${LUNA_ROOT}/toolchain/${LUNA_ARCH}-luna)
|
||||
|
||||
message(STATUS "Configuring Luna for ${LUNA_ARCH}")
|
||||
|
||||
add_subdirectory(luna)
|
||||
add_subdirectory(libluna)
|
||||
add_subdirectory(libc)
|
||||
add_subdirectory(kernel)
|
||||
add_subdirectory(apps)
|
||||
|
@ -8,7 +8,7 @@ A simple kernel and userspace for desktop computers, written mostly in C++ and C
|
||||
- [System call](kernel/src/sys/) interface and [C Library](libc/), aiming to be mostly POSIX-compatible.
|
||||
- Designed to be [portable](kernel/src/arch), no need to be restricted to x86_64.
|
||||
- Fully [UTF-8 aware](luna/include/luna/Utf8.h), **everywhere**.
|
||||
- [Thread](luna/include/luna/Atomic.h) [safety](kernel/src/thread/Spinlock.h).
|
||||
- [Thread](luna/include/luna/Atomic.h) [safety](kernel/src/thread/Spinlock.h) (supposedly).
|
||||
- Environment-agnostic [utility library](luna/), which can be used in both kernel and userspace.
|
||||
- Return-oriented [error propagation](luna/include/luna/Result.h), inspired by Rust and SerenityOS.
|
||||
- Build system uses [CMake](CMakeLists.txt).
|
||||
@ -72,7 +72,7 @@ These images do reflect the latest changes on the `main` branch, but are obvious
|
||||
|
||||
## Is there third-party software I can use on Luna?
|
||||
|
||||
Not right now, but hopefully we can start porting some software soon!
|
||||
Not right now, but hopefully we can start porting some software soon! (After the VFS and fork/exec are done, of course. So, in a long time.)
|
||||
|
||||
## License
|
||||
Luna is open-source and free software under the [BSD-2 License](LICENSE).
|
||||
|
@ -18,7 +18,6 @@ set(SOURCES
|
||||
src/arch/Serial.cpp
|
||||
src/arch/Timer.cpp
|
||||
src/arch/PCI.cpp
|
||||
src/thread/Spinlock.cpp
|
||||
src/thread/Thread.cpp
|
||||
src/thread/Scheduler.cpp
|
||||
src/sys/Syscall.cpp
|
||||
@ -27,6 +26,8 @@ set(SOURCES
|
||||
src/sys/clock_gettime.cpp
|
||||
src/sys/allocate_memory.cpp
|
||||
src/sys/usleep.cpp
|
||||
src/fs/VFS.cpp
|
||||
src/fs/tmpfs/FileSystem.cpp
|
||||
src/InitRD.cpp
|
||||
src/ELF.cpp
|
||||
)
|
||||
|
@ -1 +1,2 @@
|
||||
target_compile_definitions(moon PRIVATE LOCKED_VALUE_DEBUG)
|
||||
target_compile_options(moon PRIVATE -fsanitize=undefined)
|
||||
|
@ -1,18 +1,13 @@
|
||||
#include "InitRD.h"
|
||||
#include "arch/MMU.h"
|
||||
#include "boot/bootboot.h"
|
||||
#include "memory/MemoryManager.h"
|
||||
#include <luna/Alignment.h>
|
||||
|
||||
TarStream g_initrd;
|
||||
extern const BOOTBOOT bootboot;
|
||||
|
||||
void InitRD::initialize()
|
||||
{
|
||||
u64 virtual_initrd_address =
|
||||
MemoryManager::get_kernel_mapping_for_frames(
|
||||
bootboot.initrd_ptr, get_blocks_from_size(bootboot.initrd_size, ARCH_PAGE_SIZE), MMU::NoExecute)
|
||||
.expect_value("Unable to map the initial ramdisk into virtual memory");
|
||||
u64 virtual_initrd_address = MMU::translate_physical_address(bootboot.initrd_ptr);
|
||||
|
||||
g_initrd.initialize((void*)virtual_initrd_address, bootboot.initrd_size);
|
||||
}
|
||||
|
@ -2,10 +2,10 @@
|
||||
#include "arch/CPU.h"
|
||||
#include "arch/Serial.h"
|
||||
#include "arch/Timer.h"
|
||||
#include "thread/Spinlock.h"
|
||||
#include "video/TextConsole.h"
|
||||
#include <luna/Format.h>
|
||||
#include <luna/SourceLocation.h>
|
||||
#include <luna/Spinlock.h>
|
||||
|
||||
static bool g_debug_enabled = true;
|
||||
static bool g_serial_enabled = true;
|
||||
|
@ -26,5 +26,7 @@ namespace CPU
|
||||
void get_stack_trace_at(Registers* regs, void (*callback)(u64, void*), void* arg);
|
||||
void print_stack_trace_at(Registers* regs);
|
||||
|
||||
[[noreturn]] void bootstrap_switch_stack(u64 stack, void* function);
|
||||
|
||||
void pause();
|
||||
}
|
||||
|
@ -7,6 +7,8 @@
|
||||
#error "Unknown architecture."
|
||||
#endif
|
||||
|
||||
constexpr u64 PAGES_PER_HUGE_PAGE = ARCH_HUGE_PAGE_SIZE / ARCH_PAGE_SIZE;
|
||||
|
||||
namespace MMU
|
||||
{
|
||||
enum Flags
|
||||
@ -19,7 +21,15 @@ namespace MMU
|
||||
CacheDisable = 16,
|
||||
};
|
||||
|
||||
Result<void> map(u64 virt, u64 phys, int flags);
|
||||
enum class UseHugePages
|
||||
{
|
||||
No = 0,
|
||||
Yes = 1
|
||||
};
|
||||
|
||||
u64 translate_physical_address(u64 phys);
|
||||
|
||||
Result<void> map(u64 virt, u64 phys, int flags, UseHugePages use_huge_pages);
|
||||
Result<u64> unmap(u64 virt);
|
||||
Result<u64> get_physical(u64 virt);
|
||||
Result<int> get_flags(u64 virt);
|
||||
|
@ -295,6 +295,15 @@ namespace CPU
|
||||
&frame_index);
|
||||
}
|
||||
|
||||
[[noreturn]] void bootstrap_switch_stack(u64 stack, void* function)
|
||||
{
|
||||
asm volatile("mov %0, %%rsp\n"
|
||||
"jmp *%1"
|
||||
:
|
||||
: "r"(stack), "r"(function));
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
void pause()
|
||||
{
|
||||
asm volatile("pause");
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include "arch/MMU.h"
|
||||
#include "Log.h"
|
||||
#include "memory/MemoryManager.h"
|
||||
#include "memory/MemoryMap.h"
|
||||
#include <luna/Alignment.h>
|
||||
#include <luna/CString.h>
|
||||
#include <luna/Result.h>
|
||||
#include <luna/ScopeGuard.h>
|
||||
@ -12,6 +14,10 @@
|
||||
PageDirectory* g_kernel_directory;
|
||||
u64 g_kernel_directory_virt;
|
||||
|
||||
// The bootloader maps up to 16GiB of physical memory for us at address 0. Using this bootstrap mapping, we'll map (all)
|
||||
// physical memory at 0xFFFF800000000000.
|
||||
u64 g_physical_mapping_base = 0;
|
||||
|
||||
void PageTableEntry::set_address(u64 addr)
|
||||
{
|
||||
this->address = (addr >> 12);
|
||||
@ -36,90 +42,14 @@ static bool has_flag(int flags, MMU::Flags flag)
|
||||
|
||||
namespace MMU
|
||||
{
|
||||
|
||||
constexpr PageDirectory* l4_table()
|
||||
template <typename T> T translate_physical(T phys)
|
||||
{
|
||||
constexpr u64 l4 = sign | (rindex << 39) | (rindex << 30) | (rindex << 21) | (rindex << 12);
|
||||
return (PageDirectory*)l4;
|
||||
return (T)(g_physical_mapping_base + (u64)phys);
|
||||
}
|
||||
|
||||
constexpr u64 l4_index(u64 addr)
|
||||
u64 translate_physical_address(u64 phys)
|
||||
{
|
||||
return (addr >> 39) & 0777;
|
||||
}
|
||||
|
||||
PageTableEntry& l4_entry(u64 addr)
|
||||
{
|
||||
return l4_table()->entries[l4_index(addr)];
|
||||
}
|
||||
|
||||
constexpr PageDirectory* raw_l3_table(u64 l4)
|
||||
{
|
||||
const u64 l3 = sign | (rindex << 39) | (rindex << 30) | (rindex << 21) | (l4 << 12);
|
||||
return (PageDirectory*)l3;
|
||||
}
|
||||
|
||||
constexpr PageDirectory* l3_table(u64 addr)
|
||||
{
|
||||
const u64 l4 = l4_index(addr);
|
||||
return raw_l3_table(l4);
|
||||
}
|
||||
|
||||
constexpr u64 l3_index(u64 addr)
|
||||
{
|
||||
return (addr >> 30) & 0777;
|
||||
}
|
||||
|
||||
PageTableEntry& l3_entry(u64 addr)
|
||||
{
|
||||
return l3_table(addr)->entries[l3_index(addr)];
|
||||
}
|
||||
|
||||
constexpr PageDirectory* raw_l2_table(u64 l4, u64 l3)
|
||||
{
|
||||
const u64 l2 = sign | (rindex << 39) | (rindex << 30) | (l4 << 21) | (l3 << 12);
|
||||
return (PageDirectory*)l2;
|
||||
}
|
||||
|
||||
constexpr PageDirectory* l2_table(u64 addr)
|
||||
{
|
||||
const u64 l4 = l4_index(addr);
|
||||
const u64 l3 = l3_index(addr);
|
||||
return raw_l2_table(l4, l3);
|
||||
}
|
||||
|
||||
constexpr u64 l2_index(u64 addr)
|
||||
{
|
||||
return (addr >> 21) & 0777;
|
||||
}
|
||||
|
||||
PageTableEntry& l2_entry(u64 addr)
|
||||
{
|
||||
return l2_table(addr)->entries[l2_index(addr)];
|
||||
}
|
||||
|
||||
constexpr PageDirectory* raw_l1_table(u64 l4, u64 l3, u64 l2)
|
||||
{
|
||||
const u64 l1 = sign | (rindex << 39) | (l4 << 30) | (l3 << 21) | (l2 << 12);
|
||||
return (PageDirectory*)l1;
|
||||
}
|
||||
|
||||
constexpr PageDirectory* l1_table(u64 addr)
|
||||
{
|
||||
const u64 l4 = l4_index(addr);
|
||||
const u64 l3 = l3_index(addr);
|
||||
const u64 l2 = l2_index(addr);
|
||||
return raw_l1_table(l4, l3, l2);
|
||||
}
|
||||
|
||||
constexpr u64 l1_index(u64 addr)
|
||||
{
|
||||
return (addr >> 12) & 0777;
|
||||
}
|
||||
|
||||
PageTableEntry& l1_entry(u64 addr)
|
||||
{
|
||||
return l1_table(addr)->entries[l1_index(addr)];
|
||||
return g_physical_mapping_base + phys;
|
||||
}
|
||||
|
||||
void switch_page_directory(PageDirectory* dir)
|
||||
@ -134,6 +64,11 @@ namespace MMU
|
||||
return value;
|
||||
}
|
||||
|
||||
PageDirectory* get_virtual_page_directory()
|
||||
{
|
||||
return translate_physical(get_page_directory());
|
||||
}
|
||||
|
||||
void flush_all()
|
||||
{
|
||||
switch_page_directory(get_page_directory());
|
||||
@ -144,6 +79,26 @@ namespace MMU
|
||||
asm volatile("invlpg (%0)" : : "r"(page) : "memory");
|
||||
}
|
||||
|
||||
constexpr u64 l4_index(u64 addr)
|
||||
{
|
||||
return (addr >> 39) & 0777;
|
||||
}
|
||||
|
||||
constexpr u64 l3_index(u64 addr)
|
||||
{
|
||||
return (addr >> 30) & 0777;
|
||||
}
|
||||
|
||||
constexpr u64 l2_index(u64 addr)
|
||||
{
|
||||
return (addr >> 21) & 0777;
|
||||
}
|
||||
|
||||
constexpr u64 l1_index(u64 addr)
|
||||
{
|
||||
return (addr >> 12) & 0777;
|
||||
}
|
||||
|
||||
int arch_flags_to_mmu(const PageTableEntry& entry)
|
||||
{
|
||||
int result = Flags::None;
|
||||
@ -155,17 +110,46 @@ namespace MMU
|
||||
return result;
|
||||
}
|
||||
|
||||
PageTableEntry& l4_entry(u64 virt)
|
||||
{
|
||||
auto index = l4_index(virt);
|
||||
return get_virtual_page_directory()->entries[index];
|
||||
}
|
||||
|
||||
PageDirectory& page_table(const PageTableEntry& entry)
|
||||
{
|
||||
return *translate_physical((PageDirectory*)entry.get_address());
|
||||
}
|
||||
|
||||
PageTableEntry& l3_entry(const PageTableEntry& entry, u64 virt)
|
||||
{
|
||||
auto index = l3_index(virt);
|
||||
return page_table(entry).entries[index];
|
||||
}
|
||||
|
||||
PageTableEntry& l2_entry(const PageTableEntry& entry, u64 virt)
|
||||
{
|
||||
auto index = l2_index(virt);
|
||||
return page_table(entry).entries[index];
|
||||
}
|
||||
|
||||
PageTableEntry& l1_entry(const PageTableEntry& entry, u64 virt)
|
||||
{
|
||||
auto index = l1_index(virt);
|
||||
return page_table(entry).entries[index];
|
||||
}
|
||||
|
||||
Result<PageTableEntry*> find_entry(u64 virt)
|
||||
{
|
||||
const auto& l4 = l4_entry(virt);
|
||||
if (!l4.present) return err(EFAULT);
|
||||
auto& l3 = l3_entry(virt);
|
||||
auto& l3 = l3_entry(l4, virt);
|
||||
if (!l3.present) return err(EFAULT);
|
||||
if (l3.larger_pages) return &l3;
|
||||
auto& l2 = l2_entry(virt);
|
||||
auto& l2 = l2_entry(l3, virt);
|
||||
if (!l2.present) return err(EFAULT);
|
||||
if (l2.larger_pages) return &l2;
|
||||
return &l1_entry(virt);
|
||||
return &l1_entry(l2, virt);
|
||||
}
|
||||
|
||||
Result<PageTableEntry*> apply_cascading_flags(u64 virt, int flags)
|
||||
@ -174,21 +158,32 @@ namespace MMU
|
||||
if (!l4.present) return err(EFAULT);
|
||||
if (flags & Flags::ReadWrite) l4.read_write = true;
|
||||
if (flags & Flags::User) l4.user = true;
|
||||
auto& l3 = l3_entry(virt);
|
||||
auto& l3 = l3_entry(l4, virt);
|
||||
if (!l3.present) return err(EFAULT);
|
||||
if (l3.larger_pages) return &l3;
|
||||
if (flags & Flags::ReadWrite) l3.read_write = true;
|
||||
if (flags & Flags::User) l3.user = true;
|
||||
auto& l2 = l2_entry(virt);
|
||||
auto& l2 = l2_entry(l3, virt);
|
||||
if (!l2.present) return err(EFAULT);
|
||||
if (l2.larger_pages) return &l2;
|
||||
if (flags & Flags::ReadWrite) l2.read_write = true;
|
||||
if (flags & Flags::User) l2.user = true;
|
||||
auto& l1 = l1_entry(virt);
|
||||
auto& l1 = l1_entry(l2, virt);
|
||||
return &l1;
|
||||
}
|
||||
|
||||
Result<void> map(u64 virt, u64 phys, int flags)
|
||||
void set_page_table_entry_properties(PageTableEntry& entry, u64 phys, int flags)
|
||||
{
|
||||
entry.present = true;
|
||||
entry.read_write = has_flag(flags, Flags::ReadWrite);
|
||||
entry.user = has_flag(flags, Flags::User);
|
||||
entry.write_through = has_flag(flags, Flags::WriteThrough);
|
||||
entry.cache_disabled = has_flag(flags, Flags::CacheDisable);
|
||||
entry.no_execute = has_flag(flags, Flags::NoExecute);
|
||||
entry.set_address(phys);
|
||||
}
|
||||
|
||||
Result<void> map(u64 virt, u64 phys, int flags, UseHugePages use_huge_pages)
|
||||
{
|
||||
auto& l4 = l4_entry(virt);
|
||||
if (!l4.present)
|
||||
@ -196,46 +191,51 @@ namespace MMU
|
||||
const u64 addr = TRY(MemoryManager::alloc_frame());
|
||||
l4.present = true;
|
||||
l4.set_address(addr);
|
||||
memset(l3_table(virt), 0, ARCH_PAGE_SIZE);
|
||||
memset(&page_table(l4), 0, ARCH_PAGE_SIZE);
|
||||
}
|
||||
if (flags & Flags::ReadWrite) l4.read_write = true;
|
||||
if (flags & Flags::User) l4.user = true;
|
||||
|
||||
auto& l3 = l3_entry(virt);
|
||||
auto& l3 = l3_entry(l4, virt);
|
||||
if (!l3.present)
|
||||
{
|
||||
const u64 addr = TRY(MemoryManager::alloc_frame());
|
||||
l3.present = true;
|
||||
l3.set_address(addr);
|
||||
memset(l2_table(virt), 0, ARCH_PAGE_SIZE);
|
||||
memset(&page_table(l3), 0, ARCH_PAGE_SIZE);
|
||||
}
|
||||
if (flags & Flags::ReadWrite) l3.read_write = true;
|
||||
if (flags & Flags::User) l3.user = true;
|
||||
|
||||
if (l3.larger_pages) return err(EFIXME); // FIXME: Replacing larger pages is not supported ATM
|
||||
if (l3.larger_pages) return err(EEXIST);
|
||||
|
||||
auto& l2 = l2_entry(virt);
|
||||
auto& l2 = l2_entry(l3, virt);
|
||||
if (!l2.present)
|
||||
{
|
||||
const u64 addr = TRY(MemoryManager::alloc_frame());
|
||||
l2.present = true;
|
||||
l2.set_address(addr);
|
||||
memset(l1_table(virt), 0, ARCH_PAGE_SIZE);
|
||||
|
||||
if (use_huge_pages == UseHugePages::No)
|
||||
{
|
||||
const u64 addr = TRY(MemoryManager::alloc_frame());
|
||||
l2.set_address(addr);
|
||||
memset(&page_table(l2), 0, ARCH_PAGE_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & Flags::ReadWrite) l2.read_write = true;
|
||||
if (flags & Flags::User) l2.user = true;
|
||||
|
||||
if (l2.larger_pages) return err(EFIXME); // FIXME: Replacing larger pages is not supported ATM
|
||||
if (l2.larger_pages) return err(EEXIST);
|
||||
else if (use_huge_pages == UseHugePages::Yes)
|
||||
{
|
||||
l2.larger_pages = true;
|
||||
set_page_table_entry_properties(l2, phys, flags);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto& l1 = l1_entry(virt);
|
||||
auto& l1 = l1_entry(l2, virt);
|
||||
if (l1.present) return err(EEXIST); // Please explicitly unmap the page before mapping it again.
|
||||
l1.present = true;
|
||||
l1.read_write = has_flag(flags, Flags::ReadWrite);
|
||||
l1.user = has_flag(flags, Flags::User);
|
||||
l1.write_through = has_flag(flags, Flags::WriteThrough);
|
||||
l1.cache_disabled = has_flag(flags, Flags::CacheDisable);
|
||||
l1.no_execute = has_flag(flags, Flags::NoExecute);
|
||||
l1.set_address(phys);
|
||||
set_page_table_entry_properties(l1, phys, flags);
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -281,39 +281,39 @@ namespace MMU
|
||||
PageDirectory* const dir = get_page_directory();
|
||||
g_kernel_directory = dir;
|
||||
|
||||
const u64 paddr = (u64)dir;
|
||||
PageTableEntry& recursive_entry = dir->entries[rindex];
|
||||
recursive_entry.read_write = true;
|
||||
recursive_entry.present = true;
|
||||
recursive_entry.set_address(paddr);
|
||||
flush_all();
|
||||
const u64 physical_memory_base = 0xFFFF800000000000;
|
||||
|
||||
g_kernel_directory_virt =
|
||||
MemoryManager::get_kernel_mapping_for_frames((u64)dir, 1, MMU::ReadWrite | MMU::NoExecute).value();
|
||||
MemoryMapIterator iter;
|
||||
const MemoryMapEntry highest_entry = iter.highest();
|
||||
|
||||
const u64 physical_memory_size = highest_entry.address() + highest_entry.size();
|
||||
|
||||
check(physical_memory_size % ARCH_HUGE_PAGE_SIZE == 0);
|
||||
MemoryManager::map_huge_frames_at(physical_memory_base, 0, physical_memory_size / ARCH_HUGE_PAGE_SIZE,
|
||||
MMU::ReadWrite | MMU::NoExecute);
|
||||
|
||||
g_physical_mapping_base = physical_memory_base;
|
||||
|
||||
g_kernel_directory_virt = translate_physical((u64)g_kernel_directory);
|
||||
|
||||
kdbgln("MMU init page directory (ring0): virt %#.16lx, phys %p", g_kernel_directory_virt, g_kernel_directory);
|
||||
}
|
||||
|
||||
Result<PageDirectory*> create_page_directory_for_userspace()
|
||||
{
|
||||
const u64 directory_virt = TRY(MemoryManager::alloc_for_kernel(1, MMU::ReadWrite | MMU::NoExecute));
|
||||
const u64 directory_phys = MMU::get_physical(directory_virt).value();
|
||||
const u64 directory_phys = TRY(MemoryManager::alloc_frame());
|
||||
const u64 directory_virt = translate_physical(directory_phys);
|
||||
|
||||
PageDirectory* const directory = (PageDirectory*)directory_virt;
|
||||
memset(directory, 0, ARCH_PAGE_SIZE);
|
||||
PageTableEntry& recursive_entry = directory->entries[rindex];
|
||||
recursive_entry.read_write = true;
|
||||
recursive_entry.present = true;
|
||||
recursive_entry.set_address(directory_phys);
|
||||
|
||||
constexpr auto HALF_PAGE = ARCH_PAGE_SIZE / 2;
|
||||
// Copy the upper part of the page directory (higher half)
|
||||
memcpy(offset_ptr(directory, HALF_PAGE), offset_ptr((PageDirectory*)g_kernel_directory_virt, HALF_PAGE),
|
||||
HALF_PAGE);
|
||||
|
||||
kdbgln("MMU init page directory (ring3): virt %p, phys %#.16lx", directory, directory_phys);
|
||||
|
||||
directory->entries[511] = ((PageDirectory*)g_kernel_directory_virt)->entries[511];
|
||||
|
||||
// From now on, we're only going to use the physical address, since accessing the PageDirectory will be dealt
|
||||
// with using recursive mapping. So let's make sure we don't leak any VM.
|
||||
MemoryManager::unmap_weak_and_free_vm(directory_virt, 1);
|
||||
|
||||
return (PageDirectory*)directory_phys;
|
||||
}
|
||||
|
||||
@ -321,16 +321,11 @@ namespace MMU
|
||||
{
|
||||
check(directory);
|
||||
|
||||
// Needed in order to access page tables using the recursive mapping system.
|
||||
switch_page_directory(directory);
|
||||
switch_page_directory(g_kernel_directory);
|
||||
|
||||
auto guard = make_scope_guard([&] {
|
||||
check(g_kernel_directory);
|
||||
switch_page_directory(g_kernel_directory);
|
||||
MemoryManager::free_frame((u64)directory);
|
||||
});
|
||||
auto guard = make_scope_guard([directory] { MemoryManager::free_frame((u64)directory); });
|
||||
|
||||
PageDirectory* const table = l4_table();
|
||||
PageDirectory* const table = translate_physical(directory);
|
||||
|
||||
// Let's iterate over every top-level entry, skipping the last two entries (recursive mapping and kernel pages)
|
||||
for (u64 i = 0; i < 510; i++)
|
||||
@ -338,7 +333,7 @@ namespace MMU
|
||||
PageTableEntry& l4 = table->entries[i];
|
||||
if (!l4.present) continue;
|
||||
|
||||
PageDirectory* const pdp = raw_l3_table(i);
|
||||
PageDirectory* const pdp = &page_table(l4);
|
||||
|
||||
for (u64 j = 0; j < 512; j++)
|
||||
{
|
||||
@ -350,7 +345,7 @@ namespace MMU
|
||||
TRY(MemoryManager::free_frame(l3.get_address()));
|
||||
}
|
||||
|
||||
PageDirectory* const pd = raw_l2_table(i, j);
|
||||
PageDirectory* const pd = &page_table(l3);
|
||||
|
||||
for (u64 k = 0; k < 512; k++)
|
||||
{
|
||||
@ -362,7 +357,7 @@ namespace MMU
|
||||
TRY(MemoryManager::free_frame(l2.get_address()));
|
||||
}
|
||||
|
||||
PageDirectory* const pt = raw_l1_table(i, j, k);
|
||||
PageDirectory* const pt = &page_table(l2);
|
||||
|
||||
for (u64 l = 0; l < 512; l++)
|
||||
{
|
||||
|
@ -2,9 +2,7 @@
|
||||
#include <luna/Types.h>
|
||||
|
||||
const usize ARCH_PAGE_SIZE = 4096;
|
||||
|
||||
const u64 rindex = 0776; // recursive index
|
||||
const u64 sign = 0177777UL << 48; // sign extension
|
||||
const usize ARCH_HUGE_PAGE_SIZE = 2 * 1024 * 1024; // 2 MiB
|
||||
|
||||
struct [[gnu::packed]] PageTableEntry
|
||||
{
|
||||
|
11
kernel/src/fs/VFS.cpp
Normal file
11
kernel/src/fs/VFS.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
#include "fs/VFS.h"
|
||||
|
||||
namespace VFS
|
||||
{
|
||||
SharedPtr<FileSystem> root_fs;
|
||||
|
||||
Inode& root_inode()
|
||||
{
|
||||
return root_fs->root_inode();
|
||||
}
|
||||
}
|
70
kernel/src/fs/VFS.h
Normal file
70
kernel/src/fs/VFS.h
Normal file
@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
#include <luna/SharedPtr.h>
|
||||
|
||||
namespace VFS
|
||||
{
|
||||
enum class InodeType
|
||||
{
|
||||
RegularFile,
|
||||
Directory
|
||||
};
|
||||
|
||||
class FileSystem;
|
||||
|
||||
class Inode
|
||||
{
|
||||
public:
|
||||
// Directory-specific methods
|
||||
virtual Result<SharedPtr<Inode>> find(const char* name) const = 0;
|
||||
|
||||
virtual Result<SharedPtr<Inode>> create_file(const char* name) = 0;
|
||||
|
||||
// Generic methods
|
||||
virtual FileSystem& fs() const = 0;
|
||||
|
||||
virtual ~Inode() = default;
|
||||
|
||||
virtual InodeType type() const = 0;
|
||||
|
||||
virtual usize inode_number() const = 0;
|
||||
};
|
||||
|
||||
class FileInode : Inode
|
||||
{
|
||||
public:
|
||||
Result<SharedPtr<Inode>> find(const char*) const override
|
||||
{
|
||||
return err(ENOTDIR);
|
||||
}
|
||||
|
||||
Result<SharedPtr<Inode>> create_file(const char*) override
|
||||
{
|
||||
return err(ENOTDIR);
|
||||
}
|
||||
|
||||
InodeType type() const override
|
||||
{
|
||||
return InodeType::RegularFile;
|
||||
}
|
||||
|
||||
virtual ~FileInode() = default;
|
||||
};
|
||||
|
||||
class FileSystem
|
||||
{
|
||||
public:
|
||||
virtual Inode& root_inode() const = 0;
|
||||
|
||||
virtual Result<SharedPtr<Inode>> create_file_inode() = 0;
|
||||
|
||||
virtual Result<SharedPtr<Inode>> create_dir_inode(SharedPtr<Inode> parent) = 0;
|
||||
|
||||
virtual ~FileSystem() = default;
|
||||
};
|
||||
|
||||
extern SharedPtr<FileSystem> root_fs;
|
||||
|
||||
Result<Inode*> resolve_path(const char* path);
|
||||
|
||||
Inode& root_inode();
|
||||
}
|
71
kernel/src/fs/tmpfs/FileSystem.cpp
Normal file
71
kernel/src/fs/tmpfs/FileSystem.cpp
Normal file
@ -0,0 +1,71 @@
|
||||
#include "fs/tmpfs/FileSystem.h"
|
||||
#include <luna/Alloc.h>
|
||||
#include <luna/CString.h>
|
||||
#include <luna/Ignore.h>
|
||||
|
||||
namespace TmpFS
|
||||
{
|
||||
Result<SharedPtr<VFS::FileSystem>> FileSystem::create()
|
||||
{
|
||||
SharedPtr<FileSystem> fs = TRY(adopt_shared(new (std::nothrow) FileSystem()));
|
||||
SharedPtr<VFS::Inode> root = TRY(fs->create_dir_inode({}));
|
||||
fs->set_root(root);
|
||||
return (SharedPtr<VFS::FileSystem>)fs;
|
||||
}
|
||||
|
||||
Result<SharedPtr<VFS::Inode>> FileSystem::create_file_inode()
|
||||
{
|
||||
SharedPtr<FileInode> inode = TRY(make_shared<FileInode>());
|
||||
inode->set_fs(*this, {});
|
||||
inode->set_inode_number(m_next_inode_number, {});
|
||||
TRY(m_inodes.try_append(inode));
|
||||
m_next_inode_number++;
|
||||
return (SharedPtr<VFS::Inode>)inode;
|
||||
}
|
||||
|
||||
Result<SharedPtr<VFS::Inode>> FileSystem::create_dir_inode(SharedPtr<VFS::Inode> parent)
|
||||
{
|
||||
SharedPtr<DirInode> inode = TRY(make_shared<DirInode>());
|
||||
TRY(inode->add_entry(inode, "."));
|
||||
TRY(inode->add_entry(parent ? parent : (SharedPtr<VFS::Inode>)inode, ".."));
|
||||
|
||||
inode->set_fs(*this, {});
|
||||
inode->set_inode_number(m_next_inode_number, {});
|
||||
TRY(m_inodes.try_append(inode));
|
||||
m_next_inode_number++;
|
||||
return (SharedPtr<VFS::Inode>)inode;
|
||||
}
|
||||
|
||||
void FileSystem::set_root(SharedPtr<VFS::Inode> root)
|
||||
{
|
||||
m_root_inode = root;
|
||||
}
|
||||
|
||||
Result<SharedPtr<VFS::Inode>> DirInode::find(const char* name) const
|
||||
{
|
||||
for (const auto& entry : m_entries)
|
||||
{
|
||||
if (!strcmp(name, entry.name.chars())) return entry.inode;
|
||||
}
|
||||
|
||||
return err(ENOENT);
|
||||
}
|
||||
|
||||
Result<void> DirInode::add_entry(SharedPtr<VFS::Inode> inode, const char* name)
|
||||
{
|
||||
Entry entry { inode, name };
|
||||
|
||||
TRY(m_entries.try_append(move(entry)));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<SharedPtr<VFS::Inode>> DirInode::create_file(const char* name)
|
||||
{
|
||||
auto inode = TRY(m_fs->create_file_inode());
|
||||
|
||||
TRY(add_entry(inode, name));
|
||||
|
||||
return inode;
|
||||
}
|
||||
}
|
118
kernel/src/fs/tmpfs/FileSystem.h
Normal file
118
kernel/src/fs/tmpfs/FileSystem.h
Normal file
@ -0,0 +1,118 @@
|
||||
#pragma once
|
||||
#include "fs/VFS.h"
|
||||
#include <luna/Atomic.h>
|
||||
#include <luna/Badge.h>
|
||||
#include <luna/StaticString.h>
|
||||
#include <luna/Vector.h>
|
||||
|
||||
namespace TmpFS
|
||||
{
|
||||
class FileSystem : public VFS::FileSystem
|
||||
{
|
||||
public:
|
||||
VFS::Inode& root_inode() const override
|
||||
{
|
||||
return *m_root_inode;
|
||||
}
|
||||
|
||||
Result<SharedPtr<VFS::Inode>> create_file_inode() override;
|
||||
Result<SharedPtr<VFS::Inode>> create_dir_inode(SharedPtr<VFS::Inode> parent) override;
|
||||
|
||||
static Result<SharedPtr<VFS::FileSystem>> create();
|
||||
|
||||
virtual ~FileSystem() = default;
|
||||
|
||||
private:
|
||||
FileSystem() = default;
|
||||
|
||||
void set_root(SharedPtr<VFS::Inode> root);
|
||||
|
||||
SharedPtr<VFS::Inode> m_root_inode;
|
||||
Vector<SharedPtr<VFS::Inode>> m_inodes;
|
||||
|
||||
Atomic<usize> m_next_inode_number { 2 };
|
||||
};
|
||||
|
||||
class FileInode : public VFS::FileInode
|
||||
{
|
||||
public:
|
||||
FileInode() = default;
|
||||
|
||||
void set_fs(FileSystem& fs, Badge<FileSystem>)
|
||||
{
|
||||
m_fs = &fs;
|
||||
}
|
||||
|
||||
void set_inode_number(usize inum, Badge<FileSystem>)
|
||||
{
|
||||
m_inode_number = inum;
|
||||
}
|
||||
|
||||
VFS::FileSystem& fs() const override
|
||||
{
|
||||
return *m_fs;
|
||||
}
|
||||
|
||||
usize inode_number() const override
|
||||
{
|
||||
return m_inode_number;
|
||||
}
|
||||
|
||||
virtual ~FileInode() = default;
|
||||
|
||||
private:
|
||||
VFS::FileSystem* m_fs;
|
||||
usize m_inode_number;
|
||||
};
|
||||
|
||||
class DirInode : public VFS::Inode
|
||||
{
|
||||
public:
|
||||
DirInode() = default;
|
||||
|
||||
void set_fs(FileSystem& fs, Badge<FileSystem>)
|
||||
{
|
||||
m_fs = &fs;
|
||||
}
|
||||
|
||||
void set_inode_number(usize inum, Badge<FileSystem>)
|
||||
{
|
||||
m_inode_number = inum;
|
||||
}
|
||||
|
||||
Result<SharedPtr<VFS::Inode>> find(const char* name) const override;
|
||||
|
||||
VFS::FileSystem& fs() const override
|
||||
{
|
||||
return *m_fs;
|
||||
}
|
||||
|
||||
usize inode_number() const override
|
||||
{
|
||||
return m_inode_number;
|
||||
}
|
||||
|
||||
VFS::InodeType type() const override
|
||||
{
|
||||
return VFS::InodeType::Directory;
|
||||
}
|
||||
|
||||
Result<SharedPtr<VFS::Inode>> create_file(const char* name) override;
|
||||
|
||||
Result<void> add_entry(SharedPtr<VFS::Inode> inode, const char* name);
|
||||
|
||||
virtual ~DirInode() = default;
|
||||
|
||||
private:
|
||||
VFS::FileSystem* m_fs;
|
||||
usize m_inode_number;
|
||||
|
||||
struct Entry
|
||||
{
|
||||
SharedPtr<VFS::Inode> inode;
|
||||
StaticString<128> name;
|
||||
};
|
||||
|
||||
Vector<Entry> m_entries;
|
||||
};
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
#include "arch/Timer.h"
|
||||
#include "boot/Init.h"
|
||||
#include "config.h"
|
||||
#include "fs/tmpfs/FileSystem.h"
|
||||
#include "memory/Heap.h"
|
||||
#include "memory/KernelVM.h"
|
||||
#include "memory/MemoryManager.h"
|
||||
@ -55,6 +56,18 @@ Result<void> init()
|
||||
Thread::init();
|
||||
Scheduler::init();
|
||||
|
||||
VFS::root_fs = TRY(TmpFS::FileSystem::create());
|
||||
|
||||
VFS::Inode& root_inode = VFS::root_inode();
|
||||
kinfoln("root inode number: %zu", root_inode.inode_number());
|
||||
|
||||
kinfoln("root inode's '.' entry inode number: %zu", TRY(root_inode.find("."))->inode_number());
|
||||
kinfoln("root inode's '..' entry inode number: %zu", TRY(root_inode.find(".."))->inode_number());
|
||||
|
||||
TRY(root_inode.create_file("usr"));
|
||||
|
||||
kinfoln("root inode's 'usr' entry inode number: %zu", TRY(root_inode.find("usr"))->inode_number());
|
||||
|
||||
TarStream::Entry entry;
|
||||
while (TRY(g_initrd.read_next_entry(entry)))
|
||||
{
|
||||
@ -87,11 +100,34 @@ Result<void> init()
|
||||
return {};
|
||||
}
|
||||
|
||||
extern "C" [[noreturn]] void _start()
|
||||
[[noreturn]] void init_wrapper()
|
||||
{
|
||||
Init::check_magic();
|
||||
Init::early_init();
|
||||
auto rc = init();
|
||||
if (rc.has_error()) kerrorln("Runtime error: %s", rc.error_string());
|
||||
CPU::idle_loop();
|
||||
}
|
||||
|
||||
static constexpr u64 BOOTSTRAP_STACK_PAGES = 8;
|
||||
|
||||
// FIXME: Reclaim this memory as soon as we leave the init task (so as soon as the Scheduler runs a task switch)
|
||||
static u64 allocate_initial_kernel_stack()
|
||||
{
|
||||
u64 address = MemoryManager::alloc_for_kernel(BOOTSTRAP_STACK_PAGES + 1, MMU::ReadWrite | MMU::NoExecute).value();
|
||||
// First page is a guard page, the rest is stack.
|
||||
MMU::unmap(address); // Unmap (without deallocating VM) one guard page so that attempts to access it fail with a
|
||||
// non-present page fault.
|
||||
kdbgln("stack guard page: %p", (void*)address);
|
||||
|
||||
// The actual stack.
|
||||
Stack stack { address + ARCH_PAGE_SIZE, BOOTSTRAP_STACK_PAGES * ARCH_PAGE_SIZE };
|
||||
|
||||
return stack.top();
|
||||
}
|
||||
|
||||
extern "C" [[noreturn]] void _start()
|
||||
{
|
||||
Init::check_magic();
|
||||
Init::early_init();
|
||||
u64 bootstrap_stack_top = allocate_initial_kernel_stack();
|
||||
CPU::bootstrap_switch_stack(bootstrap_stack_top, (void*)init_wrapper);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "memory/KernelVM.h"
|
||||
#include "arch/MMU.h"
|
||||
#include "thread/Spinlock.h"
|
||||
#include <luna/Bitmap.h>
|
||||
#include <luna/Spinlock.h>
|
||||
|
||||
static const u64 KERNEL_VM_RANGE_START = 0xffffffffc0000000;
|
||||
|
||||
|
@ -2,10 +2,10 @@
|
||||
#include "arch/MMU.h"
|
||||
#include "memory/KernelVM.h"
|
||||
#include "memory/MemoryMap.h"
|
||||
#include "thread/Spinlock.h"
|
||||
#include <luna/Alignment.h>
|
||||
#include <luna/Bitmap.h>
|
||||
#include <luna/ScopeGuard.h>
|
||||
#include <luna/Spinlock.h>
|
||||
#include <luna/SystemError.h>
|
||||
#include <luna/Types.h>
|
||||
|
||||
@ -100,24 +100,16 @@ namespace MemoryManager
|
||||
void init()
|
||||
{
|
||||
init_physical_frame_allocator();
|
||||
KernelVM::init();
|
||||
|
||||
MMU::setup_initial_page_directory();
|
||||
|
||||
// NOTE: We force these operations to succeed, because if we can't map the frame bitmap to virtual memory
|
||||
// there's no point in continuing.
|
||||
auto bitmap_pages = g_frame_bitmap.lock()->size_in_bytes() / ARCH_PAGE_SIZE;
|
||||
|
||||
auto virtual_bitmap_base =
|
||||
KernelVM::alloc_several_pages(bitmap_pages)
|
||||
.expect_value("Unable to allocate virtual memory for the physical frame bitmap, cannot continue");
|
||||
|
||||
u64 phys = (u64)g_frame_bitmap.lock()->location();
|
||||
map_frames_at(virtual_bitmap_base, phys, bitmap_pages, MMU::ReadWrite | MMU::NoExecute)
|
||||
.expect_value("Unable to map the physical frame bitmap to virtual memory, cannot continue");
|
||||
|
||||
auto frame_bitmap = g_frame_bitmap.lock();
|
||||
u64 phys = (u64)frame_bitmap->location();
|
||||
|
||||
auto virtual_bitmap_base = MMU::translate_physical_address(phys);
|
||||
frame_bitmap->initialize((void*)virtual_bitmap_base, frame_bitmap->size_in_bytes());
|
||||
|
||||
KernelVM::init();
|
||||
}
|
||||
|
||||
void do_lock_frame(u64 index, Bitmap& bitmap)
|
||||
@ -202,7 +194,7 @@ namespace MemoryManager
|
||||
|
||||
while (pages_mapped < count)
|
||||
{
|
||||
TRY(MMU::map(virt, phys, flags));
|
||||
TRY(MMU::map(virt, phys, flags, MMU::UseHugePages::No));
|
||||
virt += ARCH_PAGE_SIZE;
|
||||
phys += ARCH_PAGE_SIZE;
|
||||
pages_mapped++;
|
||||
@ -213,6 +205,29 @@ namespace MemoryManager
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<void> map_huge_frames_at(u64 virt, u64 phys, usize count, int flags)
|
||||
{
|
||||
CHECK_PAGE_ALIGNED(virt);
|
||||
CHECK_PAGE_ALIGNED(phys);
|
||||
|
||||
usize pages_mapped = 0;
|
||||
|
||||
// Let's clean up after ourselves if we fail.
|
||||
auto guard = make_scope_guard([=, &pages_mapped] { unmap_weak_huge(virt, pages_mapped); });
|
||||
|
||||
while (pages_mapped < count)
|
||||
{
|
||||
TRY(MMU::map(virt, phys, flags, MMU::UseHugePages::Yes));
|
||||
virt += ARCH_HUGE_PAGE_SIZE;
|
||||
phys += ARCH_HUGE_PAGE_SIZE;
|
||||
pages_mapped++;
|
||||
}
|
||||
|
||||
guard.deactivate();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<u64> alloc_at(u64 virt, usize count, int flags)
|
||||
{
|
||||
CHECK_PAGE_ALIGNED(virt);
|
||||
@ -225,7 +240,7 @@ namespace MemoryManager
|
||||
while (pages_mapped < count)
|
||||
{
|
||||
const u64 frame = TRY(alloc_frame());
|
||||
TRY(MMU::map(virt, frame, flags));
|
||||
TRY(MMU::map(virt, frame, flags, MMU::UseHugePages::No));
|
||||
virt += ARCH_PAGE_SIZE;
|
||||
pages_mapped++;
|
||||
}
|
||||
@ -250,7 +265,7 @@ namespace MemoryManager
|
||||
while (pages_mapped < count)
|
||||
{
|
||||
const u64 frame = TRY(alloc_frame());
|
||||
TRY(MMU::map(virt, frame, flags));
|
||||
TRY(MMU::map(virt, frame, flags, MMU::UseHugePages::No));
|
||||
virt += ARCH_PAGE_SIZE;
|
||||
pages_mapped++;
|
||||
}
|
||||
@ -275,7 +290,7 @@ namespace MemoryManager
|
||||
|
||||
while (pages_mapped < count)
|
||||
{
|
||||
TRY(MMU::map(virt, phys, flags));
|
||||
TRY(MMU::map(virt, phys, flags, MMU::UseHugePages::No));
|
||||
virt += ARCH_PAGE_SIZE;
|
||||
phys += ARCH_PAGE_SIZE;
|
||||
pages_mapped++;
|
||||
@ -322,6 +337,19 @@ namespace MemoryManager
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<void> unmap_weak_huge(u64 virt, usize count)
|
||||
{
|
||||
CHECK_PAGE_ALIGNED(virt);
|
||||
|
||||
while (count--)
|
||||
{
|
||||
TRY(MMU::unmap(virt));
|
||||
virt += ARCH_HUGE_PAGE_SIZE;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<void> unmap_weak_and_free_vm(u64 virt, usize count)
|
||||
{
|
||||
CHECK_PAGE_ALIGNED(virt);
|
||||
|
@ -53,6 +53,7 @@ namespace MemoryManager
|
||||
}
|
||||
|
||||
Result<void> map_frames_at(u64 virt, u64 phys, usize count, int flags);
|
||||
Result<void> map_huge_frames_at(u64 virt, u64 phys, usize count, int flags);
|
||||
|
||||
Result<u64> alloc_at(u64 virt, usize count, int flags);
|
||||
Result<u64> alloc_for_kernel(usize count, int flags);
|
||||
@ -64,6 +65,8 @@ namespace MemoryManager
|
||||
Result<void> unmap_weak(u64 virt, usize count);
|
||||
Result<void> unmap_weak_and_free_vm(u64 virt, usize count);
|
||||
|
||||
Result<void> unmap_weak_huge(u64 virt, usize count);
|
||||
|
||||
usize free();
|
||||
usize used();
|
||||
usize reserved();
|
||||
|
@ -84,4 +84,5 @@ void debug_log_impl(const char* format, va_list ap)
|
||||
{
|
||||
pure_cstyle_format(
|
||||
format, [](char c, void*) { console_write(&c, 1); }, nullptr, ap);
|
||||
console_write("\n", 1);
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ set(FREESTANDING_SOURCES
|
||||
src/TarStream.cpp
|
||||
src/DebugLog.cpp
|
||||
src/Heap.cpp
|
||||
src/Spinlock.cpp
|
||||
src/UBSAN.cpp
|
||||
)
|
||||
|
||||
set(SOURCES
|
@ -22,6 +22,8 @@ extern "C"
|
||||
// FIXME: Replace this invented function with strlcpy().
|
||||
void nullcpy(char* dest, const char* src, usize len);
|
||||
|
||||
usize strlcpy(char* dest, const char* src, usize len);
|
||||
|
||||
[[deprecated]] char* strcpy(char* dst, const char* src);
|
||||
[[deprecated]] char* strcat(char* dst, const char* src);
|
||||
|
@ -19,6 +19,10 @@
|
||||
if (!(expr)) [[unlikely]] { __check_failed(location, message); } \
|
||||
} while (0)
|
||||
|
||||
// Fail with an error message and location.
|
||||
#define fail(message) __check_failed(SourceLocation::current(), message)
|
||||
#define fail_at(location, message) __check_failed(location, message)
|
||||
|
||||
// Like assert(), but always enabled.
|
||||
#define check(expr) \
|
||||
do { \
|
@ -101,7 +101,7 @@ template <typename T> class Option
|
||||
return true;
|
||||
}
|
||||
|
||||
bool try_move_value(T& ref) const
|
||||
bool try_move_value(T& ref)
|
||||
{
|
||||
if (!has_value()) return false;
|
||||
m_has_value = false;
|
||||
@ -138,26 +138,16 @@ template <typename T> class Option
|
||||
private:
|
||||
struct Storage
|
||||
{
|
||||
u8 buffer[sizeof(T)];
|
||||
|
||||
T* fetch_ptr()
|
||||
{
|
||||
return (T*)buffer;
|
||||
}
|
||||
alignas(T) u8 buffer[sizeof(T)];
|
||||
|
||||
T& fetch_reference()
|
||||
{
|
||||
return *fetch_ptr();
|
||||
}
|
||||
|
||||
const T* fetch_ptr() const
|
||||
{
|
||||
return (const T*)buffer;
|
||||
return *__builtin_launder(reinterpret_cast<T*>(&buffer));
|
||||
}
|
||||
|
||||
const T& fetch_reference() const
|
||||
{
|
||||
return *fetch_ptr();
|
||||
return *__builtin_launder(reinterpret_cast<const T*>(&buffer));
|
||||
}
|
||||
|
||||
void store_reference(const T& ref)
|
@ -59,6 +59,11 @@ template <typename T> class OwnedPtr
|
||||
return *m_ptr;
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return m_ptr != nullptr;
|
||||
}
|
||||
|
||||
template <typename Type> friend Result<SharedPtr<Type>> adopt_shared_from_owned(OwnedPtr<Type>&&);
|
||||
|
||||
private:
|
@ -110,7 +110,7 @@ template <typename T> class Result
|
||||
return m_value.try_set_value(ref);
|
||||
}
|
||||
|
||||
bool try_move_value(T& ref) const
|
||||
bool try_move_value(T& ref)
|
||||
{
|
||||
return m_value.try_move_value(ref);
|
||||
}
|
@ -99,31 +99,34 @@ template <typename T> class SharedPtr
|
||||
return *m_ptr;
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return m_ptr != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
T* m_ptr;
|
||||
RefCount* m_ref_count;
|
||||
};
|
||||
|
||||
template <typename T, class... Args> Result<SharedPtr<T>> make_shared(Args... args)
|
||||
// NOTE: ptr is deleted if any of the adopt_shared* functions fail to construct a SharedPtr.
|
||||
template <typename T> Result<SharedPtr<T>> adopt_shared(T* ptr)
|
||||
{
|
||||
using RefCount = __detail::RefCount;
|
||||
|
||||
RefCount* const ref_count = TRY(make<RefCount>());
|
||||
auto guard = make_scope_guard([&] { delete ref_count; });
|
||||
auto guard = make_scope_guard([ptr] { delete ptr; });
|
||||
|
||||
RefCount* const ref_count = TRY(make<RefCount>());
|
||||
|
||||
T* const ptr = TRY(make<T>(args...));
|
||||
guard.deactivate();
|
||||
|
||||
return SharedPtr<T> { ptr, ref_count };
|
||||
}
|
||||
|
||||
template <typename T> Result<SharedPtr<T>> adopt_shared(T* ptr)
|
||||
template <typename T, class... Args> Result<SharedPtr<T>> make_shared(Args... args)
|
||||
{
|
||||
using RefCount = __detail::RefCount;
|
||||
|
||||
RefCount* const ref_count = TRY(make<RefCount>());
|
||||
|
||||
return SharedPtr<T> { ptr, ref_count };
|
||||
T* raw_ptr = TRY(make<T>(args...));
|
||||
return adopt_shared(raw_ptr);
|
||||
}
|
||||
|
||||
template <typename T> Result<SharedPtr<T>> adopt_shared_if_nonnull(T* ptr)
|
||||
@ -138,13 +141,7 @@ template <typename T> Result<SharedPtr<T>> adopt_shared_from_owned(OwnedPtr<T>&&
|
||||
T* ptr = other.m_ptr;
|
||||
other.m_ptr = nullptr;
|
||||
|
||||
// FIXME: Should the pointee magically vanish on failure? Or go back into the OwnedPtr, even though it's been
|
||||
// moved...
|
||||
auto guard = make_scope_guard([&] { delete ptr; });
|
||||
|
||||
const SharedPtr<T> shared_ptr = TRY(adopt_shared(ptr));
|
||||
|
||||
guard.deactivate();
|
||||
|
||||
return shared_ptr;
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
#pragma once
|
||||
#include "Log.h"
|
||||
#include "arch/CPU.h"
|
||||
#include <luna/Atomic.h>
|
||||
#include <luna/Option.h>
|
||||
|
||||
@ -30,8 +28,15 @@ class ScopeLock
|
||||
ScopeLock(const ScopeLock&) = delete;
|
||||
ScopeLock(ScopeLock&&) = delete;
|
||||
|
||||
Spinlock& take_over()
|
||||
{
|
||||
m_taken_over = true;
|
||||
return m_lock;
|
||||
}
|
||||
|
||||
private:
|
||||
Spinlock& m_lock;
|
||||
bool m_taken_over { false };
|
||||
};
|
||||
|
||||
class SafeScopeLock
|
||||
@ -107,25 +112,11 @@ template <typename T> class LockedValue
|
||||
{
|
||||
}
|
||||
|
||||
#ifndef LOCKED_VALUE_DEBUG
|
||||
LockedValueGuard lock()
|
||||
{
|
||||
m_lock.lock();
|
||||
return { *this };
|
||||
}
|
||||
#else
|
||||
LockedValueGuard lock()
|
||||
{
|
||||
if (m_lock.try_lock()) { return { *this }; }
|
||||
|
||||
kwarnln("Spinning on a locked LockedValue. This might lead to a deadlock...");
|
||||
|
||||
CPU::print_stack_trace();
|
||||
|
||||
m_lock.lock();
|
||||
return { *this };
|
||||
}
|
||||
#endif
|
||||
|
||||
Option<LockedValueGuard> try_lock()
|
||||
{
|
52
libluna/include/luna/StaticString.h
Normal file
52
libluna/include/luna/StaticString.h
Normal file
@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
#include <luna/CString.h>
|
||||
#include <luna/Types.h>
|
||||
|
||||
template <usize Size> class StaticString
|
||||
{
|
||||
public:
|
||||
StaticString() = default;
|
||||
StaticString(const char* string)
|
||||
{
|
||||
adopt(string);
|
||||
}
|
||||
|
||||
void adopt(const char* string)
|
||||
{
|
||||
usize length = strlcpy(m_buffer, string, sizeof(m_buffer));
|
||||
if (length > Size) { m_length = Size; }
|
||||
else
|
||||
m_length = length;
|
||||
}
|
||||
|
||||
StaticString<Size>& operator=(const char* string)
|
||||
{
|
||||
adopt(string);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <usize OSize> StaticString<Size>& operator=(const StaticString<OSize>& string)
|
||||
{
|
||||
if constexpr (OSize == Size)
|
||||
{
|
||||
if (this == &string) return *this;
|
||||
}
|
||||
|
||||
adopt(string.chars());
|
||||
return *this;
|
||||
}
|
||||
|
||||
const char* chars() const
|
||||
{
|
||||
return m_buffer;
|
||||
}
|
||||
|
||||
usize length() const
|
||||
{
|
||||
return m_length;
|
||||
}
|
||||
|
||||
private:
|
||||
char m_buffer[Size + 1];
|
||||
usize m_length { 0 };
|
||||
};
|
74
libluna/include/luna/UBSAN.h
Normal file
74
libluna/include/luna/UBSAN.h
Normal file
@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
#include <luna/Types.h>
|
||||
|
||||
namespace UBSAN
|
||||
{
|
||||
struct SourceLocation
|
||||
{
|
||||
const char* file;
|
||||
u32 line;
|
||||
u32 column;
|
||||
};
|
||||
|
||||
struct TypeDescriptor
|
||||
{
|
||||
u16 kind;
|
||||
u16 info;
|
||||
char name[];
|
||||
};
|
||||
|
||||
namespace UBInfo
|
||||
{
|
||||
struct TypeMismatchInfo
|
||||
{
|
||||
SourceLocation location;
|
||||
TypeDescriptor* type;
|
||||
usize alignment;
|
||||
u8 type_check_kind;
|
||||
};
|
||||
|
||||
struct TypeMismatchInfo_v1
|
||||
{
|
||||
SourceLocation location;
|
||||
TypeDescriptor* type;
|
||||
u8 log_alignment;
|
||||
u8 type_check_kind;
|
||||
};
|
||||
|
||||
struct OverflowInfo
|
||||
{
|
||||
SourceLocation location;
|
||||
TypeDescriptor* type;
|
||||
};
|
||||
|
||||
struct UnreachableInfo
|
||||
{
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
struct OutOfBoundsInfo
|
||||
{
|
||||
SourceLocation location;
|
||||
TypeDescriptor* array_type;
|
||||
TypeDescriptor* index_type;
|
||||
};
|
||||
|
||||
struct InvalidValueInfo
|
||||
{
|
||||
SourceLocation location;
|
||||
TypeDescriptor* type;
|
||||
};
|
||||
|
||||
struct ShiftOutOfBoundsInfo
|
||||
{
|
||||
SourceLocation location;
|
||||
TypeDescriptor* lhs_type;
|
||||
TypeDescriptor* rhs_type;
|
||||
};
|
||||
|
||||
struct PointerOverflowInfo
|
||||
{
|
||||
SourceLocation location;
|
||||
};
|
||||
}
|
||||
}
|
@ -78,7 +78,7 @@ template <typename T> class Vector
|
||||
resize(capacity).release_value();
|
||||
}
|
||||
|
||||
Result<void> try_append(T item)
|
||||
Result<void> try_append(T&& item)
|
||||
{
|
||||
if (m_capacity == m_size) TRY(resize(m_capacity + 8));
|
||||
|
||||
@ -89,6 +89,11 @@ template <typename T> class Vector
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<void> try_append(const T& item)
|
||||
{
|
||||
return try_append(T(item));
|
||||
}
|
||||
|
||||
Option<T> try_pop()
|
||||
{
|
||||
if (m_size == 0) return {};
|
@ -125,4 +125,15 @@ extern "C"
|
||||
if (*str) return const_cast<char*>(str);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
usize strlcpy(char* dest, const char* src, usize len)
|
||||
{
|
||||
usize src_len = strlen(src);
|
||||
usize copy_len = src_len;
|
||||
if (len == 0) return src_len;
|
||||
if (src_len >= (len - 1)) copy_len = len - 1;
|
||||
memcpy(dest, src, copy_len);
|
||||
dest[copy_len] = 0;
|
||||
return src_len;
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
#include <luna/LinkedList.h>
|
||||
#include <luna/SafeArithmetic.h>
|
||||
#include <luna/ScopeGuard.h>
|
||||
#include <luna/Spinlock.h>
|
||||
#include <luna/SystemError.h>
|
||||
|
||||
#ifdef USE_FREESTANDING
|
||||
@ -45,9 +46,10 @@ static_assert(sizeof(HeapBlock) == 48UL);
|
||||
static const isize HEAP_BLOCK_SIZE = 48;
|
||||
|
||||
static LinkedList<HeapBlock> heap;
|
||||
static Spinlock g_heap_lock;
|
||||
|
||||
// If we're allocating a large amount of memory, map enough pages for it, but otherwise just use the default amount of
|
||||
// pages.
|
||||
// If we're allocating a large amount of memory, map enough pages for it, but otherwise just use the default amount
|
||||
// of pages.
|
||||
static usize get_pages_for_allocation(usize bytes)
|
||||
{
|
||||
usize pages = get_blocks_from_size(bytes, PAGE_SIZE);
|
||||
@ -97,7 +99,7 @@ static Option<HeapBlock*> split(HeapBlock* block, usize size)
|
||||
const usize old_size =
|
||||
block->full_size; // Save the old value of this variable since we are going to use it after modifying it
|
||||
|
||||
if (available < (size + sizeof(HeapBlock)))
|
||||
if (available <= (size + sizeof(HeapBlock)))
|
||||
return {}; // This block hasn't got enough free space to hold the requested size.
|
||||
|
||||
const usize offset = get_fair_offset_to_split_at(block, size + sizeof(HeapBlock));
|
||||
@ -128,6 +130,8 @@ static Result<void> combine_forward(HeapBlock* block)
|
||||
heap.remove(next);
|
||||
next->magic = BLOCK_DEAD;
|
||||
|
||||
block->full_size += next->full_size + sizeof(HeapBlock);
|
||||
|
||||
if (next->status & BLOCK_END_MEM)
|
||||
{
|
||||
if (next->status & BLOCK_START_MEM)
|
||||
@ -140,8 +144,6 @@ static Result<void> combine_forward(HeapBlock* block)
|
||||
block->status |= BLOCK_END_MEM;
|
||||
}
|
||||
|
||||
block->full_size += next->full_size + sizeof(HeapBlock);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -157,6 +159,8 @@ static Result<HeapBlock*> combine_backward(HeapBlock* block)
|
||||
heap.remove(block);
|
||||
block->magic = BLOCK_DEAD;
|
||||
|
||||
last->full_size += block->full_size + sizeof(HeapBlock);
|
||||
|
||||
if (block->status & BLOCK_END_MEM)
|
||||
{
|
||||
if (block->status & BLOCK_START_MEM)
|
||||
@ -169,8 +173,6 @@ static Result<HeapBlock*> combine_backward(HeapBlock* block)
|
||||
last->status |= BLOCK_END_MEM;
|
||||
}
|
||||
|
||||
last->full_size += block->full_size + sizeof(HeapBlock);
|
||||
|
||||
return last;
|
||||
}
|
||||
|
||||
@ -178,6 +180,8 @@ Result<void*> malloc_impl(usize size, bool should_scrub)
|
||||
{
|
||||
if (!size) return (void*)BLOCK_MAGIC;
|
||||
|
||||
ScopeLock lock(g_heap_lock);
|
||||
|
||||
size = align_up<16>(size);
|
||||
|
||||
Option<HeapBlock*> block = heap.first();
|
||||
@ -231,6 +235,8 @@ Result<void> free_impl(void* ptr)
|
||||
if (ptr == (void*)BLOCK_MAGIC) return {}; // This pointer was returned from a call to malloc(0)
|
||||
if (!ptr) return {};
|
||||
|
||||
ScopeLock lock(g_heap_lock);
|
||||
|
||||
HeapBlock* block = get_heap_block_for_pointer(ptr);
|
||||
|
||||
if (block->magic != BLOCK_MAGIC)
|
||||
@ -286,6 +292,8 @@ Result<void*> realloc_impl(void* ptr, usize size)
|
||||
return (void*)BLOCK_MAGIC;
|
||||
}
|
||||
|
||||
ScopeLock lock(g_heap_lock);
|
||||
|
||||
HeapBlock* const block = get_heap_block_for_pointer(ptr);
|
||||
|
||||
if (block->magic != BLOCK_MAGIC)
|
||||
@ -327,6 +335,8 @@ Result<void*> realloc_impl(void* ptr, usize size)
|
||||
|
||||
usize old_size = block->req_size;
|
||||
|
||||
lock.take_over().unlock();
|
||||
|
||||
void* const new_ptr = TRY(malloc_impl(size, false));
|
||||
memcpy(new_ptr, ptr, old_size > size ? size : old_size);
|
||||
TRY(free_impl(ptr));
|
@ -1,6 +1,11 @@
|
||||
#include "thread/Spinlock.h"
|
||||
#include "Log.h"
|
||||
#include "arch/CPU.h"
|
||||
#include <luna/DebugLog.h>
|
||||
#include <luna/Spinlock.h>
|
||||
|
||||
#ifdef ARCH_X86_64
|
||||
#define pause() asm volatile("pause")
|
||||
#else
|
||||
#error "Unsupported architecture"
|
||||
#endif
|
||||
|
||||
void Spinlock::lock()
|
||||
{
|
||||
@ -8,7 +13,7 @@ void Spinlock::lock()
|
||||
while (!m_lock.compare_exchange_strong(expected, 1))
|
||||
{
|
||||
expected = 0;
|
||||
CPU::pause();
|
||||
pause();
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +28,7 @@ void Spinlock::unlock()
|
||||
int expected = 1;
|
||||
if (!m_lock.compare_exchange_strong(expected, 0))
|
||||
{
|
||||
kwarnln("Spinlock::unlock() called on an unlocked lock with value %d", expected);
|
||||
dbgln("Spinlock::unlock() called on an unlocked lock with value %d", expected);
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,7 +39,7 @@ ScopeLock::ScopeLock(Spinlock& lock) : m_lock(lock)
|
||||
|
||||
ScopeLock::~ScopeLock()
|
||||
{
|
||||
m_lock.unlock();
|
||||
if (!m_taken_over) m_lock.unlock();
|
||||
}
|
||||
|
||||
const u32 RETRIES = 5000000;
|
||||
@ -42,7 +47,7 @@ const u32 RETRIES = 5000000;
|
||||
SafeScopeLock::SafeScopeLock(Spinlock& lock) : m_lock(lock)
|
||||
{
|
||||
u32 tries_left = RETRIES;
|
||||
while (!lock.try_lock() && --tries_left) { CPU::pause(); }
|
||||
while (!lock.try_lock() && --tries_left) { pause(); }
|
||||
|
||||
if (tries_left) m_success = true;
|
||||
}
|
121
libluna/src/UBSAN.cpp
Normal file
121
libluna/src/UBSAN.cpp
Normal file
@ -0,0 +1,121 @@
|
||||
#include <luna/Check.h>
|
||||
#include <luna/DebugLog.h>
|
||||
#include <luna/SourceLocation.h>
|
||||
#include <luna/UBSAN.h>
|
||||
|
||||
using namespace UBSAN::UBInfo;
|
||||
|
||||
[[noreturn]] void ub_panic(SourceLocation caller = SourceLocation::current())
|
||||
{
|
||||
fail_at(caller, "Undefined behavior");
|
||||
}
|
||||
|
||||
#define DISPLAY(loc) loc.file, loc.line, loc.column
|
||||
|
||||
extern "C"
|
||||
{
|
||||
void __ubsan_handle_builtin_unreachable(UnreachableInfo* info)
|
||||
{
|
||||
const auto& location = info->location;
|
||||
dbgln("ubsan: __builtin_unreachable reached at %s:%d:%d", DISPLAY(location));
|
||||
ub_panic();
|
||||
}
|
||||
|
||||
void __ubsan_handle_pointer_overflow(PointerOverflowInfo* info)
|
||||
{
|
||||
const auto& location = info->location;
|
||||
dbgln("ubsan: pointer overflow occurred at %s:%d:%d", DISPLAY(location));
|
||||
ub_panic();
|
||||
}
|
||||
|
||||
void __ubsan_handle_shift_out_of_bounds(ShiftOutOfBoundsInfo* info)
|
||||
{
|
||||
const auto& location = info->location;
|
||||
dbgln("ubsan: shift out of bounds for type %s at %s:%d:%d", info->lhs_type->name, DISPLAY(location));
|
||||
ub_panic();
|
||||
}
|
||||
|
||||
void __ubsan_handle_load_invalid_value(InvalidValueInfo* info)
|
||||
{
|
||||
const auto& location = info->location;
|
||||
dbgln("ubsan: load invalid value for type %s at %s:%d:%d", info->type->name, DISPLAY(location));
|
||||
ub_panic();
|
||||
}
|
||||
|
||||
void __ubsan_handle_out_of_bounds(OutOfBoundsInfo* info, usize index)
|
||||
{
|
||||
const auto& location = info->location;
|
||||
dbgln("ubsan: out of bounds array (of type %s) access (index %zu of type %s) at %s:%d:%d",
|
||||
info->array_type->name, index, info->index_type->name, DISPLAY(location));
|
||||
ub_panic();
|
||||
}
|
||||
|
||||
void ubsan_handle_generic_overflow(OverflowInfo* info, const char* overflow_type)
|
||||
{
|
||||
const auto& location = info->location;
|
||||
dbgln("ubsan: %s overflow (value cannot fit into type %s) at %s:%d:%d", overflow_type, info->type->name,
|
||||
DISPLAY(location));
|
||||
ub_panic();
|
||||
}
|
||||
|
||||
#define UBSAN_OVERFLOW_BINARY(operation) \
|
||||
void __ubsan_handle_##operation##_overflow(OverflowInfo* info, usize, usize) \
|
||||
{ \
|
||||
ubsan_handle_generic_overflow(info, #operation); \
|
||||
}
|
||||
|
||||
#define UBSAN_OVERFLOW_UNARY(operation) \
|
||||
void __ubsan_handle_##operation##_overflow(OverflowInfo* info, usize) \
|
||||
{ \
|
||||
ubsan_handle_generic_overflow(info, #operation); \
|
||||
}
|
||||
|
||||
UBSAN_OVERFLOW_BINARY(add);
|
||||
UBSAN_OVERFLOW_BINARY(sub);
|
||||
UBSAN_OVERFLOW_BINARY(mul);
|
||||
UBSAN_OVERFLOW_UNARY(negate);
|
||||
UBSAN_OVERFLOW_BINARY(divrem);
|
||||
|
||||
#define is_aligned(value, alignment) !(value & (alignment - 1))
|
||||
|
||||
const char* g_type_check_kinds[] = {
|
||||
"load of",
|
||||
"store to",
|
||||
"reference binding to",
|
||||
"member access within",
|
||||
"member call on",
|
||||
"constructor call on",
|
||||
"downcast of",
|
||||
"downcast of",
|
||||
"upcast of",
|
||||
"cast to virtual base of",
|
||||
};
|
||||
|
||||
void __ubsan_handle_type_mismatch(TypeMismatchInfo* info, usize pointer)
|
||||
{
|
||||
const auto& location = info->location;
|
||||
|
||||
if (pointer == 0) { dbgln("ubsan: null pointer access at %s:%d:%d", DISPLAY(location)); }
|
||||
else if (info->alignment != 0 && is_aligned(pointer, info->alignment))
|
||||
{
|
||||
dbgln("ubsan: unaligned pointer access (address %p) at %s:%d:%d", (void*)pointer, DISPLAY(location));
|
||||
}
|
||||
else
|
||||
{
|
||||
dbgln("ubsan: %s address %p with insufficient space for object of type %s at %s:%d:%d",
|
||||
g_type_check_kinds[info->type_check_kind], (void*)pointer, info->type->name, DISPLAY(location));
|
||||
}
|
||||
ub_panic();
|
||||
}
|
||||
|
||||
void __ubsan_handle_type_mismatch_v1(TypeMismatchInfo_v1* v1_info, usize pointer)
|
||||
{
|
||||
TypeMismatchInfo info = {
|
||||
.location = v1_info->location,
|
||||
.type = v1_info->type,
|
||||
.alignment = 1UL << v1_info->log_alignment,
|
||||
.type_check_kind = v1_info->type_check_kind,
|
||||
};
|
||||
__ubsan_handle_type_mismatch(&info, pointer);
|
||||
}
|
||||
}
|
@ -10,4 +10,4 @@ mkdir -p $LUNA_BASE/usr/include
|
||||
mkdir -p $LUNA_BASE/usr/include/luna
|
||||
|
||||
cp --preserve=timestamps -RT libc/include/ $LUNA_BASE/usr/include
|
||||
cp --preserve=timestamps -RT luna/include/luna/ $LUNA_BASE/usr/include/luna
|
||||
cp --preserve=timestamps -RT libluna/include/luna/ $LUNA_BASE/usr/include/luna
|
||||
|
Loading…
Reference in New Issue
Block a user