Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multicore support, new standard library, etc... #287

Open
wants to merge 588 commits into
base: multicore
Choose a base branch
from

Conversation

Woazboat
Copy link
Contributor

@Woazboat Woazboat commented Mar 14, 2023

Changes... pretty much everything. Lots of new features and a ton of bug fixes.

From a student point of view, things shouldn't actually be too different initially as the changes are intentionally mostly in the background without affecting the existing interfaces too much

Probably better to merge this into a staging branch first and then into main later (github pull requests do not allow you to create new branches however)

Changes

SMP/multicore

  • Adapt existing code to be compatible with multiple CPUs
    • e.g. assumptions about locks that are no longer valid on SMP systems, SMP safe queue for ATA driver, …
  • Support for CPU local variables
    • x86: CPU local variables via gs/fs segment offset + compiler builtins + swapgs
    • armv8: cpu local variables via TPIDR + compiler builtins
  • Per-cpu idle threads (pinned to specific cpu)
  • x86: Start and initialize other CPUs
    • Send startup interrupts via local APIC
    • Initialization of CPU local data structures (GDT, TSS, …)
  • x86: Remote function calls on other CPUs via inter processor interrupts
  • x86: TLB shootdowns on other CPUs for memory consistency
  • SMP utility functions (e.g. current cpu id, …)

Scheduler

  • Adapt scheduler for SMP systems
    • New scheduling algorithm inspired by Linux CFS (completely fair scheduler) since round robin is unsuitable for SMP. Track virtual runtime of threads via cpu timestamp -> always pick thread with lowest runtime to be scheduled next
    • x86: Use per-cpu kernel stack while scheduling to allow old thread to be immediately re-scheduled without clobbering the stack

Interrupts & Timers

  • New interrupt handling framework
    • Easier registering and mapping of interrupts to responsible device driver code
      • No longer requires changes in three different (assembly) files just to add one interrupt handler
    • Dynamic adaptation for different system configurations
      • Availability of different interrupt controllers
      • Devices can be dynamically detected and registered by device drivers
    • x86: Unified interrupt entry code No more code duplication for exceptions, interrupts, syscalls, pagefault, …
    • x86: Refactor IDT creation
  • x86: Add support for APIC (xAPIC + x2APIC) and I/O APIC
    • Use modern interrupt controllers if available (fallback to legacy PIC)
    • Use APIC local timer for scheduler timer tick if available (fallback to legacy PIT)
      • APIC timer is not calibrated (but can be easily done via PIT if required) -> left as exercise for sleep/clock syscalls if exact scheduling time is required
  • arm_rpi2: Use cpu local interrupt controller + system timer
    • Timer interrupt now actually working
  • Architecture independent timer tick handler

Drivers

  • Proper separation and initialization of architecture specific drivers
    • Dedicated initialization points for architecture specific drivers
    • arm: MMC driver is no longer shoehorned into IDE driver
  • Clearer separation of drivers and devices
    • Devices can be registered and enumerated -> allows you to see which devices are present on the system
  • Plugin framework for registering bus specific drivers + device detection
    • Used for IDE bus. Allows for easier/cleaner addition of new drivers for e.g. SATA
    • Allow drivers to probe detected bus devices for compatibility
  • IDE/ATA driver
    • Proper device detection/enumeration via identify command
      • No more sporadical disk detection failures on boot
    • Clean up code and reduce use of magic values
    • Separation between controller and device functionality
      • One controller can have multiple devices
    • Fix interrupt mapping when multiple controllers are in use
    • Ensure correct drive is selected for read/write when multiple drives are present
  • Add kernel virtual address allocator
    • Can be used by device drivers to allocate free virtual kernel address ranges for memory mapped I/O
  • Add ram disk driver for mounting of in-memory ramdisk kernel modules as block devices
    • Allows booting of sweb on real hardware (without requiring a physical IDE disk)
  • Serial Port
    • Use proper constants instead of ad-hoc magic values
    • Properly use receive and transmit buffers for multi-byte transmissions

