├── include ├── kernel │ ├── stl │ │ ├── cerron.h │ │ ├── cstddef.h │ │ ├── cstdlib.h │ │ ├── cmath.h │ │ ├── semaphore.h │ │ ├── utility.h │ │ ├── cstdint.h │ │ ├── algorithm.h │ │ ├── mutex.h │ │ ├── type_traits.h │ │ ├── source_location.h │ │ ├── cstring.h │ │ ├── span.h │ │ ├── array.h │ │ ├── iterator.h │ │ └── string_view.h │ ├── util │ │ ├── metric.inc │ │ ├── metric.h │ │ ├── bitmap.h │ │ ├── tag_list.h │ │ ├── format.h │ │ └── block_queue.h │ ├── io │ │ ├── keyboard.h │ │ ├── file │ │ │ ├── dir.h │ │ │ ├── file.h │ │ │ └── path.h │ │ ├── video │ │ │ ├── print.inc │ │ │ ├── console.h │ │ │ └── print.h │ │ ├── timer.h │ │ ├── io.h │ │ └── disk │ │ │ ├── disk.inc │ │ │ ├── file │ │ │ ├── file.h │ │ │ ├── inode.h │ │ │ ├── dir.h │ │ │ └── super_block.h │ │ │ └── ide.h │ ├── krnl.h │ ├── descriptor │ │ ├── gdt │ │ │ ├── tab.h │ │ │ └── idx.h │ │ └── desc.inc │ ├── debug │ │ └── assert.h │ ├── process │ │ ├── elf.inc │ │ ├── tss.h │ │ └── proc.h │ ├── krnl.inc │ ├── selector │ │ ├── sel.inc │ │ └── sel.h │ ├── interrupt │ │ └── pic.h │ ├── syscall │ │ └── call.h │ ├── memory │ │ └── page.inc │ └── thread │ │ └── sync.h ├── user │ ├── io │ │ ├── file │ │ │ ├── dir.h │ │ │ └── file.h │ │ └── video │ │ │ └── console.h │ ├── memory │ │ └── pool.h │ ├── process │ │ └── proc.h │ ├── stl │ │ └── cstdint.h │ └── syscall │ │ └── call.h └── boot │ └── boot.inc ├── Debugging.md ├── src ├── kernel │ ├── stl │ │ ├── mutex.cpp │ │ ├── semaphore.cpp │ │ └── cstring.cpp │ ├── main.cpp │ ├── process │ │ ├── tss.asm │ │ └── tss.cpp │ ├── io │ │ ├── file │ │ │ ├── dir.cpp │ │ │ ├── file.cpp │ │ │ └── path.cpp │ │ ├── io.cpp │ │ ├── disk │ │ │ └── file │ │ │ │ ├── file.cpp │ │ │ │ ├── dir.cpp │ │ │ │ ├── inode.cpp │ │ │ │ └── super_block.cpp │ │ ├── video │ │ │ ├── print.cpp │ │ │ └── console.cpp │ │ ├── io.asm │ │ └── timer.cpp │ ├── krnl.cpp │ ├── memory │ │ ├── page.asm │ │ └── page.cpp │ ├── descriptor │ │ ├── desc.asm │ │ └── gdt │ │ │ └── tab.cpp │ ├── syscall │ │ ├── call.asm │ │ └── call.cpp │ ├── thread │ │ ├── sync.cpp │ │ └── thd.asm │ ├── debug │ │ └── assert.cpp │ ├── util │ │ ├── format.cpp │ │ ├── tag_list.cpp │ │ └── bitmap.cpp │ └── interrupt │ │ └── intr.cpp ├── user │ ├── io │ │ ├── file │ │ │ ├── dir.cpp │ │ │ └── file.cpp │ │ └── video │ │ │ └── console.cpp │ ├── process │ │ └── proc.cpp │ └── memory │ │ └── pool.cpp └── boot │ └── mbr.asm ├── CITATION.cff ├── .github └── workflows │ └── make.yaml ├── docs ├── badges │ ├── C++.svg │ ├── NASM.svg │ ├── Linux.svg │ ├── License-MIT.svg │ ├── Made-with-Make.svg │ └── Made-with-GitHub-Actions.svg ├── Boot │ └── Master Boot Record.md ├── Kernel │ ├── File System.md │ ├── Interrupts.md │ └── System Calls.md └── Getting Started │ └── Building the System.md ├── LICENSE ├── .gitignore ├── .clang-format └── Makefile /include/kernel/stl/cerron.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace stl { 4 | 5 | using errno_t = int; 6 | 7 | } -------------------------------------------------------------------------------- /include/kernel/stl/cstddef.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace stl { 4 | 5 | using byte = unsigned char; 6 | 7 | using ptrdiff_t = int; 8 | 9 | } -------------------------------------------------------------------------------- /include/kernel/stl/cstdlib.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace stl { 4 | 5 | inline constexpr int EXIT_SUCCESS {0}; 6 | 7 | inline constexpr int EXIT_FAILURE {-1}; 8 | 9 | } -------------------------------------------------------------------------------- /Debugging.md: -------------------------------------------------------------------------------- 1 | # Debugging 2 | 3 | ## Breakpoints 4 | 5 | - The master boot record entry: `pb 0x7C00`. 6 | - The loader entry: `pb 0xD00`. 7 | - Jump to the kernel: `pb 0xE29`. -------------------------------------------------------------------------------- /include/kernel/stl/cmath.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace stl { 4 | 5 | template 6 | constexpr T abs(const T num) noexcept { 7 | return num >= 0 ? num : -num; 8 | } 9 | 10 | } // namespace stl -------------------------------------------------------------------------------- /src/kernel/stl/mutex.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/stl/mutex.h" 2 | 3 | namespace stl { 4 | 5 | void mutex::lock() noexcept { 6 | mtx_.Lock(); 7 | } 8 | 9 | void mutex::unlock() noexcept { 10 | mtx_.Unlock(); 11 | } 12 | 13 | } // namespace stl -------------------------------------------------------------------------------- /src/kernel/main.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/stl/cstdlib.h" 2 | #include "kernel/thread/thd.h" 3 | 4 | int main() { 5 | InitKernel(); 6 | 7 | while (true) { 8 | tsk::Thread::GetCurrent().Yield(); 9 | } 10 | 11 | return stl::EXIT_SUCCESS; 12 | } -------------------------------------------------------------------------------- /src/user/io/file/dir.cpp: -------------------------------------------------------------------------------- 1 | #include "user/io/file/dir.h" 2 | #include "user/syscall/call.h" 3 | 4 | namespace usr::io { 5 | 6 | bool Directory::Create(const char* const path) noexcept { 7 | return sc::SysCall(sc::SysCallType::CreateDir, const_cast(path)); 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /include/kernel/util/metric.inc: -------------------------------------------------------------------------------- 1 | %define B(n) (n) 2 | ; Convert kilobytes to bytes. 3 | %define KB(n) (n * B(1024)) 4 | ; Convert megabytes to bytes. 5 | %define MB(n) (n * KB(1024)) 6 | ; Convert gigabytes to bytes. 7 | %define GB(n) (n * MB(1024)) 8 | 9 | True equ 1 10 | False equ 0 -------------------------------------------------------------------------------- /src/kernel/process/tss.asm: -------------------------------------------------------------------------------- 1 | %include "kernel/util/metric.inc" 2 | 3 | [bits 32] 4 | section .text 5 | global SetTaskReg 6 | ; Set the task register. 7 | SetTaskReg: 8 | %push set_task_reg 9 | %stacksize flat 10 | %arg sel:word 11 | enter B(0), 0 12 | ltr [sel] 13 | leave 14 | ret 15 | %pop -------------------------------------------------------------------------------- /src/user/process/proc.cpp: -------------------------------------------------------------------------------- 1 | #include "user/process/proc.h" 2 | #include "user/syscall/call.h" 3 | 4 | namespace usr::tsk { 5 | stl::size_t Process::GetCurrPid() noexcept { 6 | return SysCall(sc::SysCallType::GetCurrPid); 7 | } 8 | 9 | stl::size_t Process::Fork() noexcept { 10 | return SysCall(sc::SysCallType::Fork); 11 | } 12 | 13 | } // namespace usr::tsk -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | authors: 3 | - family-names: Chen 4 | given-names: Zhenshuo 5 | orcid: https://orcid.org/0000-0003-2091-4160 6 | - family-names: Liu 7 | given-names: Guowen 8 | orcid: https://orcid.org/0000-0002-8375-5729 9 | title: Tiny x86 Operating System in C++ 10 | date-released: 2024-10-01 11 | url: https://github.com/Zhuagenborn/Tiny-x86-OS -------------------------------------------------------------------------------- /include/user/io/file/dir.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file dir.h 3 | * @brief User-mode directory management. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | namespace usr::io { 12 | 13 | //! User-mode directory management. 14 | class Directory { 15 | public: 16 | Directory() = delete; 17 | 18 | static bool Create(const char*) noexcept; 19 | }; 20 | 21 | } -------------------------------------------------------------------------------- /src/kernel/stl/semaphore.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/stl/semaphore.h" 2 | 3 | namespace stl { 4 | 5 | binary_semaphore::binary_semaphore(const stl::size_t desired) noexcept { 6 | sema_.Init(desired); 7 | } 8 | 9 | void binary_semaphore::release() noexcept { 10 | sema_.Increase(); 11 | } 12 | 13 | void binary_semaphore::acquire() noexcept { 14 | sema_.Decrease(); 15 | } 16 | 17 | } // namespace stl -------------------------------------------------------------------------------- /src/kernel/io/file/dir.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/io/file/dir.h" 2 | #include "kernel/io/disk/disk.h" 3 | 4 | namespace io { 5 | 6 | bool Directory::Create(const Path& path) noexcept { 7 | return GetDefaultPart().CreateDir(path); 8 | } 9 | 10 | namespace sc { 11 | 12 | bool Directory::Create(const char* const path) noexcept { 13 | return io::Directory::Create(path); 14 | } 15 | 16 | } // namespace sc 17 | 18 | } // namespace io -------------------------------------------------------------------------------- /src/user/memory/pool.cpp: -------------------------------------------------------------------------------- 1 | #include "user/memory/pool.h" 2 | #include "user/syscall/call.h" 3 | 4 | namespace usr::mem { 5 | 6 | void* Allocate(const stl::size_t size) noexcept { 7 | return reinterpret_cast( 8 | SysCall(sc::SysCallType::MemAlloc, reinterpret_cast(size))); 9 | } 10 | 11 | void Free(void* const base) noexcept { 12 | SysCall(sc::SysCallType::MemFree, base); 13 | } 14 | 15 | } // namespace usr::mem -------------------------------------------------------------------------------- /include/kernel/stl/semaphore.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kernel/thread/sync.h" 4 | 5 | namespace stl { 6 | 7 | class binary_semaphore { 8 | public: 9 | binary_semaphore(stl::size_t desired = 1) noexcept; 10 | 11 | binary_semaphore(const binary_semaphore&) = delete; 12 | 13 | void release() noexcept; 14 | 15 | void acquire() noexcept; 16 | 17 | private: 18 | sync::Semaphore<1> sema_; 19 | }; 20 | 21 | } // namespace stl -------------------------------------------------------------------------------- /include/kernel/stl/utility.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kernel/stl/type_traits.h" 4 | 5 | namespace stl { 6 | 7 | template 8 | constexpr remove_reference_t&& move(T&& t) noexcept { 9 | return static_cast&&>(t); 10 | } 11 | 12 | template 13 | constexpr void swap(T& lhs, T& rhs) noexcept { 14 | T tmp {move(lhs)}; 15 | lhs = move(rhs); 16 | rhs = move(tmp); 17 | } 18 | 19 | } // namespace stl -------------------------------------------------------------------------------- /include/user/memory/pool.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file pool.h 3 | * @brief User-mode memory management. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "user/stl/cstdint.h" 12 | 13 | namespace usr::mem { 14 | 15 | //! Allocate virtual memory in bytes in user mode. 16 | void* Allocate(stl::size_t size) noexcept; 17 | 18 | //! Free virtual memory in user mode. 19 | void Free(void* base) noexcept; 20 | 21 | } -------------------------------------------------------------------------------- /include/user/process/proc.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file proc.h 3 | * @brief User-mode process management. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "user/stl/cstdint.h" 12 | 13 | namespace usr::tsk { 14 | 15 | //! User-mode process management. 16 | class Process { 17 | public: 18 | Process() = delete; 19 | 20 | static stl::size_t GetCurrPid() noexcept; 21 | 22 | static stl::size_t Fork() noexcept; 23 | }; 24 | 25 | } // namespace usr::tsk -------------------------------------------------------------------------------- /include/kernel/io/keyboard.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file keyboard.h 3 | * @brief The *Intel 8042* keyboard controller. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/util/block_queue.h" 12 | 13 | namespace io { 14 | 15 | //! Initialize the keyboard. 16 | void InitKeyboard() noexcept; 17 | 18 | using KeyboardBuffer = BlockQueue; 19 | 20 | //! Get the keyboard buffer. 21 | KeyboardBuffer& GetKeyboardBuffer() noexcept; 22 | 23 | } // namespace io -------------------------------------------------------------------------------- /src/kernel/io/io.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/io/io.h" 2 | 3 | namespace io { 4 | 5 | namespace { 6 | 7 | extern "C" { 8 | 9 | //! Get the value of @p EFLAGS. 10 | stl::uint32_t GetEFlags() noexcept; 11 | 12 | //! Set the value of @p EFLAGS. 13 | stl::uint32_t SetEFlags(stl::uint32_t) noexcept; 14 | } 15 | 16 | } // namespace 17 | 18 | EFlags EFlags::Get() noexcept { 19 | return GetEFlags(); 20 | } 21 | 22 | void EFlags::Set(const EFlags eflags) noexcept { 23 | SetEFlags(eflags); 24 | } 25 | 26 | } // namespace io -------------------------------------------------------------------------------- /include/kernel/stl/cstdint.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace stl { 4 | 5 | using int8_t = signed char; 6 | using int16_t = signed short; 7 | using int32_t = signed int; 8 | using int64_t = signed long long; 9 | 10 | using uint8_t = unsigned char; 11 | using uint16_t = unsigned short; 12 | using uint32_t = unsigned int; 13 | using uint64_t = unsigned long long; 14 | 15 | using uint_least32_t = uint32_t; 16 | 17 | using byte = unsigned char; 18 | using size_t = unsigned int; 19 | using intptr_t = int; 20 | using uintptr_t = unsigned int; 21 | using ptrdiff_t = int; 22 | 23 | } // namespace stl -------------------------------------------------------------------------------- /include/user/stl/cstdint.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace stl { 4 | 5 | using int8_t = signed char; 6 | using int16_t = signed short; 7 | using int32_t = signed int; 8 | using int64_t = signed long long; 9 | 10 | using uint8_t = unsigned char; 11 | using uint16_t = unsigned short; 12 | using uint32_t = unsigned int; 13 | using uint64_t = unsigned long long; 14 | 15 | using uint_least32_t = uint32_t; 16 | 17 | using byte = unsigned char; 18 | using size_t = unsigned int; 19 | using intptr_t = int; 20 | using uintptr_t = unsigned int; 21 | using ptrdiff_t = int; 22 | 23 | } // namespace stl -------------------------------------------------------------------------------- /src/kernel/krnl.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/krnl.h" 2 | #include "kernel/interrupt/intr.h" 3 | #include "kernel/io/disk/disk.h" 4 | #include "kernel/io/keyboard.h" 5 | #include "kernel/io/timer.h" 6 | #include "kernel/memory/pool.h" 7 | #include "kernel/process/tss.h" 8 | #include "kernel/syscall/call.h" 9 | #include "kernel/thread/thd.h" 10 | 11 | void InitKernel() noexcept { 12 | intr::InitIntr(); 13 | sc::InitSysCall(); 14 | mem::InitMem(); 15 | tsk::InitThread(); 16 | io::InitTimer(io::timer_freq_per_second); 17 | tsk::InitTaskStateSeg(); 18 | io::InitKeyboard(); 19 | intr::EnableIntr(); 20 | io::InitDisk(); 21 | io::InitFileSys(); 22 | } -------------------------------------------------------------------------------- /src/kernel/memory/page.asm: -------------------------------------------------------------------------------- 1 | %include "kernel/util/metric.inc" 2 | 3 | [bits 32] 4 | section .text 5 | global DisableTlbEntry 6 | ; Invalidate a Translation Lookaside Buffer (TLB) entry. 7 | ; When the CPU needs to translate a virtual address into a physical address, the TLB is consulted first. 8 | ; If paging structures are modified, the TLB is not transparently informed of changes. 9 | ; Therefore the TLB has to be flushed. 10 | DisableTlbEntry: 11 | %push disable_tle_entry 12 | %stacksize flat 13 | %arg vr_addr:dword 14 | enter B(0), 0 15 | mov eax, [vr_addr] 16 | invlpg [eax] 17 | leave 18 | ret 19 | %pop -------------------------------------------------------------------------------- /include/kernel/io/file/dir.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file dir.h 3 | * @brief Directory management. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/io/file/path.h" 12 | 13 | namespace io { 14 | 15 | //! The wrapper for directory functions of @p Disk::FilePart. 16 | class Directory { 17 | public: 18 | //! Create a directory. 19 | static bool Create(const Path&) noexcept; 20 | }; 21 | 22 | //! System calls. 23 | namespace sc { 24 | 25 | class Directory { 26 | public: 27 | Directory() = delete; 28 | 29 | static bool Create(const char*) noexcept; 30 | }; 31 | 32 | } // namespace sc 33 | 34 | } // namespace io -------------------------------------------------------------------------------- /include/kernel/krnl.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file krnl.h 3 | * @brief Basic kernel configurations. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/util/metric.h" 12 | 13 | //! The size of the kernel in bytes. 14 | inline constexpr stl::size_t krnl_size {MB(1)}; 15 | 16 | //! The address of the kernel image when it is loaded. 17 | inline constexpr stl::uintptr_t krnl_base {0xC0000000}; 18 | 19 | enum class Privilege { 20 | //! The kernel. 21 | Zero = 0, 22 | 23 | One = 1, 24 | Two = 2, 25 | 26 | //! Users. 27 | Three = 3 28 | }; 29 | 30 | //! Initialize the kernel. 31 | void InitKernel() noexcept; -------------------------------------------------------------------------------- /include/kernel/stl/algorithm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace stl { 4 | 5 | template 6 | InputIter find(const InputIter begin, const InputIter end, const T& val) noexcept { 7 | for (auto it {begin}; it != end; ++it) { 8 | if (*it == val) { 9 | return it; 10 | } 11 | } 12 | 13 | return end; 14 | } 15 | 16 | template 17 | constexpr const T& min(const T& lhs, const T& rhs) noexcept { 18 | return lhs < rhs ? lhs : rhs; 19 | } 20 | 21 | template 22 | constexpr const T& max(const T& lhs, const T& rhs) noexcept { 23 | return lhs > rhs ? lhs : rhs; 24 | } 25 | 26 | } // namespace stl -------------------------------------------------------------------------------- /include/kernel/io/video/print.inc: -------------------------------------------------------------------------------- 1 | ; Video configurations. 2 | 3 | ; The screen width in VGA text mode. 4 | text_screen_width equ 80 5 | ; The screen height in VGA text mode. 6 | text_screen_height equ 25 7 | 8 | ; The VGA address register. 9 | ; The index of a data register should be saved in this register before accessing the data. 10 | vga_ctrl_addr_port equ 0x3D4 11 | ; The VGA data register. 12 | vga_ctrl_data_port equ 0x3D5 13 | 14 | ; The index of the register for the high bits of the cursor location. 15 | vga_ctrl_cursor_loc_high_idx equ 0xE 16 | ; The index of the register for the low bits of the cursor location. 17 | vga_ctrl_cursor_loc_low_idx equ 0xF -------------------------------------------------------------------------------- /include/kernel/stl/mutex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kernel/thread/sync.h" 4 | 5 | namespace stl { 6 | 7 | class mutex { 8 | public: 9 | mutex() noexcept = default; 10 | 11 | mutex(const mutex&) = delete; 12 | 13 | void lock() noexcept; 14 | 15 | void unlock() noexcept; 16 | 17 | private: 18 | sync::Mutex mtx_; 19 | }; 20 | 21 | template 22 | class lock_guard { 23 | public: 24 | explicit lock_guard(Mutex& mtx) noexcept : mtx_ {mtx} { 25 | mtx.lock(); 26 | } 27 | 28 | lock_guard(const lock_guard&) = delete; 29 | 30 | ~lock_guard() noexcept { 31 | mtx_.unlock(); 32 | } 33 | 34 | private: 35 | Mutex& mtx_; 36 | }; 37 | 38 | } // namespace stl -------------------------------------------------------------------------------- /include/kernel/stl/type_traits.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace stl { 4 | 5 | template 6 | struct remove_reference { 7 | using type = T; 8 | }; 9 | 10 | template 11 | using remove_reference_t = typename remove_reference::type; 12 | 13 | template 14 | struct remove_reference { 15 | using type = T; 16 | }; 17 | 18 | template 19 | struct remove_reference { 20 | using type = T; 21 | }; 22 | 23 | template 24 | struct remove_const { 25 | using type = T; 26 | }; 27 | 28 | template 29 | using remove_const_t = typename remove_const::type; 30 | 31 | template 32 | struct remove_const { 33 | using type = T; 34 | }; 35 | 36 | } // namespace stl -------------------------------------------------------------------------------- /include/kernel/descriptor/gdt/tab.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file tab.h 3 | * @brief The global descriptor table. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/descriptor/desc.h" 12 | 13 | namespace gdt { 14 | 15 | /** 16 | * @brief The global descriptor table. 17 | * 18 | * @note 19 | * The content of the global descriptor table is defined in @p src/boot/loader.asm, 20 | * so we should use a span to refer to it. 21 | */ 22 | using GlobalDescTab = desc::DescTabSpan; 23 | 24 | //! Get the global descriptor table register. 25 | desc::DescTabReg GetGlobalDescTabReg() noexcept; 26 | 27 | //! Get the global descriptor table. 28 | GlobalDescTab GetGlobalDescTab() noexcept; 29 | 30 | } // namespace gdt -------------------------------------------------------------------------------- /src/kernel/descriptor/desc.asm: -------------------------------------------------------------------------------- 1 | %include "kernel/util/metric.inc" 2 | 3 | [bits 32] 4 | section .text 5 | global GetGlobalDescTabReg 6 | ; Get the global descriptor table register. 7 | GetGlobalDescTabReg: 8 | %push get_global_desc_tab_reg 9 | %stacksize flat 10 | %arg reg_base:dword 11 | enter B(0), 0 12 | mov eax, [reg_base] 13 | sgdt [eax] 14 | leave 15 | ret 16 | %pop 17 | 18 | global SetGlobalDescTabReg 19 | ; Set the global descriptor table register. 20 | ; ```c++ 21 | ; void SetGlobalDescTabReg(std::uint16_t limit, std::uintptr_t base) noexcept; 22 | ; ``` 23 | SetGlobalDescTabReg: 24 | mov ax, [esp + B(4)] 25 | mov [esp + B(6)], ax 26 | lgdt [esp + B(6)] 27 | ret -------------------------------------------------------------------------------- /include/kernel/io/timer.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file timer.h 3 | * @brief *Intel 8253* Programmable Interval Timer. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/stl/cstdint.h" 12 | 13 | namespace io { 14 | 15 | //! The number of interrupts generated by the timer per second. 16 | inline constexpr stl::size_t timer_freq_per_second {100}; 17 | 18 | /** 19 | * @brief Initialize the timer. 20 | * 21 | * @param freq_per_second The number of generated interrupts per second. 22 | */ 23 | void InitTimer(stl::size_t freq_per_second) noexcept; 24 | 25 | //! Get the number of ticks after system startup. 26 | stl::size_t GetTicks() noexcept; 27 | 28 | //! Whether the timer has been initialized. 29 | bool IsTimerInited() noexcept; 30 | 31 | } // namespace io -------------------------------------------------------------------------------- /src/kernel/syscall/call.asm: -------------------------------------------------------------------------------- 1 | %include "kernel/util/metric.inc" 2 | 3 | ; The interrupt number of system calls. 4 | sys_call_intr_num equ 0x30 5 | 6 | [bits 32] 7 | section .text 8 | global SysCall 9 | ; The system call. 10 | ; It accepts an index and an optional user-defined argument, 11 | ; then calls the indexed method with the argument in the system call interrupt. 12 | ; Developers should register kernel APIs as system calls first before calling them in user mode. 13 | SysCall: 14 | %push sys_call 15 | %stacksize flat 16 | %arg func:dword, arg:dword 17 | enter B(0), 0 18 | mov ecx, [func] 19 | mov eax, [arg] 20 | ; Jump to the method `SysCallEntry` in `src/kernel/interrupt/intr.asm`. 21 | int sys_call_intr_num 22 | leave 23 | ret 24 | %pop -------------------------------------------------------------------------------- /src/kernel/descriptor/gdt/tab.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/descriptor/gdt/tab.h" 2 | 3 | namespace gdt { 4 | 5 | namespace { 6 | 7 | extern "C" { 8 | //! Set the global descriptor table register. 9 | void SetGlobalDescTabReg(stl::uint16_t limit, stl::uintptr_t base) noexcept; 10 | 11 | //! Get the global descriptor table register. 12 | void GetGlobalDescTabReg(desc::DescTabReg&) noexcept; 13 | } 14 | 15 | void SetGlobalDescTabReg(const desc::DescTabReg& reg) noexcept { 16 | SetGlobalDescTabReg(reg.GetLimit(), reg.GetBase()); 17 | } 18 | 19 | } // namespace 20 | 21 | desc::DescTabReg GetGlobalDescTabReg() noexcept { 22 | desc::DescTabReg reg; 23 | GetGlobalDescTabReg(reg); 24 | return reg; 25 | } 26 | 27 | GlobalDescTab GetGlobalDescTab() noexcept { 28 | return GlobalDescTab {GetGlobalDescTabReg()}; 29 | } 30 | 31 | } // namespace gdt -------------------------------------------------------------------------------- /.github/workflows/make.yaml: -------------------------------------------------------------------------------- 1 | name: Build the project with Make on Ubuntu 2 | on: 3 | push: 4 | branches: [main, master] 5 | paths: 6 | - '!docs/**' 7 | - '.github/workflows/make.yaml' 8 | - 'include/**' 9 | - 'src/**' 10 | - '**/Makefile' 11 | pull_request: 12 | branches: [main, master] 13 | paths: 14 | - '!docs/**' 15 | - '.github/workflows/make.yaml' 16 | - 'include/**' 17 | - 'src/**' 18 | - '**/Makefile' 19 | jobs: 20 | make: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Download package information 24 | run: sudo apt-get update 25 | - name: Install NASM 26 | run: sudo apt-get install -y nasm 27 | - name: Check out the repository 28 | uses: actions/checkout@main 29 | - name: Build the project 30 | run: make -------------------------------------------------------------------------------- /docs/badges/C++.svg: -------------------------------------------------------------------------------- 1 | C++C++ -------------------------------------------------------------------------------- /docs/badges/NASM.svg: -------------------------------------------------------------------------------- 1 | NASMNASM -------------------------------------------------------------------------------- /include/user/io/video/console.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file console.h 3 | * @brief The user-mode thread-safe text console. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "user/stl/cstdint.h" 12 | 13 | namespace usr::io { 14 | 15 | //! The user-mode thread-safe text console. 16 | class Console { 17 | public: 18 | Console() = delete; 19 | 20 | static void PrintlnStr(const char*) noexcept; 21 | 22 | static void PrintStr(const char*) noexcept; 23 | 24 | static void PrintlnChar(char) noexcept; 25 | 26 | static void PrintChar(char) noexcept; 27 | 28 | static void PrintlnHex(stl::uint32_t) noexcept; 29 | 30 | static void PrintHex(stl::uint32_t) noexcept; 31 | 32 | static void PrintlnHex(stl::int32_t) noexcept; 33 | 34 | static void PrintHex(stl::int32_t) noexcept; 35 | }; 36 | 37 | } // namespace usr::io -------------------------------------------------------------------------------- /docs/badges/Linux.svg: -------------------------------------------------------------------------------- 1 | LinuxLinux -------------------------------------------------------------------------------- /include/kernel/debug/assert.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file assert.h 3 | * @brief * Diagnostics tools. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/stl/source_location.h" 12 | #include "kernel/stl/string_view.h" 13 | 14 | namespace dbg { 15 | 16 | #ifdef NDEBUG 17 | inline constexpr bool enabled {false}; 18 | #else 19 | inline constexpr bool enabled {true}; 20 | #endif 21 | 22 | /** 23 | * @brief 24 | * Check for a condition. 25 | * If it is @p false, the method displays a message, shows the source code information and pauses the system. 26 | * 27 | * @param cond A condition to evaluate. 28 | * @param msg An optional message to display. 29 | * @param src The source code information. The developer should not change this parameter. 30 | */ 31 | void Assert(bool cond, stl::string_view msg = nullptr, 32 | const stl::source_location& src = stl::source_location::current()) noexcept; 33 | 34 | } // namespace dbg -------------------------------------------------------------------------------- /src/kernel/thread/sync.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/thread/sync.h" 2 | 3 | namespace sync { 4 | 5 | void Mutex::Lock() noexcept { 6 | auto& curr_thd {tsk::Thread::GetCurrent()}; 7 | // The mutex is held by another thread. 8 | if (holder_ != &curr_thd) { 9 | sema_.Decrease(); 10 | holder_ = &curr_thd; 11 | dbg::Assert(repeat_times_ == 0); 12 | repeat_times_ = 1; 13 | } else { 14 | // Repeatedly lock. 15 | dbg::Assert(repeat_times_ > 0); 16 | ++repeat_times_; 17 | } 18 | } 19 | 20 | void Mutex::Unlock() noexcept { 21 | dbg::Assert(holder_ == &tsk::Thread::GetCurrent()); 22 | if (repeat_times_ == 1) { 23 | repeat_times_ = 0; 24 | // Reset the holder before unlocking the mutex. 25 | holder_ = nullptr; 26 | sema_.Increase(); 27 | } else { 28 | // Repeatedly unlock. 29 | dbg::Assert(repeat_times_ > 1); 30 | --repeat_times_; 31 | } 32 | } 33 | 34 | } // namespace sync -------------------------------------------------------------------------------- /include/kernel/descriptor/gdt/idx.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file idx.h 3 | * @brief Global descriptor indexes. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/stl/cstdint.h" 12 | 13 | namespace gdt { 14 | 15 | //! The maximum number of global descriptors. 16 | inline constexpr stl::size_t count {60}; 17 | 18 | namespace idx { 19 | 20 | //! The kernel descriptor for code. 21 | inline constexpr stl::size_t krnl_code {1}; 22 | //! The kernel descriptor for data. 23 | inline constexpr stl::size_t krnl_data {2}; 24 | //! The kernel descriptor for the VGA text buffer. 25 | inline constexpr stl::size_t gs {3}; 26 | //! The kernel descriptor for the task state segment. 27 | inline constexpr stl::size_t tss {4}; 28 | 29 | //! The user descriptor for code. 30 | inline constexpr stl::size_t usr_code {5}; 31 | //! The user descriptor for data. 32 | inline constexpr stl::size_t usr_data {6}; 33 | 34 | } // namespace idx 35 | 36 | } // namespace gdt -------------------------------------------------------------------------------- /include/kernel/process/elf.inc: -------------------------------------------------------------------------------- 1 | ; The Executable and Linkable Format (ELF). 2 | 3 | ; The file header. 4 | struc ElfFileHeader 5 | .e_ident resb 16 6 | .e_type resw 1 7 | .e_machine resw 1 8 | .e_version resd 1 9 | .e_entry resd 1 10 | .e_phoff resd 1 11 | .e_shoff resd 1 12 | .e_flags resd 1 13 | .e_ehsize resw 1 14 | .e_phentsize resw 1 15 | .e_phnum resw 1 16 | .e_shentsize resw 1 17 | .e_shnum resw 1 18 | .e_shstrndx resw 1 19 | endstruc 20 | 21 | ; The section header. 22 | struc ElfSectHeader 23 | .p_type resd 1 24 | .p_offset resd 1 25 | .p_vaddr resd 1 26 | .p_paddr resd 1 27 | .p_filesz resd 1 28 | .p_memsz resd 1 29 | .p_flags resd 1 30 | .p_align resd 1 31 | endstruc 32 | 33 | PT_NULL equ 0 -------------------------------------------------------------------------------- /include/boot/boot.inc: -------------------------------------------------------------------------------- 1 | ; Basic configurations of the master boot record and the kernel loader. 2 | 3 | %include "kernel/util/metric.inc" 4 | 5 | ; The physical address of the master boot record, specified by the BIOS. 6 | mbr_base equ 0x7C00 7 | ; The physical stack top address of the master boot record. 8 | mbr_stack_top equ mbr_base 9 | 10 | ; The physical address of the loader. 11 | loader_base equ 0x900 12 | ; The size of the data at the beginning of the loader, mainly global descriptors. 13 | loader_data_size equ B(0x400) 14 | ; The physical code entry address of the loader. 15 | loader_code_entry equ loader_base + loader_data_size 16 | ; The first disk sector of the loader (the sector `0` is for the master boot record). 17 | loader_start_sector equ 1 18 | ; The size of the loader in disk sectors. 19 | loader_sector_count equ 5 20 | ; The physical stack top address of the loader. 21 | loader_stack_top equ loader_base -------------------------------------------------------------------------------- /docs/Boot/Master Boot Record.md: -------------------------------------------------------------------------------- 1 | # Master Boot Record 2 | 3 | The *Master Boot Record (MBR)* is the information in the first sector of a hard disk. It identifies how and where the system's operating system is located and a program that loads the rest of the operating system into memory. It will be loaded at physical address `0x7C00` by BIOS. 4 | 5 | The following code shows the main functions of `mbr.bin`: 6 | 7 | 1. Clearing the screen. 8 | 2. Loading `loader.bin` from the disk. 9 | 3. Jumping to `loader.bin`. 10 | 11 | ```nasm 12 | ; src/boot/mbr.asm 13 | 14 | section mbr vstart=mbr_base 15 | ; ... 16 | call ClearScreen 17 | call ReadLoader 18 | jmp loader_code_entry 19 | ; ... 20 | times disk_sector_size - 2 - ($ - $$) db 0 21 | db 0x55, 0xAA 22 | ``` 23 | 24 | `mbr.bin` is 512 bytes, ending with `0x55` and `0xAA`. We need to set the start address to `0x7C00` using the `vstart` command. During installation, `mbr.bin` will be written into the first sector of `kernel.img`. -------------------------------------------------------------------------------- /include/user/io/file/file.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file file.h 3 | * @brief User-mode file management. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "user/stl/cstdint.h" 12 | 13 | namespace usr::io { 14 | 15 | //! User-mode file management. 16 | class File { 17 | public: 18 | File() = delete; 19 | 20 | enum class OpenMode { ReadOnly = 0, WriteOnly = 1, ReadWrite = 2, CreateNew = 4 }; 21 | 22 | enum class SeekOrigin { Begin, Curr, End }; 23 | 24 | static stl::size_t Open(const char*, stl::uint32_t) noexcept; 25 | 26 | static void Close(stl::size_t desc) noexcept; 27 | 28 | static bool Delete(const char* path) noexcept; 29 | 30 | static stl::size_t Write(stl::size_t desc, const void* data, stl::size_t size) noexcept; 31 | 32 | static stl::size_t Read(stl::size_t desc, void* buf, stl::size_t size) noexcept; 33 | 34 | static stl::size_t Seek(stl::size_t desc, stl::int32_t offset, SeekOrigin) noexcept; 35 | }; 36 | 37 | } // namespace usr::io -------------------------------------------------------------------------------- /include/user/syscall/call.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file call.h 3 | * @brief User-mode system calls. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "user/stl/cstdint.h" 12 | 13 | namespace usr::sc { 14 | 15 | /** 16 | * @brief Types of system calls. 17 | * 18 | * @warning 19 | * Their order must be the same as @p sc::SysCallType. 20 | */ 21 | enum class SysCallType { 22 | GetCurrPid, 23 | PrintChar, 24 | PrintHex, 25 | PrintStr, 26 | MemAlloc, 27 | MemFree, 28 | OpenFile, 29 | CloseFile, 30 | WriteFile, 31 | ReadFile, 32 | SeekFile, 33 | DeleteFile, 34 | CreateDir, 35 | Fork 36 | }; 37 | 38 | extern "C" { 39 | 40 | /** 41 | * @brief Call a kernel method by a system call in user mode. 42 | * 43 | * @param func The type of a system call. 44 | * @param arg A user-defined argument. 45 | * @return The returned value of the kernel method. 46 | */ 47 | stl::int32_t SysCall(SysCallType func, void* arg = nullptr) noexcept; 48 | } 49 | 50 | } // namespace usr::sc -------------------------------------------------------------------------------- /docs/badges/License-MIT.svg: -------------------------------------------------------------------------------- 1 | License: MITLicenseMIT -------------------------------------------------------------------------------- /docs/badges/Made-with-Make.svg: -------------------------------------------------------------------------------- 1 | Made with: MakeMade withMake -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Zhuagenborn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /include/kernel/krnl.inc: -------------------------------------------------------------------------------- 1 | ; Basic kernel configurations. 2 | 3 | %include "boot/boot.inc" 4 | %include "kernel/memory/page.inc" 5 | 6 | ; The size of the kernel in bytes. 7 | krnl_size equ MB(1) 8 | ; The first disk sector of the kernel. 9 | krnl_start_sector equ loader_start_sector + loader_sector_count 10 | ; The size of the kernel in disk sectors. 11 | krnl_sector_count equ 350 12 | ; The buffer address for reading the raw kernel from the file. 13 | raw_krnl_base equ 0x00070000 14 | ; The address of the kernel image when it is loaded. 15 | krnl_base equ 0xC0000000 16 | ; The stack top address of the kernel. 17 | krnl_stack_top equ krnl_base + 0x0009F000 18 | ; The code entry address of the kernel. 19 | krnl_code_entry equ krnl_base + 0x00001500 20 | 21 | ; The address of the page directory table. 22 | page_dir_base equ krnl_size 23 | ; The index of the first kernel page directory entry. 24 | krnl_page_dir_start equ (krnl_base & 0xFFC00000) >> 22 25 | ; The number of kernel page directory entries. 26 | krnl_page_dir_count equ page_dir_count - krnl_page_dir_start - 1 -------------------------------------------------------------------------------- /docs/badges/Made-with-GitHub-Actions.svg: -------------------------------------------------------------------------------- 1 | Made with: GitHub ActionsMade withGitHub Actions -------------------------------------------------------------------------------- /include/kernel/selector/sel.inc: -------------------------------------------------------------------------------- 1 | ; Segment selectors. 2 | 3 | ; The segment selector. 4 | ; It can identify a descriptor in a descriptor table. 5 | ; ``` 6 | ; 15-3 2 1-0 7 | ; ┌───────┬────┬─────┐ 8 | ; │ Index │ TI │ RPL │ 9 | ; └───────┴────┴─────┘ 10 | ; ▲ 11 | ; └─ 0: The index is for the global descriptor table. 12 | ; 1: The index is for a local descriptor table. 13 | ; ``` 14 | ; The selector's requested privilege level is 0. 15 | sel_rpl_0 equ 0 16 | ; The selector's requested privilege level is 1. 17 | sel_rpl_1 equ 1 18 | ; The selector's requested privilege level is 2. 19 | sel_rpl_2 equ 2 20 | ; The selector's requested privilege level is 3. 21 | sel_rpl_3 equ 3 22 | ; The selector is for the global descriptor table. 23 | sel_ti_gdt equ 0b000 24 | ; The selector is for a local descriptor table. 25 | sel_ti_ldt equ 0b100 26 | 27 | ; The selector for code. 28 | sel_code equ (1 << 3) + sel_ti_gdt + sel_rpl_0 29 | ; The selector for data. 30 | sel_data equ (2 << 3) + sel_ti_gdt + sel_rpl_0 31 | ; The selector for the VGA text buffer. 32 | sel_video equ (3 << 3) + sel_ti_gdt + sel_rpl_0 -------------------------------------------------------------------------------- /include/kernel/interrupt/pic.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file pic.h 3 | * @brief *Intel 8259A* Programmable Interrupt Controller. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/stl/span.h" 12 | 13 | namespace intr::pic { 14 | 15 | //! Interrupt requests. 16 | enum class Intr { 17 | //! The clock. 18 | Clock = 0, 19 | //! The keyboard. 20 | Keyboard = 1, 21 | 22 | /** 23 | * @brief The slave *Intel 8259A* chip. 24 | * 25 | * @details 26 | * The IBM extended the computer architecture by adding a second *Intel 8259A* chip, 27 | * This was possible due to the *Intel 8259A*'s ability to cascade interrupts. 28 | * When we cascade chips, *Intel 8259A* needs to use one of the interrupt requests to signal the other chip. 29 | */ 30 | SlavePic = 2, 31 | 32 | //! The primary IDE channel. 33 | PrimaryIdeChnl = 14, 34 | //! The secondary IDE channel. 35 | SecondaryIdeChnl = 15 36 | }; 37 | 38 | /** 39 | * @brief Initialize the interrupt controller. 40 | * 41 | * @param intrs Interrupts to be enabled. 42 | */ 43 | void InitPgmIntrCtrl(stl::span intrs) noexcept; 44 | 45 | } // namespace intr::pic -------------------------------------------------------------------------------- /src/kernel/io/disk/file/file.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/io/disk/file/file.h" 2 | #include "kernel/interrupt/intr.h" 3 | #include "kernel/io/disk/file/inode.h" 4 | 5 | namespace io::fs { 6 | 7 | File::File(File&& o) noexcept : flags {o.flags}, inode {o.inode}, pos {o.pos} { 8 | o.Clear(); 9 | } 10 | 11 | File& File::Clear() noexcept { 12 | pos = 0; 13 | flags = 0; 14 | inode = nullptr; 15 | return *this; 16 | } 17 | 18 | bool File::IsOpen() const noexcept { 19 | return inode != nullptr; 20 | } 21 | 22 | stl::size_t File::GetNodeIdx() const noexcept { 23 | return GetNode().idx; 24 | } 25 | 26 | void File::Close() noexcept { 27 | if (IsOpen()) { 28 | if (flags.IsSet(io::File::OpenMode::WriteOnly) 29 | || flags.IsSet(io::File::OpenMode::ReadWrite)) { 30 | dbg::Assert(inode->write_deny); 31 | inode->write_deny = false; 32 | } 33 | 34 | inode->Close(); 35 | } 36 | 37 | Clear(); 38 | } 39 | 40 | IdxNode& File::GetNode() const noexcept { 41 | dbg::Assert(IsOpen()); 42 | return *inode; 43 | } 44 | 45 | FileTab& GetFileTab() noexcept { 46 | static FileTab files; 47 | return files; 48 | } 49 | 50 | } // namespace io::fs -------------------------------------------------------------------------------- /src/kernel/debug/assert.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/debug/assert.h" 2 | #include "kernel/interrupt/intr.h" 3 | #include "kernel/io/video/print.h" 4 | 5 | namespace dbg { 6 | 7 | namespace { 8 | 9 | /** 10 | * @brief Show the source code information, display a message and pause the system. 11 | * 12 | * @param src The source code information. The developer should not change this parameter. 13 | * @param msg An optional message to display. 14 | */ 15 | [[noreturn]] void PanicSpin(const stl::source_location& src, 16 | const stl::string_view msg = nullptr) noexcept { 17 | const intr::IntrGuard guard; 18 | io::PrintlnStr("\n!!!!! System Panic !!!!!"); 19 | io::Printf("\tFile: {}.\n", src.file_name()); 20 | io::Printf("\tLine: 0x{}.\n", src.line()); 21 | io::Printf("\tFunction: {}.\n", src.function_name()); 22 | if (!msg.empty()) { 23 | io::Printf("\tMessage: {}.\n", msg.data()); 24 | } 25 | 26 | while (true) { 27 | } 28 | } 29 | 30 | } // namespace 31 | 32 | void Assert(bool cond, const stl::string_view msg, const stl::source_location& src) noexcept { 33 | if constexpr (enabled) { 34 | if (!cond) { 35 | PanicSpin(src, msg); 36 | } 37 | } 38 | } 39 | 40 | } // namespace dbg -------------------------------------------------------------------------------- /src/user/io/video/console.cpp: -------------------------------------------------------------------------------- 1 | #include "user/io/video/console.h" 2 | #include "user/syscall/call.h" 3 | 4 | namespace usr::io { 5 | 6 | void Console::PrintlnStr(const char* const str) noexcept { 7 | PrintStr(str); 8 | PrintChar('\n'); 9 | } 10 | 11 | void Console::PrintlnChar(const char ch) noexcept { 12 | PrintChar(ch); 13 | PrintChar('\n'); 14 | } 15 | 16 | void Console::PrintlnHex(const stl::uint32_t num) noexcept { 17 | PrintHex(num); 18 | PrintChar('\n'); 19 | } 20 | 21 | void Console::PrintlnHex(const stl::int32_t num) noexcept { 22 | PrintHex(num); 23 | PrintChar('\n'); 24 | } 25 | 26 | void Console::PrintChar(const char ch) noexcept { 27 | sc::SysCall(sc::SysCallType::PrintChar, reinterpret_cast(ch)); 28 | } 29 | 30 | void Console::PrintStr(const char* const str) noexcept { 31 | sc::SysCall(sc::SysCallType::PrintStr, const_cast(str)); 32 | } 33 | 34 | void Console::PrintHex(const stl::uint32_t num) noexcept { 35 | sc::SysCall(sc::SysCallType::PrintHex, reinterpret_cast(num)); 36 | } 37 | 38 | void Console::PrintHex(const stl::int32_t num) noexcept { 39 | if (num >= 0) { 40 | PrintHex(static_cast(num)); 41 | } else { 42 | PrintChar('-'); 43 | PrintHex(static_cast(-num)); 44 | } 45 | } 46 | 47 | } // namespace usr::io -------------------------------------------------------------------------------- /include/kernel/io/video/console.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file console.h 3 | * @brief The thread-safe text console. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/io/video/print.h" 12 | #include "kernel/stl/mutex.h" 13 | 14 | namespace io { 15 | 16 | class Console { 17 | public: 18 | Console() = delete; 19 | 20 | static void PrintlnStr(stl::string_view) noexcept; 21 | 22 | static void PrintStr(stl::string_view) noexcept; 23 | 24 | static void PrintStr(const char*) noexcept; 25 | 26 | static void PrintlnChar(char) noexcept; 27 | 28 | static void PrintChar(char) noexcept; 29 | 30 | static void PrintlnHex(stl::uint32_t) noexcept; 31 | 32 | static void PrintHex(stl::uint32_t) noexcept; 33 | 34 | static void PrintlnHex(stl::int32_t) noexcept; 35 | 36 | static void PrintHex(stl::int32_t) noexcept; 37 | 38 | //! Read characters from the keyboard buffer. 39 | static void Read(char* buf, stl::size_t count) noexcept; 40 | 41 | template 42 | static void Printf(const stl::string_view format, const Args... args) noexcept { 43 | const stl::lock_guard guard {GetMutex()}; 44 | io::Printf(format, args...); 45 | } 46 | 47 | private: 48 | static stl::mutex& GetMutex() noexcept; 49 | }; 50 | 51 | } // namespace io -------------------------------------------------------------------------------- /src/kernel/io/disk/file/dir.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/io/disk/file/dir.h" 2 | #include "kernel/io/disk/disk.h" 3 | #include "kernel/io/disk/file/inode.h" 4 | #include "kernel/memory/pool.h" 5 | 6 | namespace io::fs { 7 | 8 | void Directory::Close() noexcept { 9 | if (this != &GetRootDir()) { 10 | dbg::Assert(IsOpen()); 11 | inode->Close(); 12 | mem::Free(this); 13 | } 14 | } 15 | 16 | void Directory::Rewind() noexcept { 17 | pos = 0; 18 | } 19 | 20 | bool Directory::IsEmpty() const noexcept { 21 | dbg::Assert(IsOpen()); 22 | return GetNode().size == min_entry_count * sizeof(DirEntry); 23 | } 24 | 25 | bool Directory::IsOpen() const noexcept { 26 | return inode != nullptr; 27 | } 28 | 29 | IdxNode& Directory::GetNode() const noexcept { 30 | dbg::Assert(IsOpen()); 31 | return *inode; 32 | } 33 | 34 | stl::size_t Directory::GetNodeIdx() const noexcept { 35 | return GetNode().idx; 36 | } 37 | 38 | DirEntry& DirEntry::SetName(const stl::string_view name) noexcept { 39 | dbg::Assert(!name.empty()); 40 | stl::strcpy_s(this->name.data(), this->name.max_size(), name.data()); 41 | return *this; 42 | } 43 | 44 | DirEntry::DirEntry(const FileType type, const stl::string_view name, 45 | const stl::size_t inode_idx) noexcept : 46 | type {type}, inode_idx {inode_idx} { 47 | SetName(name); 48 | } 49 | 50 | } // namespace io::fs -------------------------------------------------------------------------------- /include/kernel/stl/source_location.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kernel/stl/cstdint.h" 4 | 5 | namespace stl { 6 | 7 | class source_location { 8 | public: 9 | static constexpr source_location current(const char* const file = __builtin_FILE(), 10 | const char* const func = __builtin_FUNCTION(), 11 | const uint_least32_t line = __builtin_LINE(), 12 | const uint_least32_t col = 0) noexcept { 13 | return {file, func, line, col}; 14 | } 15 | 16 | constexpr const char* file_name() const noexcept { 17 | return file_; 18 | } 19 | 20 | constexpr const char* function_name() const noexcept { 21 | return func_; 22 | } 23 | 24 | constexpr uint_least32_t line() const noexcept { 25 | return line_; 26 | } 27 | 28 | constexpr uint_least32_t column() const noexcept { 29 | return col_; 30 | } 31 | 32 | private: 33 | constexpr source_location(const char* const file, const char* const func, 34 | const uint_least32_t line, const uint_least32_t col) noexcept : 35 | file_ {file}, func_ {func}, line_ {line}, col_ {col} {} 36 | 37 | const char* file_; 38 | const char* func_; 39 | const uint_least32_t line_; 40 | const uint_least32_t col_; 41 | }; 42 | 43 | } // namespace stl -------------------------------------------------------------------------------- /include/kernel/util/metric.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file metric.h 3 | * @brief Metrics. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/stl/cstdint.h" 12 | 13 | //! The value of an invalid index. 14 | inline constexpr stl::size_t npos {static_cast(-1)}; 15 | 16 | //! Convert kilobytes to bytes. 17 | constexpr stl::size_t KB(const stl::size_t size) noexcept { 18 | return size * 1024; 19 | } 20 | 21 | //! Convert megabytes to bytes. 22 | constexpr stl::size_t MB(const stl::size_t size) noexcept { 23 | return size * KB(1024); 24 | } 25 | 26 | //! Convert gigabytes to bytes. 27 | constexpr stl::size_t GB(const stl::size_t size) noexcept { 28 | return size * MB(2014); 29 | } 30 | 31 | //! Convert seconds to milliseconds. 32 | constexpr stl::size_t SecondsToMilliseconds(const stl::size_t seconds) noexcept { 33 | return seconds * 1000; 34 | } 35 | 36 | template 37 | constexpr T RoundUpDivide(const T dividend, const T divisor) noexcept { 38 | return (dividend + divisor - 1) / divisor; 39 | } 40 | 41 | //! Align a value backward. 42 | template 43 | constexpr T BackwardAlign(const T val, const T align) noexcept { 44 | return val - val % align; 45 | } 46 | 47 | //! Align a value forward. 48 | template 49 | constexpr T ForwardAlign(const T val, const T align) noexcept { 50 | return RoundUpDivide(val, align) * align; 51 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # #################################################################### 2 | # For C 3 | # #################################################################### 4 | # Prerequisites 5 | *.d 6 | 7 | # Object files 8 | *.o 9 | *.ko 10 | *.obj 11 | *.elf 12 | *.bin 13 | 14 | # Linker output 15 | *.ilk 16 | *.map 17 | *.exp 18 | 19 | # Precompiled Headers 20 | *.gch 21 | *.pch 22 | 23 | # Libraries 24 | *.lib 25 | *.a 26 | *.la 27 | *.lo 28 | 29 | # Shared objects (inc. Windows DLLs) 30 | *.dll 31 | *.so 32 | *.so.* 33 | *.dylib 34 | 35 | # Executables 36 | *.exe 37 | *.out 38 | *.app 39 | *.i*86 40 | *.x86_64 41 | *.hex 42 | 43 | # Debug files 44 | *.dSYM/ 45 | *.su 46 | *.idb 47 | *.pdb 48 | 49 | # Kernel Module Compile Results 50 | *.mod* 51 | *.cmd 52 | .tmp_versions/ 53 | modules.order 54 | Module.symvers 55 | Mkfile.old 56 | dkms.conf 57 | 58 | 59 | # #################################################################### 60 | # For CMake 61 | # #################################################################### 62 | CMakeLists.txt.user 63 | CMakeCache.txt 64 | CMakeFiles 65 | CMakeScripts 66 | Testing 67 | cmake_install.cmake 68 | install_manifest.txt 69 | compile_commands.json 70 | CTestTestfile.cmake 71 | _deps 72 | build 73 | 74 | 75 | # #################################################################### 76 | # For Visual Studio Code 77 | # #################################################################### 78 | .vscode/* 79 | *.code-workspace -------------------------------------------------------------------------------- /docs/Kernel/File System.md: -------------------------------------------------------------------------------- 1 | # File System 2 | 3 | ## Index Nodes 4 | 5 | In *Linux*, *Index Nodes* describe file system objects such as files or directories. Each index node stores the attributes and disk block locations of the object's data. In our system, an index node `io::fs::IdxNode` has 12 direct blocks (a block is a disk sector) and a single indirect block table, which is one sector large and contains 128 block LBAs. So an index node can save up to 6 | 7 | $$ 8 | (12 + \frac{512}{4}) \times 512 = 71680 9 | $$ 10 | 11 | bytes. 12 | 13 | ![index-node](Images/file-system/index-node.svg) 14 | 15 | ## Directory Entries 16 | 17 | Index nodes do not indicate their data type. Instead, we use directory entries `io::fs::DirEntry` to determine whether an item is a file or a directory. 18 | 19 | ![directory-entries](Images/file-system/directory-entries.svg) 20 | 21 | ## Super Block 22 | 23 | The *Super Block* is the "configuration" of a file system. It is created when the file system is created for a disk partition. Its size is 4096 bytes and starts at offset `4096` bytes in a partition, behind the boot sector. The following diagram shows a disk's partitions. 24 | 25 | ```mermaid 26 | block-beta 27 | block 28 | MBR part1["Partition"] ... part2["Partition"] 29 | end 30 | ``` 31 | 32 | In our system, the internal structure of a partition is: 33 | 34 | ```mermaid 35 | block-beta 36 | block 37 | boot["Boot Sector"] super_block["Super Block"] block_bitmap["Block Bitmap"] inode_bitmap["Index Node Bitmap"] inodes["Index Nodes"] root_dir["Root Directory"] Blocks 38 | end 39 | ``` -------------------------------------------------------------------------------- /include/kernel/stl/cstring.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kernel/stl/cerron.h" 4 | #include "kernel/stl/cstdint.h" 5 | 6 | namespace stl { 7 | 8 | constexpr size_t strlen(const char* const str) noexcept { 9 | size_t len {0}; 10 | if (str) { 11 | while (str[len] != '\0') { 12 | ++len; 13 | } 14 | } 15 | return len; 16 | } 17 | 18 | char* strcpy(char* dest, const char* src) noexcept; 19 | 20 | errno_t strcpy_s(char* dest, size_t dest_size, const char* src) noexcept; 21 | 22 | int strcmp(const char* lhs, const char* rhs) noexcept; 23 | 24 | char* strcat(char* dest, const char* src) noexcept; 25 | 26 | constexpr char* strrchr(const char* str, const char ch) noexcept { 27 | if (!str) { 28 | return nullptr; 29 | } 30 | 31 | const char* last {nullptr}; 32 | while (*str != '\0') { 33 | if (*str == ch) { 34 | last = str; 35 | } 36 | ++str; 37 | } 38 | 39 | return const_cast(last); 40 | } 41 | 42 | constexpr char* strchr(const char* str, const char ch) noexcept { 43 | if (!str) { 44 | return nullptr; 45 | } 46 | 47 | while (*str != '\0') { 48 | if (*str == ch) { 49 | return const_cast(str); 50 | } else { 51 | ++str; 52 | } 53 | } 54 | 55 | return nullptr; 56 | } 57 | 58 | void memset(void* addr, byte val, size_t size) noexcept; 59 | 60 | void memcpy(void* dest, const void* src, size_t size) noexcept; 61 | 62 | int memcmp(const void* lhs, const void* rhs, size_t size) noexcept; 63 | 64 | } // namespace stl -------------------------------------------------------------------------------- /src/kernel/io/video/print.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/io/video/print.h" 2 | #include "kernel/debug/assert.h" 3 | 4 | namespace io { 5 | 6 | void PrintlnStr(const stl::string_view str) noexcept { 7 | PrintStr(str); 8 | PrintChar('\n'); 9 | } 10 | 11 | void PrintStr(const stl::string_view str) noexcept { 12 | if (!str.empty()) { 13 | PrintStr(str.data()); 14 | } 15 | } 16 | 17 | void PrintlnHex(const stl::int32_t num) noexcept { 18 | PrintHex(num); 19 | PrintChar('\n'); 20 | } 21 | 22 | void PrintHex(const stl::int32_t num) noexcept { 23 | if (num >= 0) { 24 | PrintHex(static_cast(num)); 25 | } else { 26 | PrintChar('-'); 27 | PrintHex(static_cast(-num)); 28 | } 29 | } 30 | 31 | void PrintlnChar(const char ch) noexcept { 32 | PrintChar(ch); 33 | PrintChar('\n'); 34 | } 35 | 36 | void PrintlnHex(const stl::uint32_t num) noexcept { 37 | PrintHex(num); 38 | PrintChar('\n'); 39 | } 40 | 41 | namespace _printf_impl { 42 | 43 | void Print(const stl::uint32_t num) noexcept { 44 | PrintHex(num); 45 | } 46 | 47 | void Print(const stl::int32_t num) noexcept { 48 | PrintHex(num); 49 | } 50 | 51 | void Print(const char ch) noexcept { 52 | PrintChar(ch); 53 | } 54 | 55 | void Print(const stl::string_view str) noexcept { 56 | PrintStr(str); 57 | } 58 | 59 | void Print(const char* const str) noexcept { 60 | dbg::Assert(str); 61 | PrintStr(str); 62 | } 63 | 64 | void Printf(const stl::string_view format) noexcept { 65 | PrintStr(format); 66 | } 67 | 68 | } // namespace _printf_impl 69 | 70 | } // namespace io -------------------------------------------------------------------------------- /src/kernel/io/disk/file/inode.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/io/disk/file/inode.h" 2 | #include "kernel/memory/pool.h" 3 | 4 | namespace io::fs { 5 | 6 | IdxNode::IdxNode() noexcept { 7 | Init(); 8 | } 9 | 10 | IdxNode& IdxNode::Init() noexcept { 11 | tag = {}; 12 | idx = npos; 13 | size = 0; 14 | open_times = 0; 15 | write_deny = false; 16 | return *this; 17 | } 18 | 19 | void IdxNode::Close() noexcept { 20 | const intr::IntrGuard guard; 21 | if (--open_times == 0) { 22 | tag.Detach(); 23 | mem::Free(mem::PoolType::Kernel, this); 24 | } 25 | } 26 | 27 | IdxNode& IdxNode::GetByTag(const TagList::Tag& tag) noexcept { 28 | return tag.GetElem(); 29 | } 30 | 31 | void IdxNode::CloneToPure(IdxNode& inode) const noexcept { 32 | stl::memcpy(&inode, this, sizeof(IdxNode)); 33 | inode.open_times = 0; 34 | inode.write_deny = false; 35 | inode.tag = {}; 36 | } 37 | 38 | bool IdxNode::IsOpen() const noexcept { 39 | return open_times != 0; 40 | } 41 | 42 | stl::size_t IdxNode::GetIndirectTabLba() const noexcept { 43 | return indirect_tab_lba_; 44 | } 45 | 46 | IdxNode& IdxNode::SetIndirectTabLba(const stl::size_t lba) noexcept { 47 | indirect_tab_lba_ = lba; 48 | return *this; 49 | } 50 | 51 | IdxNode& IdxNode::SetDirectLba(const stl::size_t idx, const stl::size_t lba) noexcept { 52 | dbg::Assert(idx < direct_block_count); 53 | direct_lbas_[idx] = lba; 54 | return *this; 55 | } 56 | 57 | stl::size_t IdxNode::GetDirectLba(const stl::size_t idx) const noexcept { 58 | dbg::Assert(idx < direct_block_count); 59 | return direct_lbas_[idx]; 60 | } 61 | 62 | } // namespace io::fs -------------------------------------------------------------------------------- /src/kernel/io/video/console.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/io/video/console.h" 2 | #include "kernel/io/keyboard.h" 3 | 4 | namespace io { 5 | 6 | stl::mutex& Console::GetMutex() noexcept { 7 | static stl::mutex mtx; 8 | return mtx; 9 | } 10 | 11 | void Console::Read(char* const buf, const stl::size_t count) noexcept { 12 | auto& keyboard {GetKeyboardBuffer()}; 13 | for (stl::size_t i {0}; i != count; ++i) { 14 | buf[i] = keyboard.Pop(); 15 | } 16 | } 17 | 18 | void Console::PrintlnStr(const stl::string_view str) noexcept { 19 | const stl::lock_guard guard {GetMutex()}; 20 | io::PrintlnStr(str); 21 | } 22 | 23 | void Console::PrintStr(const stl::string_view str) noexcept { 24 | const stl::lock_guard guard {GetMutex()}; 25 | io::PrintStr(str); 26 | } 27 | 28 | void Console::PrintlnChar(const char ch) noexcept { 29 | const stl::lock_guard guard {GetMutex()}; 30 | io::PrintlnChar(ch); 31 | } 32 | 33 | void Console::PrintlnHex(const stl::uint32_t num) noexcept { 34 | const stl::lock_guard guard {GetMutex()}; 35 | io::PrintlnHex(num); 36 | } 37 | 38 | void Console::PrintlnHex(const stl::int32_t num) noexcept { 39 | const stl::lock_guard guard {GetMutex()}; 40 | io::PrintlnHex(num); 41 | } 42 | 43 | void Console::PrintChar(const char ch) noexcept { 44 | const stl::lock_guard guard {GetMutex()}; 45 | io::PrintChar(ch); 46 | } 47 | 48 | void Console::PrintStr(const char* const str) noexcept { 49 | const stl::lock_guard guard {GetMutex()}; 50 | io::PrintStr(str); 51 | } 52 | 53 | void Console::PrintHex(const stl::uint32_t num) noexcept { 54 | const stl::lock_guard guard {GetMutex()}; 55 | io::PrintHex(num); 56 | } 57 | 58 | void Console::PrintHex(const stl::int32_t num) noexcept { 59 | const stl::lock_guard guard {GetMutex()}; 60 | io::PrintHex(num); 61 | } 62 | 63 | } // namespace io -------------------------------------------------------------------------------- /src/kernel/process/tss.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/process/tss.h" 2 | #include "kernel/descriptor/gdt/tab.h" 3 | #include "kernel/io/video/print.h" 4 | #include "kernel/selector/sel.h" 5 | 6 | namespace tsk { 7 | 8 | namespace { 9 | extern "C" { 10 | 11 | /** 12 | * @brief Set the task register. 13 | * 14 | * @param sel A selector to a task state segment. 15 | */ 16 | void SetTaskReg(stl::uint16_t sel) noexcept; 17 | } 18 | 19 | } // namespace 20 | 21 | TaskStateSeg& GetTaskStateSeg() noexcept { 22 | static TaskStateSeg tss {.ss0 = sel::krnl_stack, .io_base = sizeof(TaskStateSeg)}; 23 | return tss; 24 | } 25 | 26 | TaskStateSeg& TaskStateSeg::Update(const Thread& thd) noexcept { 27 | esp0 = thd.GetKrnlStackBottom(); 28 | return *this; 29 | } 30 | 31 | void InitTaskStateSeg() noexcept { 32 | dbg::Assert(IsThreadInited()); 33 | auto gdt {gdt::GetGlobalDescTab()}; 34 | 35 | // Create a kernel descriptor for the task state segment. 36 | const auto& tss {GetTaskStateSeg()}; 37 | dbg::Assert(gdt[gdt::idx::tss].IsInvalid()); 38 | gdt[gdt::idx::tss] = {reinterpret_cast(&tss), 39 | sizeof(tss) - 1, 40 | {desc::SysType::Tss32, Privilege::Zero}}; 41 | 42 | // Create user descriptors for code and data. 43 | dbg::Assert(gdt[gdt::idx::usr_code].IsInvalid()); 44 | gdt[gdt::idx::usr_code] = static_cast( 45 | desc::Descriptor {gdt[gdt::idx::krnl_code]}.SetDpl(Privilege::Three)); 46 | 47 | dbg::Assert(gdt[gdt::idx::usr_data].IsInvalid()); 48 | gdt[gdt::idx::usr_data] = static_cast( 49 | desc::Descriptor {gdt[gdt::idx::krnl_data]}.SetDpl(Privilege::Three)); 50 | 51 | // Load the task state segment. 52 | SetTaskReg(sel::tss); 53 | io::PrintlnStr("The task state segment has been initialized."); 54 | } 55 | 56 | } // namespace tsk -------------------------------------------------------------------------------- /src/user/io/file/file.cpp: -------------------------------------------------------------------------------- 1 | #include "user/io/file/file.h" 2 | #include "user/syscall/call.h" 3 | 4 | namespace usr::io { 5 | 6 | stl::size_t File::Open(const char* const path, const stl::uint32_t flags) noexcept { 7 | struct Args { 8 | const char* path; 9 | stl::uint32_t flags; 10 | }; 11 | 12 | Args args {path, flags}; 13 | return sc::SysCall(sc::SysCallType::OpenFile, reinterpret_cast(&args)); 14 | } 15 | 16 | void File::Close(const stl::size_t desc) noexcept { 17 | sc::SysCall(sc::SysCallType::CloseFile, reinterpret_cast(desc)); 18 | } 19 | 20 | bool File::Delete(const char* const path) noexcept { 21 | return sc::SysCall(sc::SysCallType::DeleteFile, const_cast(path)); 22 | } 23 | 24 | stl::size_t File::Write(const stl::size_t desc, const void* const data, 25 | const stl::size_t size) noexcept { 26 | struct Args { 27 | stl::size_t desc; 28 | const void* data; 29 | stl::size_t size; 30 | }; 31 | 32 | Args args {desc, data, size}; 33 | return sc::SysCall(sc::SysCallType::WriteFile, reinterpret_cast(&args)); 34 | } 35 | 36 | stl::size_t File::Seek(const stl::size_t desc, const stl::int32_t offset, 37 | const SeekOrigin origin) noexcept { 38 | struct Args { 39 | stl::size_t desc; 40 | stl::int32_t offset; 41 | SeekOrigin origin; 42 | }; 43 | 44 | Args args {desc, offset, origin}; 45 | return sc::SysCall(sc::SysCallType::SeekFile, reinterpret_cast(&args)); 46 | } 47 | 48 | stl::size_t File::Read(const stl::size_t desc, void* const buf, const stl::size_t size) noexcept { 49 | struct Args { 50 | stl::size_t desc; 51 | void* buf; 52 | stl::size_t size; 53 | }; 54 | 55 | Args args {desc, buf, size}; 56 | return sc::SysCall(sc::SysCallType::ReadFile, reinterpret_cast(&args)); 57 | } 58 | 59 | } // namespace usr::io -------------------------------------------------------------------------------- /include/kernel/util/bitmap.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file bitmap.h 3 | * @brief The bitmap. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/stl/cstdint.h" 12 | 13 | class Bitmap { 14 | public: 15 | Bitmap() noexcept = default; 16 | 17 | /** 18 | * @brief Create a bitmap. 19 | * 20 | * @param bits A bit buffer. 21 | * @param byte_len The length of the buffer in bytes. 22 | * @param clear Whether to clear all bits in the buffer. 23 | */ 24 | Bitmap(void* bits, stl::size_t byte_len, bool clear = true) noexcept; 25 | 26 | Bitmap(Bitmap&&) noexcept; 27 | 28 | Bitmap& operator=(Bitmap&&) noexcept; 29 | 30 | void swap(Bitmap&) noexcept; 31 | 32 | Bitmap& Init(void* bits, stl::size_t byte_len, bool clear = true) noexcept; 33 | 34 | /** 35 | * @brief Try to allocate the specified number of bits. 36 | * 37 | * @param count The number of bits to be allocated. 38 | * @return 39 | * The beginning index of the allocated bits if it succeeds. 40 | * Otherwise, @p npos. 41 | */ 42 | stl::size_t Alloc(stl::size_t count = 1) noexcept; 43 | 44 | //! Forcefully mark the specified bits as allocated. 45 | Bitmap& ForceAlloc(stl::size_t begin, stl::size_t count = 1) noexcept; 46 | 47 | Bitmap& Free(stl::size_t begin, stl::size_t count = 1) noexcept; 48 | 49 | stl::size_t GetCapacity() const noexcept; 50 | 51 | stl::size_t GetByteLen() const noexcept; 52 | 53 | Bitmap& Clear() noexcept; 54 | 55 | const void* GetBits() const noexcept; 56 | 57 | bool IsAlloc(stl::size_t idx) const noexcept; 58 | 59 | private: 60 | Bitmap& SetBit(stl::size_t idx, bool val) noexcept; 61 | 62 | Bitmap& Set(stl::size_t begin, stl::size_t count) noexcept; 63 | 64 | Bitmap& Reset(stl::size_t begin, stl::size_t count) noexcept; 65 | 66 | stl::size_t byte_len_ {0}; 67 | stl::uint8_t* bits_ {nullptr}; 68 | }; 69 | 70 | void swap(Bitmap&, Bitmap&) noexcept; -------------------------------------------------------------------------------- /src/kernel/util/format.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/util/format.h" 2 | 3 | stl::size_t ConvertUIntToString(char* const buf, const stl::uint32_t num, 4 | const stl::size_t base) noexcept { 5 | dbg::Assert(buf); 6 | const auto digit {num % base}; 7 | const auto remain {num / base}; 8 | stl::size_t len {0}; 9 | if (remain > 0) { 10 | len = ConvertUIntToString(buf, remain, base); 11 | } 12 | 13 | buf[len] = digit < 10 ? digit + '0' : digit - 10 + 'A'; 14 | buf[len + 1] = '\0'; 15 | return len + 1; 16 | } 17 | 18 | stl::size_t ConvertIntToString(char* const buf, const stl::int32_t num, 19 | const stl::size_t base) noexcept { 20 | dbg::Assert(buf); 21 | if (num < 0) { 22 | buf[0] = '-'; 23 | return ConvertUIntToString(buf + 1, -num, base) + 1; 24 | } else { 25 | return ConvertUIntToString(buf, num, base); 26 | } 27 | } 28 | 29 | namespace _format_string_buffer_impl { 30 | 31 | stl::size_t Format(char* const buf, const stl::uint32_t num) noexcept { 32 | dbg::Assert(buf); 33 | return ConvertUIntToString(buf, num, 10); 34 | } 35 | 36 | stl::size_t Format(char* const buf, const stl::int32_t num) noexcept { 37 | dbg::Assert(buf); 38 | return ConvertIntToString(buf, num, 10); 39 | } 40 | 41 | stl::size_t Format(char* const buf, const char ch) noexcept { 42 | dbg::Assert(buf); 43 | buf[0] = ch; 44 | return 1; 45 | } 46 | 47 | stl::size_t Format(char* const buf, const char* const str) noexcept { 48 | dbg::Assert(buf && str); 49 | stl::strcpy(buf, str); 50 | return stl::strlen(str); 51 | } 52 | 53 | stl::size_t Format(char* const buf, const stl::string_view str) noexcept { 54 | dbg::Assert(buf && !str.empty()); 55 | stl::strcpy(buf, str.data()); 56 | return str.size(); 57 | } 58 | 59 | stl::size_t FormatStringBuffer(char* const buf, const stl::string_view format) noexcept { 60 | dbg::Assert(buf); 61 | return !format.empty() ? Format(buf, format) : 0; 62 | } 63 | 64 | } // namespace _format_string_buffer_impl -------------------------------------------------------------------------------- /src/kernel/thread/thd.asm: -------------------------------------------------------------------------------- 1 | %include "kernel/util/metric.inc" 2 | 3 | ; This structure must be the same as the beginning of `tsk::Thread`. 4 | struc Thread 5 | .tags: resq 2 6 | .krnl_stack: resd 1 7 | ; ... 8 | endstruc 9 | 10 | [bits 32] 11 | section .text 12 | global HaltCpu 13 | ; Halt the CPU until the next external interrupt is fired. 14 | HaltCpu: 15 | hlt 16 | ret 17 | 18 | global GetCurrThread 19 | ; Get the current running thread. 20 | ; The size of a thread block is one memory page. 21 | ; The stack is located in the thread block. So the page address of `ESP` is the thread address. 22 | ; 23 | ; Note that the compiler might allocate more memory for the control block in a thread, especially with debug options. 24 | ; In that case, the size of a thread block is larger than one memory page, 25 | ; and this method no longer works. 26 | GetCurrThread: 27 | mov eax, esp 28 | and eax, 0xFFFFF000 29 | ret 30 | 31 | global SwitchThread 32 | ; Switch the running thread. 33 | ; ```c++ 34 | ; void SwitchThread(Thread& from, Thread& to) noexcept; 35 | ; ``` 36 | SwitchThread: 37 | ; The stack top is the return address of the current thread. 38 | ; When it is scheduled next time, it will continue to run from that address. 39 | 40 | ; Save the remaining registers as the structure `tsk::Thread::SwitchStack`. 41 | push esi 42 | push edi 43 | push ebx 44 | push ebp 45 | ; Get the current thread from the first argument. 46 | mov eax, [esp + B(20)] 47 | ; Save the stack address to the current thread block. 48 | mov [eax + Thread.krnl_stack], esp 49 | 50 | ; Get the new thread from the second argument. 51 | mov eax, [esp + B(24)] 52 | ; Get the new stack address from the new thread block. 53 | mov esp, [eax + Thread.krnl_stack] 54 | ; Restore the registers of the new thread. 55 | pop ebp 56 | pop ebx 57 | pop edi 58 | pop esi 59 | ; Pop the return address of the new thread and continue to run it. 60 | ret -------------------------------------------------------------------------------- /src/kernel/memory/page.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/memory/page.h" 2 | #include "kernel/debug/assert.h" 3 | #include "kernel/memory/pool.h" 4 | #include "kernel/stl/cstring.h" 5 | 6 | namespace mem { 7 | 8 | namespace { 9 | 10 | extern "C" { 11 | //! Invalidate a Translation Lookaside Buffer (TLB) entry. 12 | void DisableTlbEntry(stl::uintptr_t vr_addr) noexcept; 13 | } 14 | 15 | } // namespace 16 | 17 | const VrAddr& VrAddr::MapToPhyAddr(const stl::uintptr_t phy_addr) const noexcept { 18 | auto& page_dir_entry {GetPageDirEntry()}; 19 | auto& page_tab_entry {GetPageTabEntry()}; 20 | if (!page_dir_entry.IsPresent()) { 21 | // Allocate a new page for the page table. 22 | const auto page_tab_phy_base {GetPhyMemPagePool(PoolType::Kernel).AllocPages()}; 23 | AssertAlloc(page_tab_phy_base); 24 | // Make the page directory entry point to the new page table. 25 | page_dir_entry.SetAddress(page_tab_phy_base) 26 | .SetSupervisor(false) 27 | .SetWritable() 28 | .SetPresent(); 29 | // Clear the new page table. 30 | const auto page_tab_base {VrAddr {&page_tab_entry}.GetPageAddr()}; 31 | stl::memset(reinterpret_cast(page_tab_base), 0, page_size); 32 | } 33 | 34 | dbg::Assert(!page_tab_entry.IsPresent()); 35 | page_tab_entry.SetAddress(phy_addr).SetSupervisor(false).SetWritable().SetPresent(); 36 | return *this; 37 | } 38 | 39 | VrAddr& VrAddr::MapToPhyAddr(const stl::uintptr_t phy_addr) noexcept { 40 | return const_cast(const_cast(*this).MapToPhyAddr(phy_addr)); 41 | } 42 | 43 | VrAddr& VrAddr::Unmap() noexcept { 44 | return const_cast(const_cast(*this).Unmap()); 45 | } 46 | 47 | const VrAddr& VrAddr::Unmap() const noexcept { 48 | if (GetPageDirEntry().IsPresent()) { 49 | GetPageTabEntry().SetPresent(false); 50 | DisableTlbEntry(addr_); 51 | } 52 | 53 | return *this; 54 | } 55 | 56 | stl::uintptr_t VrAddr::GetPhyAddr() const noexcept { 57 | return GetPageTabEntry().GetAddress() + GetOffset(); 58 | } 59 | 60 | } // namespace mem -------------------------------------------------------------------------------- /include/kernel/io/io.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file io.h 3 | * @brief Port I/O and register control. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/stl/cstdint.h" 12 | #include "kernel/util/bit.h" 13 | 14 | namespace io { 15 | 16 | //! The @p EFLAGS register. 17 | class EFlags { 18 | public: 19 | static EFlags Get() noexcept; 20 | 21 | static void Set(EFlags) noexcept; 22 | 23 | constexpr EFlags(const stl::uint32_t val = 0) noexcept : val_ {val} { 24 | SetMbs(); 25 | } 26 | 27 | constexpr operator stl::uint32_t() const noexcept { 28 | return val_; 29 | } 30 | 31 | constexpr EFlags& Clear() noexcept { 32 | val_ = 0; 33 | return SetMbs(); 34 | } 35 | 36 | constexpr bool If() const noexcept { 37 | return bit::IsBitSet(val_, if_pos); 38 | } 39 | 40 | constexpr EFlags& SetIf() noexcept { 41 | bit::SetBit(val_, if_pos); 42 | return *this; 43 | } 44 | 45 | constexpr EFlags& ResetIf() noexcept { 46 | bit::ResetBit(val_, if_pos); 47 | return *this; 48 | } 49 | 50 | private: 51 | static constexpr stl::size_t if_pos {9}; 52 | 53 | constexpr EFlags& SetMbs() noexcept { 54 | bit::SetBit(val_, 1); 55 | return *this; 56 | } 57 | 58 | stl::uint32_t val_; 59 | }; 60 | 61 | static_assert(sizeof(EFlags) == sizeof(stl::uint32_t)); 62 | 63 | extern "C" { 64 | 65 | //! Get the value of @p CR2. 66 | stl::uint32_t GetCr2() noexcept; 67 | 68 | //! Set the value of @p CR3. 69 | void SetCr3(stl::uint32_t) noexcept; 70 | 71 | //! Write a byte to a port. 72 | void WriteByteToPort(stl::uint16_t port, stl::byte data) noexcept; 73 | 74 | //! Write a number of words to a port. 75 | void WriteWordsToPort(stl::uint16_t port, const void* data, stl::size_t count = 1) noexcept; 76 | 77 | //! Read a byte from a port. 78 | stl::byte ReadByteFromPort(stl::uint16_t port) noexcept; 79 | 80 | //! Read a number of words from a port. 81 | void ReadWordsFromPort(stl::uint16_t port, void* buf, stl::size_t count = 1) noexcept; 82 | } 83 | 84 | } // namespace io -------------------------------------------------------------------------------- /include/kernel/util/tag_list.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file tag_list.h 3 | * @brief The tag list. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/stl/cstdint.h" 12 | 13 | /** 14 | * @brief 15 | * The doubly linked tag list. 16 | * 17 | * @details 18 | * It connects a number of tags. Each tag is a member of an object. 19 | * 20 | * @code 21 | * Object Object 22 | * Head ┌───────┐ ┌───────┐ Tail 23 | * ┌───┐ ───► │ ┌───┐ │ ───► │ ┌───┐ │ ───► ┌───┐ 24 | * │Tag│ │ │Tag│ │ │ │Tag│ │ │Tag│ 25 | * └───┘ ◄─── │ └───┘ │ ◄─── │ └───┘ │ ◄─── └───┘ 26 | * └───────┘ └───────┘ 27 | * @endcode 28 | */ 29 | class TagList { 30 | public: 31 | struct Tag { 32 | Tag() noexcept = default; 33 | 34 | Tag(const Tag&) = delete; 35 | 36 | /** 37 | * @brief Get the object containing the tag. 38 | * 39 | * @tparam T The object type. 40 | * @tparam offset The tag offset from the beginning of the object. 41 | */ 42 | template 43 | T& GetElem() const noexcept { 44 | return const_cast( 45 | *reinterpret_cast(reinterpret_cast(this) - offset)); 46 | } 47 | 48 | //! Detach the tag from the list. 49 | void Detach() noexcept; 50 | 51 | Tag* prev {nullptr}; 52 | Tag* next {nullptr}; 53 | }; 54 | 55 | using Visitor = bool (*)(const Tag&, void*) noexcept; 56 | 57 | static void InsertBefore(Tag& before, Tag& tag) noexcept; 58 | 59 | TagList() noexcept; 60 | 61 | TagList(const TagList&) = delete; 62 | 63 | TagList& Init() noexcept; 64 | 65 | TagList& PushFront(Tag&) noexcept; 66 | 67 | TagList& PushBack(Tag&) noexcept; 68 | 69 | Tag& Pop() noexcept; 70 | 71 | bool Find(const Tag&) const noexcept; 72 | 73 | Tag* Find(Visitor, void* arg = nullptr) const noexcept; 74 | 75 | stl::size_t GetSize() const noexcept; 76 | 77 | bool IsEmpty() const noexcept; 78 | 79 | private: 80 | Tag head_; 81 | Tag tail_; 82 | }; -------------------------------------------------------------------------------- /src/kernel/util/tag_list.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/util/tag_list.h" 2 | #include "kernel/debug/assert.h" 3 | #include "kernel/interrupt/intr.h" 4 | 5 | TagList::TagList() noexcept { 6 | Init(); 7 | } 8 | 9 | TagList& TagList::Init() noexcept { 10 | head_.next = &tail_; 11 | tail_.prev = &head_; 12 | return *this; 13 | } 14 | 15 | void TagList::InsertBefore(Tag& before, Tag& tag) noexcept { 16 | const intr::IntrGuard guard; 17 | before.prev->next = &tag; 18 | tag.prev = before.prev; 19 | tag.next = &before; 20 | before.prev = &tag; 21 | } 22 | 23 | TagList& TagList::PushFront(Tag& tag) noexcept { 24 | InsertBefore(*head_.next, tag); 25 | return *this; 26 | } 27 | 28 | TagList& TagList::PushBack(Tag& tag) noexcept { 29 | InsertBefore(tail_, tag); 30 | return *this; 31 | } 32 | 33 | void TagList::Tag::Detach() noexcept { 34 | dbg::Assert(prev && next); 35 | const intr::IntrGuard guard; 36 | prev->next = next; 37 | next->prev = prev; 38 | } 39 | 40 | TagList::Tag& TagList::Pop() noexcept { 41 | dbg::Assert(!IsEmpty()); 42 | const auto top {head_.next}; 43 | dbg::Assert(top); 44 | top->Detach(); 45 | return *top; 46 | } 47 | 48 | bool TagList::Find(const Tag& tag) const noexcept { 49 | auto curr {head_.next}; 50 | while (curr != &tail_) { 51 | if (curr == &tag) { 52 | return true; 53 | } else { 54 | curr = curr->next; 55 | } 56 | } 57 | 58 | return false; 59 | } 60 | 61 | TagList::Tag* TagList::Find(const Visitor visitor, void* const arg) const noexcept { 62 | dbg::Assert(visitor); 63 | auto curr {head_.next}; 64 | while (curr != &tail_) { 65 | if (visitor(*curr, arg)) { 66 | return curr; 67 | } else { 68 | curr = curr->next; 69 | } 70 | } 71 | 72 | return nullptr; 73 | } 74 | 75 | stl::size_t TagList::GetSize() const noexcept { 76 | stl::size_t len {0}; 77 | auto curr {head_.next}; 78 | while (curr != &tail_) { 79 | curr = curr->next; 80 | ++len; 81 | } 82 | 83 | return len; 84 | } 85 | 86 | bool TagList::IsEmpty() const noexcept { 87 | return head_.next == &tail_; 88 | } -------------------------------------------------------------------------------- /include/kernel/syscall/call.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file call.h 3 | * @brief System calls. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/debug/assert.h" 12 | 13 | namespace sc { 14 | 15 | //! The maximum number of supported system calls. 16 | inline constexpr stl::size_t count {0x60}; 17 | 18 | //! The system call handler. 19 | using Handler = stl::int32_t (*)(void*) noexcept; 20 | 21 | /** 22 | * @brief Types of system calls. 23 | * 24 | * @details 25 | * Each type corresponds to a kernel function. 26 | */ 27 | enum class SysCallType { 28 | GetCurrPid, 29 | PrintChar, 30 | PrintHex, 31 | PrintStr, 32 | MemAlloc, 33 | MemFree, 34 | OpenFile, 35 | CloseFile, 36 | WriteFile, 37 | ReadFile, 38 | SeekFile, 39 | DeleteFile, 40 | CreateDir, 41 | Fork 42 | }; 43 | 44 | /** 45 | * @brief The system call handler table. 46 | * 47 | * @details 48 | * It can be regarded as a manager for an array of function pointers. 49 | */ 50 | template 51 | class SysCallHandlerTab { 52 | static_assert(count > 0); 53 | 54 | public: 55 | constexpr SysCallHandlerTab(stl::uintptr_t (&handlers)[count]) noexcept : 56 | handlers_ {handlers} {} 57 | 58 | template 59 | SysCallHandlerTab& Register(const SysCallType func, const Handler handler) noexcept { 60 | return Register(static_cast(func), reinterpret_cast(handler)); 61 | } 62 | 63 | constexpr stl::size_t GetCount() const noexcept { 64 | return count; 65 | } 66 | 67 | constexpr const stl::uintptr_t* GetHandlers() const noexcept { 68 | return handlers_; 69 | } 70 | 71 | private: 72 | SysCallHandlerTab& Register(const stl::size_t idx, const stl::uintptr_t handler) noexcept { 73 | dbg::Assert(idx < count); 74 | handlers_[idx] = handler; 75 | return *this; 76 | } 77 | 78 | stl::uintptr_t* handlers_ {nullptr}; 79 | }; 80 | 81 | //! Get the system call handler table. 82 | SysCallHandlerTab& GetSysCallHandlerTab() noexcept; 83 | 84 | //! Initialize system calls. 85 | void InitSysCall() noexcept; 86 | 87 | } // namespace sc -------------------------------------------------------------------------------- /src/kernel/stl/cstring.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/stl/cstring.h" 2 | #include "kernel/debug/assert.h" 3 | 4 | namespace stl { 5 | 6 | char* strcpy(char* dest, const char* src) noexcept { 7 | dbg::Assert(dest && src); 8 | const auto begin {dest}; 9 | while ((*dest++ = *src++) != '\0') { 10 | } 11 | return begin; 12 | } 13 | 14 | errno_t strcpy_s(char* dest, const size_t dest_size, const char* src) noexcept { 15 | dbg::Assert(src); 16 | dbg::Assert(dest && dest_size > 0); 17 | const auto begin {dest}; 18 | while (static_cast(dest - begin) != dest_size && (*dest++ = *src++)) { 19 | } 20 | 21 | *(dest - 1) = '\0'; 22 | return 0; 23 | } 24 | 25 | int strcmp(const char* lhs, const char* rhs) noexcept { 26 | dbg::Assert(lhs && rhs); 27 | while (*lhs != '\0' && *lhs == *rhs) { 28 | ++lhs; 29 | ++rhs; 30 | } 31 | 32 | if (*lhs != *rhs) { 33 | return *lhs > *rhs ? 1 : -1; 34 | } else { 35 | return 0; 36 | } 37 | } 38 | 39 | void memset(void* const addr, const byte val, const size_t size) noexcept { 40 | dbg::Assert(addr); 41 | for (size_t i {0}; i != size; ++i) { 42 | *(static_cast(addr) + i) = val; 43 | } 44 | } 45 | 46 | void memcpy(void* const dest, const void* const src, const size_t size) noexcept { 47 | dbg::Assert(dest && src); 48 | for (size_t i {0}; i != size; ++i) { 49 | *(static_cast(dest) + i) = *(static_cast(src) + i); 50 | } 51 | } 52 | 53 | int memcmp(const void* const lhs, const void* const rhs, const size_t size) noexcept { 54 | dbg::Assert(lhs && rhs); 55 | for (size_t i {0}; i != size; ++i) { 56 | const auto v1 {*(static_cast(lhs) + i)}; 57 | const auto v2 {*(static_cast(rhs) + i)}; 58 | if (v1 != v2) { 59 | return v1 > v2 ? 1 : -1; 60 | } 61 | } 62 | 63 | return 0; 64 | } 65 | 66 | char* strcat(char* const dest, const char* src) noexcept { 67 | dbg::Assert(dest && src); 68 | char* str {dest}; 69 | while (*str != '\0') { 70 | ++str; 71 | } 72 | 73 | while ((*str++ = *src++) != '\0') { 74 | } 75 | 76 | return dest; 77 | } 78 | 79 | } // namespace stl -------------------------------------------------------------------------------- /include/kernel/memory/page.inc: -------------------------------------------------------------------------------- 1 | ; Memory paging. 2 | 3 | %include "kernel/util/metric.inc" 4 | 5 | ; The page table entry and page directory entry. 6 | ; They are a part of memory paging, which allows each process to see a full virtual address space, 7 | ; without actually requiring the full amount of physical memory to be available. 8 | ; Memory paging is based on memory segmentation, it translates the linear address obtained from segmentation into a physical address. 9 | ; In the page directory, each entry points to a page table. 10 | ; In the page table, each entry points to a 4 KB physical page frame. 11 | ; ``` 12 | ; 31-12 11-9 8 7 6 5 4 3 2 1 0 13 | ; ┌────────────┬─────┬───┬───┬───┬───┬─────┬─────┬─────┬───┬───┐ 14 | ; │ Base 31-12 │ AVL │ G │ 0 │ D │ A │ PCD │ PWT │ U/S │ W │ P │ 15 | ; └────────────┴─────┴───┴───┴───┴───┴─────┴─────┴─────┴───┴───┘ 16 | ; ▲ ▲ ▲ ▲ ▲ ▲ 17 | ; │ │ │ │ │ └─ 1: The page presents. 18 | ; │ │ │ │ └─ 1: The page is writable. 19 | ; │ │ │ └─ 1: The page is at user level. 20 | ; │ │ │ 0: The page is at supervisor level. 21 | ; │ │ └─ 1: The page has been accessed. 22 | ; │ └─ 1: The page is dirty (modified). 23 | ; └─ 1: The page is global. 24 | ; ``` 25 | ; The page presents. 26 | mem_page_p equ 1 27 | ; The page is read-only. 28 | mem_page_rw_r equ 0b00 29 | ; The page is writable. 30 | mem_page_rw_w equ 0b10 31 | ; The page is at supervisor level. 32 | mem_page_us_s equ 0b000 33 | ; The page is at user level. 34 | mem_page_us_u equ 0b100 35 | 36 | ; The size of a page in bytes. 37 | mem_page_size equ KB(4) 38 | ; The size of a page table entry or a page directory entry in bytes. 39 | mem_page_entry_size equ B(4) 40 | ; The number of page directory entries in the page directory table. 41 | page_dir_count equ mem_page_size / mem_page_entry_size 42 | ; The index of the page directory entry (the last one) used to refer to the page directory table itself. 43 | page_dir_self_ref equ page_dir_count - 1 -------------------------------------------------------------------------------- /include/kernel/io/disk/disk.inc: -------------------------------------------------------------------------------- 1 | ; Disk configurations. 2 | 3 | %include "kernel/util/metric.inc" 4 | 5 | ; The size of a sector in bytes. 6 | disk_sector_size equ B(512) 7 | ; The maximum number of sectors that can be manipulated per disk access. 8 | ; It should be 256, but we set it to 255 to simplify the code. 9 | max_disk_sector_count_per_access equ 255 10 | 11 | ; The register for the sector count. 12 | disk_sector_count_port equ 0x1F2 13 | ; The register for the low bits of the start LBA. 14 | disk_lba_low_port equ 0x1F3 15 | ; The register for the middle bits of the start LBA. 16 | disk_lba_mid_port equ 0x1F4 17 | ; The register for the high bits of the start LBA. 18 | disk_lba_high_port equ 0x1F5 19 | 20 | ; The device register: 21 | ; ``` 22 | ; 7 6 5 4 3-0 23 | ; ┌───┬─────┬───┬─────┬─────┐ 24 | ; │ 1 │ MOD │ 1 │ DEV │ LBA │ 25 | ; └───┴─────┴───┴─────┴─────┘ 26 | ; ▲ ▲ ▲ 27 | ; │ │ └─ The bits 24-27 of the LBA. 28 | ; │ └─ 0: The drive is the master. 29 | ; │ 1: The drive is the slave. 30 | ; └─ 0: The addressing mode is Cylinder-Head-Sector (CHS). 31 | ; 1: The addressing mode is Logical Block Addressing (LBA). 32 | ; ``` 33 | disk_device_port equ 0x1F6 34 | ; The disk is in LBA mode. 35 | disk_device_lba_mode equ 0b0100_0000 36 | 37 | ; The status register: 38 | ; ``` 39 | ; 7 6 5 4 3 2 1 0 40 | ; ┌─────┬──────┬─┬─┬─────┬─┬─┬─────┐ 41 | ; │ BSY │ DRDY │ │ │ DRQ │ │ │ ERR │ 42 | ; └─────┴──────┴─┴─┴─────┴─┴─┴─────┘ 43 | ; ▲ ▲ ▲ ▲ 44 | ; │ │ │ └─ 1: Error occurred. 45 | ; │ │ └─ 1: Data is ready. 46 | ; │ └─ 1: The drive is ready for a command. 47 | ; └─ 1: The drive is busy. 48 | ; ``` 49 | disk_status_port equ 0x1F7 50 | ; The disk is ready for reading or writing. 51 | disk_status_ready equ 0b0000_1000 52 | ; The disk is busy. 53 | disk_status_busy equ 0b1000_0000 54 | 55 | ; The command register. 56 | disk_cmd_port equ disk_status_port 57 | ; The data register. 58 | disk_data_port equ 0x1F0 59 | 60 | ; The identify command. 61 | disk_identify_cmd equ 0xEC 62 | ; The reading command. 63 | disk_read_cmd equ 0x20 64 | ; The writing command. 65 | disk_write_cmd equ 0x30 -------------------------------------------------------------------------------- /include/kernel/process/tss.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file tss.h 3 | * @brief The task state segment. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/thread/thd.h" 12 | 13 | namespace tsk { 14 | 15 | /** 16 | * @brief The task state segment. 17 | * 18 | * @details 19 | * To support multitasking, CPUs provide two native supports: local descriptor tables and task state segments. 20 | * They require that each task is equipped with a local descriptor table and a task state segment. 21 | * The local descriptor table saves data and code. 22 | * The task state segment saves context state and stack pointers for different privilege levels, etc. 23 | * Task switching is switching these two structures. 24 | * But this approach has low performance. 25 | * 26 | * @em Linux only creates one task state segment for each CPU and all tasks on each CPU share the same task state segment. 27 | * When switching tasks, it only needs to update @p ss0 and @p esp0 in the task state segment to the segment address and pointer of the new task's kernel stack. 28 | * For example, when an interrupt occurs in user mode, the CPU gets the kernel stack from @p ss0 and @p esp0 for interrupt handling. 29 | * The original task state in user mode are manually saved in the kernel stack using the @p push instruction. 30 | */ 31 | struct TaskStateSeg { 32 | stl::uint32_t backlink; 33 | stl::uint32_t esp0; 34 | stl::uint32_t ss0; 35 | stl::uint32_t esp1; 36 | stl::uint32_t ss1; 37 | stl::uint32_t esp2; 38 | stl::uint32_t ss2; 39 | stl::uint32_t cr3; 40 | stl::uint32_t eip; 41 | stl::uint32_t eflags; 42 | stl::uint32_t eax; 43 | stl::uint32_t ecx; 44 | stl::uint32_t edx; 45 | stl::uint32_t ebx; 46 | stl::uint32_t esp; 47 | stl::uint32_t ebp; 48 | stl::uint32_t esi; 49 | stl::uint32_t edi; 50 | stl::uint32_t es; 51 | stl::uint32_t cs; 52 | stl::uint32_t ss; 53 | stl::uint32_t ds; 54 | stl::uint32_t fs; 55 | stl::uint32_t gs; 56 | stl::uint32_t ldt; 57 | stl::uint32_t trace; 58 | stl::uint32_t io_base; 59 | 60 | //! Update @p esp0 to a thread's kernel stack. 61 | TaskStateSeg& Update(const Thread&) noexcept; 62 | }; 63 | 64 | //! Initialize the task state segment. 65 | void InitTaskStateSeg() noexcept; 66 | 67 | /** 68 | * @brief Get the task state segment. 69 | * 70 | * @details 71 | * It is shared by all tasks. 72 | */ 73 | TaskStateSeg& GetTaskStateSeg() noexcept; 74 | 75 | } // namespace tsk -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | 3 | Standard: Latest 4 | 5 | ColumnLimit: 100 6 | 7 | DeriveLineEnding: false 8 | UseCRLF: false 9 | 10 | AlignAfterOpenBracket: Align 11 | AlignConsecutiveAssignments: false 12 | AlignConsecutiveDeclarations: false 13 | AlignConsecutiveMacros: false 14 | AlignEscapedNewlines: Left 15 | AlignTrailingComments: true 16 | AlignOperands: true 17 | 18 | AllowAllArgumentsOnNextLine: true 19 | AllowAllConstructorInitializersOnNextLine: true 20 | AllowAllParametersOfDeclarationOnNextLine: true 21 | AllowShortBlocksOnASingleLine: Never 22 | AllowShortCaseLabelsOnASingleLine: false 23 | AllowShortFunctionsOnASingleLine: Empty 24 | AllowShortIfStatementsOnASingleLine: Never 25 | AllowShortLambdasOnASingleLine: Inline 26 | AllowShortLoopsOnASingleLine: false 27 | 28 | AlwaysBreakAfterReturnType: None 29 | AlwaysBreakBeforeMultilineStrings: false 30 | AlwaysBreakTemplateDeclarations: Yes 31 | BinPackArguments: true 32 | BinPackParameters: true 33 | BreakBeforeBinaryOperators: NonAssignment 34 | BreakBeforeBraces: Attach 35 | BreakBeforeTernaryOperators: true 36 | BreakConstructorInitializers: AfterColon 37 | BreakInheritanceList: AfterColon 38 | BreakStringLiterals: true 39 | CompactNamespaces: false 40 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 41 | DerivePointerAlignment: false 42 | PointerAlignment: Left 43 | 44 | IncludeBlocks: Preserve 45 | IndentCaseLabels: true 46 | IndentGotoLabels: true 47 | IndentPPDirectives: BeforeHash 48 | IndentWrappedFunctionNames: false 49 | NamespaceIndentation: None 50 | AccessModifierOffset: -4 51 | IndentWidth: 4 52 | ContinuationIndentWidth: 4 53 | ConstructorInitializerIndentWidth: 4 54 | TabWidth: 4 55 | UseTab: Never 56 | 57 | KeepEmptyLinesAtTheStartOfBlocks: false 58 | MaxEmptyLinesToKeep: 2 59 | ReflowComments: false 60 | FixNamespaceComments: true 61 | SortIncludes: true 62 | SortUsingDeclarations: true 63 | 64 | SpaceAfterCStyleCast: false 65 | Cpp11BracedListStyle: true 66 | SpaceAfterLogicalNot: false 67 | SpaceAfterTemplateKeyword: true 68 | SpaceBeforeAssignmentOperators: true 69 | SpaceBeforeCpp11BracedList: true 70 | SpaceBeforeCtorInitializerColon: true 71 | SpaceBeforeInheritanceColon: true 72 | SpaceBeforeParens: ControlStatements 73 | SpaceBeforeRangeBasedForLoopColon: true 74 | SpaceInEmptyBlock: false 75 | SpaceInEmptyParentheses: false 76 | SpacesBeforeTrailingComments: 2 77 | SpacesInAngles: false 78 | SpacesInCStyleCastParentheses: false 79 | SpacesInConditionalStatement: false 80 | SpacesInContainerLiterals: false 81 | SpacesInParentheses: false 82 | SpacesInSquareBrackets: false -------------------------------------------------------------------------------- /include/kernel/util/format.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file format.h 3 | * @brief String formatting. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/debug/assert.h" 12 | #include "kernel/stl/string_view.h" 13 | 14 | //! Convert an unsigned integer to a string and write it to a buffer. 15 | stl::size_t ConvertUIntToString(char* buf, stl::uint32_t num, stl::size_t base = 10) noexcept; 16 | 17 | //! Convert an integer to a string and write it to a buffer. 18 | stl::size_t ConvertIntToString(char* buf, stl::int32_t num, stl::size_t base = 10) noexcept; 19 | 20 | namespace _format_string_buffer_impl { 21 | 22 | stl::size_t Format(char* buf, stl::uint32_t) noexcept; 23 | 24 | stl::size_t Format(char* buf, stl::int32_t) noexcept; 25 | 26 | stl::size_t Format(char* buf, char) noexcept; 27 | 28 | stl::size_t Format(char* buf, const char*) noexcept; 29 | 30 | stl::size_t Format(char* buf, stl::string_view) noexcept; 31 | 32 | stl::size_t FormatStringBuffer(char* buf, stl::string_view) noexcept; 33 | 34 | template 35 | stl::size_t FormatStringBuffer(char* const buf, const stl::string_view format, const Arg arg, 36 | const Args... args) noexcept { 37 | dbg::Assert(buf); 38 | stl::size_t len {0}; 39 | for (stl::size_t i {0}; i != format.size(); ++i) { 40 | if (format[i] == '{' && i + 1 != format.size() && format[i + 1] == '}') { 41 | len += Format(buf + len, arg); 42 | return len + FormatStringBuffer(buf + len, format.substr(i + 2), args...); 43 | } else { 44 | buf[len++] = format[i]; 45 | } 46 | } 47 | 48 | return len; 49 | } 50 | 51 | } // namespace _format_string_buffer_impl 52 | 53 | /** 54 | * @brief Convert variadic values to a string and write it to a buffer. 55 | * 56 | * @param buf A string buffer. 57 | * @param format 58 | * A format string with a number of @p {}. 59 | * They will be replaced by the string representations of the arguments. 60 | * The following types are supported: 61 | * - `const char*` 62 | * - `char` 63 | * - `stl::string_view` 64 | * - `stl::uint32_t` 65 | * - `stl::int32_t` 66 | * @param args Variadic arguments to be converted. 67 | */ 68 | template 69 | stl::size_t FormatStringBuffer(char* const buf, const stl::string_view format, 70 | const Args... args) noexcept { 71 | dbg::Assert(buf && !format.empty()); 72 | return _format_string_buffer_impl::FormatStringBuffer(buf, format, args...); 73 | } -------------------------------------------------------------------------------- /include/kernel/stl/span.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kernel/stl/array.h" 4 | 5 | namespace stl { 6 | 7 | template 8 | class span { 9 | public: 10 | using value_type = T; 11 | using size_type = size_t; 12 | using difference_type = ptrdiff_t; 13 | using reference = value_type&; 14 | using const_reference = const value_type&; 15 | using pointer = value_type*; 16 | using const_pointer = const value_type*; 17 | using iterator = value_type*; 18 | using const_iterator = const value_type*; 19 | using reverse_iterator = stl::reverse_iterator; 20 | using const_reverse_iterator = stl::reverse_iterator; 21 | 22 | constexpr span() noexcept = default; 23 | 24 | constexpr span(const const_pointer vals, const size_type size) noexcept : 25 | vals_ {vals}, size_ {size} {} 26 | 27 | template 28 | constexpr span(const array& vals) noexcept : vals_ {vals.data()}, size_ {vals.size()} {} 29 | 30 | constexpr bool empty() const noexcept { 31 | return size_ == 0; 32 | } 33 | 34 | constexpr size_type size() const noexcept { 35 | return size_; 36 | } 37 | 38 | constexpr const_reference operator[](const size_type idx) const noexcept { 39 | return vals_[idx]; 40 | } 41 | 42 | constexpr const_pointer data() const noexcept { 43 | return vals_; 44 | } 45 | 46 | constexpr const_reference front() const { 47 | return vals_[0]; 48 | } 49 | 50 | constexpr const_reference back() const { 51 | return vals_[size_ - 1]; 52 | } 53 | 54 | constexpr const_iterator begin() const noexcept { 55 | return vals_; 56 | } 57 | 58 | constexpr const_iterator end() const noexcept { 59 | return vals_ + size_; 60 | } 61 | 62 | constexpr const_iterator cbegin() const noexcept { 63 | return begin(); 64 | } 65 | 66 | constexpr const_iterator cend() const noexcept { 67 | return end(); 68 | } 69 | 70 | constexpr const_reverse_iterator rbegin() const noexcept { 71 | return const_reverse_iterator {end()}; 72 | } 73 | 74 | constexpr const_reverse_iterator rend() const noexcept { 75 | return const_reverse_iterator {begin()}; 76 | } 77 | 78 | constexpr const_reverse_iterator crbegin() const noexcept { 79 | return rbegin(); 80 | } 81 | 82 | constexpr const_reverse_iterator crend() const noexcept { 83 | return rend(); 84 | } 85 | 86 | private: 87 | const_pointer vals_ {nullptr}; 88 | size_type size_ {0}; 89 | }; 90 | 91 | } // namespace stl -------------------------------------------------------------------------------- /include/kernel/thread/sync.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file sync.h 3 | * @brief Multi-threading synchronization. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/debug/assert.h" 12 | #include "kernel/interrupt/intr.h" 13 | #include "kernel/thread/thd.h" 14 | #include "kernel/util/tag_list.h" 15 | 16 | namespace sync { 17 | 18 | template 19 | class Semaphore { 20 | public: 21 | Semaphore() noexcept = default; 22 | 23 | Semaphore(const stl::size_t val) noexcept { 24 | Init(val); 25 | } 26 | 27 | Semaphore(const Semaphore&) = delete; 28 | 29 | Semaphore& Init(const stl::size_t val) noexcept { 30 | dbg::Assert(val <= max); 31 | val_ = val; 32 | return *this; 33 | } 34 | 35 | void Increase() noexcept { 36 | const intr::IntrGuard guard; 37 | dbg::Assert(val_ <= max); 38 | if (val_ != max) { 39 | if (!waiters_.IsEmpty()) { 40 | // Wakes up a waiting thread. 41 | auto& next_thd {tsk::Thread::GetByTag(waiters_.Pop())}; 42 | tsk::Thread::Unblock(next_thd); 43 | } 44 | 45 | ++val_; 46 | } 47 | } 48 | 49 | void Decrease() noexcept { 50 | const intr::IntrGuard guard; 51 | auto& curr_thd {tsk::Thread::GetCurrent()}; 52 | // Keep waiting until the semaphore is not zero. 53 | while (val_ == 0) { 54 | // Push the current thread into the wait queue and then block it. 55 | dbg::Assert(!waiters_.Find(curr_thd.GetTag())); 56 | waiters_.PushBack(curr_thd.GetTag()); 57 | curr_thd.Block(tsk::Thread::Status::Blocked); 58 | // When the thread is woken up by the method `Increase`, 59 | // it is possible that another thread may have grabbed the semaphore faster than it. 60 | // So we use a loop to check the semaphore again. 61 | } 62 | 63 | --val_; 64 | } 65 | 66 | private: 67 | stl::size_t val_ {max}; 68 | 69 | //! The threads waiting for a semaphore. 70 | TagList waiters_; 71 | }; 72 | 73 | class Mutex { 74 | public: 75 | Mutex() noexcept = default; 76 | 77 | Mutex(const Mutex&) = delete; 78 | 79 | void Lock() noexcept; 80 | 81 | void Unlock() noexcept; 82 | 83 | private: 84 | //! A mutex is a semaphore with a maximum value of 1. 85 | Semaphore<1> sema_; 86 | 87 | //! The thread currently holding the mutex. 88 | tsk::Thread* holder_ {nullptr}; 89 | 90 | //! The number of times it is locked. 91 | stl::size_t repeat_times_ {0}; 92 | }; 93 | 94 | } // namespace sync -------------------------------------------------------------------------------- /src/kernel/io/io.asm: -------------------------------------------------------------------------------- 1 | %include "kernel/util/metric.inc" 2 | 3 | [bits 32] 4 | section .text 5 | global GetCr2 6 | ; Get the value of `CR2`. 7 | GetCr2: 8 | mov eax, cr2 9 | ret 10 | 11 | global SetCr3 12 | ; Set the value of `CR3`. 13 | SetCr3: 14 | %push set_cr3 15 | %stacksize flat 16 | %arg val:dword 17 | enter B(0), 0 18 | mov eax, [val] 19 | mov cr3, eax 20 | leave 21 | ret 22 | %pop 23 | 24 | global GetEFlags 25 | ; Get the value of `EFLAGS`. 26 | GetEFlags: 27 | pushfd 28 | pop eax 29 | ret 30 | 31 | global SetEFlags 32 | ; Set the value of `EFLAGS`. 33 | SetEFlags: 34 | %push set_eflags 35 | %stacksize flat 36 | %arg val:dword 37 | enter B(0), 0 38 | call GetEFlags 39 | mov edx, [val] 40 | push edx 41 | popf 42 | leave 43 | ret 44 | %pop 45 | 46 | global WriteByteToPort 47 | ; Write a byte to a port. 48 | WriteByteToPort: 49 | %push write_byte_to_port 50 | %stacksize flat 51 | %arg port:word, val:byte 52 | enter B(0), 0 53 | mov dx, [port] 54 | mov al, [val] 55 | out dx, al 56 | leave 57 | ret 58 | %pop 59 | 60 | global WriteWordsToPort 61 | ; Write a number of words to a port. 62 | WriteWordsToPort: 63 | %push write_words_to_port 64 | %stacksize flat 65 | %arg port:word, data:dword, count:dword 66 | enter B(0), 0 67 | push esi 68 | cld 69 | mov dx, [port] 70 | mov esi, [data] 71 | mov ecx, [count] 72 | rep outsw 73 | pop esi 74 | leave 75 | ret 76 | %pop 77 | 78 | global ReadByteFromPort 79 | ; Read a byte from a port. 80 | ReadByteFromPort: 81 | %push read_byte_from_port 82 | %stacksize flat 83 | %arg port:word 84 | enter B(0), 0 85 | mov dx, [port] 86 | in al, dx 87 | leave 88 | ret 89 | %pop 90 | 91 | global ReadWordsFromPort 92 | ; Read a number of words from a port. 93 | ReadWordsFromPort: 94 | %push read_words_from_port 95 | %stacksize flat 96 | %arg port:word, buf:dword, count:dword 97 | enter B(0), 0 98 | push esi 99 | cld 100 | mov dx, [port] 101 | mov edi, [buf] 102 | mov ecx, [count] 103 | rep insw 104 | pop esi 105 | leave 106 | ret 107 | %pop -------------------------------------------------------------------------------- /src/kernel/io/file/file.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/io/file/file.h" 2 | #include "kernel/debug/assert.h" 3 | #include "kernel/io/disk/disk.h" 4 | #include "kernel/io/disk/file/file.h" 5 | #include "kernel/thread/thd.h" 6 | 7 | namespace io { 8 | 9 | void FileDesc::Close() noexcept { 10 | if (IsValid() && desc_ >= std_stream_count) { 11 | // Get the global file descriptor from the process file table. 12 | const auto global {tsk::ProcFileDescTab::GetGlobal(desc_)}; 13 | auto& file_tab {fs::GetFileTab()}; 14 | dbg::Assert(global < file_tab.GetSize()); 15 | // Close the file in the global file table. 16 | file_tab[global].Close(); 17 | tsk::ProcFileDescTab::Reset(desc_); 18 | } 19 | } 20 | 21 | File::File(const Path& path, const bit::Flags flags) noexcept : 22 | desc_ {Open(path, flags)} {} 23 | 24 | File::File(const FileDesc desc) noexcept : desc_ {desc} {} 25 | 26 | File::File(File&& o) noexcept : desc_ {o.desc_} { 27 | o.desc_.Reset(); 28 | } 29 | 30 | File::~File() noexcept { 31 | Close(); 32 | } 33 | 34 | void File::Close() noexcept { 35 | if (IsOpen()) { 36 | desc_.Close(); 37 | desc_.Reset(); 38 | } 39 | } 40 | 41 | bool File::IsOpen() const noexcept { 42 | return desc_.IsValid(); 43 | } 44 | 45 | stl::size_t File::Write(const void* const data, const stl::size_t size) noexcept { 46 | dbg::Assert(IsOpen()); 47 | return GetDefaultPart().WriteFile(desc_, data, size); 48 | } 49 | 50 | stl::size_t File::Read(void* const buf, const stl::size_t size) noexcept { 51 | dbg::Assert(IsOpen()); 52 | return GetDefaultPart().ReadFile(desc_, buf, size); 53 | } 54 | 55 | stl::size_t File::Seek(const stl::int32_t offset, const SeekOrigin origin) noexcept { 56 | dbg::Assert(IsOpen()); 57 | return GetDefaultPart().SeekFile(desc_, offset, origin); 58 | } 59 | 60 | bool File::Delete(const Path& path) noexcept { 61 | return GetDefaultPart().DeleteFile(path); 62 | } 63 | 64 | FileDesc File::Open(const Path& path, const bit::Flags flags) noexcept { 65 | return GetDefaultPart().OpenFile(path, flags); 66 | } 67 | 68 | namespace sc { 69 | 70 | stl::size_t File::Open(const OpenArgs& args) noexcept { 71 | return io::File::Open(Path {args.path}, args.flags); 72 | } 73 | 74 | bool File::Delete(const char* const path) noexcept { 75 | return io::File::Delete(path); 76 | } 77 | 78 | stl::size_t File::Write(const WriteArgs& args) noexcept { 79 | return io::File {args.desc}.Write(args.data, args.size); 80 | } 81 | 82 | stl::size_t File::Read(const ReadArgs& args) noexcept { 83 | return io::File {args.desc}.Read(args.buf, args.size); 84 | } 85 | 86 | stl::size_t File::Seek(const SeekArgs& args) noexcept { 87 | return io::File {args.desc}.Seek(args.offset, args.origin); 88 | } 89 | 90 | void File::Close(const stl::size_t desc) noexcept { 91 | io::File {desc}.Close(); 92 | } 93 | 94 | } // namespace sc 95 | 96 | } // namespace io -------------------------------------------------------------------------------- /include/kernel/io/video/print.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file print.h 3 | * @brief Text printing. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/debug/assert.h" 12 | #include "kernel/stl/cstdint.h" 13 | #include "kernel/stl/string_view.h" 14 | 15 | namespace io { 16 | 17 | //! The screen width in VGA text mode. 18 | inline constexpr stl::size_t text_screen_width {80}; 19 | //! The screen height in VGA text mode. 20 | inline constexpr stl::size_t text_screen_height {25}; 21 | 22 | //! Print a string with a new line. 23 | void PrintlnStr(stl::string_view) noexcept; 24 | 25 | //! Print a string. 26 | void PrintStr(stl::string_view) noexcept; 27 | 28 | //! Print a character with a new line. 29 | void PrintlnChar(char) noexcept; 30 | 31 | //! Print an unsigned hexadecimal integer with a new line. 32 | void PrintlnHex(stl::uint32_t) noexcept; 33 | 34 | //! Print a hexadecimal integer with a new line. 35 | void PrintlnHex(stl::int32_t) noexcept; 36 | 37 | //! Print a hexadecimal integer. 38 | void PrintHex(stl::int32_t) noexcept; 39 | 40 | extern "C" { 41 | 42 | //! Print a character. 43 | void PrintChar(char) noexcept; 44 | 45 | //! Print a string. 46 | void PrintStr(const char*) noexcept; 47 | 48 | //! Print an unsigned hexadecimal integer. 49 | void PrintHex(stl::uint32_t) noexcept; 50 | 51 | //! Set the cursor position. 52 | void SetCursorPos(stl::uint16_t) noexcept; 53 | 54 | //! Get the cursor position. 55 | stl::uint16_t GetCursorPos() noexcept; 56 | } 57 | 58 | namespace _printf_impl { 59 | 60 | void Print(stl::uint32_t) noexcept; 61 | 62 | void Print(stl::int32_t) noexcept; 63 | 64 | void Print(char) noexcept; 65 | 66 | void Print(stl::string_view) noexcept; 67 | 68 | void Print(const char*) noexcept; 69 | 70 | void Printf(stl::string_view) noexcept; 71 | 72 | template 73 | void Printf(const stl::string_view format, const Arg arg, const Args... args) noexcept { 74 | for (stl::size_t i {0}; i != format.size(); ++i) { 75 | if (format[i] == '{' && i + 1 != format.size() && format[i + 1] == '}') { 76 | Print(arg); 77 | return Printf(format.substr(i + 2), args...); 78 | } else { 79 | Print(format[i]); 80 | } 81 | } 82 | } 83 | 84 | } // namespace _printf_impl 85 | 86 | /** 87 | * @brief Print variadic values. 88 | * 89 | * @param format 90 | * A format string with a number of @p {}. 91 | * They will be replaced by the string representations of the arguments. 92 | * The following types are supported: 93 | * - `const char*` 94 | * - `char` 95 | * - `stl::string_view` 96 | * - `stl::uint32_t` 97 | * - `stl::int32_t` 98 | * @param args Variadic arguments to be printed. 99 | */ 100 | template 101 | void Printf(const stl::string_view format, const Args... args) noexcept { 102 | dbg::Assert(!format.empty()); 103 | _printf_impl::Printf(format, args...); 104 | } 105 | 106 | } // namespace io -------------------------------------------------------------------------------- /src/kernel/syscall/call.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/syscall/call.h" 2 | #include "kernel/debug/assert.h" 3 | #include "kernel/io/file/dir.h" 4 | #include "kernel/io/file/file.h" 5 | #include "kernel/io/video/console.h" 6 | #include "kernel/io/video/print.h" 7 | #include "kernel/memory/pool.h" 8 | #include "kernel/process/proc.h" 9 | 10 | namespace sc { 11 | 12 | /** 13 | * @brief System call handlers. 14 | * 15 | * @details 16 | * When a user application needs to call a kernel method, @p mem::Allocate, for example: 17 | * 1. They can only call its user version @p usr::mem::Allocate, which is a wrapper for a system call. 18 | * 2. @p usr::mem::Allocate calls the method @p usr::sc::SysCall with the type @p usr::sc::SysCallType::MemAlloc. 19 | * 3. @p usr::sc::SysCall generates a @p sys_call_intr_num interrupt in @p src/kernel/syscall/call.asm. 20 | * 4. The CPU jumps to the interrupt entry point @p SysCallEntry in @p src/kernel/interrupt/intr.asm. 21 | * 5. @p SysCallEntry calls the corresponding kernel method @p sys_call_handlers[usr::sc::SysCallType::MemAlloc], which is @p mem::Allocate. 22 | */ 23 | extern "C" stl::uintptr_t sys_call_handlers[]; 24 | 25 | stl::uintptr_t sys_call_handlers[count] {}; 26 | 27 | SysCallHandlerTab& GetSysCallHandlerTab() noexcept { 28 | static SysCallHandlerTab handlers {sys_call_handlers}; 29 | return handlers; 30 | } 31 | 32 | void InitSysCall() noexcept { 33 | GetSysCallHandlerTab() 34 | .Register(SysCallType::GetCurrPid, &tsk::Process::GetCurrPid) 35 | .Register(SysCallType::PrintChar, &io::Console::PrintChar) 36 | .Register(SysCallType::PrintHex, 37 | static_cast(&io::Console::PrintHex)) 38 | .Register(SysCallType::PrintStr, static_cast(&io::Console::PrintStr)) 39 | .Register(SysCallType::MemAlloc, static_cast(&mem::Allocate)) 40 | .Register(SysCallType::Fork, static_cast(&tsk::Process::ForkCurrent)) 41 | .Register(SysCallType::MemFree, static_cast(&mem::Free)) 42 | .Register(SysCallType::OpenFile, 43 | static_cast(&io::sc::File::Open)) 44 | .Register(SysCallType::ReadFile, 45 | static_cast(&io::sc::File::Read)) 46 | .Register(SysCallType::SeekFile, 47 | static_cast(&io::sc::File::Seek)) 48 | .Register( 49 | SysCallType::WriteFile, 50 | static_cast(&io::sc::File::Write)) 51 | .Register(SysCallType::CloseFile, static_cast(&io::sc::File::Close)) 52 | .Register(SysCallType::DeleteFile, 53 | static_cast(&io::sc::File::Delete)) 54 | .Register(SysCallType::CreateDir, 55 | static_cast(&io::sc::Directory::Create)); 56 | 57 | io::PrintlnStr("System calls have been initialized."); 58 | } 59 | 60 | } // namespace sc -------------------------------------------------------------------------------- /include/kernel/io/disk/file/file.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file file.h 3 | * @brief Underlying file storage. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/debug/assert.h" 12 | #include "kernel/io/file/file.h" 13 | #include "kernel/io/video/print.h" 14 | #include "kernel/stl/array.h" 15 | #include "kernel/util/bit.h" 16 | 17 | namespace io::fs { 18 | 19 | //! The maximum number of files that can be open simultaneously in the system. 20 | inline constexpr stl::size_t max_open_file_times {32}; 21 | 22 | class IdxNode; 23 | 24 | struct File { 25 | File() noexcept = default; 26 | 27 | File(File&&) noexcept; 28 | 29 | bool IsOpen() const noexcept; 30 | 31 | IdxNode& GetNode() const noexcept; 32 | 33 | stl::size_t GetNodeIdx() const noexcept; 34 | 35 | File& Clear() noexcept; 36 | 37 | void Close() noexcept; 38 | 39 | //! Open and access modes. 40 | bit::Flags flags {0}; 41 | 42 | /** 43 | * The index node for file content storage. 44 | * It is @p nullptr when the file is not open. 45 | */ 46 | IdxNode* inode {nullptr}; 47 | 48 | //! The access offset. 49 | mutable stl::size_t pos {0}; 50 | }; 51 | 52 | /** 53 | * @brief The open file table. 54 | * 55 | * @details 56 | * The open file table saves all open files in the system. 57 | * A global file descriptor is an index to this table. 58 | */ 59 | template 60 | class FileTab { 61 | static_assert(size > std_stream_count); 62 | 63 | public: 64 | /** 65 | * @brief Get a free descriptor. 66 | * 67 | * @return A free descriptor or @p npos if there is no free descriptor. 68 | */ 69 | FileDesc GetFreeDesc() const noexcept { 70 | for (stl::size_t i {std_stream_count}; i != files_.size(); ++i) { 71 | if (!files_[i].IsOpen()) { 72 | return i; 73 | } 74 | } 75 | 76 | io::PrintlnStr("The system file table is full."); 77 | return npos; 78 | } 79 | 80 | //! Whether an index node is open. 81 | bool Contain(const stl::size_t inode_idx) const noexcept { 82 | for (stl::size_t i {std_stream_count}; i != files_.size(); ++i) { 83 | if (files_[i].IsOpen() && files_[i].GetNodeIdx() == inode_idx) { 84 | return true; 85 | } 86 | } 87 | 88 | return false; 89 | } 90 | 91 | //! Get the file by a descriptor. 92 | const File& operator[](const FileDesc desc) const noexcept { 93 | dbg::Assert(desc < files_.size()); 94 | return files_[desc]; 95 | } 96 | 97 | File& operator[](const FileDesc desc) noexcept { 98 | return const_cast(const_cast(*this)[desc]); 99 | } 100 | 101 | constexpr stl::size_t GetSize() const noexcept { 102 | return files_.size(); 103 | } 104 | 105 | private: 106 | //! Open files. 107 | stl::array files_; 108 | }; 109 | 110 | //! Get the open file table. 111 | FileTab& GetFileTab() noexcept; 112 | 113 | } // namespace io::fs -------------------------------------------------------------------------------- /include/kernel/io/file/file.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file file.h 3 | * @brief File management. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/io/file/path.h" 12 | #include "kernel/util/bit.h" 13 | #include "kernel/util/metric.h" 14 | 15 | namespace io { 16 | 17 | //! The file descriptor. 18 | class FileDesc { 19 | public: 20 | constexpr FileDesc(const stl::size_t desc = npos) noexcept : desc_ {desc} {} 21 | 22 | constexpr bool IsValid() const noexcept { 23 | return desc_ != npos; 24 | } 25 | 26 | constexpr operator stl::size_t() const noexcept { 27 | return desc_; 28 | } 29 | 30 | constexpr FileDesc& Reset() noexcept { 31 | desc_ = npos; 32 | return *this; 33 | } 34 | 35 | void Close() noexcept; 36 | 37 | private: 38 | stl::size_t desc_; 39 | }; 40 | 41 | //! The wrapper for file functions of @p Disk::FilePart. 42 | class File { 43 | public: 44 | enum class OpenMode { ReadOnly = 0, WriteOnly = 1, ReadWrite = 2, CreateNew = 4 }; 45 | 46 | enum class SeekOrigin { Begin, Curr, End }; 47 | 48 | static FileDesc Open(const Path&, bit::Flags) noexcept; 49 | 50 | static bool Delete(const Path&) noexcept; 51 | 52 | explicit File(FileDesc) noexcept; 53 | 54 | explicit File(const Path&, bit::Flags) noexcept; 55 | 56 | File(File&&) noexcept; 57 | 58 | ~File() noexcept; 59 | 60 | stl::size_t Write(const void* data, stl::size_t size) noexcept; 61 | 62 | stl::size_t Read(void* buf, stl::size_t size) noexcept; 63 | 64 | stl::size_t Seek(stl::int32_t offset, SeekOrigin) noexcept; 65 | 66 | void Close() noexcept; 67 | 68 | bool IsOpen() const noexcept; 69 | 70 | private: 71 | FileDesc desc_; 72 | }; 73 | 74 | //! The standard input stream. 75 | inline constexpr FileDesc std_in {0}; 76 | //! The standard output stream. 77 | inline constexpr FileDesc std_out {1}; 78 | //! The standard error stream. 79 | inline constexpr FileDesc std_err {2}; 80 | 81 | //! The number of standard streams. 82 | inline constexpr stl::size_t std_stream_count {3}; 83 | 84 | //! System calls. 85 | namespace sc { 86 | 87 | class File { 88 | public: 89 | File() = delete; 90 | 91 | struct OpenArgs { 92 | const char* path; 93 | stl::uint32_t flags; 94 | }; 95 | 96 | struct WriteArgs { 97 | stl::size_t desc; 98 | const void* data; 99 | stl::size_t size; 100 | }; 101 | 102 | struct ReadArgs { 103 | stl::size_t desc; 104 | void* buf; 105 | stl::size_t size; 106 | }; 107 | 108 | struct SeekArgs { 109 | stl::size_t desc; 110 | stl::int32_t offset; 111 | io::File::SeekOrigin origin; 112 | }; 113 | 114 | static stl::size_t Open(const OpenArgs&) noexcept; 115 | 116 | static void Close(stl::size_t) noexcept; 117 | 118 | static bool Delete(const char*) noexcept; 119 | 120 | static stl::size_t Write(const WriteArgs&) noexcept; 121 | 122 | static stl::size_t Read(const ReadArgs&) noexcept; 123 | 124 | static stl::size_t Seek(const SeekArgs&) noexcept; 125 | }; 126 | 127 | } // namespace sc 128 | 129 | } // namespace io -------------------------------------------------------------------------------- /src/boot/mbr.asm: -------------------------------------------------------------------------------- 1 | ; The Master Boot Record (MBR) 2 | ; It loads the kernel loader. 3 | 4 | %include "boot/boot.inc" 5 | %include "kernel/krnl.inc" 6 | %include "kernel/io/video/print.inc" 7 | %include "kernel/io/disk/disk.inc" 8 | 9 | ; The BIOS will load the master boot record to `0x7C00`. 10 | section mbr vstart=mbr_base 11 | mov ax, cs 12 | mov ds, ax 13 | mov es, ax 14 | mov ss, ax 15 | mov fs, ax 16 | mov gs, ax 17 | mov sp, mbr_stack_top 18 | 19 | call ClearScreen 20 | 21 | call ReadLoader 22 | jmp loader_code_entry 23 | 24 | ; Read the loader from the disk. 25 | ReadLoader: 26 | %if loader_sector_count > max_disk_sector_count_per_access 27 | %error "The loader is too large" 28 | %endif 29 | mov ax, loader_start_sector 30 | mov bx, loader_base 31 | mov cx, loader_sector_count 32 | call ReadDisk 33 | ret 34 | 35 | ; Read data from the disk to memory in 16-bit mode. 36 | ; Input: 37 | ; `AX` = A start sector where data will be read from. 38 | ; `BX` = A physical address where data will be written to. 39 | ; `CX` = The number of sectors to be read. 40 | ReadDisk: 41 | mov si, ax 42 | mov di, cx 43 | cmp cx, 0 44 | je .end 45 | 46 | ; Set the sector count. 47 | mov dx, disk_sector_count_port 48 | mov al, cl 49 | out dx, al 50 | 51 | mov ax, si 52 | 53 | ; Set the start sector. 54 | ; The bits `0`-`23` of the sector should be saved to disk ports `0x1F3`, `0x1F4`, `0x1F5`. 55 | ; Set the bits `0`-`7`. 56 | mov dx, disk_lba_low_port 57 | out dx, al 58 | ; Set the bits `8`-`15`. 59 | mov cl, 8 60 | shr ax, cl 61 | mov dx, disk_lba_mid_port 62 | out dx, al 63 | ; Set the bits `16`-`23`. 64 | shr ax, cl 65 | mov dx, disk_lba_high_port 66 | out dx, al 67 | ; The bits `24`-`27` of the sector should be saved to the bits `0`-`3` of the disk port `0x1F6`. 68 | shr ax, cl 69 | and al, 0xF 70 | ; Set the bits `4`-`7` of the port `0x1F6` to `0b1110`. 71 | or al, 0b1010_0000 | disk_device_lba_mode 72 | mov dx, disk_device_port 73 | out dx, al 74 | 75 | ; Write a reading command to the disk port `0x1F7`. 76 | mov dx, disk_cmd_port 77 | mov al, disk_read_cmd 78 | out dx, al 79 | 80 | ; Check disk status using the same port `0x1F7`. 81 | .not_ready: 82 | nop ; Sleep 83 | in al, dx 84 | and al, disk_status_ready | disk_status_busy 85 | cmp al, disk_status_ready 86 | jnz .not_ready 87 | 88 | ; Read words from the disk port `0x1F0`. 89 | ; `DI` is the number of sectors to be read. Each sector has 512 bytes. 90 | ; Each reading reads 2 bytes, so `(512 * DI) / 2` readings are needed. 91 | mov ax, di 92 | mov dx, 256 93 | mul dx 94 | ; The high bits of the multiplication result in `DX` are ignored since the loader size is small. 95 | mov cx, ax 96 | 97 | mov dx, disk_data_port 98 | ; The offset ranges from `0x0000` to `0xFFFF`, so the maximum loader size is 64 KB. 99 | mov di, bx 100 | cld 101 | rep insw 102 | .end: 103 | ret 104 | 105 | ClearScreen: 106 | mov ax, 0x600 107 | mov bx, 0x700 108 | xor cx, cx 109 | mov dx, ((text_screen_height - 1) << 8) + (text_screen_width - 1) 110 | int 0x10 111 | ret 112 | 113 | ; The master boot record has 512 bytes and ends with `0x55`, `0xAA`. 114 | times disk_sector_size - 2 - ($ - $$) db 0 115 | db 0x55, 0xAA -------------------------------------------------------------------------------- /include/kernel/stl/array.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kernel/stl/cstdint.h" 4 | #include "kernel/stl/iterator.h" 5 | 6 | namespace stl { 7 | 8 | template 9 | class array { 10 | static_assert(n > 0); 11 | 12 | public: 13 | using value_type = T; 14 | using size_type = size_t; 15 | using difference_type = ptrdiff_t; 16 | using reference = value_type&; 17 | using const_reference = const value_type&; 18 | using pointer = value_type*; 19 | using const_pointer = const value_type*; 20 | using iterator = value_type*; 21 | using const_iterator = const value_type*; 22 | using reverse_iterator = stl::reverse_iterator; 23 | using const_reverse_iterator = stl::reverse_iterator; 24 | 25 | constexpr array() noexcept = default; 26 | 27 | constexpr array(const value_type (&vals)[n]) noexcept : vals_ {vals} {} 28 | 29 | constexpr bool empty() const noexcept { 30 | return n == 0; 31 | } 32 | 33 | constexpr size_type size() const noexcept { 34 | return n; 35 | } 36 | 37 | constexpr size_type max_size() const noexcept { 38 | return n; 39 | } 40 | 41 | constexpr const_reference operator[](const size_type idx) const noexcept { 42 | return vals_[idx]; 43 | } 44 | 45 | constexpr reference operator[](const size_type idx) noexcept { 46 | return const_cast(const_cast(*this)[idx]); 47 | } 48 | 49 | constexpr pointer data() noexcept { 50 | return const_cast(const_cast(*this).data()); 51 | } 52 | 53 | constexpr const_pointer data() const noexcept { 54 | return vals_; 55 | } 56 | 57 | constexpr reference front() { 58 | return const_cast(const_cast(*this).front()); 59 | } 60 | 61 | constexpr const_reference front() const { 62 | return vals_[0]; 63 | } 64 | 65 | constexpr reference back() { 66 | return const_cast(const_cast(*this).back()); 67 | } 68 | 69 | constexpr const_reference back() const { 70 | return vals_[n - 1]; 71 | } 72 | 73 | constexpr iterator begin() noexcept { 74 | return const_cast(const_cast(*this).begin()); 75 | } 76 | 77 | constexpr const_iterator begin() const noexcept { 78 | return vals_; 79 | } 80 | 81 | constexpr iterator end() noexcept { 82 | return const_cast(const_cast(*this).end()); 83 | } 84 | 85 | constexpr const_iterator end() const noexcept { 86 | return vals_ + n; 87 | } 88 | 89 | constexpr const_iterator cbegin() const noexcept { 90 | return begin(); 91 | } 92 | 93 | constexpr const_iterator cend() const noexcept { 94 | return end(); 95 | } 96 | 97 | constexpr reverse_iterator rbegin() noexcept { 98 | return reverse_iterator {end()}; 99 | } 100 | 101 | constexpr const_reverse_iterator rbegin() const noexcept { 102 | return const_reverse_iterator {end()}; 103 | } 104 | 105 | constexpr reverse_iterator rend() noexcept { 106 | return reverse_iterator {begin()}; 107 | } 108 | 109 | constexpr const_reverse_iterator rend() const noexcept { 110 | return const_reverse_iterator {begin()}; 111 | } 112 | 113 | constexpr const_reverse_iterator crbegin() const noexcept { 114 | return rbegin(); 115 | } 116 | 117 | constexpr const_reverse_iterator crend() const noexcept { 118 | return rend(); 119 | } 120 | 121 | private: 122 | value_type vals_[n] {}; 123 | }; 124 | 125 | } // namespace stl -------------------------------------------------------------------------------- /include/kernel/stl/iterator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kernel/stl/cstdint.h" 4 | 5 | namespace stl { 6 | 7 | class input_iterator_tag {}; 8 | class output_iterator_tag {}; 9 | class forward_iterator_tag : public input_iterator_tag {}; 10 | class bidirectional_iterator_tag : public forward_iterator_tag {}; 11 | class random_access_iterator_tag : public bidirectional_iterator_tag {}; 12 | 13 | template 15 | struct iterator { 16 | using iterator_category = Category; 17 | using value_type = T; 18 | using pointer = Pointer; 19 | using reference = Reference; 20 | using difference_type = Distance; 21 | }; 22 | 23 | template 24 | struct iterator_traits { 25 | using iterator_category = typename Iterator::iterator_category; 26 | using value_type = typename Iterator::value_type; 27 | using pointer = typename Iterator::pointer; 28 | using reference = typename Iterator::reference; 29 | using difference_type = typename Iterator::difference_type; 30 | }; 31 | 32 | template 33 | struct iterator_traits { 34 | using iterator_category = random_access_iterator_tag; 35 | using value_type = T; 36 | using pointer = T*; 37 | using reference = T&; 38 | using difference_type = ptrdiff_t; 39 | }; 40 | 41 | template 42 | struct iterator_traits { 43 | using iterator_category = random_access_iterator_tag; 44 | using value_type = T; 45 | using pointer = const T*; 46 | using reference = const T&; 47 | using difference_type = ptrdiff_t; 48 | }; 49 | 50 | template 51 | class reverse_iterator { 52 | public: 53 | using iterator_category = typename iterator_traits::iterator_category; 54 | using value_type = typename iterator_traits::value_type; 55 | using pointer = typename iterator_traits::pointer; 56 | using reference = typename iterator_traits::reference; 57 | using difference_type = typename iterator_traits::difference_type; 58 | 59 | constexpr reverse_iterator() noexcept = default; 60 | 61 | constexpr reverse_iterator(const reverse_iterator& it) noexcept : curr_ {it.curr_} {} 62 | 63 | constexpr explicit reverse_iterator(const Iterator it) noexcept : curr_ {it} {} 64 | 65 | constexpr Iterator base() const noexcept { 66 | return curr_; 67 | } 68 | 69 | constexpr reference operator*() const noexcept { 70 | auto tmp {curr_}; 71 | return *--tmp; 72 | } 73 | 74 | constexpr pointer operator->() const noexcept { 75 | return &operator*(); 76 | } 77 | 78 | constexpr reverse_iterator& operator++() noexcept { 79 | --curr_; 80 | return *this; 81 | } 82 | 83 | constexpr reverse_iterator& operator++(int) noexcept { 84 | const auto old {*this}; 85 | --curr_; 86 | return old; 87 | } 88 | 89 | constexpr reverse_iterator& operator--() noexcept { 90 | ++curr_; 91 | return *this; 92 | } 93 | 94 | constexpr reverse_iterator& operator--(int) noexcept { 95 | const auto old {*this}; 96 | ++curr_; 97 | return old; 98 | } 99 | 100 | constexpr reverse_iterator operator+(const difference_type n) const noexcept { 101 | return curr_ - n; 102 | } 103 | 104 | constexpr reference operator[](const difference_type n) const noexcept { 105 | return *(*this + n); 106 | } 107 | 108 | private: 109 | Iterator curr_ {}; 110 | }; 111 | 112 | } // namespace stl -------------------------------------------------------------------------------- /include/kernel/stl/string_view.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kernel/stl/algorithm.h" 4 | #include "kernel/stl/cstring.h" 5 | 6 | namespace stl { 7 | 8 | class string_view { 9 | public: 10 | using value_type = char; 11 | using size_type = size_t; 12 | using difference_type = ptrdiff_t; 13 | using reference = value_type&; 14 | using const_reference = const value_type&; 15 | using pointer = value_type*; 16 | using const_pointer = const value_type*; 17 | using const_iterator = const value_type*; 18 | using iterator = const_iterator; 19 | 20 | static constexpr size_type npos {static_cast(-1)}; 21 | 22 | constexpr string_view(const const_pointer str = nullptr) noexcept : str_ {str} { 23 | if (str_) { 24 | len_ = strlen(str_); 25 | } 26 | } 27 | 28 | constexpr string_view(const const_pointer str, const size_type len) noexcept : 29 | str_ {str}, len_ {len} {} 30 | 31 | constexpr const_pointer data() const noexcept { 32 | return str_; 33 | } 34 | 35 | constexpr bool empty() const noexcept { 36 | return len_ == 0; 37 | } 38 | 39 | constexpr size_type size() const noexcept { 40 | return len_; 41 | } 42 | 43 | constexpr size_type rfind(const value_type ch) const noexcept { 44 | if (const auto sub_str {strrchr(str_, ch)}; sub_str) { 45 | return sub_str - str_; 46 | } else { 47 | return npos; 48 | } 49 | } 50 | 51 | constexpr size_type find(const value_type ch) const noexcept { 52 | if (const auto sub_str {strchr(str_, ch)}; sub_str) { 53 | return sub_str - str_; 54 | } else { 55 | return npos; 56 | } 57 | } 58 | 59 | constexpr const_reference operator[](const size_type idx) const noexcept { 60 | return str_[idx]; 61 | } 62 | 63 | constexpr const_iterator begin() const noexcept { 64 | return str_; 65 | } 66 | 67 | constexpr const_iterator cbegin() const noexcept { 68 | return begin(); 69 | } 70 | 71 | constexpr const_iterator end() const noexcept { 72 | return str_ + len_; 73 | } 74 | 75 | constexpr const_iterator cend() const noexcept { 76 | return end(); 77 | } 78 | 79 | constexpr reference front() { 80 | return const_cast(const_cast(*this).front()); 81 | } 82 | 83 | constexpr const_reference front() const { 84 | return str_[0]; 85 | } 86 | 87 | constexpr reference back() { 88 | return const_cast(const_cast(*this).back()); 89 | } 90 | 91 | constexpr const_reference back() const { 92 | return str_[len_ - 1]; 93 | } 94 | 95 | string_view substr(const size_type idx = 0, const size_type count = npos) const noexcept { 96 | if (idx >= len_) { 97 | return nullptr; 98 | } 99 | 100 | if (const auto actual_count {min(count, len_ - idx)}; actual_count > 0) { 101 | return {str_ + idx, actual_count}; 102 | } else { 103 | return nullptr; 104 | } 105 | } 106 | 107 | private: 108 | const_pointer str_; 109 | size_type len_ {0}; 110 | }; 111 | 112 | constexpr bool operator==(const string_view lhs, const string_view rhs) noexcept { 113 | if (lhs.empty() && rhs.empty()) { 114 | return true; 115 | } else if (!lhs.empty() && !rhs.empty()) { 116 | return strcmp(lhs.data(), rhs.data()) == 0; 117 | } else { 118 | return false; 119 | } 120 | } 121 | 122 | constexpr bool operator!=(const string_view lhs, const string_view rhs) noexcept { 123 | return !(lhs == rhs); 124 | } 125 | 126 | } // namespace stl -------------------------------------------------------------------------------- /docs/Getting Started/Building the System.md: -------------------------------------------------------------------------------- 1 | # Building the System 2 | 3 | To build the system, we need to clone the project and run the following commands in the project folder: 4 | 5 | ```bash 6 | # Build the system. 7 | make 8 | # Write built files into a virtual disk. 9 | make install 10 | ``` 11 | 12 | The `make` command only generates binary files. We also need to write them into the virtual disk image `kernel.img` by `make install`. 13 | 14 | ## Compilation 15 | 16 | ### *Assembly* 17 | 18 | The *assembly* code is built by *NASM*. 19 | 20 | ```make 21 | # Makefile 22 | 23 | ASFLAGS := -f elf \ 24 | -i$(INC_DIR) 25 | ``` 26 | 27 | - `-f elf` generates ELF files 28 | 29 | ### *C++* 30 | 31 | The *C++* code is built by *g++*. 32 | 33 | ```make 34 | # Makefile 35 | 36 | CXXFLAGS := -m32 \ 37 | -std=c++20 \ 38 | -c \ 39 | -I$(INC_DIR) \ 40 | -Wall \ 41 | -O1 \ 42 | -fno-pic \ 43 | -fno-builtin \ 44 | -fno-rtti \ 45 | -fno-exceptions \ 46 | -fno-threadsafe-statics \ 47 | -fno-stack-protector \ 48 | -Wno-missing-field-initializers 49 | ``` 50 | 51 | - `-m32` generates 32-bit code. 52 | - `-c` only compiles code but does not link them. 53 | - `-std=c++20` enables *C++20* features. 54 | - `-fno-pic` generates position-dependent code without a global offset table. Our kernel does not need address relocation or dynamic libraries. 55 | - `-O1` can reduce the stack size for local variables. Otherwise threads may have stack overflow errors. 56 | 57 | We also have to add the following options since our kernel does not have *C++* runtime. 58 | 59 | - `-fno-builtin` 60 | - `-fno-rtti` 61 | - `-fno-exceptions` 62 | - `-fno-threadsafe-statics` 63 | - `-fno-stack-protector` 64 | 65 | ## Linking 66 | 67 | We use the `ld` command to link *assembly* and *C++* files. 68 | 69 | ```make 70 | # Makefile 71 | 72 | LDFLAGS := -m elf_i386 \ 73 | -Ttext $(CODE_ENTRY) \ 74 | -e main 75 | ``` 76 | 77 | - `-m elf_i386` generates 32-bit ELF files. 78 | - `-e main` uses the `main` function as the entry point. 79 | - `-Ttext $(CODE_ENTRY)` uses `CODE_ENTRY` as the starting address of the `text` segment. 80 | 81 | We can get three binary files after linking: 82 | 83 | - `mbr.bin` is the master boot record called by BIOS. It loads `loader.bin`. 84 | - `loader.bin` enables memory segmentation, enters protected mode, enables memory paging and loads `kernel.bin`. 85 | - `kernel.bin` is our kernel. 86 | 87 | ## Installation 88 | 89 | The `dd` command can write generated binary files into the virtual system drive `kernel.img`. The following table shows their *Logical Block Addressing (LBA)* ranges. The size of a disk sector is 512 bytes. 90 | 91 | | Module | LBAs | 92 | | :----------: | :-----: | 93 | | `mbr.bin` | `0` | 94 | | `loader.bin` | `1`-`5` | 95 | | `kernel.bin` | `6`- | 96 | 97 | ```make 98 | # Makefile 99 | 100 | dd if=$(BUILD_DIR)/boot/mbr.bin of=$(DISK) bs=512 count=1 conv=notrunc 101 | dd if=$(BUILD_DIR)/boot/loader.bin of=$(DISK) seek=1 bs=512 count=$(LOADER_SECTOR_COUNT) conv=notrunc 102 | dd if=$(BUILD_DIR)/kernel.bin of=$(DISK) bs=512 seek=$(KRNL_START_SECTOR) count=$(KRNL_SECTOR_COUNT) conv=notrunc 103 | ``` 104 | 105 | This table shows all `make` targets in `Makefile`. 106 | 107 | | Target | Usage | 108 | | :-------: | :---------------------------------------------------------------: | 109 | | `build` | Building all modules by default | 110 | | `boot` | Building `mbr.bin` and `loader.bin` | 111 | | `kernel` | Building `kernel.bin` | 112 | | `user` | Building user modules | 113 | | `install` | Installing all modules into the virtual system drive `kernel.img` | 114 | | `clean` | Cleaning up built files | -------------------------------------------------------------------------------- /include/kernel/selector/sel.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file sel.h 3 | * @brief Segment selectors. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/descriptor/gdt/idx.h" 12 | #include "kernel/krnl.h" 13 | #include "kernel/util/bit.h" 14 | 15 | namespace sel { 16 | 17 | //! Types of descriptor tables. 18 | enum class DescTabType { 19 | //! The global descriptor table. 20 | Gdt = 0, 21 | //! The local descriptor table. 22 | Ldt = 1 23 | }; 24 | 25 | /** 26 | * @brief The segment selector. 27 | * 28 | * @details 29 | * It can identify a descriptor in a descriptor table. 30 | * 31 | * @code 32 | * 15-3 2 1-0 33 | * ┌───────┬────┬─────┐ 34 | * │ Index │ TI │ RPL │ 35 | * └───────┴────┴─────┘ 36 | * ▲ 37 | * └─ 0: The index is for the global descriptor table. 38 | * 1: The index is for a local descriptor table. 39 | * @endcode 40 | */ 41 | class Selector { 42 | public: 43 | /** 44 | * @brief Create a selector. 45 | * 46 | * @param tab The type of the descriptor table. 47 | * @param rpl The requested privilege level. 48 | * @param idx The descriptor index. 49 | */ 50 | constexpr Selector(const DescTabType tab, const Privilege rpl, const stl::size_t idx) noexcept { 51 | if (tab == DescTabType::Ldt) { 52 | bit::SetBit(sel_, tab_pos); 53 | } 54 | 55 | bit::SetBits(sel_, static_cast(rpl), rpl_pos, rpl_len); 56 | bit::SetBits(sel_, idx, idx_pos, idx_len); 57 | } 58 | 59 | constexpr Selector(const stl::uint16_t sel = 0) noexcept : sel_ {sel} {} 60 | 61 | constexpr operator stl::uint16_t() const noexcept { 62 | return sel_; 63 | } 64 | 65 | constexpr DescTabType GetTabType() const noexcept { 66 | return bit::IsBitSet(sel_, tab_pos) ? DescTabType::Ldt : DescTabType::Gdt; 67 | } 68 | 69 | constexpr Privilege GetRpl() const noexcept { 70 | return static_cast(bit::GetBits(sel_, rpl_pos, rpl_len)); 71 | } 72 | 73 | constexpr Selector& SetRpl(const Privilege rpl) noexcept { 74 | bit::SetBits(sel_, static_cast(rpl), rpl_pos, rpl_len); 75 | return *this; 76 | } 77 | 78 | constexpr stl::size_t GetIdx() const noexcept { 79 | return bit::GetBits(sel_, idx_pos, idx_len); 80 | } 81 | 82 | constexpr Selector& Clear() noexcept { 83 | sel_ = 0; 84 | return *this; 85 | } 86 | 87 | private: 88 | static constexpr stl::size_t rpl_pos {0}; 89 | static constexpr stl::size_t rpl_len {2}; 90 | static constexpr stl::size_t tab_pos {rpl_pos + rpl_len}; 91 | static constexpr stl::size_t idx_pos {tab_pos + 1}; 92 | static constexpr stl::size_t idx_len {13}; 93 | 94 | stl::uint16_t sel_ {0}; 95 | }; 96 | 97 | static_assert(sizeof(Selector) == sizeof(stl::uint16_t)); 98 | 99 | //! The kernel selector for code. 100 | inline constexpr Selector krnl_code {DescTabType::Gdt, Privilege::Zero, gdt::idx::krnl_code}; 101 | //! The kernel selector for data. 102 | inline constexpr Selector krnl_data {DescTabType::Gdt, Privilege::Zero, gdt::idx::krnl_data}; 103 | //! The kernel selector for the stack. 104 | inline constexpr Selector krnl_stack {krnl_data}; 105 | //! The kernel selector for the VGA text buffer. 106 | inline constexpr Selector gs {DescTabType::Gdt, Privilege::Zero, gdt::idx::gs}; 107 | //! The kernel selector for the task state segment. 108 | inline constexpr Selector tss {DescTabType::Gdt, Privilege::Zero, gdt::idx::tss}; 109 | 110 | //! The user selector for code. 111 | inline constexpr Selector usr_code {DescTabType::Gdt, Privilege::Three, gdt::idx::usr_code}; 112 | //! The user selector for data. 113 | inline constexpr Selector usr_data {DescTabType::Gdt, Privilege::Three, gdt::idx::usr_data}; 114 | //! The user selector for the stack. 115 | inline constexpr Selector usr_stack {usr_data}; 116 | 117 | } // namespace sel -------------------------------------------------------------------------------- /include/kernel/io/disk/file/inode.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file inode.h 3 | * @brief The index node. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/stl/array.h" 12 | #include "kernel/util/metric.h" 13 | #include "kernel/util/tag_list.h" 14 | 15 | namespace io::fs { 16 | 17 | /** 18 | * @brief The index node. 19 | * 20 | * @details 21 | * In @em Linux, the index node describes a file system object such as a file or a directory. 22 | * Each index node stores the attributes and disk block locations of the object's data. 23 | * 24 | * In our system, an index node has 12 direct blocks and a single indirect block table. 25 | * The size of a single indirect block table is one sector, so it can save 128 block addresses. 26 | * Totally, an index node has up to 140 blocks for data storage. 27 | * 28 | * @code 29 | * Index Node 30 | * ┌────────────┬───────────────┬───────────────────────┐ 31 | * │ Attributes │ Direct Blocks │ Single Indirect Block │ 32 | * └────────────┴───────────────┴───────────────────────┘ 33 | * │ │ │ 34 | * ▼ ▼ │ 35 | * ┌───────┐ ┌───────┐ ▼ 36 | * │ Block │ │ Block │ ┌───────────────┐ 37 | * └───────┘ └───────┘ │ Direct Blocks │ 38 | * └───────────────┘ 39 | * │ │ 40 | * ▼ ▼ 41 | * ┌───────┐ ┌───────┐ 42 | * │ Block │ │ Block │ 43 | * └───────┘ └───────┘ 44 | * @endcode 45 | * 46 | * Index nodes do not indicate their data type. 47 | * Instead, we use directory entries @p DirEntry to determine whether an item is a file or a directory. 48 | */ 49 | struct IdxNode { 50 | static constexpr stl::size_t direct_block_count {12}; 51 | 52 | static IdxNode& GetByTag(const TagList::Tag&) noexcept; 53 | 54 | IdxNode() noexcept; 55 | 56 | IdxNode(const IdxNode&) = delete; 57 | 58 | IdxNode& Init() noexcept; 59 | 60 | /** 61 | * @brief Close the index node. 62 | * 63 | * @details 64 | * If it is not used by any task, it will be removed from the list of open index nodes, 65 | * and its memory will be rreed. 66 | */ 67 | void Close() noexcept; 68 | 69 | bool IsOpen() const noexcept; 70 | 71 | stl::size_t GetIndirectTabLba() const noexcept; 72 | 73 | stl::size_t GetDirectLba(stl::size_t idx) const noexcept; 74 | 75 | IdxNode& SetIndirectTabLba(stl::size_t) noexcept; 76 | 77 | IdxNode& SetDirectLba(stl::size_t idx, stl::size_t lba) noexcept; 78 | 79 | //! Clone a new index node but reset its open times, writing status and tag. 80 | void CloneToPure(IdxNode&) const noexcept; 81 | 82 | //! The tag for the list of open index nodes. 83 | TagList::Tag tag; 84 | 85 | //! The ID or index. 86 | stl::size_t idx {npos}; 87 | 88 | /** 89 | * @brief The data size. 90 | * 91 | * @details 92 | * - If the index node refers to a file, it is the size of the file. 93 | * - If the index node refers to a directory, it is the total size of all entries in the directory. 94 | */ 95 | stl::size_t size {0}; 96 | 97 | //! The number of times the file has been opened. 98 | stl::size_t open_times {0}; 99 | 100 | //! Whether the file is being written. 101 | bool write_deny {false}; 102 | 103 | private: 104 | //! The LBAs of direct blocks. 105 | stl::array direct_lbas_; 106 | 107 | /** 108 | * @brief The LBA of the single indirect block table. 109 | * 110 | * @details 111 | * Each entry in the single indirect block table is a block's LBA. 112 | */ 113 | stl::size_t indirect_tab_lba_; 114 | }; 115 | 116 | //! The index of the root directory's index node. 117 | inline constexpr stl::size_t root_inode_idx {0}; 118 | 119 | } // namespace io::fs -------------------------------------------------------------------------------- /include/kernel/io/disk/ide.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ide.h 3 | * @brief The IDE channel. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/io/disk/disk.h" 12 | #include "kernel/stl/mutex.h" 13 | #include "kernel/stl/semaphore.h" 14 | 15 | namespace io { 16 | 17 | //! The IDE channel. 18 | class IdeChnl { 19 | public: 20 | //! A machine usually has two channels: primary and secondary channels. 21 | enum class Type { Invalid, Primary, Secondary }; 22 | 23 | //! Each channel has up to two disks. 24 | static constexpr stl::size_t max_disk_count {2}; 25 | 26 | using Disks = stl::array; 27 | 28 | IdeChnl() noexcept = default; 29 | 30 | IdeChnl(const IdeChnl&) = delete; 31 | 32 | IdeChnl& SetType(Type) noexcept; 33 | 34 | Type GetType() const noexcept; 35 | 36 | IdeChnl& SetIntrNum(stl::size_t) noexcept; 37 | 38 | IdeChnl& SetName(stl::string_view) noexcept; 39 | 40 | const IdeChnl& NeedToWaitForIntr(bool wait = true) const noexcept; 41 | 42 | IdeChnl& NeedToWaitForIntr(bool wait = true) noexcept; 43 | 44 | bool IsWaitingForIntr() const noexcept; 45 | 46 | stl::size_t GetIntrNum() const noexcept; 47 | 48 | stl::uint16_t GetSectorCountPort() const noexcept; 49 | 50 | stl::uint16_t GetLbaLowPort() const noexcept; 51 | 52 | stl::uint16_t GetLbaMidPort() const noexcept; 53 | 54 | stl::uint16_t GetLbaHighPort() const noexcept; 55 | 56 | stl::uint16_t GetDevicePort() const noexcept; 57 | 58 | stl::uint16_t GetStatusPort() const noexcept; 59 | 60 | stl::uint16_t GetAltStatusPort() const noexcept; 61 | 62 | stl::uint16_t GetCmdPort() const noexcept; 63 | 64 | stl::uint16_t GetDataPort() const noexcept; 65 | 66 | stl::uint16_t GetErrorPort() const noexcept; 67 | 68 | stl::uint16_t GetCtrlPort() const noexcept; 69 | 70 | Disk& GetMasterDisk() noexcept; 71 | 72 | Disk& GetSlaveDisk() noexcept; 73 | 74 | Disk& GetDisk(stl::size_t) noexcept; 75 | 76 | Disks& GetDisks() noexcept; 77 | 78 | const Disk& GetMasterDisk() const noexcept; 79 | 80 | const Disk& GetSlaveDisk() const noexcept; 81 | 82 | const Disk& GetDisk(stl::size_t) const noexcept; 83 | 84 | const Disks& GetDisks() const noexcept; 85 | 86 | stl::mutex& GetLock() const noexcept; 87 | 88 | /** 89 | * @brief Block the thread. 90 | * 91 | * @details 92 | * Call this method when we need to wait for the disk to finish an operation. 93 | */ 94 | void Block() const noexcept; 95 | 96 | /** 97 | * @brief Unblock the thread. 98 | * 99 | * @details 100 | * Call this method when the disk has finished its operation. 101 | */ 102 | void Unblock() const noexcept; 103 | 104 | stl::string_view GetName() const noexcept; 105 | 106 | private: 107 | static constexpr stl::size_t name_len {8}; 108 | 109 | stl::uint16_t GetBasePort() const noexcept; 110 | 111 | mutable stl::mutex mtx_; 112 | 113 | stl::array name_; 114 | 115 | //! The channel type. 116 | Type type_ {Type::Invalid}; 117 | 118 | //! The base I/O port. 119 | stl::uint16_t base_port_ {0}; 120 | 121 | //! The interrupt number. 122 | stl::size_t intr_num_ {0}; 123 | 124 | //! The disks under the channel. 125 | Disks disks_; 126 | 127 | /** 128 | * @brief 129 | * Whether the channel is waiting for an interrupt. 130 | * 131 | * @details 132 | * If an operation is submitted to the disk, we need to wait for an interrupt. 133 | */ 134 | mutable bool waiting_intr_ {false}; 135 | 136 | //! Whether the disk has finished its operation. 137 | mutable stl::binary_semaphore disk_done_ {0}; 138 | }; 139 | 140 | //! A machine usually has two IDE channels. 141 | inline constexpr stl::size_t max_ide_chnl_count {2}; 142 | 143 | using IdeChnls = stl::array; 144 | 145 | //! Get IDE channels. 146 | IdeChnls& GetIdeChnls() noexcept; 147 | 148 | //! Get the number of IDE channels. 149 | stl::size_t GetIdeChnlCount() noexcept; 150 | 151 | } // namespace io -------------------------------------------------------------------------------- /include/kernel/io/file/path.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file path.h 3 | * @brief File path operations. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/stl/array.h" 12 | #include "kernel/stl/string_view.h" 13 | 14 | namespace io { 15 | 16 | class Path { 17 | public: 18 | //! The maximum length of a path. 19 | static constexpr stl::size_t max_len {512}; 20 | //! The maximum length of a file or directory name. 21 | static constexpr stl::size_t max_name_len {16}; 22 | 23 | //! The root directory. 24 | static constexpr stl::string_view root_dir_name {"/"}; 25 | //! The current directory. 26 | static constexpr stl::string_view curr_dir_name {"."}; 27 | //! The parent directory. 28 | static constexpr stl::string_view parent_dir_name {".."}; 29 | 30 | static constexpr char separator {'/'}; 31 | 32 | //! A callback function used to visit names in a path. 33 | using Visitor = bool (*)(stl::string_view sub_path, stl::string_view name, void* arg) noexcept; 34 | 35 | static bool IsRootDir(stl::string_view) noexcept; 36 | 37 | static bool IsDir(stl::string_view) noexcept; 38 | 39 | static bool IsAbsolute(stl::string_view) noexcept; 40 | 41 | /** 42 | * @brief Get the depth of a path. 43 | * 44 | * @details 45 | * @code {.cpp} 46 | * Path::GetDepth("/") == 0; 47 | * Path::GetDepth("/a") == 1; 48 | * Path::GetDepth("/a/b") == 2; 49 | * @endcode 50 | */ 51 | static stl::size_t GetDepth(stl::string_view) noexcept; 52 | 53 | /** 54 | * @brief Get the file name of a path. 55 | * 56 | * @details 57 | * @code {.cpp} 58 | * Path::GetFileName("/") == ""; 59 | * Path::GetFileName("/a") == "a"; 60 | * Path::GetFileName("/a/") == ""; 61 | * @endcode 62 | */ 63 | static stl::string_view GetFileName(stl::string_view) noexcept; 64 | 65 | static Path Join(stl::string_view parent, stl::string_view child) noexcept; 66 | 67 | /** 68 | * @brief Parse the first name in a path. 69 | * 70 | * @param[in] path A path to be parsed. 71 | * @param[out] name The first name in the path. 72 | * @return The remaining path. 73 | */ 74 | static stl::string_view Parse(stl::string_view path, 75 | stl::array& name) noexcept; 76 | 77 | /** 78 | * @brief Visit all names in a path. 79 | * 80 | * @param path A path to be visited. 81 | * @param visitor 82 | * A callback function accepting each name and the remaining path. 83 | * If it returns @p false, the visiting stops and returns @p false. 84 | * @param arg An optional argument passed to the callback function. 85 | * @return Whether all names have been visited. 86 | * 87 | * @details 88 | * For example, the path @p "/a/b/c" will be visited as: 89 | * 1. The name is @p "a". The subpath is @p "/b/c". 90 | * 2. The name is @p "b". The subpath is @p "/c". 91 | * 3. The name is @p "c". The subpath is @p "". 92 | */ 93 | static bool Visit(stl::string_view path, Visitor visitor, void* arg = nullptr) noexcept; 94 | 95 | Path(stl::string_view = nullptr) noexcept; 96 | 97 | Path(const char*) noexcept; 98 | 99 | bool IsRootDir() const noexcept; 100 | 101 | bool IsDir() const noexcept; 102 | 103 | bool IsAbsolute() const noexcept; 104 | 105 | stl::size_t GetDepth() const noexcept; 106 | 107 | Path Parse(stl::array& name) const noexcept; 108 | 109 | Path Join(stl::string_view child) const noexcept; 110 | 111 | Path& Join(stl::string_view child) noexcept; 112 | 113 | bool Visit(Visitor, void* arg = nullptr) const noexcept; 114 | 115 | Path& Clear() noexcept; 116 | 117 | stl::string_view GetFileName() const noexcept; 118 | 119 | stl::string_view GetPath() const noexcept; 120 | 121 | stl::size_t GetSize() const noexcept; 122 | 123 | operator stl::string_view() const noexcept { 124 | return path_.data(); 125 | } 126 | 127 | private: 128 | stl::array path_; 129 | }; 130 | 131 | } // namespace io -------------------------------------------------------------------------------- /src/kernel/util/bitmap.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/util/bitmap.h" 2 | #include "kernel/debug/assert.h" 3 | #include "kernel/stl/cstring.h" 4 | #include "kernel/stl/utility.h" 5 | #include "kernel/util/bit.h" 6 | #include "kernel/util/metric.h" 7 | 8 | Bitmap& Bitmap::Init(void* const bits, const stl::size_t byte_len, const bool clear) noexcept { 9 | dbg::Assert(bits && byte_len > 0); 10 | bits_ = static_cast(bits); 11 | byte_len_ = byte_len; 12 | if (clear) { 13 | Clear(); 14 | } 15 | 16 | return *this; 17 | } 18 | 19 | Bitmap::Bitmap(Bitmap&& o) noexcept { 20 | swap(o); 21 | o.bits_ = nullptr; 22 | o.byte_len_ = 0; 23 | } 24 | 25 | Bitmap& Bitmap::operator=(Bitmap&& o) noexcept { 26 | swap(o); 27 | o.bits_ = nullptr; 28 | o.byte_len_ = 0; 29 | return *this; 30 | } 31 | 32 | const void* Bitmap::GetBits() const noexcept { 33 | return bits_; 34 | } 35 | 36 | void Bitmap::swap(Bitmap& o) noexcept { 37 | using stl::swap; 38 | swap(bits_, o.bits_); 39 | swap(byte_len_, o.byte_len_); 40 | } 41 | 42 | stl::size_t Bitmap::GetCapacity() const noexcept { 43 | return byte_len_ * bit::byte_len; 44 | } 45 | 46 | stl::size_t Bitmap::GetByteLen() const noexcept { 47 | return byte_len_; 48 | } 49 | 50 | Bitmap::Bitmap(void* const bits, const stl::size_t byte_len, const bool clear) noexcept { 51 | Init(bits, byte_len, clear); 52 | } 53 | 54 | bool Bitmap::IsAlloc(const stl::size_t idx) const noexcept { 55 | dbg::Assert(bits_); 56 | const auto byte_idx {idx / bit::byte_len}; 57 | const auto bit_idx {idx % bit::byte_len}; 58 | dbg::Assert(byte_idx < byte_len_); 59 | return bit::IsBitSet(bits_[byte_idx], bit_idx); 60 | } 61 | 62 | Bitmap& Bitmap::Set(const stl::size_t begin, const stl::size_t count) noexcept { 63 | dbg::Assert(bits_); 64 | dbg::Assert(count > 0); 65 | for (stl::size_t i {0}; i != count; ++i) { 66 | SetBit(i + begin, true); 67 | } 68 | 69 | return *this; 70 | } 71 | 72 | Bitmap& Bitmap::Reset(const stl::size_t begin, const stl::size_t count) noexcept { 73 | dbg::Assert(bits_); 74 | dbg::Assert(count > 0); 75 | for (stl::size_t i {0}; i != count; ++i) { 76 | SetBit(i + begin, false); 77 | } 78 | 79 | return *this; 80 | } 81 | 82 | Bitmap& Bitmap::SetBit(const stl::size_t idx, const bool val) noexcept { 83 | dbg::Assert(bits_); 84 | const auto byte_idx {idx / bit::byte_len}; 85 | const auto bit_idx {idx % bit::byte_len}; 86 | dbg::Assert(byte_idx < byte_len_); 87 | if (val) { 88 | bit::SetBit(bits_[byte_idx], bit_idx); 89 | } else { 90 | bit::ResetBit(bits_[byte_idx], bit_idx); 91 | } 92 | 93 | return *this; 94 | } 95 | 96 | Bitmap& Bitmap::Free(const stl::size_t begin, const stl::size_t count) noexcept { 97 | return Reset(begin, count); 98 | } 99 | 100 | Bitmap& Bitmap::Clear() noexcept { 101 | stl::memset(bits_, 0, byte_len_); 102 | return *this; 103 | } 104 | 105 | Bitmap& Bitmap::ForceAlloc(const stl::size_t begin, const stl::size_t count) noexcept { 106 | return count > 0 ? Set(begin, count) : *this; 107 | } 108 | 109 | stl::size_t Bitmap::Alloc(const stl::size_t count) noexcept { 110 | dbg::Assert(bits_ && count > 0); 111 | stl::size_t byte_idx {0}; 112 | while (byte_idx != byte_len_ && bits_[byte_idx] == 0xFF) { 113 | ++byte_idx; 114 | } 115 | 116 | if (byte_idx == byte_len_) { 117 | return npos; 118 | } 119 | 120 | stl::size_t bit_idx {0}; 121 | while (bit_idx != bit::byte_len && bit::IsBitSet(bits_[byte_idx], bit_idx)) { 122 | ++bit_idx; 123 | } 124 | 125 | dbg::Assert(bit_idx < bit::byte_len); 126 | bit_idx += byte_idx * bit::byte_len; 127 | stl::size_t found_count {0}; 128 | for (auto i {bit_idx}; i != byte_len_ * bit::byte_len; ++i) { 129 | if (!IsAlloc(i)) { 130 | if (++found_count == count) { 131 | const auto begin {i - count + 1}; 132 | Set(begin, count); 133 | return begin; 134 | } 135 | } else { 136 | found_count = 0; 137 | } 138 | } 139 | 140 | return npos; 141 | } 142 | 143 | void swap(Bitmap& lhs, Bitmap& rhs) noexcept { 144 | lhs.swap(rhs); 145 | } -------------------------------------------------------------------------------- /docs/Kernel/Interrupts.md: -------------------------------------------------------------------------------- 1 | # Interrupts 2 | 3 | In *x86* systems, interrupts are managed by the *Interrupt Descriptor Table (IDT)* in protected mode. The entries in the interrupt descriptor table are called *Gates*, including *Interrupt Gates*, *Task Gates* and *Trap Gates*. We only use interrupt gates. 4 | 5 | We have two arrays for interrupt entries and handlers: 6 | 7 | - `intr::intr_entries` is an array of interrupt entries, which are registered in the interrupt descriptor table. 8 | 9 | ```nasm 10 | ; src/kernel/interrupt/intr.asm 11 | 12 | intr_entries: 13 | intr_entry 0x00, zero 14 | intr_entry 0x01, zero 15 | intr_entry 0x02, zero 16 | intr_entry 0x03, zero 17 | ; ... 18 | ``` 19 | 20 | ```c++ 21 | // src/kernel/interrupt/intr.cpp 22 | 23 | void InitIntrDescTab() noexcept { 24 | // Register interrupt entries. 25 | for (stl::size_t i {0}; i != GetIntrDescTab().GetCount(); ++i) { 26 | GetIntrDescTab()[i] = {sel::krnl_code, intr_entries[i], {desc::SysType::Intr32, Privilege::Zero}}; 27 | } 28 | // ... 29 | } 30 | ``` 31 | 32 | - `intr::intr_handlers` is an array of interrupt handlers, called from `intr::intr_entries`. 33 | 34 | ```c++ 35 | // src/kernel/interrupt/intr.cpp 36 | 37 | Handler intr_handlers[count] {}; 38 | ``` 39 | 40 | ```nasm 41 | ; src/kernel/interrupt/intr.asm 42 | 43 | Intr%1HandlerEntry: 44 | ; ... 45 | ; Call the interrupt handler from the entry. 46 | push %1 47 | call [intr_handlers + %1 * B(4)] 48 | ; ... 49 | ``` 50 | 51 | When an interrupt `i` occurs, the CPU: 52 | 53 | 1. Get the address of the interrupt descriptor table `intr::IntrDescTab` from the `IDTR` register. 54 | 2. Get the gate descriptor `desc::GateDesc` by the interrupt number `i` in the interrupt descriptor table. 55 | 3. Get the code segment selector `sel::Selector` where the interrupt entry is located from the gate descriptor. 56 | 4. Get the code segment descriptor `desc::SegDesc` in the global descriptor table `gdt::GlobalDescTab`. 57 | 5. Get the base address of the code segment from the segment descriptor. 58 | 6. Get the interrupt entry offset from the gate descriptor. 59 | 7. Jump to the entry point `intr::intr_entries[i]`. 60 | 8. Call the interrupt handler `intr::intr_handlers[i]` after registers are saved. 61 | 9. Restore registers and exit the interrupt. 62 | 63 | ```mermaid 64 | sequenceDiagram 65 | participant CPU 66 | box 67 | participant IDTR 68 | participant IDT 69 | participant Gate Descriptor 70 | end 71 | box 72 | participant GDT 73 | participant Segment Descriptor 74 | end 75 | box 76 | participant Interrupt Entry 77 | participant Interrupt Handler 78 | end 79 | 80 | CPU ->> CPU : Trigger an interrupt 81 | CPU ->> IDTR: Get the address of the interrupt descriptor table. 82 | CPU ->> IDT: Get the gate descriptor by the interrupt number 83 | CPU ->> Gate Descriptor: Get the code segment selector 84 | CPU ->> GDT: Get the code segment descriptor 85 | CPU ->> Segment Descriptor: Get the base address of the code segment 86 | CPU ->> Gate Descriptor: Get the interrupt entry offset 87 | CPU ->> Interrupt Entry: Jump to the interrupt entry 88 | 89 | Interrupt Entry ->> Interrupt Handler: Save registers and call the handler 90 | Interrupt Handler ->> Interrupt Handler: Process the interrupt 91 | Interrupt Handler ->> Interrupt Entry: Restore registers 92 | Interrupt Entry ->> CPU: Exit the interrupt 93 | ``` 94 | 95 | We have two table wrappers for them in `include/kernel/interrupt/intr.h`: 96 | 97 | - `intr::IntrDescTab` manages `intr::intr_entries`. 98 | - `intr::IntrHandlerTab` manages `intr::intr_handlers`. 99 | 100 | ```mermaid 101 | classDiagram 102 | class IntrHandlerTab~count~ { 103 | Register(idx, name, handler) 104 | } 105 | 106 | IntrHandlerTab~count~ ..> intr_handlers 107 | 108 | class DescTab~T~ { 109 | #GetDesc(idx) T* 110 | } 111 | 112 | DescTab~T~ <|.. DescTabArray~T, count~ 113 | DescTabArray~T, count~ <|-- IntrDescTab~GateDesc~ 114 | IntrDescTab~GateDesc~ ..> intr_entries 115 | 116 | intr_entries ..> intr_handlers 117 | ``` -------------------------------------------------------------------------------- /include/kernel/util/block_queue.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file block_queue.h 3 | * @brief The block queue. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/debug/assert.h" 12 | #include "kernel/interrupt/intr.h" 13 | #include "kernel/stl/array.h" 14 | #include "kernel/stl/mutex.h" 15 | #include "kernel/thread/thd.h" 16 | 17 | /** 18 | * @brief The block queue based on a circular buffer. 19 | * 20 | * @details 21 | * For a queue of size @p n, the circular buffer has `n + 1` slots. 22 | * When the queue is empty, `head == tail`. 23 | * When the queue is full, `head + 1 == tail`. 24 | * 25 | * @warning 26 | * This queue only works on a single-core processor. 27 | */ 28 | template 29 | class BlockQueue { 30 | static_assert(n > 0); 31 | 32 | public: 33 | static constexpr stl::size_t GetNextPos(const stl::size_t pos) noexcept { 34 | return (pos + 1) % (n + 1); 35 | } 36 | 37 | BlockQueue() noexcept = default; 38 | 39 | BlockQueue(const BlockQueue&) = delete; 40 | 41 | bool IsFull() const noexcept { 42 | dbg::Assert(!intr::IsIntrEnabled()); 43 | return GetNextPos(head_) == tail_; 44 | } 45 | 46 | bool IsEmpty() const noexcept { 47 | dbg::Assert(!intr::IsIntrEnabled()); 48 | return head_ == tail_; 49 | } 50 | 51 | /** 52 | * @brief Push an object into the queue. 53 | * 54 | * @details 55 | * If the queue is full, the current thread will be blocked. 56 | * When another consumer thread pops elements, the blocked thread will be resumed. 57 | */ 58 | BlockQueue& Push(T val) noexcept { 59 | dbg::Assert(!intr::IsIntrEnabled()); 60 | while (IsFull()) { 61 | // Only one thread can change the waiting producer at a time. 62 | const stl::lock_guard guard {mtx_}; 63 | // The queue is full. The current thread is waiting as a producer. 64 | Wait(prod_); 65 | } 66 | 67 | buf_[head_] = stl::move(val); 68 | head_ = GetNextPos(head_); 69 | if (consr_) { 70 | // Wake up a consumer if it is waiting. 71 | WakeUp(consr_); 72 | } 73 | 74 | return *this; 75 | } 76 | 77 | /** 78 | * @brief Push an object into the queue. 79 | * 80 | * @details 81 | * If the queue is empty, the current thread will be blocked. 82 | * When another producer thread pushes elements, the blocked thread will be resumed. 83 | */ 84 | T Pop() noexcept { 85 | dbg::Assert(!intr::IsIntrEnabled()); 86 | while (IsEmpty()) { 87 | // Only one thread can change the waiting consumer at a time. 88 | const stl::lock_guard guard {mtx_}; 89 | // The queue is empty. The current thread is waiting as a consumer. 90 | Wait(consr_); 91 | } 92 | 93 | const auto val {stl::move(buf_[tail_])}; 94 | tail_ = GetNextPos(tail_); 95 | if (prod_) { 96 | // Wake up a producer if it is waiting. 97 | WakeUp(prod_); 98 | } 99 | 100 | return val; 101 | } 102 | 103 | private: 104 | /** 105 | * @brief Block the current thread. 106 | * 107 | * @param[out] waiter 108 | * The input parameter should be @p nullptr. 109 | * The output is a pointer to the current thread. 110 | */ 111 | static void Wait(tsk::Thread*& waiter) noexcept { 112 | dbg::Assert(!waiter); 113 | waiter = &tsk::Thread::GetCurrent(); 114 | waiter->Block(tsk::Thread::Status::Blocked); 115 | } 116 | 117 | /** 118 | * @brief Wake up a thread. 119 | * 120 | * @param waiter 121 | * A blocked thread. 122 | * The parameter will be set to @p nullptr after waking it up. 123 | */ 124 | static void WakeUp(tsk::Thread*& waiter) noexcept { 125 | dbg::Assert(waiter); 126 | tsk::Thread::Unblock(*waiter); 127 | waiter = nullptr; 128 | } 129 | 130 | mutable stl::mutex mtx_; 131 | //! A waiting producer thread. 132 | tsk::Thread* prod_ {nullptr}; 133 | //! A waiting consumer thread. 134 | tsk::Thread* consr_ {nullptr}; 135 | stl::array buf_; 136 | stl::size_t head_ {0}; 137 | stl::size_t tail_ {0}; 138 | }; -------------------------------------------------------------------------------- /include/kernel/process/proc.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file proc.h 3 | * @brief User process management. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/memory/pool.h" 12 | #include "kernel/stl/array.h" 13 | #include "kernel/stl/string_view.h" 14 | #include "kernel/thread/thd.h" 15 | 16 | namespace tsk { 17 | 18 | //! The user process. 19 | class Process { 20 | public: 21 | //! The priority of the main thread. 22 | static constexpr stl::size_t default_priority {31}; 23 | 24 | //! Get the current thread's process, or @p nullptr for kernel threads. 25 | static Process* GetCurrent() noexcept; 26 | 27 | //! Fork the current process. 28 | static stl::size_t ForkCurrent() noexcept; 29 | 30 | //! Get the current process's ID, or @p 0 for kernel threads. 31 | static stl::size_t GetCurrPid() noexcept; 32 | 33 | /** 34 | * @brief Create and start a process. 35 | * 36 | * @param name A process name. 37 | * @param code The code entry. 38 | */ 39 | static Process& Create(stl::string_view name, void* code) noexcept; 40 | 41 | Process(const Process&) = delete; 42 | 43 | //! Get the virtual address pool of the process. 44 | const mem::VrAddrPool& GetVrAddrPool() const noexcept; 45 | 46 | mem::VrAddrPool& GetVrAddrPool() noexcept; 47 | 48 | //! Get the memory block descriptor table of the process. 49 | const mem::MemBlockDescTab& GetMemBlockDescTab() const noexcept; 50 | 51 | mem::MemBlockDescTab& GetMemBlockDescTab() noexcept; 52 | 53 | //! Get the file descriptor file. 54 | const FileDescTab& GetFileDescTab() const noexcept; 55 | 56 | FileDescTab& GetFileDescTab() noexcept; 57 | 58 | const Thread& GetMainThread() const noexcept; 59 | 60 | Thread& GetMainThread() noexcept; 61 | 62 | //! Get the page directory table of the process. 63 | const mem::PageEntry* GetPageDir() const noexcept; 64 | 65 | //! Get the parent process's ID, or @p npos if the process has no parent. 66 | stl::size_t GetPid() const noexcept; 67 | 68 | //! Get the process's ID. 69 | stl::size_t GetParentPid() const noexcept; 70 | 71 | /** 72 | * @brief Fork the process. 73 | * 74 | * @return 75 | * Return values are different in two processes. 76 | * - The child process's ID in the parent process. 77 | * - @p 0 in the child process. 78 | */ 79 | stl::size_t Fork() const noexcept; 80 | 81 | private: 82 | //! The virtual base address, same as @em Linux. 83 | static constexpr stl::uintptr_t image_base {0x8048000}; 84 | 85 | //! Allocate a new process ID. 86 | static stl::size_t CreateNewPid() noexcept; 87 | 88 | Process& Init(stl::string_view name, void* code) noexcept; 89 | 90 | //! Initialize the virtual address pool of the process. 91 | Process& InitVrAddrPool() noexcept; 92 | 93 | //! Initialize the page directory table of the process. 94 | Process& InitPageDir() noexcept; 95 | 96 | //! Initialize the memory block descriptor table of the process. 97 | Process& InitMemBlockDescTab() noexcept; 98 | 99 | Thread& CreateThread(stl::string_view name, stl::size_t priority, Thread::Callback callback, 100 | void* arg = nullptr) noexcept; 101 | 102 | //! Copy the file descriptor file to another process. 103 | const Process& CopyFileDescTabTo(Process&) const noexcept; 104 | 105 | Process& CopyFileDescTabTo(Process&) noexcept; 106 | 107 | //! Copy memory to another process. 108 | const Process& CopyMemTo(Process&, void* buf, stl::size_t buf_size) const noexcept; 109 | 110 | Process& CopyMemTo(Process&, void* buf, stl::size_t buf_size) noexcept; 111 | 112 | /** 113 | * @details 114 | * Each process has its own virtual address space. 115 | * Different virtual address pools allow processes to use the same virtual address, 116 | * but mapped to various physical addresses with the help of page directory tables. 117 | */ 118 | mem::VrAddrPool vr_addrs_; 119 | 120 | mem::MemBlockDescTab mem_block_descs_; 121 | 122 | /** 123 | * @details 124 | * Each process has its own page directory table. 125 | * Different page directory tables map the same virtual address in different processes to various physical addresses. 126 | */ 127 | mem::PageEntry* page_dir_ {nullptr}; 128 | 129 | stl::size_t pid_ {npos}; 130 | 131 | stl::size_t parent_pid_ {npos}; 132 | 133 | FileDescTab file_descs_; 134 | 135 | Thread* main_thd_ {nullptr}; 136 | }; 137 | 138 | } // namespace tsk -------------------------------------------------------------------------------- /include/kernel/descriptor/desc.inc: -------------------------------------------------------------------------------- 1 | ; Descriptors. 2 | 3 | %include "kernel/util/metric.inc" 4 | 5 | ; The descriptor table register. 6 | struc DescTabReg 7 | .limit: resw 1 8 | .base: resd 1 9 | endstruc 10 | 11 | ; The size of a descriptor in bytes. 12 | desc_size equ B(8) 13 | 14 | ; The segment descriptor. 15 | ; It is a part of memory segmentation, used for translating a logical address into a linear address. 16 | ; It describes the memory segment referred to in the logical address. 17 | ; ``` 18 | ; --------------------------------------------- High 32 bits --------------------------------------------- 19 | ; 31-24 23 22 21 20 19-16 15 14-13 12 11-8 7-0 20 | ; ┌────────────┬───┬─────┬───┬─────┬─────────────┬───┬─────┬───┬──────┬────────────┐ 21 | ; │ Base 31-24 │ G │ D/B │ L │ AVL │ Limit 19-16 │ P │ DPL │ S │ TYPE │ Base 23-16 │ 22 | ; └────────────┴───┴─────┴───┴─────┴─────────────┴───┴─────┴───┴──────┴────────────┘ 23 | ; ▲ ▲ ▲ ▲ ▲ 24 | ; │ │ │ │ └─ 0: The segment is a system segment. 25 | ; │ │ │ │ 1: The segment is a data or code segment. 26 | ; │ │ │ └─ 1: The segment presents. 27 | ; │ │ └─ 0: The segment is 32-bit. 28 | ; │ │ 1: The segment is 64-bit. 29 | ; │ └─ D (code segments): 0: The segment is 16-bit. 30 | ; │ 1: The segment is 32-bit. 31 | ; │ B (data segments): 0: The offset is 16-bit. 32 | ; │ 1: The offset is 64-bit. 33 | ; └─ 0: The limit is in units of bytes. 34 | ; 1: The limit is in units of 4 KB. 35 | ; --------------------------------------------- Low 32 bits --------------------------------------------- 36 | ; 31-16 15-0 37 | ; ┌───────────┬────────────┐ 38 | ; │ Base 15-0 │ Limit 15-0 │ 39 | ; └───────────┴────────────┘ 40 | ; ``` 41 | ; The segment limit in units of 4 KB. 42 | desc_g_4k equ 0b1000_0000_0000_0000_0000_0000 43 | ; The segment is 32-bit. 44 | desc_d_32 equ 0b100_0000_0000_0000_0000_0000 45 | ; The segment is 32-bit. 46 | desc_l_32 equ 0 47 | ; This is not used by hardware. 48 | desc_avl equ 0 49 | ; The segment presents. 50 | desc_p equ 0b1000_0000_0000_0000 51 | ; The descriptor privilege level is 0. 52 | desc_dpl_0 equ 0b000_0000_0000_0000 53 | ; The descriptor privilege level is 1. 54 | desc_dpl_1 equ 0b010_0000_0000_0000 55 | ; The descriptor privilege level is 2. 56 | desc_dpl_2 equ 0b100_0000_0000_0000 57 | ; The descriptor privilege level is 3. 58 | desc_dpl_3 equ 0b110_0000_0000_0000 59 | ; The segment is a system segment. 60 | desc_s_sys equ 0b0_0000_0000_0000 61 | ; The segment is a code segment. 62 | desc_s_code equ 0b1_0000_0000_0000 63 | ; The segment is a data segment. 64 | desc_s_data equ desc_s_code 65 | 66 | ; Code segments are executable, non-conforming, unreadable. 67 | desc_type_code equ 0b1000_0000_0000 68 | 69 | ; Data segments are non-executable, expand-up, non-conforming and writable. 70 | desc_type_data equ 0b0010_0000_0000 71 | 72 | ; The limit of code or data segments is `0xFFFFF`. 73 | ; They can span full 4 GB memory in 32-bit mode when the granularity flag is set. 74 | desc_limit_code_16_19_bits equ 0b1111_0000_0000_0000_0000 75 | desc_limit_data_16_19_bits equ desc_limit_code_16_19_bits 76 | 77 | ; The limit of the VGA text buffer is `(0xBFFFF - 0xB8000) / 4 KB = 7`. 78 | desc_limit_video_16_19_bits equ 0 79 | 80 | ; Code segments range from `0x00000000` to `0xFFFFFFFF`. 81 | desc_code_high_32_bits equ (0 << 24) + desc_g_4k + desc_d_32 + desc_l_32 \ 82 | + desc_avl + desc_limit_code_16_19_bits + desc_p \ 83 | + desc_dpl_0 + desc_s_code + desc_type_code + 0 84 | 85 | ; Data segments range from `0x00000000` to `0xFFFFFFFF`. 86 | desc_data_high_32_bits equ (0 << 24) + desc_g_4k + desc_d_32 + desc_l_32 \ 87 | + desc_avl + desc_limit_data_16_19_bits + desc_p \ 88 | + desc_dpl_0 + desc_s_data + desc_type_data + 0 89 | 90 | ; The VGA text buffer ranges from `0x000B8000` to `0x00007FFF`. 91 | desc_video_high_32_bits equ (0 << 24) + desc_g_4k + desc_d_32 + desc_l_32 \ 92 | + desc_avl + desc_limit_video_16_19_bits + desc_p \ 93 | + desc_dpl_0 + desc_s_data + desc_type_data + 0xB -------------------------------------------------------------------------------- /docs/Kernel/System Calls.md: -------------------------------------------------------------------------------- 1 | # System Calls 2 | 3 | System calls can be used in user mode and trigger a switch to kernel mode to execute privileged code. 4 | 5 | ## Interrupt Implementation 6 | 7 | We use interrupts to implement system calls. 8 | 9 | 1. Create a table of system call hanlders `sc::sys_call_handlers`. Each handler has an optional argument. 10 | 11 | ```c++ 12 | // src/kernel/syscall/call.cpp 13 | 14 | stl::uintptr_t sys_call_handlers[count] {}; 15 | ``` 16 | 17 | 2. The interrupt number `0x30` is used for system calls. 18 | 19 | ```nasm 20 | ; src/kernel/syscall/call.asm 21 | 22 | sys_call_intr_num equ 0x30 23 | ``` 24 | 25 | 3. Create an interrupt gate in the interrupt descriptor table and register the interrupt entry `SysCallEntry`. 26 | 27 | ```c++ 28 | // src/kernel/interrupt/intr.cpp 29 | 30 | void InitIntrDescTab() noexcept { 31 | // ... 32 | const auto sys_call {static_cast(Intr::SysCall)}; 33 | GetIntrDescTab()[sys_call] = {sel::krnl_code, intr_entries[sys_call], {desc::SysType::Intr32, Privilege::Three}}; 34 | } 35 | ``` 36 | 37 | 4. `SysCallEntry` uses `ECX` as an index to call a system call hanlder in `sc::sys_call_handlers` and passes `EAX` as its argument. 38 | 39 | ```nasm 40 | ; src/kernel/interrupt/intr.asm 41 | 42 | SysCallEntry: 43 | ; ... 44 | ; Call a handler. 45 | push eax 46 | call [sys_call_handlers + ecx * B(4)] 47 | ; ... 48 | ``` 49 | 50 | 5. Create a user-mode API `SysCall` which triggers a system call interrupt with a handler index and its argument. 51 | 52 | ```nasm 53 | ; src/kernel/syscall/call.asm 54 | 55 | SysCall: 56 | %push sys_call 57 | %stacksize flat 58 | %arg func:dword, arg:dword 59 | enter B(0), 0 60 | mov ecx, [func] 61 | mov eax, [arg] 62 | int sys_call_intr_num 63 | leave 64 | ret 65 | %pop 66 | ``` 67 | 68 | 6. For each system call, we can create a user-mode API where `SysCall` is called with a handler index and an optional argument. 69 | 70 | ```c++ 71 | // src/user/process/proc.cpp 72 | 73 | stl::size_t Process::GetCurrPid() noexcept { 74 | return SysCall(sc::SysCallType::GetCurrPid); 75 | } 76 | ``` 77 | 78 | ```mermaid 79 | sequenceDiagram 80 | box User Mode 81 | participant User-Mode API 82 | participant System Call API 83 | end 84 | box Kernel Mode 85 | participant System Call Interrupt Entry 86 | participant Kernel-Mode Handler 87 | end 88 | 89 | User-Mode API ->> System Call API : Call the system call API with the handler index 90 | System Call API ->> System Call Interrupt Entry : Trigger a system call interrupt 91 | System Call Interrupt Entry ->> Kernel-Mode Handler : Call the privileged handler 92 | Kernel-Mode Handler ->> Kernel-Mode Handler : Process the request 93 | 94 | Kernel-Mode Handler ->> System Call Interrupt Entry : Exit 95 | System Call Interrupt Entry ->> System Call API : Exit the interrupt 96 | System Call API ->> User-Mode API : Exit 97 | ``` 98 | 99 | ## Adaptation and Registration 100 | 101 | A system call hanlder can only have an optional argument, so for those system calls with multiple arguments, we need to: 102 | 103 | 1. Create a kernel-mode adapter with only one argument as the system call handler. 104 | 105 | ```c++ 106 | // src/kernel/io/file/file.cpp 107 | 108 | stl::size_t File::Read(const ReadArgs& args) noexcept { 109 | return io::File {args.desc}.Read(args.buf, args.size); 110 | } 111 | ``` 112 | 113 | 2. Register the system call handler to `sc::sys_call_handlers` with `sc::SysCallHandlerTab`. 114 | 115 | ```c++ 116 | // src/kernel/syscall/call.cpp 117 | 118 | void InitSysCall() noexcept { 119 | GetSysCallHandlerTab() 120 | .Register(SysCallType::ReadFile, 121 | static_cast(&io::sc::File::Read)) 122 | // ... 123 | } 124 | ``` 125 | 126 | 3. Create a user-mode API where multiple arguments are grouped together and passed to `SysCall`. 127 | 128 | ```c++ 129 | // src/user/io/file/file.cpp 130 | 131 | stl::size_t File::Read(const stl::size_t desc, void* const buf, const stl::size_t size) noexcept { 132 | struct Args { 133 | stl::size_t desc; 134 | void* buf; 135 | stl::size_t size; 136 | }; 137 | 138 | Args args {desc, buf, size}; 139 | return sc::SysCall(sc::SysCallType::ReadFile, reinterpret_cast(&args)); 140 | } 141 | ``` -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/sh 2 | AS := nasm 3 | LD := ld 4 | 5 | DISK := ../kernel.img 6 | 7 | INC_DIR := ./include 8 | SRC_DIR := ./src 9 | BUILD_DIR := ./build 10 | 11 | vpath %.h $(INC_DIR):$(SRC_DIR) 12 | vpath %.cpp $(SRC_DIR) 13 | 14 | vpath %.inc $(INC_DIR):$(SRC_DIR) 15 | vpath %.asm $(SRC_DIR) 16 | 17 | vpath %.d $(BUILD_DIR) 18 | vpath %.o $(BUILD_DIR) 19 | 20 | CODE_ENTRY := 0xC0001500 21 | 22 | LOADER_SECTOR_COUNT := 5 23 | KRNL_START_SECTOR := 6 24 | KRNL_SECTOR_COUNT := 350 25 | 26 | ASFLAGS := -f elf \ 27 | -i$(INC_DIR) 28 | 29 | # `-fno-pic` can generate position-dependent code that does not use a global pointer register. 30 | # Because the loader does not support address relocation. 31 | # `-O1` can reduce the stack size for local variables. Otherwise threads may have stack overflow errors. 32 | CXXFLAGS := -m32 \ 33 | -std=c++20 \ 34 | -c \ 35 | -I$(INC_DIR) \ 36 | -Wall \ 37 | -O1 \ 38 | -fno-pic \ 39 | -fno-builtin \ 40 | -fno-rtti \ 41 | -fno-exceptions \ 42 | -fno-threadsafe-statics \ 43 | -fno-stack-protector \ 44 | -Wno-missing-field-initializers 45 | 46 | LDFLAGS := -m elf_i386 \ 47 | -Ttext $(CODE_ENTRY) \ 48 | -e main 49 | 50 | .PHONY: build 51 | build: boot kernel 52 | 53 | .PHONY: install 54 | install: 55 | dd if=$(BUILD_DIR)/boot/mbr.bin of=$(DISK) bs=512 count=1 conv=notrunc 56 | dd if=$(BUILD_DIR)/boot/loader.bin of=$(DISK) seek=1 bs=512 count=$(LOADER_SECTOR_COUNT) conv=notrunc 57 | dd if=$(BUILD_DIR)/kernel.bin of=$(DISK) bs=512 seek=$(KRNL_START_SECTOR) count=$(KRNL_SECTOR_COUNT) conv=notrunc 58 | 59 | .PHONY: clean 60 | clean: 61 | $(RM) $(shell find $(BUILD_DIR) -name '*.d') 62 | $(RM) $(shell find $(BUILD_DIR) -name '*.o') 63 | $(RM) $(shell find $(BUILD_DIR) -name '*.bin') 64 | find $(BUILD_DIR) -empty -type d -delete 65 | 66 | ########################## Build the master boot record and loader ########################## 67 | 68 | BOOT_HEADERS := boot/boot.inc \ 69 | kernel/krnl.inc \ 70 | kernel/util/metric.inc \ 71 | kernel/process/elf.inc \ 72 | kernel/descriptor/desc.inc \ 73 | kernel/selector/sel.inc \ 74 | kernel/io/video/print.inc \ 75 | kernel/io/disk/disk.inc \ 76 | kernel/memory/page.inc 77 | 78 | BOOT_SRC := $(SRC_DIR)/boot/mbr.asm $(SRC_DIR)/boot/loader.asm 79 | BOOT_OBJS := $(addprefix $(BUILD_DIR),$(patsubst $(SRC_DIR)%.asm,%.bin,$(BOOT_SRC))) 80 | 81 | $(BOOT_OBJS): $(BUILD_DIR)/%.bin: %.asm $(BOOT_HEADERS) 82 | @mkdir -p $(dir $@) 83 | $(AS) -i$(INC_DIR) -o $@ $< 84 | 85 | .PHONY: boot 86 | boot: $(BOOT_OBJS) 87 | 88 | ############################### Generate C++ dependency files ############################### 89 | 90 | CXX_SRC := $(shell find $(SRC_DIR) -name '*.cpp') 91 | CXX_INC_PREQS := $(addprefix $(BUILD_DIR),$(patsubst $(SRC_DIR)%.cpp,%.d,$(CXX_SRC))) 92 | 93 | $(CXX_INC_PREQS): $(BUILD_DIR)/%.d: %.cpp 94 | @mkdir -p $(dir $@) 95 | $(CXX) -MM -I$(INC_DIR) -I$(dir $<) -I$(subst $(SRC_DIR),$(INC_DIR),$(dir $<)) $< > $@ 96 | sed -Ei 's#^(.*\.o: *)$(SRC_DIR:./%=%)/(.*/)?(.*\.cpp)#$(BUILD_DIR:./%=%)/\2\1$(SRC_DIR:./%=%)/\2\3#' $@ 97 | 98 | include $(CXX_INC_PREQS) 99 | 100 | ###################################### Build the user ####################################### 101 | 102 | USR_CXX_SRC := $(shell find $(SRC_DIR)/user -name '*.cpp') 103 | USR_CXX_OBJS := $(addprefix $(BUILD_DIR),$(patsubst $(SRC_DIR)%.cpp,%.o,$(USR_CXX_SRC))) 104 | 105 | $(USR_CXX_OBJS): $(BUILD_DIR)/%.o: %.cpp 106 | @mkdir -p $(dir $@) 107 | $(CXX) $(CXXFLAGS) -I$(dir $<) -I$(subst $(SRC_DIR),$(INC_DIR),$(dir $<)) -o $@ $< 108 | 109 | .PHONY: user 110 | user: $(USR_CXX_OBJS) 111 | 112 | ##################################### Build the kernel ###################################### 113 | 114 | KRNL_AS_HEADERS := $(shell find $(INC_DIR)/kernel -name '*.inc') $(shell find $(SRC_DIR) -name '*.inc') 115 | KRNL_AS_SRC := $(filter-out $(BOOT_SRC),$(shell find $(SRC_DIR) -name '*.asm')) 116 | KRNL_AS_OBJS := $(addprefix $(BUILD_DIR),$(patsubst $(SRC_DIR)%.asm,%_nasm.o,$(KRNL_AS_SRC))) 117 | 118 | $(KRNL_AS_OBJS): $(BUILD_DIR)/%_nasm.o: %.asm $(KRNL_AS_HEADERS) 119 | @mkdir -p $(dir $@) 120 | $(AS) $(ASFLAGS) -i$(dir $<) -i$(subst $(SRC_DIR),$(INC_DIR),$(dir $<)) -o $@ $< 121 | 122 | KRNL_CXX_SRC := $(shell find $(SRC_DIR)/kernel -name '*.cpp') 123 | KRNL_CXX_OBJS := $(addprefix $(BUILD_DIR),$(patsubst $(SRC_DIR)%.cpp,%.o,$(KRNL_CXX_SRC))) 124 | 125 | $(KRNL_CXX_OBJS): $(BUILD_DIR)/%.o: %.cpp 126 | @mkdir -p $(dir $@) 127 | $(CXX) $(CXXFLAGS) -I$(dir $<) -I$(subst $(SRC_DIR),$(INC_DIR),$(dir $<)) -o $@ $< 128 | 129 | $(BUILD_DIR)/kernel.bin: $(KRNL_AS_OBJS) $(KRNL_CXX_OBJS) $(CRT_CXX_OBJS) $(UTIL_CXX_OBJS) $(USR_CXX_OBJS) 130 | # `main.o` must be the first object file, otherwise another function wil be placed at `0xC0001500`. 131 | $(LD) $(LDFLAGS) $(BUILD_DIR)/kernel/main.o $(filter-out %/main.o,$^) -o $@ 132 | 133 | .PHONY: kernel 134 | kernel: $(BUILD_DIR)/kernel.bin -------------------------------------------------------------------------------- /include/kernel/io/disk/file/dir.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file dir.h 3 | * @brief Underlying directory storage. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/io/file/file.h" 12 | #include "kernel/stl/string_view.h" 13 | 14 | namespace io::fs { 15 | 16 | class IdxNode; 17 | 18 | struct Directory { 19 | /** 20 | * @brief The minimum number of entries in a directory. 21 | * 22 | * @details 23 | * A directory at least has two entries: 24 | * - The current directory. 25 | * - The parent directory. 26 | */ 27 | static constexpr stl::size_t min_entry_count {2}; 28 | 29 | Directory() noexcept = default; 30 | 31 | Directory(const Directory&) = delete; 32 | 33 | void Close() noexcept; 34 | 35 | bool IsOpen() const noexcept; 36 | 37 | bool IsEmpty() const noexcept; 38 | 39 | IdxNode& GetNode() const noexcept; 40 | 41 | stl::size_t GetNodeIdx() const noexcept; 42 | 43 | //! Reset the access offset to the begging of the directory. 44 | void Rewind() noexcept; 45 | 46 | /** 47 | * @brief The index node for directory entry storage. 48 | * 49 | * @details 50 | * It is @p nullptr when the directory is not open. 51 | */ 52 | IdxNode* inode {nullptr}; 53 | 54 | //! The access offset. 55 | mutable stl::size_t pos {0}; 56 | }; 57 | 58 | enum class FileType { Unknown, Regular, Directory }; 59 | 60 | /** 61 | * @brief The directory entry. 62 | * 63 | * @details 64 | * The directory entry represents an item in a directory. 65 | * It can be a file or a directory. 66 | * 67 | * Index nodes @p IdxNode do not indicate their data type. 68 | * Instead, we use directory entries to determine whether an item is a file or a directory. 69 | * 70 | * @code 71 | * Root Directory 72 | * ┌──────────────────┬──────────────────┐ 73 | * │ │ Name: "file" │ 74 | * │ ├──────────────────┤ 75 | * │ Directory Entry │ Index Node ID: 3 │ ──────┐ 76 | * ┌────────────────┐ │ ├──────────────────┤ │ 77 | * │ Root Directory │ │ │ Type: File │ │ 78 | * │ Index Node │ ────► ├──────────────────┼──────────────────┤ │ 79 | * └────────────────┘ │ │ Name: "dir" │ │ 80 | * │ ├──────────────────┤ │ 81 | * │ Directory Entry │ Index Node ID: 2 │ ───┐ │ 82 | * │ ├──────────────────┤ │ │ 83 | * │ │ Type: Directory │ │ │ 84 | * ├──────────────────┴──────────────────┤ │ │ 85 | * │ ... │ │ │ 86 | * └─────────────────────────────────────┘ │ │ 87 | * │ │ 88 | * ┌──────────────────────────────────────────────────────────┘ │ 89 | * │ │ 90 | * │ "dir" Directory │ 91 | * │ ┌──────────────────┬───────────────┐ │ 92 | * │ │ │ Name │ │ 93 | * ▼ │ ├───────────────┤ │ 94 | * ┌────────────────┐ │ Directory Entry │ Index Node ID │ │ 95 | * │ Index Node (2) │ ───► │ ├───────────────┤ │ 96 | * └────────────────┘ │ │ Type │ │ 97 | * ├──────────────────┴───────────────┤ │ 98 | * │ ... │ │ 99 | * └──────────────────────────────────┘ │ 100 | * │ 101 | * ┌─────────────────────────────────────────────────────────────┘ 102 | * ▼ 103 | * ┌────────────────┐ ┌─────────────┐ 104 | * │ Index Node (3) │ ──► │ "file" Data │ 105 | * └────────────────┘ └─────────────┘ 106 | * @endcode 107 | */ 108 | struct DirEntry { 109 | DirEntry() noexcept = default; 110 | 111 | DirEntry(FileType, stl::string_view name, stl::size_t inode_idx) noexcept; 112 | 113 | DirEntry& SetName(stl::string_view) noexcept; 114 | 115 | //! The entry type. 116 | FileType type {FileType::Unknown}; 117 | 118 | //! The directory or file name. 119 | stl::array name; 120 | 121 | //! The ID of the index node. 122 | stl::size_t inode_idx {npos}; 123 | }; 124 | 125 | } // namespace io::fs -------------------------------------------------------------------------------- /include/kernel/io/disk/file/super_block.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file super_block.h 3 | * @brief The super block. 4 | * 5 | * @par GitHub 6 | * https://github.com/Zhuagenborn 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kernel/io/disk/disk.h" 12 | #include "kernel/stl/array.h" 13 | 14 | namespace io::fs { 15 | 16 | #pragma pack(push, 1) 17 | 18 | /** 19 | * @brief The super block. 20 | * 21 | * @details 22 | * The super block is 4096 bytes in size and starts at offset @p 4096 bytes in a partition, behind the boot sector. 23 | * It maintains information about the entire file system. 24 | * 25 | * @code 26 | * Disk 27 | * ┌─────┬───────────┬─────┬───────────┐ 28 | * │ MBR │ Partition │ ... │ Partition │ 29 | * └─────┴───────────┴─────┴───────────┘ 30 | * │ │ 31 | * ┌────────────────────────────────────────┘ └─────────────────────────────────────────────────┐ 32 | * ▼ ▼ 33 | * ┌─────────────┬─────────────┬──────────────┬───────────────────┬─────────────┬────────────────┬────────┐ 34 | * │ Boot Sector │ Super Block │ Block Bitmap │ Index Node Bitmap │ Index Nodes │ Root Directory │ Blocks │ 35 | * └─────────────┴─────────────┴──────────────┴───────────────────┴─────────────┴────────────────┴────────┘ 36 | * @endcode 37 | */ 38 | struct SuperBlock { 39 | private: 40 | static constexpr stl::uint32_t sign {0x11223344}; 41 | 42 | //! The magic number. 43 | stl::uint32_t sign_ {sign}; 44 | 45 | public: 46 | //! The start LBA. 47 | static constexpr stl::uint32_t start_lba {boot_sector_count}; 48 | 49 | /** 50 | * @brief Whether the super block has a valid signature. 51 | * 52 | * @details 53 | * This method can be used to check whether a partition has been formatted. 54 | */ 55 | bool IsSignValid() const noexcept; 56 | 57 | //! The start LBA of the partition. 58 | stl::size_t part_start_lba; 59 | //! The number of sectors in the partition. 60 | stl::size_t part_sector_count; 61 | //! The number of index nodes in the partition. 62 | stl::size_t part_inode_count; 63 | 64 | //! The start LBA of the block bitmap area. 65 | stl::size_t block_bitmap_start_lba; 66 | //! The number of sectors in the block bitmap area. 67 | stl::size_t block_bitmap_sector_count; 68 | 69 | //! The start LBA of the index node bitmap area. 70 | stl::size_t inode_bitmap_start_lba; 71 | //! The number of sectors in the index node bitmap area. 72 | stl::size_t inode_bitmap_sector_count; 73 | 74 | //! The start LBA of the index node area. 75 | stl::size_t inodes_start_lba; 76 | //! The number of sectors in the index node area. 77 | stl::size_t inodes_sector_count; 78 | 79 | //! The start LBA of the data area. 80 | stl::size_t data_start_lba; 81 | //! The index of the root directory's index node. 82 | stl::size_t root_inode_idx; 83 | }; 84 | 85 | //! The 4096-byte padded super block. 86 | class PaddedSuperBlock : public SuperBlock { 87 | public: 88 | /** 89 | * @brief Write the super block and initialization data to a disk. 90 | * 91 | * @param part A disk. 92 | * @param block_bitmap_bit_len The number of bits in the block bitmap. 93 | */ 94 | PaddedSuperBlock& WriteTo(Disk::Part& part, stl::size_t block_bitmap_bit_len) noexcept; 95 | 96 | private: 97 | /** 98 | * @brief Write the block bitmap to a disk. 99 | * 100 | * @param disk A disk. 101 | * @param bit_len The number of bits in the block bitmap. 102 | * @param io_buf An I/O buffer for temporary data storage. 103 | * @param io_buf_size The size of the I/O buffer. 104 | */ 105 | PaddedSuperBlock& WriteBlockBitmap(Disk& disk, stl::size_t bit_len, void* io_buf, 106 | stl::size_t io_buf_size) noexcept; 107 | 108 | //! Write the index node bitmap to a disk. 109 | PaddedSuperBlock& WriteNodeBitmap(Disk& disk, void* io_buf, stl::size_t io_buf_size) noexcept; 110 | 111 | //! Write the index node of the root directory to a disk. 112 | PaddedSuperBlock& WriteRootDirNode(Disk& disk, void* io_buf, stl::size_t io_buf_size) noexcept; 113 | 114 | /** 115 | * @details 116 | * Write the entries in the root directory to a disk, including: 117 | * - The current directory. 118 | * - The parent directory. 119 | */ 120 | PaddedSuperBlock& WriteRootDirEntries(Disk& disk, void* io_buf, 121 | stl::size_t io_buf_size) noexcept; 122 | 123 | stl::array padding_; 124 | }; 125 | 126 | static_assert(sizeof(PaddedSuperBlock) == Disk::sector_size); 127 | 128 | #pragma pack(pop) 129 | 130 | } // namespace io::fs -------------------------------------------------------------------------------- /src/kernel/io/disk/file/super_block.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/io/disk/file/super_block.h" 2 | #include "kernel/io/disk/file/dir.h" 3 | #include "kernel/io/disk/file/inode.h" 4 | #include "kernel/io/file/path.h" 5 | #include "kernel/memory/pool.h" 6 | 7 | namespace io::fs { 8 | 9 | namespace { 10 | //! The number of bits in a sector. 11 | inline constexpr stl::size_t bit_count_per_sector {Disk::sector_size * bit::byte_len}; 12 | } // namespace 13 | 14 | bool SuperBlock::IsSignValid() const noexcept { 15 | return sign_ == sign; 16 | } 17 | 18 | PaddedSuperBlock& PaddedSuperBlock::WriteTo(Disk::Part& part, 19 | const stl::size_t block_bitmap_bit_len) noexcept { 20 | // Write the super block to the disk. 21 | auto& disk {part.GetDisk()}; 22 | disk.WriteSectors(part.GetStartLba() + start_lba, this, 23 | RoundUpDivide(sizeof(PaddedSuperBlock), Disk::sector_size)); 24 | 25 | // Write initialization data to the disk. 26 | const auto io_buf_size {stl::max(stl::max(inode_bitmap_sector_count, inodes_sector_count), 27 | block_bitmap_sector_count) 28 | * Disk::sector_size}; 29 | const auto io_buf {mem::Allocate(io_buf_size)}; 30 | mem::AssertAlloc(io_buf); 31 | WriteBlockBitmap(disk, block_bitmap_bit_len, io_buf, io_buf_size); 32 | WriteNodeBitmap(disk, io_buf, io_buf_size); 33 | WriteRootDirNode(disk, io_buf, io_buf_size); 34 | WriteRootDirEntries(disk, io_buf, io_buf_size); 35 | mem::Free(io_buf); 36 | return *this; 37 | } 38 | 39 | PaddedSuperBlock& PaddedSuperBlock::WriteBlockBitmap(Disk& disk, const stl::size_t bit_len, 40 | void* const io_buf, 41 | const stl::size_t io_buf_size) noexcept { 42 | dbg::Assert(io_buf && io_buf_size >= block_bitmap_sector_count * Disk::sector_size); 43 | stl::memset(io_buf, 0, io_buf_size); 44 | const auto round_up_bit_len {block_bitmap_sector_count * bit_count_per_sector}; 45 | dbg::Assert(round_up_bit_len >= bit_len); 46 | Bitmap {io_buf, round_up_bit_len / bit::byte_len} 47 | // The bit for the root directory is occupied. 48 | .ForceAlloc(root_inode_idx) 49 | // There are usually some extra bits at the end of the last sector in the block bitmap. 50 | // They do not indicate any available blocks. 51 | // Marking them as occupied prevents them from being allocated improperly in the future. 52 | .ForceAlloc(bit_len, round_up_bit_len - bit_len); 53 | disk.WriteSectors(block_bitmap_start_lba, io_buf, block_bitmap_sector_count); 54 | return *this; 55 | } 56 | 57 | PaddedSuperBlock& PaddedSuperBlock::WriteNodeBitmap(Disk& disk, void* const io_buf, 58 | const stl::size_t io_buf_size) noexcept { 59 | dbg::Assert(io_buf && io_buf_size >= inode_bitmap_sector_count * Disk::sector_size); 60 | stl::memset(io_buf, 0, io_buf_size); 61 | Bitmap {io_buf, inode_bitmap_sector_count * Disk::sector_size} 62 | // The bit for the root directory is occupied. 63 | .ForceAlloc(root_inode_idx); 64 | disk.WriteSectors(inode_bitmap_start_lba, io_buf, inode_bitmap_sector_count); 65 | return *this; 66 | } 67 | 68 | PaddedSuperBlock& PaddedSuperBlock::WriteRootDirNode(Disk& disk, void* const io_buf, 69 | const stl::size_t io_buf_size) noexcept { 70 | dbg::Assert(io_buf && io_buf_size >= inodes_sector_count * Disk::sector_size); 71 | stl::memset(io_buf, 0, io_buf_size); 72 | const auto root {static_cast(io_buf) + root_inode_idx}; 73 | root->idx = root_inode_idx; 74 | root->size = Directory::min_entry_count * sizeof(DirEntry); 75 | // The entries in the root directory are saved at the begging of the data area. 76 | root->SetDirectLba(0, data_start_lba); 77 | disk.WriteSectors(inodes_start_lba, io_buf, inodes_sector_count); 78 | return *this; 79 | } 80 | 81 | PaddedSuperBlock& PaddedSuperBlock::WriteRootDirEntries(Disk& disk, void* const io_buf, 82 | const stl::size_t io_buf_size) noexcept { 83 | const auto sector_count {RoundUpDivide( 84 | Directory::min_entry_count * sizeof(DirEntry), Disk::sector_size)}; 85 | dbg::Assert(io_buf && io_buf_size >= sector_count * Disk::sector_size); 86 | stl::memset(io_buf, 0, io_buf_size); 87 | 88 | const auto curr_dir {static_cast(io_buf)}; 89 | curr_dir->SetName(Path::curr_dir_name); 90 | // The current directory is the root directory. 91 | curr_dir->inode_idx = root_inode_idx; 92 | curr_dir->type = FileType::Directory; 93 | 94 | const auto parent_dir {curr_dir + 1}; 95 | parent_dir->SetName(Path::parent_dir_name); 96 | // The parent directory of the root directory is itself. 97 | parent_dir->inode_idx = root_inode_idx; 98 | parent_dir->type = FileType::Directory; 99 | 100 | // Write entries to the begging of the data area. 101 | disk.WriteSectors(data_start_lba, io_buf, sector_count); 102 | return *this; 103 | } 104 | 105 | } // namespace io::fs -------------------------------------------------------------------------------- /src/kernel/interrupt/intr.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/interrupt/intr.h" 2 | #include "kernel/interrupt/pic.h" 3 | #include "kernel/io/io.h" 4 | #include "kernel/io/video/print.h" 5 | #include "kernel/selector/sel.h" 6 | 7 | namespace intr { 8 | 9 | /** 10 | * @brief Interrupt handlers. 11 | * 12 | * @details 13 | * When an interrupt @p i occurs: 14 | * 1. The CPU jumps to the entry point @p intr_entries[i], defined in @p src/interrupt/intr.asm. 15 | * 2. The interrupt handler @p intr_handlers[i] is called by @p intr_entries[i] after registers are saved. 16 | */ 17 | extern "C" Handler intr_handlers[]; 18 | 19 | namespace { 20 | 21 | extern "C" { 22 | 23 | /** 24 | * @brief The entry points of interrupts, defined in @p src/interrupt/intr.asm. 25 | * 26 | * @details 27 | * They will be registered in the interrupt descriptor table. 28 | * When an interrupt @p i occurs: 29 | * 1. The CPU jumps to the entry point @p intr_entries[i]. 30 | * 2. After saving registers, @p intr_entries[i] calls the interrupt handler @p intr_handlers[i]. 31 | */ 32 | extern stl::uintptr_t intr_entries[count]; 33 | 34 | //! Set the interrupt descriptor table register. 35 | void SetIntrDescTabReg(stl::uint16_t limit, stl::uintptr_t base) noexcept; 36 | 37 | //! Get the interrupt descriptor table register. 38 | void GetIntrDescTabReg(desc::DescTabReg&) noexcept; 39 | } 40 | 41 | void SetIntrDescTabReg(const desc::DescTabReg& reg) noexcept { 42 | SetIntrDescTabReg(reg.GetLimit(), reg.GetBase()); 43 | } 44 | 45 | IntrDescTab& GetIntrDescTab() noexcept { 46 | static IntrDescTab idt; 47 | return idt; 48 | } 49 | 50 | //! Initialize the interrupt descriptor table. 51 | void InitIntrDescTab() noexcept { 52 | for (stl::size_t i {0}; i != GetIntrDescTab().GetCount(); ++i) { 53 | GetIntrDescTab()[i] = { 54 | sel::krnl_code, intr_entries[i], {desc::SysType::Intr32, Privilege::Zero}}; 55 | } 56 | 57 | // The system call will be used by user applications, so its privilege is 3. 58 | const auto sys_call {static_cast(Intr::SysCall)}; 59 | GetIntrDescTab()[sys_call] = { 60 | sel::krnl_code, intr_entries[sys_call], {desc::SysType::Intr32, Privilege::Three}}; 61 | } 62 | 63 | void RegisterIntrHandlers() noexcept { 64 | GetIntrHandlerTab() 65 | .Register(0x00, "#DE Divide Error") 66 | .Register(0x01, "#DB Debug Exception") 67 | .Register(0x02, "NMI Intr") 68 | .Register(0x03, "#BP Breakpoint Exception") 69 | .Register(0x04, "#OF Overflow Exception") 70 | .Register(0x05, "#BR Bound Range Exceeded Exception") 71 | .Register(0x06, "#UD Invalid Opcode Exception") 72 | .Register(0x07, "#NM Device Not Available Exception") 73 | .Register(0x08, "#DF Double Fault Exception") 74 | .Register(0x09, "Coprocessor Segment Overrun") 75 | .Register(0x0A, "#TS Invalid TSS Exception") 76 | .Register(0x0B, "#NP Segment Not Present") 77 | .Register(0x0C, "#SS Stack Fault Exception") 78 | .Register(0x0D, "#GP General Protection Exception") 79 | .Register(Intr::PageFault, "#PF Page-Fault Exception") 80 | .Register(0x10, "#MF x87 FPU Floating-Point Error") 81 | .Register(0x11, "#AC Alignment Check Exception") 82 | .Register(0x12, "#MC Machine-Check Exception") 83 | .Register(0x13, "#XF SIMD Floating-Point Exception"); 84 | } 85 | 86 | } // namespace 87 | 88 | Handler intr_handlers[count] {}; 89 | 90 | desc::DescTabReg GetIntrDescTabReg() noexcept { 91 | desc::DescTabReg reg; 92 | GetIntrDescTabReg(reg); 93 | return reg; 94 | } 95 | 96 | IntrHandlerTab& GetIntrHandlerTab() noexcept { 97 | static IntrHandlerTab handlers {intr_handlers, "Unknown", DefaultIntrHandler}; 98 | return handlers; 99 | } 100 | 101 | void InitIntr() noexcept { 102 | InitIntrDescTab(); 103 | RegisterIntrHandlers(); 104 | 105 | const pic::Intr intrs[] {pic::Intr::Keyboard, pic::Intr::Clock, pic::Intr::SlavePic, 106 | pic::Intr::PrimaryIdeChnl, pic::Intr::SecondaryIdeChnl}; 107 | pic::InitPgmIntrCtrl({intrs, sizeof(intrs) / sizeof(pic::Intr)}); 108 | SetIntrDescTabReg(GetIntrDescTab().BuildReg()); 109 | io::PrintlnStr("The interrupt descriptor table has been initialized."); 110 | } 111 | 112 | extern "C" { 113 | 114 | bool IsIntrEnabled() noexcept { 115 | return io::EFlags::Get().If(); 116 | } 117 | } 118 | 119 | IntrGuard::IntrGuard() noexcept : enabled_ {intr::IsIntrEnabled()} { 120 | if (enabled_) { 121 | intr::DisableIntr(); 122 | } 123 | } 124 | 125 | IntrGuard::~IntrGuard() noexcept { 126 | if (enabled_) { 127 | intr::EnableIntr(); 128 | } 129 | } 130 | 131 | void DefaultIntrHandler(const stl::size_t intr_num) noexcept { 132 | if (intr_num == 0x27 || intr_num == 0x2F) { 133 | // Ignore spurious interrupts. 134 | return; 135 | } 136 | 137 | io::PrintlnStr("\n!!!!! Exception !!!!!"); 138 | io::Printf("\t0x{} {}\n", intr_num, GetIntrHandlerTab().GetName(intr_num)); 139 | if (static_cast(intr_num) == intr::Intr::PageFault) { 140 | io::Printf("\tThe page fault address is 0x{}.\n", io::GetCr2()); 141 | } 142 | 143 | while (true) { 144 | } 145 | } 146 | 147 | } // namespace intr -------------------------------------------------------------------------------- /src/kernel/io/timer.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/io/timer.h" 2 | #include "kernel/debug/assert.h" 3 | #include "kernel/interrupt/intr.h" 4 | #include "kernel/io/io.h" 5 | #include "kernel/io/video/print.h" 6 | #include "kernel/thread/thd.h" 7 | #include "kernel/util/bit.h" 8 | 9 | namespace io { 10 | 11 | namespace { 12 | 13 | /** 14 | * @brief A wrapper of a global @p bool variable representing whether the timer has been initialized. 15 | * 16 | * @details 17 | * Global variables cannot be initialized properly. 18 | * We use @p static variables in methods instead since they can be initialized by methods. 19 | */ 20 | bool& IsTimerInitedImpl() noexcept { 21 | static bool inited {false}; 22 | return inited; 23 | } 24 | 25 | //! *Intel 8253* registers. 26 | namespace port { 27 | //! The data register for the counter @p 0. 28 | inline constexpr stl::uint16_t counter_0 {0x40}; 29 | //! The data register for the counter @p 1. 30 | inline constexpr stl::uint16_t counter_1 {0x41}; 31 | //! The data register for the counter @p 2. 32 | inline constexpr stl::uint16_t counter_2 {0x42}; 33 | //! The mode and command register. 34 | static constexpr stl::uint16_t pit_ctrl {0x43}; 35 | } // namespace port 36 | 37 | //! The number of ticks after system startup. 38 | stl::size_t ticks {0}; 39 | 40 | enum class ReadWriteMode { 41 | LatchRead = 0, 42 | ReadWriteLowByte = 1, 43 | ReadWriteHighByte = 2, 44 | ReadWriteLowHighBytes = 3 45 | }; 46 | 47 | enum class CountMode { 48 | IntrOnTerminalCount = 0, 49 | HardRetriggerOneShot = 1, 50 | RateGenerator = 2, 51 | SquareWaveGenerator = 3, 52 | SoftTriggerStrobe = 4, 53 | HardTriggerStrobe = 5, 54 | }; 55 | 56 | enum class DigitalMode { Binary, BinaryCodedDecimal }; 57 | 58 | //! The control word. 59 | class CtrlWord { 60 | public: 61 | constexpr CtrlWord(const stl::uint8_t val = 0) noexcept : val_ {val} {} 62 | 63 | constexpr operator stl::uint8_t() const noexcept { 64 | return val_; 65 | } 66 | 67 | constexpr CtrlWord& SetDigitalMode(const DigitalMode mode) noexcept { 68 | if (mode == DigitalMode::BinaryCodedDecimal) { 69 | bit::SetBit(val_, bcd_pos); 70 | } else { 71 | bit::ResetBit(val_, bcd_pos); 72 | } 73 | 74 | return *this; 75 | } 76 | 77 | constexpr CtrlWord& SetCountMode(const CountMode mode) noexcept { 78 | bit::SetBits(val_, static_cast(mode), m_pos, m_len); 79 | return *this; 80 | } 81 | 82 | constexpr CtrlWord& SetReadWriteMode(const ReadWriteMode mode) noexcept { 83 | bit::SetBits(val_, static_cast(mode), rw_pos, rw_len); 84 | return *this; 85 | } 86 | 87 | CtrlWord& SetSelectCounter(const stl::size_t id) noexcept { 88 | dbg::Assert(id < 3); 89 | bit::SetBits(val_, id, sc_pos, sc_len); 90 | return *this; 91 | } 92 | 93 | void WriteToPort() noexcept { 94 | io::WriteByteToPort(port::pit_ctrl, val_); 95 | } 96 | 97 | private: 98 | static constexpr stl::size_t bcd_pos {0}; 99 | static constexpr stl::size_t m_pos {bcd_pos + 1}; 100 | static constexpr stl::size_t m_len {3}; 101 | static constexpr stl::size_t rw_pos {m_pos + m_len}; 102 | static constexpr stl::size_t rw_len {2}; 103 | static constexpr stl::size_t sc_pos {rw_pos + rw_len}; 104 | static constexpr stl::size_t sc_len {2}; 105 | 106 | stl::uint8_t val_; 107 | }; 108 | 109 | //! Calculate the initial counter value by a timer interrupt frequency. 110 | constexpr stl::uint32_t CalcInitCounterVal(const stl::size_t freq_per_second) noexcept { 111 | constexpr stl::size_t input_freq {1193180}; 112 | return input_freq / freq_per_second; 113 | } 114 | 115 | void InitCounter(const stl::size_t freq_per_second) noexcept { 116 | CtrlWord {} 117 | .SetSelectCounter(0) 118 | .SetCountMode(CountMode::RateGenerator) 119 | .SetReadWriteMode(ReadWriteMode::ReadWriteLowHighBytes) 120 | .SetDigitalMode(DigitalMode::Binary) 121 | .WriteToPort(); 122 | 123 | const auto init_val {CalcInitCounterVal(freq_per_second)}; 124 | io::WriteByteToPort(port::counter_0, bit::GetLowByte(init_val)); 125 | io::WriteByteToPort(port::counter_0, bit::GetHighByte(init_val)); 126 | } 127 | 128 | /** 129 | * @brief The clock interrupt handler. 130 | * 131 | * @details 132 | * It increases ticks and schedules threads. 133 | */ 134 | void ClockIntrHandler(stl::size_t) noexcept { 135 | auto& curr_thd {tsk::Thread::GetCurrent()}; 136 | dbg::Assert(curr_thd.IsStackValid()); 137 | ++ticks; 138 | if (!curr_thd.Tick()) { 139 | curr_thd.Schedule(); 140 | } 141 | } 142 | 143 | } // namespace 144 | 145 | stl::size_t GetTicks() noexcept { 146 | return ticks; 147 | } 148 | 149 | void InitTimer(const stl::size_t freq_per_second) noexcept { 150 | dbg::Assert(!IsTimerInited()); 151 | dbg::Assert(tsk::IsThreadInited()); 152 | ticks = 0; 153 | InitCounter(freq_per_second); 154 | intr::GetIntrHandlerTab().Register(intr::Intr::Clock, &ClockIntrHandler); 155 | IsTimerInitedImpl() = true; 156 | io::PrintStr("Intel 8253 Programmable Interval Timer has been initialized.\n"); 157 | } 158 | 159 | bool IsTimerInited() noexcept { 160 | return IsTimerInitedImpl(); 161 | } 162 | 163 | } // namespace io -------------------------------------------------------------------------------- /src/kernel/io/file/path.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel/io/file/path.h" 2 | #include "kernel/debug/assert.h" 3 | 4 | namespace io { 5 | 6 | bool Path::IsRootDir(const stl::string_view path) noexcept { 7 | dbg::Assert(0 < path.size() && path.size() <= max_len); 8 | return path == root_dir_name || path == "/." || path == "/.."; 9 | } 10 | 11 | bool Path::IsDir(const stl::string_view path) noexcept { 12 | dbg::Assert(path.size() <= max_len); 13 | return path.empty() || IsRootDir(path) || path.back() == separator; 14 | } 15 | 16 | bool Path::IsAbsolute(const stl::string_view path) noexcept { 17 | dbg::Assert(path.size() <= max_len); 18 | return !path.empty() ? path.front() == separator : false; 19 | } 20 | 21 | stl::string_view Path::GetFileName(const stl::string_view path) noexcept { 22 | if (!IsDir(path)) { 23 | const auto last_sep {path.rfind(separator)}; 24 | return last_sep != stl::string_view::npos ? path.substr(last_sep + 1) : path; 25 | } else { 26 | return ""; 27 | } 28 | } 29 | 30 | Path::Path(const char* const path) noexcept : Path {static_cast(path)} {} 31 | 32 | Path::Path(const stl::string_view path) noexcept { 33 | Join(path); 34 | } 35 | 36 | stl::string_view Path::Parse(const stl::string_view path, 37 | stl::array& name) noexcept { 38 | dbg::Assert(path.size() <= max_len); 39 | stl::memset(name.data(), '\0', name.size()); 40 | if (path.empty()) { 41 | return nullptr; 42 | } 43 | 44 | stl::size_t src {0}; 45 | while (path[src] == separator) { 46 | ++src; 47 | } 48 | 49 | stl::size_t dest {0}; 50 | while (path[src] != separator && path[src] != '\0') { 51 | dbg::Assert(dest < max_len); 52 | name[dest++] = path[src++]; 53 | } 54 | 55 | dbg::Assert(dest <= max_len); 56 | name[dest] = '\0'; 57 | return path.substr(src); 58 | } 59 | 60 | stl::size_t Path::GetDepth(const stl::string_view path) noexcept { 61 | dbg::Assert(path.size() <= max_len); 62 | stl::size_t depth {0}; 63 | Visit( 64 | path, 65 | [](const stl::string_view sub_path, const stl::string_view name, void* const arg) noexcept { 66 | dbg::Assert(arg); 67 | ++(*static_cast(arg)); 68 | return true; 69 | }, 70 | &depth); 71 | return depth; 72 | } 73 | 74 | bool Path::Visit(const stl::string_view path, const Visitor visitor, void* const arg) noexcept { 75 | dbg::Assert(path.size() <= max_len); 76 | dbg::Assert(visitor); 77 | stl::array name; 78 | auto sub_path {Parse(path, name)}; 79 | while (stl::strlen(name.data()) != 0) { 80 | if (!visitor(sub_path, name.data(), arg)) { 81 | return false; 82 | } 83 | 84 | if (!sub_path.empty()) { 85 | sub_path = Parse(sub_path, name); 86 | } else { 87 | stl::memset(name.data(), '\0', name.size()); 88 | } 89 | } 90 | 91 | return true; 92 | } 93 | 94 | Path Path::Join(const stl::string_view parent, const stl::string_view child) noexcept { 95 | return Path {parent}.Join(child); 96 | } 97 | 98 | bool Path::IsRootDir() const noexcept { 99 | return IsRootDir(path_.data()); 100 | } 101 | 102 | bool Path::IsDir() const noexcept { 103 | return IsDir(path_.data()); 104 | } 105 | 106 | bool Path::IsAbsolute() const noexcept { 107 | return IsAbsolute(path_.data()); 108 | } 109 | 110 | Path Path::Parse(stl::array& name) const noexcept { 111 | return Parse(path_.data(), name); 112 | } 113 | 114 | stl::size_t Path::GetDepth() const noexcept { 115 | return GetDepth(path_.data()); 116 | } 117 | 118 | stl::string_view Path::GetFileName() const noexcept { 119 | return GetFileName(path_.data()); 120 | } 121 | 122 | stl::string_view Path::GetPath() const noexcept { 123 | return path_.data(); 124 | } 125 | 126 | bool Path::Visit(const Visitor visitor, void* const arg) const noexcept { 127 | return Visit(path_.data(), visitor, arg); 128 | } 129 | 130 | Path Path::Join(const stl::string_view child) const noexcept { 131 | return Path {path_.data()}.Join(child); 132 | } 133 | 134 | Path& Path::Join(const stl::string_view child) noexcept { 135 | dbg::Assert(child.size() <= max_len); 136 | 137 | if (GetSize() == 0 && IsAbsolute(child)) { 138 | path_.front() = separator; 139 | } 140 | 141 | Visit( 142 | child, 143 | [](const stl::string_view sub_path, const stl::string_view name, void* const arg) noexcept { 144 | const auto full {static_cast(arg)}; 145 | dbg::Assert(full); 146 | const auto len {stl::strlen(full)}; 147 | if (len > 0 && full[len - 1] != separator) { 148 | full[len] = separator; 149 | } 150 | 151 | dbg::Assert(stl::strlen(full) + stl::strlen(name.data()) <= max_len); 152 | stl::strcat(full, name.data()); 153 | return true; 154 | }, 155 | path_.data()); 156 | 157 | if (IsDir(child)) { 158 | const auto size {GetSize()}; 159 | dbg::Assert(size < max_len); 160 | path_[size] = separator; 161 | path_[size + 1] = '\0'; 162 | } 163 | 164 | return *this; 165 | } 166 | 167 | stl::size_t Path::GetSize() const noexcept { 168 | return stl::strlen(path_.data()); 169 | } 170 | 171 | Path& Path::Clear() noexcept { 172 | stl::memset(path_.data(), '\0', path_.size()); 173 | return *this; 174 | } 175 | 176 | } // namespace io --------------------------------------------------------------------------------