Libraries + utility code

  • Add basic libc for kernel
    • Standalone without dependencies on other kernel code, for use by e.g. EASTL
    • Common header files implemented so they are usable in the kernel
    • Use types as defined by the compiler for consistency
  • ustl replaced with new C++ standard library: EASTL
    • Actively maintained, fewer bugs, well tested (used in EA Frostbyte engine), more feature complete
    • Includes alternative/variant implementations for many features (e.g. has both node based and vector based maps)
    • Intended for use on bare metal. Built in support for use without exceptions, etc…
    • Easily configurable via configuration file
    • Included as a git subtree for easier updates
  • Reduce code duplication for sweb atomics
  • Implement base C++20 coroutine and ranges support
  • IntervalSet + RangeAllocator for allocation of address range intervals
  • Implement C++20 source_location utility for easier debugging

Kernel Initialization

  • Run global constructors/initialize global variables on boot
    • Preserve corresponding sections in linker file + call init functions on startup
  • Create proper InitThread for threaded initialization functions that were previously defined in (/shoehorned into) ProcessRegistry
    • Initialization of drivers that require working interrupts
    • Mounting of user partitions + start of user programs

Feature detection

  • x86: Detect, map and parse ACPI tables
    • Detect available CPUs
    • Detect APIC + I/O APIC
      • Detect presence of legacy PIC for backwards compatibility
  • x86: CPU feature detection via CPUID
    • detected features can be overridden via software (e.g. to force use of legacy PIC instead of APIC)

File system

  • Major refactor + clean up of file system structure
    • Proper teardown on unmount (e.g. check for open files) -> already merged into main branch previously
  • Simple example device driver files/inodes for block device access
  • Better debug output
  • Safety assertions to prevent out of bounds read/write
  • Proper dirent syscall
  • Remove code duplication of MBR partition detection and move to common code

Debugging

Debug output

  • Serialized + locked debug output to make debug output usable on SMP systems (prevent message interleaving)
  • Recursion detection to avoid deadlock on nested calls of kprintfd (e.g. pagefault in debug print)
  • Convenience macros for advanced debug output + debug output that should always be enabled (e.g. for printing information before assertions)
  • Add option to print debug output to framebuffer (e.g. when running on real hardware with no qemu console available)

Debug info

  • Replace deprecated STABS debug info with DWARF
  • Update + fix bug in libelfin library used for custom debug info/internal backtrace. (was not working properly on 32 bit targets)
    • Update to newest library version
  • x86_32: Load debug infos from kernel module like on x86_64
  • Internal backtrace:
    • Remove bogus "UNKOWN FUNCTION" from start of backtrace by ensuring last return address on stack is 0
    • x86: Ensure proper backtrace + debugger handling across interrupts by manually fixing/inserting stack frames on interrupt entry
  • Add hint on assert in interrupt handler that currentThread may not actually be valid or related to the problem

Other debug information

  • Color code timer tick heartbeat spinner depending on currently scheduled thread
    • grey = idle thread, blue = user thread, green = kernel thread, brown = cleanup thread
  • Per-CPU heartbeat spinners
  • Show scheduler lock contention (Indication of how often threads are blocked on the scheduler lock -> sign that scheduler is slow)
  • x86: Propagate assert failures to other CPUs
    • Stop and print backtrace for all CPUs
  • Display % of free pages in status bar
  • Display number of threads in status bar
  • Option to print mapping of interrupts to devices on boot (dynamic according to available & initialized devices)

Userspace

  • Allow use of C++ in userspace
  • Call global constructors & initialize static variables in userspace programs
  • Userspace libc
    • Add strnlen_s function
    • New syscalls to fetch SMP related info (current cpu id)

Build system

  • Clean up and systematize compiler option propagation via cmake targets
  • Automatically use new source files without having to re-run cmake
  • Export compile_commmands.json file from cmake for proper IDE intellisense
    • Find correct files for the selected architecture
    • Use correct compiler arguments for IDE built in linters
  • Always start qemu with gdbserver
    • Easier debugging even when not started via explicit qemugdb target
  • Always save unstripped kernel binary for use with external debuggers
  • Properly mark generated files as build byproducts
  • Replace use of external tools with cmake builtins where possible
  • Add cleandisk target to remove and recreate disk image without requiring complete recompilation
  • exe2minixfs
    • Lock disk image file while exe2minixfs is running
    • Use MBR partition info instead of raw byte offset
  • Use C++20

Misc. other changes

  • PageManager
    • Plug in/switchable allocator backends
      • Use range based allocator for PageManager initialization (avoid large stack allocation)
    • Move unrelated functionality out of PageManager (KMM, modules, …)
  • Add atomic multiple producer/single consumer queue container
  • Shell improvements & bugfixes
    • Proper ls function + autocomplete & completion suggestions for commands (including partial commands with multiple matches)
    • Ignore empty commands + preserve remembered last command
  • Bitmap
    • New constructor for initialization with existing data
    • data() + getByteSize() functions
  • Reduce use of "magic values" everywhere (constants, i/o ports, MSRs, etc…)
    • Utility classes to define registers with proper value type and read/write permissions. Allows structured definition of e.g. memory mapped registers or i/o ports.
  • New mkminixfs utility
    • Create ramdisk files formatted with minixfs
  • x86: Preserve bootloader command line arguments for eventual later use
  • Simplify initialization of various singleton objects via static local style
  • Sort and categorize include statements (architecture dependent headers, …)
  • Remove duplicated assert header file
  • Mark functions as const nodiscard if applicable, prefer override instead of virtual
  • clangd LSP server configuration for better IDE integration
  • Add BasicSpinLock with minimal functionality that's usable in contrained environments (no debug outut, no cpu local storage, etc…)
  • x86: Reduce code duplication in ArchMemory by sharing common code for 32/32_pae/64 bit architectures
  • Map and manage kernel memory via ArchMemory object for consistency
  • Allow dynamic page table page allocation when mapping kernel pages (optional)
  • Pass instruction address to pagefault handler
  • Slightly increase kernel heap memory

Misc. Bugfixes

  • SWEB compilation
    • Fix broken build of other architectures (only x86_64 was actually working, all other architectures were broken)
    • Abort compilation with error if correct compiler for architecture is not installed instead of proceeding with default host compiler Produced strange and incomprehensible errors when trying to compile for arm without installing the correct cross compiler first.
  • Fix initial kernel mapping on boot
    • Mapping was sometimes too small for kernel depending on e.g. enabled debug flags, etc… and could lead to extremely unintuitive and hard to debug errors e.g. enabling a debug flag could cause the framebuffer to be mapped as read only and cause a crash
  • arm: Check proper register to detect previous interrupt status (stored pre-interrupt state instead of current state)
  • exe2minixfs: detect and abort when there's no more space on a partition
    • previously simply overflowed and corrupted the data in the next partition on the block device e.g. writing a large kernel binary to the boot partition would overwrite the userspace partition
  • x86: PIC 8259
    • Fix incorrect masking of IRQs 8-15
  • Forward errors from inside UserProcess constructor back to userspace (would previously silently return success value)
  • Check if file open actually succeeded in vfs open syscall
  • minixfs: various fixes for potential endless loops, incorrect flushing of data, …
  • Assert instead of silently failing when freeing invalid kernel memory in KMM
  • Fix stack overflow in PageManager initialization with more than ~60MB of memory

Fix infinite recursion due to atomic set/add for size_t referring to itself
… ensuring return address in top stack frame is 0
@dgruss
Copy link
Member

dgruss commented Mar 14, 2023

There is way too much in this pull request. We need to split this up into separate parts. Some is whitespace-reformatting that I would definitely not merge. Also some other parts I might not merge --> we should split this up as much as possible so that we can decide piece by piece whether or not to merge it.

@dgruss
Copy link
Member

dgruss commented Mar 14, 2023

I also would like to omit the EASTL tests, no need to ship those to everyone.

@Woazboat
Copy link
Contributor Author

The EASTL/test/packages folder actually contains a sub-library (EABase) that the EASTL depends on. (No idea why it's in that location but it's there)
We could remove the tests/source folder however

@Woazboat
Copy link
Contributor Author

The EASTL is imported as a git subtree to allow updates from upstream. I tried to modify it as little as possible so merging of updates is easier.

@dgruss
Copy link
Member

dgruss commented Mar 14, 2023 via email

@Woazboat
Copy link
Contributor Author

A subtree is more or less the same as if the files were added normally in the repo, so they can be modified as needed without even having to know it's a subtree. The only difference is that it records the external commit where the files came from to to allow for a clean merge of updates using git tools and history instead of having to manually copy over files and resolving conflicts by hand.
The EASTL thankfully allows most configuration to be made via an external configuration file and is built to be used on bare metal, which is the main reason why only a few changes to the source itself were required.

@Woazboat
Copy link
Contributor Author

Woazboat commented Mar 14, 2023

The one external dependency of EASTL (the aforementioned EABase, which more or less just contains some basic libc level functions) was included as a submodule in the original source. Since git submodules can be a nightmare and would absolutely cause trouble (recursive checkout required, etc...) I also directly included that as a subtree.

@dgruss
Copy link
Member

dgruss commented Mar 14, 2023

that makes a lot of sense. i guess we should figure out how to proceed here. is there any chance that we can split this up? maybe also a meeting would make sense to go through the code and see how we can merge which part.
i can also create branch for this one if you like, but i'm not sure this would change anything?

@@ -81,7 +81,7 @@ __attribute__((noreturn)) void pre_new_sweb_assert(const char* condition, uint32
if (ArchMulticore::numRunningCPUs() > 1)
{
ArchMulticore::stopAllCpus();
volatile size_t wait = 0x10000000;
eastl::atomic<size_t> wait = 0x10000000;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is correct. indeed volatile is deprecated for many use cases where users commonly had the misconception that volatile would provide some form of thread safety... but here we literally want what volatile is there for: "do not optimize out any operations on this variable".

do i misremember that atomic<> may be optimized out?

Copy link
Contributor Author

@Woazboat Woazboat Mar 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That line no longer exists in the current version of the file.
https://github.com/Woazboat/sweb/blob/multicore/arch/x86/common/source/assert.cpp#L98

The reason why it was there in the first place was as a quick interim measure to prevent backtraces and assert messages from individual cpu cores from overlapping and interspersing, but that has shortly afterwards been replaced by proper locking/serialization.
(the version with volatile also no longer compiled with the volatile deprecation changes)

@Woazboat
Copy link
Contributor Author

A big issue when attempting to split this up is that it includes a lot of general baseline improvements and utility functionality that is then used in a lot of other places, as well as changes that require (minor) adaptation throughout the codebase to work properly. Individual components should be encapsulated as best as they can in the code, but the changes how they came to be that way were not necessarily always close together in time and have gone trough many revisions.

I think a meeting where we can go over the changes would be best

Merging this into a branch until it can be integrated into main would allow others/students to try it out without having to go through the process of getting it from an external repo (but yes, it wouldn't change the end result)

@dgruss dgruss changed the base branch from main to multicore March 14, 2023 19:04
@v4m1n
Copy link
Collaborator

v4m1n commented Mar 15, 2023

make debug seems to be broken (at least on ubuntu 22.04).
booting sweb with make debug results in "Error 24: Attempt to access block outside partition"

@Woazboat
Copy link
Contributor Author

Woazboat commented Mar 15, 2023

Yes, I'm afraid we're running into a(nother) bug in minixfs there. With debug infos enabled and the new dwarf debug infos now that stabs is depecated, the kernel binary is large enough to trigger it.

I changed the build system to always make the unstripped kernel binary available as kernel.unstripped for use with external debuggers as a partial mitigation , so only the internal backtrace is affected.

EDIT: backtrace was a different issue that is now fixed

@v4m1n
Copy link
Collaborator

v4m1n commented Mar 15, 2023

The internal backtrace is a very important debug feature for students.
It might be a good time to (finally) ditch minixfs.

@Woazboat
Copy link
Contributor Author

Woazboat commented Mar 15, 2023

It might be a good time to (finally) ditch minixfs.

I have actually started to write an ext2 implementation for exactly that reason. It's just as simple as minixfs but a lot more practical. The code will hopefully also be a lot easier to understand than the current rather messy minixfs implementation. Maybe that will prompt students to do more file system related things in the future if they can actually understand what's going on there.

I added some assertions so that an overflow/out of bounds write is at least detected

@Woazboat
Copy link
Contributor Author

The internal backtraces should be fixed now. I think we can simply remove the make debug target as it doesn't really serve a purpose. The binary with debug information is always available outside qemu via the saved unstripped file while the stripped version with the custom debug info is loaded inside qemu.

The only thing make debug does is to switch whether the binary loaded inside qemu is stripped or not, but that isn't used for anything anyway. (The custom debug info is used instead, but that's always available regardless of the debug flag)
I enabled the qemu internal gdbserver for all targets since there is no reason not to (and it's really annoying when you run into an issue that you want to debug via gdb and you didn't start sweb via make qemugdb). The gdbserver could previously be enabled retroactively via the qemu console, but I doubt many students know that.

@Ferdi265
Copy link
Contributor

Ferdi265 commented Mar 25, 2023

I just tested this branch and it looks quite nice :)

However, add-debug seems to be crashing due to an assertion in libstdc++ when encountering an empty string in the DWARF debug info:

[ 98%] Linking CXX executable kernel.x
/usr/src/debug/gcc/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/basic_string.h:1280: std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::reference std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::front() [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>; reference = char&]: Assertion '!empty()' failed.

The fix is quite simple though, just don't copy data when the string is empty (since accessing .front() on it is UB when empty)

commit c4b9d29bcfa8e0a48ea220c6430a2906b8b883be (multicore)
Author: Ferdinand Bachmann <[email protected]>
Date:   Sat Mar 25 13:58:44 2023 +0100

    dwarf++: fix UB when getting a string of zero length

diff --git a/utils/add-debug/dwarf/cursor.cc b/utils/add-debug/dwarf/cursor.cc
index 19902da8..1eb6c49b 100644
--- a/utils/add-debug/dwarf/cursor.cc
+++ b/utils/add-debug/dwarf/cursor.cc
@@ -86,7 +86,7 @@ cursor::string(std::string &out)
         size_t size;
         const char *p = this->cstr(&size);
         out.resize(size);
-        memmove(&out.front(), p, size);
+        if (size) memmove(&out.front(), p, size);
 }
 
 const char *

PS: Works fine with GCC 13

@Woazboat
Copy link
Contributor Author

Woazboat commented Mar 25, 2023

Thanks for tracking that down! Under what circumstances did the compiler produce an empty dwarf string?

The libelfin library used for ELF/DWARF parsing unfortunately seems to have a few issues. It actually incorrectly parsed 32 bit ELF files, so debug info on 32 bit platforms didn't work at all previously. Woazboat@48433ac

That particular bug was already fixed in a new version of the library that I have since updated (Woazboat@b653cc4), but apparently there are unfortunately still bugs lingering in there and it seems to be no longer maintained.

Open CVEs for the libelfin library

Edit: Turns out there's even an open pull request for exactly the bug you ran into... aclements/libelfin#63

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants