Final push before system formatting
This commit is contained in:
106
AdvOpsys/notes/report/1.tex
Normal file
106
AdvOpsys/notes/report/1.tex
Normal file
@@ -0,0 +1,106 @@
|
||||
\section{Boot Process Background}
|
||||
|
||||
This section gives background context for the boot process before the kernel-specific implementation described in the rest of the report. It covers the firmware stages, bootloader responsibilities, memory layout, and the effect of virtualization on booting.
|
||||
|
||||
|
||||
|
||||
\subsection{Power-On Self-Test}
|
||||
When a computer is powered on or reset, the processor begins execution from a firmware-defined reset vector. At this point, the operating system is not running yet. The first responsibility belongs to the system firmware, either Basic Input/Output system (BIOS) on older x86 systems or Unified Extensive Firmware Interface (UEFI) on newer systems \cite{littleosbook, wikipediaPOST}.
|
||||
|
||||
One of the earliest firmware tasks is the Power-On Self-Test, usually shortened to POST. POST checks that the basic hardware needed to continue booting is present and responding \cite{manybutfinite}.
|
||||
|
||||
POST might include testing and verifying some of these components:
|
||||
\begin{itemize}
|
||||
\item Processors
|
||||
\item Storage
|
||||
\item Memory
|
||||
\item Keyboard (Keyboard not found. Press f1 to continue.)
|
||||
\end{itemize}
|
||||
|
||||
Note: The exact list depends on the system \cite{techtargetPOST}.
|
||||
|
||||
POST is important because later boot stages assume that the basic machine state is usable. If a required device fails or memory cannot be initialized, the firmware may stop the boot process and report the failure through screen output, status LEDs, or beep codes \cite{techtargetPOST}.
|
||||
|
||||
The interaction between POST and hardware is therefore direct and low-level. Firmware initializes chipset state, configures memory controllers, discovers attached devices, and prepares enough of the platform that a bootloader can be found and executed. This is different from normal operating-system hardware management, because POST happens before the OS has loaded drivers or enabled its own abstractions.
|
||||
|
||||
|
||||
|
||||
\subsection{Boot Sequence After POST}
|
||||
After a successful POST, the firmware chooses a boot device according to its configured boot order. On BIOS systems, this usually means reading the first sector of a bootable disk into memory and jumping to it. On UEFI systems, the firmware instead loads an EFI application from a filesystem on the EFI System Partition \cite{cyberraidenUEFIBoot, manybutfinite}.
|
||||
|
||||
The high-level sequence after POST is:
|
||||
\begin{enumerate}
|
||||
\item firmware completes hardware initialization,
|
||||
\item firmware selects a boot device,
|
||||
\item the first bootloader stage or EFI boot application is loaded,
|
||||
\item the bootloader prepares the environment expected by the kernel,
|
||||
\item the kernel image is loaded into memory,
|
||||
\item control is transferred to the kernel entry point.
|
||||
\end{enumerate}
|
||||
|
||||
In a BIOS boot flow, the first loaded code is small because the initial boot sector is limited to 512 bytes. This usually forces the bootloader to use multiple stages. The first stage is responsible for loading a larger second stage, and the second stage can then parse filesystems, load the kernel, and prepare boot information\cite{osdevBootSequence, manybutfinite}.
|
||||
|
||||
In a UEFI boot flow, the firmware provides more services before the operating system starts. A UEFI bootloader can use firmware-provided filesystem and device services, and it is loaded as a normal executable rather than as raw code from a fixed disk sector. This makes UEFI bootloaders more flexible, but the kernel must still eventually take control and stop depending on firmware runtime assumptions \cite{cyberraidenUEFIBoot}.
|
||||
|
||||
\subsection{Bootloaders}
|
||||
A bootloader is the bridge between firmware and the operating-system kernel. Its purpose is not only to start the kernel, but also to place the machine into a state the kernel understands \cite{ionosBootloader, littleosbook}.
|
||||
|
||||
Common bootloader responsibilities include:
|
||||
\begin{itemize}
|
||||
\item locating and loading the kernel image,
|
||||
\item loading additional modules or initrd files,
|
||||
\item obtaining a memory map from the firmware,
|
||||
\item selecting or setting a graphics mode,
|
||||
\item preparing boot information structures,
|
||||
\item switching CPU mode when required,
|
||||
\item transferring control to the kernel entry point.
|
||||
\end{itemize}
|
||||
|
||||
Different bootloaders provide different levels of support. GRUB is a general-purpose bootloader with filesystem support, configuration files, multiboot support, and broad hardware compatibility. Limine is a modern boot protocol and bootloader often used in hobby OS development because it provides a clear boot protocol and supports both BIOS and UEFI systems\cite{osdevBootSequence,limineProtocol, wikipediaLimine}. A manually implemented bootloader gives full control over the boot path, but requires more work and exposes the project to more early-stage hardware and filesystem details.
|
||||
|
||||
The choice of bootloader depends on the goals of the operating-system project. For a kernel-focused project, using an existing bootloader is usually the most practical choice because it avoids spending most of the work on disk loading, filesystem parsing, firmware differences, and CPU mode transitions. For a project specifically about bootstrapping, implementing a bootloader manually can be useful because it exposes the details normally hidden by existing tools \cite{osdevBootSequence}.
|
||||
|
||||
Manual bootloader implementation is challenging because the environment is very limited. In a BIOS first-stage bootloader, code size is constrained, there is no standard library, only a small amount of state is initialized, and disk access must use firmware interrupts or direct hardware access. The bootloader must also be careful about where it places itself, the kernel, stacks, tables, and temporary buffers in memory.
|
||||
|
||||
\subsection{Memory Layout in the Boot Process}
|
||||
In a traditional i386 BIOS boot process, early memory layout is constrained by legacy conventions. The first MiB of memory contains several important regions, including the interrupt vector table, BIOS data area, conventional memory, video memory, and firmware regions\cite{osdevMemoryMapX86}.
|
||||
|
||||
The bootloader and kernel must avoid overwriting memory that is already used by firmware, hardware mappings, or the bootloader itself. In small BIOS examples, a common simple kernel load address is \texttt{0x10000}. This address is above the lowest BIOS data structures and gives the kernel more room than the original boot sector area, while still being within conventional memory that is easy to access in early boot stages. In this project, the linker script places the kernel at \texttt{1 MiB}, which is also a common protected-mode kernel load address and keeps the kernel away from the lowest legacy BIOS regions \cite{osdevMemoryMapX86}.
|
||||
|
||||
Choosing a fixed early kernel load address has practical implications:
|
||||
\begin{itemize}
|
||||
\item the bootloader needs to copy or load the kernel to a known address,
|
||||
\item the kernel linker script must match the address where the kernel expects to run,
|
||||
\item early stacks and temporary buffers must be placed so they do not overlap the kernel,
|
||||
\item later memory management must identify which regions are already occupied.
|
||||
\end{itemize}
|
||||
|
||||
Once the kernel has control, it can replace this simple early memory model with its own memory management. In this project, that later stage is represented by kernel heap initialization, page-aligned allocation, and paging setup.
|
||||
|
||||
\subsection{Boot Process in Modern Operating Systems}
|
||||
Modern operating systems use more complex boot processes than small teaching kernels, but the same basic idea remains: firmware initializes the platform, a bootloader or boot manager loads the kernel, and the kernel takes control of the machine.
|
||||
|
||||
Linux systems commonly use firmware to start a bootloader such as GRUB or systemd-boot. The bootloader loads the kernel and an initramfs, passes a command line and boot information, and then transfers control to the Linux kernel. Windows systems use Windows Boot Manager, which loads the Windows OS loader before the kernel and core system components are started. macOS uses Apple's boot chain, which is tightly integrated with Apple hardware, APFS, Secure Boot policies, and system volume verification\cite{linuxX86BootProtocol,microsoftWindowsBootOptions,appleSiliconBootProcess,appleIntelBootProcess}.
|
||||
|
||||
Modern boot processes have changed because hardware and security requirements have changed. UEFI replaced many BIOS conventions, disks moved from MBR partitioning toward GPT, and Secure Boot introduced signature verification into the boot chain. Storage devices are also more complex than older BIOS-era disks, and modern systems often need early support for NVMe, encryption, graphics initialization, and platform security features.
|
||||
|
||||
The result is that modern booting is both more capable and more controlled. Firmware and bootloaders provide richer services, but the operating system must also participate in a stricter trust chain and handle more platform variation.
|
||||
|
||||
\subsection{Virtual Machines and Booting}
|
||||
Booting inside a virtual machine follows the same conceptual stages as booting on physical hardware, but the hardware being initialized is virtual. The guest operating system still sees firmware, CPU state, memory, storage devices, timers, and interrupt controllers. However, those devices are provided or emulated by the hypervisor\cite{qemuPcMachine}.
|
||||
|
||||
The main difference is that a virtual machine does not start from real motherboard hardware. Instead, the hypervisor creates a virtual hardware environment and then starts the guest firmware inside it. The guest firmware performs a boot sequence that looks normal from inside the VM, but many device operations are handled by the hypervisor on the host \cite{hashnodeVMboot}.
|
||||
|
||||
The hypervisor has several roles in this process:
|
||||
\begin{itemize}
|
||||
\item allocating guest memory,
|
||||
\item exposing virtual CPUs,
|
||||
\item providing virtual disks and network devices,
|
||||
\item emulating or virtualizing interrupt controllers and timers,
|
||||
\item presenting BIOS or UEFI firmware to the guest,
|
||||
\item handling privileged operations that cannot run directly on the host CPU.
|
||||
\end{itemize}
|
||||
|
||||
Virtualized booting has advantages for operating-system development. It is faster to test than rebooting physical hardware, the virtual machine can be reset easily, debugging is easier with tools such as QEMU and GDB, and hardware behaviour is more reproducible. This is why the kernel in this project was tested through QEMU rather than directly on physical hardware.
|
||||
|
||||
There are also limitations. Emulated hardware may not behave exactly like real hardware, and some devices are simplified compared to physical machines. For example, PC speaker output in QEMU can be limited, which affected the clarity of fast note changes in the music player. Even so, virtualization is highly useful for kernel development because it provides a controlled environment for testing boot, interrupts, memory management, and device interaction\cite{qemuPcMachine, hashnodeVMboot}.
|
||||
122
AdvOpsys/notes/report/2.tex
Normal file
122
AdvOpsys/notes/report/2.tex
Normal file
@@ -0,0 +1,122 @@
|
||||
\section{Boot Process and Processor Setup}
|
||||
|
||||
The first stage of the project established a more complete early boot path for the kernel. Before this work, the kernel entered C code, initialized the VGA terminal, printed \texttt{Hello, World!}, and then remained in an infinite halt loop. The terminal output in this stage used direct VGA text-mode writes rather than BIOS text services, which is the normal approach once the kernel is running in protected mode \cite{osdevPrintingToScreen}. The visible output remained the same after this stage, but the processor setup became more explicit and reliable.
|
||||
|
||||
The main change was the addition of a minimal Global Descriptor Table for the i386 kernel. Even though the kernel uses a flat memory model, protected mode still requires valid segment descriptors. The GDT therefore gives the processor valid code and data segment definitions before the kernel continues into higher-level initialization\cite{osdevGdtTutorial}.
|
||||
|
||||
\subsection{Kernel Entry Flow}
|
||||
After the GDT work, the early kernel flow became:
|
||||
\begin{enumerate}
|
||||
\item the bootloader transfers control to the kernel entry point,
|
||||
\item the kernel begins execution in C,
|
||||
\item the GDT is initialized and loaded,
|
||||
\item segment registers are reloaded,
|
||||
\item the VGA terminal is initialized,
|
||||
\item \texttt{Hello, World!} is written to the screen,
|
||||
\item the kernel enters an infinite halt loop.
|
||||
\end{enumerate}
|
||||
|
||||
This keeps processor setup before terminal setup. Later stages build on this ordering by adding interrupts, memory management, timing, and applications after the low-level CPU state has been initialized.
|
||||
\clearpage
|
||||
|
||||
\subsection{GDT Layout}
|
||||
The implemented GDT contains three descriptors:
|
||||
\begin{itemize}
|
||||
\item a null descriptor,
|
||||
\item a kernel code segment descriptor,
|
||||
\item a kernel data segment descriptor.
|
||||
\end{itemize}
|
||||
|
||||
The code and data segments both use base address \texttt{0x00000000}, a 4 GiB address span, 4 KiB granularity, and ring 0 privilege level. The code segment uses selector \texttt{0x08}, while the data segment uses selector \texttt{0x10}. This matches the flat protected-mode layout commonly used in small i386 kernels \cite{osdevGdtTutorial,intelSdm}.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
void GdtInitialize(void) {
|
||||
gdtDescriptor.size = sizeof(gdtEntries) - 1;
|
||||
gdtDescriptor.offset = (uint32_t)&gdtEntries;
|
||||
|
||||
GdtSetEntry(0, 0, 0, 0, 0);
|
||||
GdtSetEntry(1, 0, 0x000FFFFF, 0x9A, 0xCF);
|
||||
GdtSetEntry(2, 0, 0x000FFFFF, 0x92, 0xCF);
|
||||
|
||||
GdtFlush((uint32_t)&gdtDescriptor);
|
||||
}
|
||||
\end{minted}
|
||||
\caption{Minimal GDT initialization with null, kernel code, and kernel data descriptors.}
|
||||
\end{listing}
|
||||
|
||||
\subsection{Descriptor Construction}
|
||||
The GDT setup is split between C and assembly. The C code builds the descriptor table and prepares the GDT descriptor that contains the table size and address. The helper that creates each descriptor splits the base and limit into the fields expected by the processor \cite{osdevGdtTutorial,intelSdm}.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
static void GdtSetEntry(
|
||||
uint32_t index,
|
||||
uint32_t base,
|
||||
uint32_t limit,
|
||||
uint8_t access,
|
||||
uint8_t granularity
|
||||
) {
|
||||
gdtEntries[index].base_low = (uint16_t)(base & 0xFFFF);
|
||||
gdtEntries[index].base_middle = (uint8_t)((base >> 16) & 0xFF);
|
||||
gdtEntries[index].base_high = (uint8_t)((base >> 24) & 0xFF);
|
||||
|
||||
gdtEntries[index].limit_low = (uint16_t)(limit & 0xFFFF);
|
||||
gdtEntries[index].granularity = (uint8_t)((limit >> 16) & 0x0F);
|
||||
gdtEntries[index].granularity |= (uint8_t)(granularity & 0xF0);
|
||||
gdtEntries[index].access = access;
|
||||
}
|
||||
\end{minted}
|
||||
\caption{Building an x86 GDT descriptor from base, limit, access, and granularity fields.}
|
||||
\end{listing}
|
||||
\clearpage
|
||||
|
||||
\subsection{Reloading Segment Registers}
|
||||
Loading the GDT with \texttt{lgdt} is not enough by itself. The CPU caches segment descriptor information in the segment registers, so the kernel must reload those registers after installing the new table. The assembly routine performs the architecture-specific part: it loads the GDT, performs a far jump to reload \texttt{cs}, and then reloads the data segment registers\cite{osdevGdtTutorial}.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{nasm}
|
||||
GdtFlush:
|
||||
mov eax, [esp + 4]
|
||||
lgdt [eax]
|
||||
|
||||
jmp GDT_CODE_SELECTOR:.reload_cs
|
||||
|
||||
.reload_cs:
|
||||
mov ax, GDT_DATA_SELECTOR
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
mov ss, ax
|
||||
ret
|
||||
\end{minted}
|
||||
\caption{Loading the GDT and refreshing the cached code and data segment registers.}
|
||||
\end{listing}
|
||||
|
||||
\section{Build and Verification}
|
||||
|
||||
The build configuration was updated so that both the C GDT implementation and the assembly reload routine are compiled and linked into the kernel binary. New build artifacts were generated, including an updated kernel binary and bootable ISO image.
|
||||
|
||||
\subsection{Static Binary Inspection}
|
||||
The generated kernel binary was inspected to confirm that the expected GDT setup sequence was present. The inspection showed a call to the GDT load routine, a far jump to selector \texttt{0x08}, and data segment reloads using selector \texttt{0x10}.
|
||||
|
||||
\subsection{Runtime Debugging}
|
||||
The GDT setup was also verified in QEMU using \texttt{gdb-multiarch}. Execution was stopped inside the GDT initialization and reload path. The GDT descriptor limit was \texttt{0x17}, which matches three 8-byte entries minus one. After the far jump, \texttt{cs} contained \texttt{0x08}; after the reload instructions, \texttt{ds}, \texttt{es}, \texttt{fs}, \texttt{gs}, and \texttt{ss} contained \texttt{0x10}.
|
||||
\clearpage
|
||||
|
||||
\section{State After Processor Setup}
|
||||
|
||||
At the end of this stage, the kernel:
|
||||
\begin{itemize}
|
||||
\item builds inside the development container,
|
||||
\item includes a minimal i386 GDT implementation,
|
||||
\item loads the GDT during startup,
|
||||
\item reloads the code and data segment registers correctly,
|
||||
\item still initializes the VGA terminal,
|
||||
\item still prints \texttt{Hello, World!} after boot.
|
||||
\end{itemize}
|
||||
|
||||
This stage did not add new user-facing behaviour. Its purpose was to make the early processor state explicit so later kernel subsystems could be added on top of a controlled protected-mode setup.
|
||||
|
||||
Later stages changed the visible startup flow by initializing the terminal before later setup code prints diagnostics for interrupt, memory, and paging work. The important dependency from this stage remains that the GDT is loaded before the kernel relies on later interrupt and application behaviour.
|
||||
185
AdvOpsys/notes/report/3.tex
Normal file
185
AdvOpsys/notes/report/3.tex
Normal file
@@ -0,0 +1,185 @@
|
||||
\newpage
|
||||
|
||||
\section{Interrupt Handling}
|
||||
|
||||
The next stage added interrupt support to the kernel. This required an Interrupt Descriptor Table so the processor has defined entry points for CPU exceptions and hardware interrupts. The implementation follows the same model used by the OSDev Wiki material, the \texttt{os-tutorial} project, and the lecturer-provided assignment files \cite{osdevInterruptsTutorial,fenollosaOsTutorial,assignmentFiles}.
|
||||
|
||||
Interrupt support changed the kernel from a purely linear boot program into a system that can react to events while it is running. CPU exceptions are handled through ISR stubs, hardware interrupts are handled through IRQ stubs, and both paths eventually reach C handlers with a saved register state.
|
||||
|
||||
\subsection{IDT Descriptor Setup}
|
||||
Each IDT entry stores the handler address, the kernel code segment selector, and the descriptor attributes used by the processor. The handler address is split into low and high parts because this is the layout expected by the 32-bit x86 IDT format \cite{osdevInterruptsTutorial}.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
void IdtSetDescriptor(uint8_t vector, uint32_t interrupt, uint8_t flags) {
|
||||
struct IdtEntry* descriptor = &idt[vector];
|
||||
|
||||
descriptor->interrupt_low = (uint32_t)interrupt & 0xFFFF;
|
||||
descriptor->kernel_cs = GDT_CODE_SELECTOR;
|
||||
descriptor->attributes = flags;
|
||||
descriptor->interrupt_high = (uint32_t)interrupt >> 16;
|
||||
descriptor->reserved = 0;
|
||||
}
|
||||
\end{minted}
|
||||
\caption{Constructing an IDT gate for a specific interrupt vector.}
|
||||
\end{listing}
|
||||
\clearpage
|
||||
|
||||
\subsection{IDT Initialization}
|
||||
During initialization, the kernel fills vectors \texttt{0} through \texttt{31} with CPU exception stubs and vectors \texttt{32} through \texttt{47} with hardware IRQ stubs. This means the implementation provides ISR coverage for all 32 standard CPU exception vectors, which is more complete than only defining the minimum three handlers required for basic testing. The PIC is then remapped before the IDT is loaded with \texttt{lidt} and interrupts are enabled with \texttt{sti}.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
void IdtInitialize(void) {
|
||||
idtr.base = (uint32_t)&idt[0];
|
||||
idtr.limit = (uint16_t)sizeof(struct IdtEntry) * IDT_ENTRIES - 1;
|
||||
|
||||
for (uint8_t iNum = 0; iNum < 32; iNum++) {
|
||||
IdtSetDescriptor(iNum, (uint32_t)isr_stub_table[iNum], 0x8E);
|
||||
}
|
||||
|
||||
for (uint8_t iNum = 32; iNum < 48; iNum++) {
|
||||
IdtSetDescriptor(iNum, (uint32_t)irq_stub_table[iNum - 32], 0x8E);
|
||||
}
|
||||
|
||||
PicRemap();
|
||||
__asm__ volatile ("lidt %0" : : "m"(idtr));
|
||||
__asm__ volatile ("sti");
|
||||
}
|
||||
\end{minted}
|
||||
\caption{Initializing the IDT with CPU exception and hardware IRQ entries.}
|
||||
\end{listing}
|
||||
\clearpage
|
||||
|
||||
\section{CPU Exceptions}
|
||||
|
||||
Basic Interrupt Service Routines were implemented for CPU exceptions. The low-level stubs save the CPU state and pass a \texttt{Registers} structure into the shared C handler. This lets the kernel inspect which exception occurred and print useful debugging information to the terminal. Instead of each ISR containing its own separate print logic, the individual stubs share the same C handler, which prints the interrupt number and message for the triggered exception.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
void IsrHandler(struct Registers* registers) {
|
||||
TerminalWriteString("\n=== INTERRUPT RECEIVED ===\n");
|
||||
|
||||
TerminalWriteString("Interrupt Number: ");
|
||||
TerminalWriteUInt(registers->int_no);
|
||||
TerminalWriteString("\n");
|
||||
|
||||
TerminalWriteString("Message: ");
|
||||
if (registers->int_no < 32) {
|
||||
TerminalWriteString(isrMessages[registers->int_no]);
|
||||
} else {
|
||||
TerminalWriteString("Unknown Interrupt");
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
__asm__ volatile("cli; hlt");
|
||||
}
|
||||
}
|
||||
\end{minted}
|
||||
\caption{Shared CPU exception handler using the saved interrupt number.}
|
||||
\end{listing}
|
||||
|
||||
The handler stops the kernel after reporting the exception. This is appropriate for the current stage because exception recovery has not yet been implemented, and halting prevents the kernel from continuing after an undefined or unsafe processor state.
|
||||
\clearpage
|
||||
|
||||
\section{Hardware Interrupts}
|
||||
|
||||
Hardware interrupt support was added for IRQ0 through IRQ15. These interrupts come from devices through the Programmable Interrupt Controller. The PIC is remapped so that hardware IRQs begin at vector \texttt{32} instead of overlapping with the CPU exception vectors\cite{osdev8259Pic}.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
void PicRemap(void) {
|
||||
OutPortByte(PIC1_COMMAND, 0x11);
|
||||
OutPortByte(PIC2_COMMAND, 0x11);
|
||||
|
||||
OutPortByte(PIC1_DATA, 0x20);
|
||||
OutPortByte(PIC2_DATA, 0x28);
|
||||
|
||||
OutPortByte(PIC1_DATA, 0x04);
|
||||
OutPortByte(PIC2_DATA, 0x02);
|
||||
|
||||
OutPortByte(PIC1_DATA, 0x01);
|
||||
OutPortByte(PIC2_DATA, 0x01);
|
||||
|
||||
OutPortByte(PIC1_DATA, 0x00);
|
||||
OutPortByte(PIC2_DATA, 0x00);
|
||||
}
|
||||
\end{minted}
|
||||
\caption{Remapping the PIC so IRQs use interrupt vectors \texttt{32} through \texttt{47}.}
|
||||
\end{listing}
|
||||
|
||||
The IRQ handler dispatches to a registered C handler when one exists. After the interrupt has been handled, the kernel sends an end-of-interrupt signal to the PIC. This tells the controller that the current interrupt has been processed and that new interrupts may be delivered.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
void IrqHandler(struct Registers* registers) {
|
||||
uint8_t irqNum = (uint8_t)(registers->int_no - 32);
|
||||
|
||||
if (interruptHandlers[registers->int_no] != 0) {
|
||||
interruptHandlers[registers->int_no](registers);
|
||||
} else if (irqNum != 0) {
|
||||
TerminalWriteString("IRQ triggered: ");
|
||||
TerminalWriteUInt(irqNum);
|
||||
TerminalWriteString("\n");
|
||||
}
|
||||
|
||||
PicSendEoi(irqNum);
|
||||
}
|
||||
\end{minted}
|
||||
\caption{Dispatching hardware interrupts and acknowledging them with the PIC.}
|
||||
\end{listing}
|
||||
\clearpage
|
||||
|
||||
\section{Keyboard Input}
|
||||
|
||||
Keyboard support was implemented using IRQ1. The keyboard handler reads the scancode from port \texttt{0x60}, stores it in a small buffer, ignores key-release events, and translates key-press scancodes into ASCII through a lookup table\cite{osdevPs2Controller}.
|
||||
|
||||
The assignment describes the keyboard task as printing translated ASCII characters to the screen. In this project, the keyboard logic was extended slightly beyond a direct logger: the handler stores the latest translated ASCII character, and higher-level terminal or application code consumes it. This allows the same keyboard path to support the application menu and Snake input instead of only echoing characters immediately from the interrupt handler.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
void KeyboardHandler(struct Registers* registers) {
|
||||
(void) registers;
|
||||
|
||||
uint8_t scancode = InPortByte(KEYBOARD_DATA_PORT);
|
||||
|
||||
if (index < KEYBOARD_BUFFER_SIZE) {
|
||||
keyboardBuffer[index] = scancode;
|
||||
index++;
|
||||
}
|
||||
|
||||
if (scancode & 0x80) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scancode < 128) {
|
||||
char ascii = scancodeToAscii[scancode];
|
||||
if (ascii != 0) {
|
||||
lastKeyPressed = ascii;
|
||||
}
|
||||
}
|
||||
}
|
||||
\end{minted}
|
||||
\caption{Keyboard IRQ handler reading scancodes and storing the latest ASCII key.}
|
||||
\end{listing}
|
||||
|
||||
The most recent translated key can then be consumed by higher-level code through \texttt{GetLastKeyPressed()}. This becomes important in later stages where the application menu and Snake game need keyboard input without directly handling raw scancodes.
|
||||
\clearpage
|
||||
|
||||
\section{State After Interrupt Work}
|
||||
|
||||
At the end of this stage, the kernel:
|
||||
\begin{itemize}
|
||||
\item initializes and loads an Interrupt Descriptor Table,
|
||||
\item handles CPU exceptions through ISR support,
|
||||
\item supports hardware IRQs from IRQ0 to IRQ15,
|
||||
\item remaps the PIC so hardware interrupts do not overlap with CPU exceptions,
|
||||
\item dispatches hardware interrupts to registered handlers,
|
||||
\item sends end-of-interrupt signals after IRQ handling,
|
||||
\item handles keyboard input through IRQ1,
|
||||
\item reads keyboard scancodes from port \texttt{0x60},
|
||||
\item translates key presses into ASCII characters,
|
||||
\item stores the latest key for later application-level input.
|
||||
\end{itemize}
|
||||
|
||||
This stage moved the kernel from static startup output to event-driven execution. The later PIT, music, menu, and Snake work all depend on this interrupt layer.
|
||||
164
AdvOpsys/notes/report/4-5.tex
Normal file
164
AdvOpsys/notes/report/4-5.tex
Normal file
@@ -0,0 +1,164 @@
|
||||
\section{Memory Management}
|
||||
|
||||
The next stage added basic memory management to the kernel. This work introduced kernel heap initialization, paging, dynamic allocation, page-aligned allocation, and memory debugging output. Together these changes allow later kernel components to request memory dynamically instead of relying only on static storage\cite{assignmentFiles,osdevMemoryAllocation}.
|
||||
|
||||
\subsection{Kernel Heap Initialization}
|
||||
The heap is initialized from the linker-provided end of the kernel image. The kernel stores this address in \texttt{last\_alloc}, then reserves a normal heap region below the page heap area. A separate page heap is placed below \texttt{0x400000}, with one descriptor byte per page-aligned allocation \cite{assignmentFiles}.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
void InitKernelMemory(uint32_t* kernel_end) {
|
||||
uint32_t kernelEndAddr = (uint32_t)kernel_end;
|
||||
|
||||
last_alloc = kernelEndAddr + 0x1000;
|
||||
heap_begin = last_alloc;
|
||||
pheap_end = 0x400000;
|
||||
pheap_begin = pheap_end - (MAX_PAGE_ALIGNED_ALLOCS * 4096);
|
||||
heap_end = pheap_begin;
|
||||
memset((char*)heap_begin, 0, heap_end - heap_begin);
|
||||
pheap_desc = (uint8_t *)malloc(MAX_PAGE_ALIGNED_ALLOCS);
|
||||
}
|
||||
\end{minted}
|
||||
\caption{Kernel heap initialization using the linker-provided kernel end symbol.}
|
||||
\end{listing}
|
||||
|
||||
This creates two allocation areas:
|
||||
\begin{itemize}
|
||||
\item a byte-addressed heap for normal \texttt{malloc()} allocations,
|
||||
\item a page heap for 4 KiB page-aligned allocations through \texttt{pmalloc()}.
|
||||
\end{itemize}
|
||||
\clearpage
|
||||
|
||||
\subsection{Paging}
|
||||
Paging was implemented using a page directory at \texttt{0x400000} and page tables starting at \texttt{0x404000}. The setup identity-maps the first 4 MiB of memory and the 4 MiB region beginning at \texttt{0x400000}. This keeps virtual addresses equal to physical addresses for the early kernel environment while still enabling the processor's paging mechanism\cite{assignmentFiles,osdevSettingUpPaging}.
|
||||
|
||||
The paging setup writes the page directory address to \texttt{cr3} and sets the paging bit in \texttt{cr0}. After that point, memory references are translated through the page tables \cite{osdevSettingUpPaging,intelSdm}.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
void paging_enable() {
|
||||
asm volatile("mov %%eax, %%cr3": :"a"(page_dir_loc));
|
||||
asm volatile("mov %cr0, %eax");
|
||||
asm volatile("orl $0x80000000, %eax");
|
||||
asm volatile("mov %eax, %cr0");
|
||||
}
|
||||
\end{minted}
|
||||
\caption{Enabling paging by loading \texttt{cr3} and setting the paging bit in \texttt{cr0}.}
|
||||
\end{listing}
|
||||
|
||||
\subsection{Dynamic Allocation}
|
||||
A simple heap allocator was implemented for \texttt{malloc()} and \texttt{free()}. Each allocation is preceded by a small metadata header containing the allocation status and size. New allocations advance \texttt{last\_alloc}, while freed blocks are marked as unused and may be reused by later allocations \cite{osdevMemoryAllocation}.
|
||||
|
||||
The allocator scans existing blocks before creating a new one. If it finds an unused block large enough for the request, that block is reactivated and returned. This was verified by freeing one allocation, requesting a smaller block, and observing that the allocator reused the same address.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
if(a->size >= size) {
|
||||
a->status = 1;
|
||||
memset(mem + sizeof(alloc_t), 0, size);
|
||||
memory_used += a->size + sizeof(alloc_t) + 4;
|
||||
return (char*)(mem + sizeof(alloc_t));
|
||||
}
|
||||
\end{minted}
|
||||
\caption{Reusing a freed heap block when it is large enough for a new allocation.}
|
||||
\end{listing}
|
||||
|
||||
\subsection{Memory Debugging}
|
||||
The kernel also gained memory debugging output. \texttt{PrintMemoryLayout()} prints the amount of memory used, the amount free, the heap size, and the start and end addresses of both the normal heap and page heap. \texttt{TerminalWriteHex()} was added so addresses can be printed in hexadecimal instead of being confused with decimal values \cite{assignmentFiles}.
|
||||
\clearpage
|
||||
|
||||
\section{Programmable Interval Timer}
|
||||
|
||||
The Programmable Interval Timer was then added so the kernel can measure time and schedule delays. The PIT is configured on channel 0 at \texttt{1000 Hz}, so one timer tick corresponds approximately to one millisecond \cite{osdevPit}.
|
||||
|
||||
\subsection{PIT Initialization}
|
||||
The PIT driver defines the command port, channel 0 port, base frequency, target frequency, divider, and tick conversion in \texttt{pit.h}. During initialization, the kernel registers an IRQ0 handler, programs the PIT command byte, and writes the divisor as low byte followed by high byte\cite{assignmentFiles,osdevPit}.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
void PitInitialize(void){
|
||||
uint16_t divisor = DIVIDER;
|
||||
|
||||
RegisterInterruptHandler(IRQ0, PitIrqHandler);
|
||||
|
||||
OutPortByte(PIT_CMD_PORT, 0x36);
|
||||
OutPortByte(PIT_CHANNEL0_PORT, (uint8_t)(divisor & 0xFF));
|
||||
OutPortByte(PIT_CHANNEL0_PORT, (uint8_t)((divisor >> 8) & 0xFF));
|
||||
}
|
||||
\end{minted}
|
||||
\caption{PIT channel 0 initialization and IRQ0 handler registration.}
|
||||
\end{listing}
|
||||
|
||||
The IRQ0 handler increments a \texttt{volatile} tick counter. Keeping the counter inside \texttt{pit.c} reduces the chance of accidental writes from unrelated kernel code \cite{assignmentFiles,osdevPit}.
|
||||
|
||||
\subsection{Sleep Functions}
|
||||
Two sleep functions were implemented. \texttt{SleepBusy()} repeatedly checks the tick counter until enough time has passed. This is simple but consumes CPU time for the whole delay.
|
||||
|
||||
\texttt{SleepInterrupt()} uses \texttt{sti; hlt} inside the loop. This enables interrupts and halts the CPU until the next interrupt occurs. The loop then rechecks the current tick and halts again if the requested delay has not elapsed \cite{assignmentFiles,intelSdm}.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
void SleepInterrupt(uint32_t ticks_to_wait){
|
||||
uint32_t start_tick = GetCurrentTick();
|
||||
uint32_t end_tick = start_tick + ticks_to_wait;
|
||||
|
||||
while(GetCurrentTick() < end_tick){
|
||||
__asm__ volatile ("sti; hlt");
|
||||
}
|
||||
}
|
||||
\end{minted}
|
||||
\caption{Interrupt-based sleeping using the PIT tick counter and \texttt{hlt}.}
|
||||
\end{listing}
|
||||
|
||||
\subsection{Verification}
|
||||
Several integration issues were fixed during PIT work. Incorrect inline assembly syntax was corrected, terminal output functions replaced unsuitable \texttt{printf()} calls, and the missing \texttt{TerminalWriteHex()} declaration was added to \texttt{terminal.h}.
|
||||
|
||||
The build process also showed that rebuilding only \texttt{uiaos-kernel} updates \texttt{kernel.bin}, but does not update the bootable ISO image. Rebuilding \texttt{uiaos-create-image} was necessary before QEMU booted the newest kernel. Runtime testing then showed paging output, heap output, allocation output, and repeated sleep-test messages for both busy-wait and interrupt-based sleeping.
|
||||
|
||||
\section{PC Speaker Music Player}
|
||||
|
||||
Assignment 5 used the PIT and PC speaker to implement a simple music player. The PC speaker is controlled through I/O port \texttt{0x61}, while PIT channel 2 generates the square wave used for audible notes\cite{osdevPcSpeaker}.
|
||||
|
||||
\subsection{Sound Generation}
|
||||
The speaker is enabled by setting bits 0 and 1 on port \texttt{0x61}. To play a note, the kernel calculates a divisor from the PIT base frequency and the requested note frequency, programs PIT channel 2, and then enables the speaker.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
void PlaySound(uint32_t frequency) {
|
||||
if (!frequency) return;
|
||||
|
||||
uint32_t divisor = PIT_BASE_FREQ / frequency;
|
||||
|
||||
OutPortByte(PIT_CMD_PORT, 0xB6);
|
||||
OutPortByte(PIT_CHANNEL2_PORT, divisor & 0xFF);
|
||||
OutPortByte(PIT_CHANNEL2_PORT, (divisor >> 8) & 0xFF);
|
||||
|
||||
EnableSpeaker();
|
||||
}
|
||||
\end{minted}
|
||||
\caption{Programming PIT channel 2 to generate a PC speaker tone.}
|
||||
\end{listing}
|
||||
\clearpage
|
||||
|
||||
\subsection{Song Representation and Playback}
|
||||
Songs are represented as arrays of notes. Each note stores a frequency and duration in milliseconds. A rest is represented by frequency \texttt{R}, which is defined as \texttt{0}. Playback therefore consists of selecting a frequency, sleeping for the note duration, and then stopping the speaker.
|
||||
|
||||
The implementation uses \texttt{SleepInterrupt()} for note timing. This replaced busy-wait delays because the interrupt-based delay gave better timing and avoided wasting CPU cycles while each note was playing.
|
||||
|
||||
QEMU's PC speaker support made fast note changes unclear. To make playback more distinguishable, durations were doubled during playback.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
if (currentNote.frequency == R) {
|
||||
StopSound();
|
||||
SleepInterrupt(currentNote.duration * 2);
|
||||
} else {
|
||||
PlaySound(currentNote.frequency);
|
||||
SleepInterrupt(currentNote.duration * 2);
|
||||
StopSound();
|
||||
}
|
||||
\end{minted}
|
||||
\caption{Song playback using rests, PC speaker tones, and interrupt-based timing.}
|
||||
\end{listing}
|
||||
\clearpage
|
||||
|
||||
227
AdvOpsys/notes/report/6.tex
Normal file
227
AdvOpsys/notes/report/6.tex
Normal file
@@ -0,0 +1,227 @@
|
||||
\section{Application Framework, Snake, and Piano}
|
||||
|
||||
The final stage integrated the individual OS components into a simple application framework. Instead of booting into a single test routine, the kernel now presents a terminal menu where the user can choose between the music player, Snake, and a piano application.
|
||||
|
||||
\subsection{Menu-Based Control Flow}
|
||||
The kernel initialization path now sets up the terminal, GDT, IDT, PIT, keyboard interrupt handler, kernel memory, and paging. After that initialization, the kernel repeatedly asks for an application number and dispatches to one of the available programs. This ties the higher-level menu flow back to the keyboard, PIT, and memory-management infrastructure implemented earlier \cite{osdevPs2Controller,osdevPit,osdevMemoryAllocation}.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
while (1) {
|
||||
TerminalWriteString("Enter application number:\n");
|
||||
TerminalWriteString("0. Play Music\n");
|
||||
TerminalWriteString("1. Play Snake\n");
|
||||
TerminalWriteString("2. Play Piano\n");
|
||||
char input = TerminalGetChar();
|
||||
|
||||
switch (input) {
|
||||
case '0':
|
||||
TerminalClear();
|
||||
PlayMusic();
|
||||
TerminalClear();
|
||||
break;
|
||||
case '1':
|
||||
TerminalClear();
|
||||
PlayGame();
|
||||
TerminalClear();
|
||||
break;
|
||||
case '2':
|
||||
TerminalClear();
|
||||
PlayPiano();
|
||||
TerminalClear();
|
||||
break;
|
||||
default:
|
||||
TerminalWriteString("\nInvalid application number.\n");
|
||||
SleepInterrupt(1000);
|
||||
TerminalClear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
\end{minted}
|
||||
\caption{Application menu dispatch in the kernel main loop.}
|
||||
\end{listing}
|
||||
|
||||
This demonstrates that the terminal, keyboard, interrupt, timer, memory, and application code can cooperate through a single kernel control flow.
|
||||
\clearpage
|
||||
|
||||
\subsection{Snake Game State}
|
||||
Snake uses a dynamically allocated game state. The game state contains the board, snake, food, score, and pseudo-random state. Creating and destroying the game therefore exercises the kernel's \texttt{malloc()} and \texttt{free()} implementation in a more realistic setting than isolated allocation tests.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
struct GameState* CreateGame(void) {
|
||||
struct GameState* game = (struct GameState*)malloc(sizeof(struct GameState));
|
||||
if (!game) return 0;
|
||||
|
||||
game->snake = (struct Snake*)malloc(sizeof(struct Snake));
|
||||
if (!game->snake) {
|
||||
free(game);
|
||||
return 0;
|
||||
}
|
||||
|
||||
game->food = (struct Food*)malloc(sizeof(struct Food));
|
||||
if (!game->food) {
|
||||
free(game->snake);
|
||||
free(game);
|
||||
return 0;
|
||||
}
|
||||
|
||||
game->score = 0;
|
||||
game->rngState = GetCurrentTick();
|
||||
return game;
|
||||
}
|
||||
\end{minted}
|
||||
\caption{Snake creates its game objects using the kernel heap allocator.}
|
||||
\end{listing}
|
||||
\clearpage
|
||||
|
||||
\subsection{Input, Timing, and Feedback}
|
||||
The Snake loop reads the last key pressed by the keyboard handler, updates the snake direction, moves the snake, checks collisions, redraws the board, and then sleeps using the PIT. The game uses \texttt{GAME\_SPEED\_MS} to control pacing.
|
||||
|
||||
Sound is also integrated into the game. Eating food, dying, and winning each trigger short PC speaker effects. This connects the application layer back to the PIT and PC speaker support implemented earlier.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
input = GetLastKeyPressed();
|
||||
HandleInput(game, input);
|
||||
tail = MoveSnake(game->snake);
|
||||
|
||||
collisionType = CheckCollision(game->snake, game->food);
|
||||
if (collisionType == FOOD) {
|
||||
game->score++;
|
||||
PlayFoodSound();
|
||||
AddSegment(game->snake, tail.x, tail.y);
|
||||
SpawnFood(game);
|
||||
}
|
||||
|
||||
DrawBoard(game);
|
||||
SleepInterrupt(GAME_SPEED_MS);
|
||||
\end{minted}
|
||||
\caption{Main Snake loop combining keyboard input, game state, sound, drawing, and PIT timing.}
|
||||
\end{listing}
|
||||
\clearpage
|
||||
|
||||
\subsection{Piano Application State}
|
||||
The piano application adds a second interactive program that uses the same core kernel services in a different way. Instead of a continuously advancing game loop, the piano keeps a dynamically allocated application state with recording flags, the current note, timing information, and a song library. This makes the application a direct integration point between the heap allocator, keyboard input, PIT timing, and PC speaker output.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
struct PianoAppState* CreatePiano(void) {
|
||||
struct PianoAppState* piano =
|
||||
(struct PianoAppState*)malloc(sizeof(struct PianoAppState));
|
||||
if (!piano) return 0;
|
||||
|
||||
piano->songLibrary =
|
||||
(struct SongLibrary*)malloc(sizeof(struct SongLibrary));
|
||||
if (!piano->songLibrary) {
|
||||
free(piano);
|
||||
return 0;
|
||||
}
|
||||
|
||||
piano->songLibrary->songs =
|
||||
(struct Song*)malloc(sizeof(struct Song) * MAX_SONG_COUNT);
|
||||
if (!piano->songLibrary->songs) {
|
||||
free(piano->songLibrary);
|
||||
free(piano);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
\end{minted}
|
||||
\caption{The piano allocates persistent application state and storage for recorded songs.}
|
||||
\end{listing}
|
||||
|
||||
\subsection{Key Mapping and Sound Generation}
|
||||
The piano maps keyboard characters to musical notes. White and black keys are laid out in the terminal UI, and pressing a mapped key programs PIT channel 2 with the matching frequency before enabling the PC speaker. This reuses the same low-level mechanism as the earlier music player, but now the sound generation is driven directly by live keyboard input rather than a predefined song array \cite{osdevPcSpeaker,osdevPit}.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
case 'z':
|
||||
PianoPlaySound(C4);
|
||||
piano->activeFrequency = C4;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
PianoPlaySound(Cs4);
|
||||
piano->activeFrequency = Cs4;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
PianoPlaySound(D4);
|
||||
piano->activeFrequency = D4;
|
||||
break;
|
||||
\end{minted}
|
||||
\caption{Keyboard input is translated into note frequencies for live piano playback.}
|
||||
\end{listing}
|
||||
|
||||
\subsection{Recording and Playback}
|
||||
The piano also adds a simple song-recording feature. When recording is enabled, played notes are stored in a song buffer together with their durations. The code also tracks the PIT tick count between notes, so pauses longer than a small threshold are stored as rests. During playback, those notes and rests are replayed through the PC speaker using \texttt{SleepInterrupt()} for timing \cite{osdevPit,osdevPcSpeaker}.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
if (piano->recording && piano->lastNoteEndTick != 0) {
|
||||
uint32_t now = GetCurrentTick();
|
||||
uint32_t restDuration = now - piano->lastNoteEndTick;
|
||||
|
||||
if (restDuration > 20) {
|
||||
RecordNote(piano, R, restDuration);
|
||||
}
|
||||
}
|
||||
|
||||
PianoHandleInput(piano);
|
||||
|
||||
if (piano->recording) {
|
||||
RecordNote(piano, piano->activeFrequency, PIANO_NOTE_DURATION);
|
||||
}
|
||||
\end{minted}
|
||||
\caption{The piano stores both played notes and longer gaps so recorded songs preserve rhythm.}
|
||||
\end{listing}
|
||||
\clearpage
|
||||
|
||||
\subsection{Interactive Piano Loop}
|
||||
The runtime structure of the piano differs from Snake. Snake updates on every timer-based iteration, while the piano mostly waits for keyboard input, reacts to note and control keys, redraws the terminal UI, and uses short PIT sleeps to avoid a tight polling loop when no key has been pressed. This makes the piano a useful example of an event-driven terminal application built from the same kernel mechanisms as the rest of the project \cite{osdevPs2Controller,osdevPit}.
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{minted}{c}
|
||||
while (1) {
|
||||
char input = GetLastKeyPressed();
|
||||
|
||||
if (!input) {
|
||||
SleepInterrupt(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input == 'q') {
|
||||
PianoStopSound();
|
||||
break;
|
||||
}
|
||||
|
||||
if (input == 'r' || input == 'p') {
|
||||
piano->lastInput = input;
|
||||
PianoHandleInput(piano);
|
||||
TerminalClear();
|
||||
DrawPianoUi(piano);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
\end{minted}
|
||||
\caption{The piano loop combines keyboard polling, terminal redraws, and PIT-backed waiting.}
|
||||
\end{listing}
|
||||
\clearpage
|
||||
|
||||
\section{Final State of the Project}
|
||||
|
||||
At the end of the documented work, the kernel contains:
|
||||
\begin{itemize}
|
||||
\item early GDT setup and segment register reloading,
|
||||
\item IDT, ISR, IRQ, PIC, and keyboard interrupt support,
|
||||
\item kernel heap initialization and simple dynamic allocation,
|
||||
\item identity-mapped paging for the early kernel address space,
|
||||
\item PIT-based timer ticks and sleep functions,
|
||||
\item PC speaker sound generation through PIT channel 2,
|
||||
\item a menu-driven application flow,
|
||||
\item a music player application,
|
||||
\item an interactive Snake game using memory allocation, keyboard input, timing, terminal drawing, and sound feedback,
|
||||
\item an interactive piano application with live note input, song recording, and playback.
|
||||
\end{itemize}
|
||||
|
||||
The project therefore moved from a minimal booting kernel that printed static text into a small interactive operating-system environment with hardware interrupts, timing, memory management, terminal input, sound, and application-level control flow. The final piano addition is especially useful as an integration example because it combines dynamic allocation, live keyboard input, PIT-based timing, terminal rendering, and PC speaker output inside one self-contained application.
|
||||
244
AdvOpsys/notes/report/bib.tex
Normal file
244
AdvOpsys/notes/report/bib.tex
Normal file
@@ -0,0 +1,244 @@
|
||||
%Referansene listet her kan bli brukt med \cite{name}
|
||||
%De dukker bare opp om de refereres til minst en gang.
|
||||
%OBS! om man har mer enn 3 forfattere er det mulig referansen ikke vil fungere, skriv da heller de to første og så et. al.
|
||||
|
||||
@misc{techtargetPOST,
|
||||
author = {Robert Sheldon},
|
||||
title = {{POST (Power-On Self-Test)}},
|
||||
year = {2022},
|
||||
month = {august},
|
||||
day = {2},
|
||||
howpublished = {\url{https://www.techtarget.com/whatis/definition/POST-Power-On-Self-Test}},
|
||||
note = {Accessed: 2026-04-28}
|
||||
}
|
||||
|
||||
@misc{osdevBootloader,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{Bootloader}},
|
||||
year = {2023},
|
||||
month = {july},
|
||||
day = {9},
|
||||
howpublished = {\url{https://wiki.osdev.org/Bootloader}},
|
||||
note = {Accessed: 2026-04-28}
|
||||
}
|
||||
|
||||
@misc{wikipediaLimine,
|
||||
author = {{Wikipedia contributors}},
|
||||
title = {{Limine (bootloader)}},
|
||||
year = {2026},
|
||||
month = {april},
|
||||
day = {9},
|
||||
howpublished = {\url{https://en.wikipedia.org/wiki/Limine_(bootloader)}},
|
||||
note = {Accessed: 2026-04-28}
|
||||
}
|
||||
@misc{ionosBootloader,
|
||||
author = {{IONOS}},
|
||||
title = {{What is a bootloader?}},
|
||||
year = {2022},
|
||||
month = {november},
|
||||
day = {5},
|
||||
howpublished = {\url{https://www.ionos.com/digitalguide/server/configuration/what-is-a-bootloader/}},
|
||||
note = {Accessed: 2026-04-28}
|
||||
}
|
||||
|
||||
@misc{cyberraidenUEFIBoot,
|
||||
author = {{Raiden}},
|
||||
title = {{The Windows Operating System Boot Process in UEFI Mode}},
|
||||
year = {2025},
|
||||
month = {july},
|
||||
day = {24},
|
||||
howpublished = {\url{https://cyberraiden.wordpress.com/2025/07/24/the-windows-operating-system-boot-process-in-uefi-mode/}},
|
||||
note = {Accessed: 2026-04-28}
|
||||
}
|
||||
|
||||
@misc{wikipediaPOST,
|
||||
author = {{Wikipedia}},
|
||||
title = {{Power-on self-test}},
|
||||
year = {2026},
|
||||
month = {april},
|
||||
day = {25},
|
||||
howpublished = {\url{https://en.wikipedia.org/wiki/Power-on_self-test}},
|
||||
note = {Accessed: 2026-04-28}
|
||||
}
|
||||
|
||||
@misc{littleosbook,
|
||||
author = {Erik Helin and Adam Renberg},
|
||||
title = {The Little Book About OS Development},
|
||||
year = {2015},
|
||||
howpublished = {\url{https://littleosbook.github.io/}},
|
||||
note = {Accessed: 2026-04-28}
|
||||
}
|
||||
|
||||
@misc{manybutfinite,
|
||||
author = {Gustavo Duarte},
|
||||
title = {How Computers Boot Up},
|
||||
year = {2011},
|
||||
howpublished = {\url{https://manybutfinite.com/post/how-computers-boot-up/}},
|
||||
note = {Accessed: 2026-04-28}
|
||||
}
|
||||
|
||||
@misc{osdevInterruptsTutorial,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{Interrupts Tutorial}},
|
||||
howpublished = {\url{https://wiki.osdev.org/Interrupts_Tutorial}},
|
||||
note = {Accessed 2026-04-16}
|
||||
}
|
||||
|
||||
@misc{fenollosaOsTutorial,
|
||||
author = {Carlos Fenollosa},
|
||||
title = {{os-tutorial}},
|
||||
howpublished = {\url{https://github.com/cfenollosa/os-tutorial/tree/master}},
|
||||
note = {Accessed 2026-04-16}
|
||||
}
|
||||
|
||||
@misc{assignmentFiles,
|
||||
author = {{Turgay Celik}},
|
||||
title = {{assignment\_files.zip}},
|
||||
note = {Source code and assignment material provided by course lecturer}
|
||||
}
|
||||
|
||||
@misc{osdevBootSequence,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{Boot Sequence}},
|
||||
howpublished = {\url{https://wiki.osdev.org/Boot_Sequence}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{osdevMemoryMapX86,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{Memory Map (x86)}},
|
||||
howpublished = {\url{https://wiki.osdev.org/Memory_Map_(x86)}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{osdevGdtTutorial,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{GDT Tutorial}},
|
||||
howpublished = {\url{https://wiki.osdev.org/GDT_Tutorial}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{osdev8259Pic,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{8259 PIC}},
|
||||
howpublished = {\url{https://wiki.osdev.org/8259_PIC}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{osdevPs2Controller,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{I8042 PS/2 Controller}},
|
||||
howpublished = {\url{https://wiki.osdev.org/I8042_PS/2_Controller}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{osdevMemoryAllocation,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{Memory Allocation}},
|
||||
howpublished = {\url{https://wiki.osdev.org/Memory_Allocation}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{osdevSettingUpPaging,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{Setting Up Paging}},
|
||||
howpublished = {\url{https://wiki.osdev.org/Setting_Up_Paging}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{osdevPit,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{Programmable Interval Timer}},
|
||||
howpublished = {\url{https://wiki.osdev.org/Programmable_Interval_Timer}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{osdevPcSpeaker,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{PC Speaker}},
|
||||
howpublished = {\url{https://wiki.osdev.org/PC_Speaker}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{osdevInterrupts,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{Interrupts}},
|
||||
year = {2026},
|
||||
month = {march},
|
||||
day = {17},
|
||||
howpublished = {\url{https://wiki.osdev.org/Interrupts}},
|
||||
note = {Accessed: 2026-04-28}
|
||||
}
|
||||
|
||||
@misc{uefiSpecifications,
|
||||
author = {{UEFI Forum}},
|
||||
title = {{UEFI Specifications}},
|
||||
howpublished = {\url{https://uefi.org/specifications}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{limineProtocol,
|
||||
author = {{Limine Bootloader Project}},
|
||||
title = {{Limine Boot Protocol}},
|
||||
howpublished = {\url{https://github.com/limine-bootloader/limine-protocol}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{linuxX86BootProtocol,
|
||||
author = {{Linux Kernel Documentation}},
|
||||
title = {{The Linux/x86 Boot Protocol}},
|
||||
howpublished = {\url{https://docs.kernel.org/arch/x86/boot.html}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{microsoftWindowsBootOptions,
|
||||
author = {{Microsoft}},
|
||||
title = {{Configure and edit boot options in Windows for driver development}},
|
||||
howpublished = {\url{https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/boot-options-in-windows}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{appleSiliconBootProcess,
|
||||
author = {{Apple}},
|
||||
title = {{Boot process for a Mac with Apple silicon}},
|
||||
howpublished = {\url{https://support.apple.com/guide/security/secac71d5623/web}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{appleIntelBootProcess,
|
||||
author = {{Apple}},
|
||||
title = {{Boot process for an Intel-based Mac}},
|
||||
howpublished = {\url{https://support.apple.com/guide/security/sec5d0fab7c6/web}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{qemuPcMachine,
|
||||
author = {{QEMU Project}},
|
||||
title = {{i440fx PC (pc-i440fx, pc)}},
|
||||
howpublished = {\url{https://qemu.readthedocs.io/en/v8.1.5/system/i386/pc.html}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{hashnodeVMboot,
|
||||
author = {{Logeshwaran N}},
|
||||
title = {{Virtual Machine Boot Process Explained – Easy Guide}},
|
||||
year = {2024},
|
||||
month = {october},
|
||||
day = {12},
|
||||
howpublished = {\url{https://logeshwrites.hashnode.dev/virtual-machine-boot-process-explained-easy-guide}},
|
||||
note = {Accessed: 2026-04-28}
|
||||
}
|
||||
|
||||
@misc{intelSdm,
|
||||
author = {{Intel}},
|
||||
title = {{Intel 64 and IA-32 Architectures Software Developer's Manual}},
|
||||
howpublished = {\url{https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html}},
|
||||
note = {Accessed 2026-04-28}
|
||||
}
|
||||
|
||||
@misc{osdevPrintingToScreen,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{Printing To Screen}},
|
||||
howpublished = {\url{https://wiki.osdev.org/Printing_To_Screen}},
|
||||
note = {Accessed 2026-04-28}
|
||||
}
|
||||
100
AdvOpsys/notes/report/boot_topics.tex
Normal file
100
AdvOpsys/notes/report/boot_topics.tex
Normal file
@@ -0,0 +1,100 @@
|
||||
\section{Boot Process Background}
|
||||
|
||||
This section gives background context for the boot process before the kernel-specific implementation described in the rest of the report. It covers the firmware stages, bootloader responsibilities, memory layout, and the effect of virtualization on booting.
|
||||
|
||||
\subsection{Power-On Self-Test}
|
||||
When a computer is powered on or reset, the processor begins execution from a firmware-defined reset vector. At this point, the operating system is not running yet. The first responsibility belongs to the system firmware, either BIOS on older x86 systems or UEFI on newer systems \cite{osdevBootSequence,uefiSpecifications}.
|
||||
|
||||
One of the earliest firmware tasks is the Power-On Self-Test, usually shortened to POST. POST checks that the basic hardware needed to continue booting is present and responding. This includes components such as:
|
||||
\begin{itemize}
|
||||
\item the CPU,
|
||||
\item system memory,
|
||||
\item firmware storage,
|
||||
\item video output,
|
||||
\item keyboard or input controllers,
|
||||
\item storage controllers and bootable devices.
|
||||
\end{itemize}
|
||||
|
||||
POST is important because later boot stages assume that the basic machine state is usable. If a required device fails or memory cannot be initialized, the firmware may stop the boot process and report the failure through screen output, status LEDs, or beep codes.
|
||||
|
||||
The interaction between POST and hardware is therefore direct and low-level. Firmware initializes chipset state, configures memory controllers, discovers attached devices, and prepares enough of the platform that a bootloader can be found and executed. This is different from normal operating-system hardware management, because POST happens before the OS has loaded drivers or enabled its own abstractions.
|
||||
|
||||
\subsection{Boot Sequence After POST}
|
||||
After a successful POST, the firmware chooses a boot device according to its configured boot order. On BIOS systems, this usually means reading the first sector of a bootable disk into memory and jumping to it. On UEFI systems, the firmware instead loads an EFI application from a filesystem on the EFI System Partition \cite{osdevBootSequence,uefiSpecifications}.
|
||||
|
||||
The high-level sequence after POST is:
|
||||
\begin{enumerate}
|
||||
\item firmware completes hardware initialization,
|
||||
\item firmware selects a boot device,
|
||||
\item the first bootloader stage or EFI boot application is loaded,
|
||||
\item the bootloader prepares the environment expected by the kernel,
|
||||
\item the kernel image is loaded into memory,
|
||||
\item control is transferred to the kernel entry point.
|
||||
\end{enumerate}
|
||||
|
||||
In a BIOS boot flow, the first loaded code is small because the initial boot sector is limited to 512 bytes. This usually forces the bootloader to use multiple stages. The first stage is responsible for loading a larger second stage, and the second stage can then parse filesystems, load the kernel, and prepare boot information \cite{osdevBootSequence}.
|
||||
|
||||
In a UEFI boot flow, the firmware provides more services before the operating system starts. A UEFI bootloader can use firmware-provided filesystem and device services, and it is loaded as a normal executable rather than as raw code from a fixed disk sector. This makes UEFI bootloaders more flexible, but the kernel must still eventually take control and stop depending on firmware runtime assumptions.
|
||||
|
||||
\subsection{Bootloaders}
|
||||
A bootloader is the bridge between firmware and the operating-system kernel. Its purpose is not only to start the kernel, but also to place the machine into a state the kernel understands.
|
||||
|
||||
Common bootloader responsibilities include:
|
||||
\begin{itemize}
|
||||
\item locating and loading the kernel image,
|
||||
\item loading additional modules or initrd files,
|
||||
\item obtaining a memory map from the firmware,
|
||||
\item selecting or setting a graphics mode,
|
||||
\item preparing boot information structures,
|
||||
\item switching CPU mode when required,
|
||||
\item transferring control to the kernel entry point.
|
||||
\end{itemize}
|
||||
|
||||
Different bootloaders provide different levels of support. GRUB is a general-purpose bootloader with filesystem support, configuration files, multiboot support, and broad hardware compatibility. Limine is a modern boot protocol and bootloader often used in hobby OS development because it provides a clear boot protocol and supports both BIOS and UEFI systems \cite{osdevBootSequence,limineProtocol}. A manually implemented bootloader gives full control over the boot path, but requires more work and exposes the project to more early-stage hardware and filesystem details.
|
||||
|
||||
The choice of bootloader depends on the goals of the operating-system project. For a kernel-focused project, using an existing bootloader is usually the most practical choice because it avoids spending most of the work on disk loading, filesystem parsing, firmware differences, and CPU mode transitions. For a project specifically about bootstrapping, implementing a bootloader manually can be useful because it exposes the details normally hidden by existing tools.
|
||||
|
||||
Manual bootloader implementation is challenging because the environment is very limited. In a BIOS first-stage bootloader, code size is constrained, there is no standard library, only a small amount of state is initialized, and disk access must use firmware interrupts or direct hardware access. The bootloader must also be careful about where it places itself, the kernel, stacks, tables, and temporary buffers in memory.
|
||||
|
||||
\subsection{Memory Layout in the Boot Process}
|
||||
In a traditional i386 BIOS boot process, early memory layout is constrained by legacy conventions. The first MiB of memory contains several important regions, including the interrupt vector table, BIOS data area, conventional memory, video memory, and firmware regions \cite{osdevMemoryMapX86}.
|
||||
|
||||
The bootloader and kernel must avoid overwriting memory that is already used by firmware, hardware mappings, or the bootloader itself. In small BIOS examples, a common simple kernel load address is \texttt{0x10000}. This address is above the lowest BIOS data structures and gives the kernel more room than the original boot sector area, while still being within conventional memory that is easy to access in early boot stages. In this project, the linker script places the kernel at \texttt{1 MiB}, which is also a common protected-mode kernel load address and keeps the kernel away from the lowest legacy BIOS regions.
|
||||
|
||||
Choosing a fixed early kernel load address has practical implications:
|
||||
\begin{itemize}
|
||||
\item the bootloader needs to copy or load the kernel to a known address,
|
||||
\item the kernel linker script must match the address where the kernel expects to run,
|
||||
\item early stacks and temporary buffers must be placed so they do not overlap the kernel,
|
||||
\item later memory management must identify which regions are already occupied.
|
||||
\end{itemize}
|
||||
|
||||
Once the kernel has control, it can replace this simple early memory model with its own memory management. In this project, that later stage is represented by kernel heap initialization, page-aligned allocation, and paging setup.
|
||||
|
||||
\subsection{Boot Process in Modern Operating Systems}
|
||||
Modern operating systems use more complex boot processes than small teaching kernels, but the same basic idea remains: firmware initializes the platform, a bootloader or boot manager loads the kernel, and the kernel takes control of the machine.
|
||||
|
||||
Linux systems commonly use firmware to start a bootloader such as GRUB or systemd-boot. The bootloader loads the kernel and an initramfs, passes a command line and boot information, and then transfers control to the Linux kernel. Windows systems use Windows Boot Manager, which loads the Windows OS loader before the kernel and core system components are started. macOS uses Apple's boot chain, which is tightly integrated with Apple hardware, APFS, Secure Boot policies, and system volume verification \cite{linuxX86BootProtocol,microsoftWindowsBootOptions,appleSiliconBootProcess,appleIntelBootProcess}.
|
||||
|
||||
Modern boot processes have changed because hardware and security requirements have changed. UEFI replaced many BIOS conventions, disks moved from MBR partitioning toward GPT, and Secure Boot introduced signature verification into the boot chain. Storage devices are also more complex than older BIOS-era disks, and modern systems often need early support for NVMe, encryption, graphics initialization, and platform security features.
|
||||
|
||||
The result is that modern booting is both more capable and more controlled. Firmware and bootloaders provide richer services, but the operating system must also participate in a stricter trust chain and handle more platform variation.
|
||||
|
||||
\subsection{Virtual Machines and Booting}
|
||||
Booting inside a virtual machine follows the same conceptual stages as booting on physical hardware, but the hardware being initialized is virtual. The guest operating system still sees firmware, CPU state, memory, storage devices, timers, and interrupt controllers. However, those devices are provided or emulated by the hypervisor \cite{qemuPcMachine}.
|
||||
|
||||
The main difference is that a virtual machine does not start from real motherboard hardware. Instead, the hypervisor creates a virtual hardware environment and then starts the guest firmware inside it. The guest firmware performs a boot sequence that looks normal from inside the VM, but many device operations are handled by the hypervisor on the host.
|
||||
|
||||
The hypervisor has several roles in this process:
|
||||
\begin{itemize}
|
||||
\item allocating guest memory,
|
||||
\item exposing virtual CPUs,
|
||||
\item providing virtual disks and network devices,
|
||||
\item emulating or virtualizing interrupt controllers and timers,
|
||||
\item presenting BIOS or UEFI firmware to the guest,
|
||||
\item handling privileged operations that cannot run directly on the host CPU.
|
||||
\end{itemize}
|
||||
|
||||
Virtualized booting has advantages for operating-system development. It is faster to test than rebooting physical hardware, the virtual machine can be reset easily, debugging is easier with tools such as QEMU and GDB, and hardware behaviour is more reproducible. This is why the kernel in this project was tested through QEMU rather than directly on physical hardware.
|
||||
|
||||
There are also limitations. Emulated hardware may not behave exactly like real hardware, and some devices are simplified compared to physical machines. For example, PC speaker output in QEMU can be limited, which affected the clarity of fast note changes in the music player. Even so, virtualization is highly useful for kernel development because it provides a controlled environment for testing boot, interrupts, memory management, and device interaction \cite{qemuPcMachine}.
|
||||
14
AdvOpsys/notes/report/conclusion.tex
Normal file
14
AdvOpsys/notes/report/conclusion.tex
Normal file
@@ -0,0 +1,14 @@
|
||||
\section{Conclusion}
|
||||
|
||||
The project started with a minimal bootable kernel and developed it into a small interactive operating-system environment. The final kernel includes early processor setup, interrupt handling, memory management, PIT-based timing, PC speaker sound, keyboard input, terminal output, a menu system, a music player, and a Snake game.
|
||||
|
||||
The first major step was to make the processor setup explicit by adding a minimal Global Descriptor Table and reloading the segment registers correctly. This provided a controlled protected-mode foundation for later work. Interrupt handling then extended the kernel from linear startup code into an event-driven system, with CPU exceptions, hardware IRQs, PIC remapping, and keyboard input.
|
||||
|
||||
Memory management and paging added another important layer. The kernel gained heap initialization, dynamic allocation, page-aligned allocation, memory utility functions, and identity-mapped paging. These features made it possible for later code to allocate state dynamically rather than relying only on static data.
|
||||
|
||||
The PIT then provided a timing source through IRQ0. This supported both busy-wait and interrupt-based sleeping, with the interrupt-based version becoming important for later features. The same timing infrastructure was reused by the music player and Snake game. The PC speaker work showed how PIT channel 2 could be used for sound generation, while also showing the practical limits of testing audio through QEMU.
|
||||
|
||||
The final application framework tied the lower-level pieces together. The menu system used terminal output and keyboard input to choose between applications. The music player used PIT timing and PC speaker output. Snake combined memory allocation, keyboard input, PIT-based pacing, terminal drawing, and sound effects. This made the final application stage a practical test of whether the kernel subsystems could work together.
|
||||
|
||||
Overall, the project demonstrates how small operating-system mechanisms build on each other. GDT setup makes the early CPU state reliable, interrupts make asynchronous hardware events usable, memory management allows dynamic state, timing enables delays and pacing, and device output makes applications interactive. The result is not a complete general-purpose operating system, but it is a working kernel environment that shows the relationship between low-level hardware control and higher-level program behaviour.
|
||||
|
||||
11
AdvOpsys/notes/report/intro.tex
Normal file
11
AdvOpsys/notes/report/intro.tex
Normal file
@@ -0,0 +1,11 @@
|
||||
\section{Introduction}
|
||||
|
||||
This report documents both the boot-process background behind operating-system startup and the development of a small 32-bit operating-system kernel for the \texttt{Advanced Operatingsystems} project. It starts by explaining the firmware and bootloader stages that happen before a kernel runs, then follows the project implementation from a minimal bootable kernel into a more interactive environment with processor setup, interrupts, memory management, timing, sound, keyboard input, and simple applications.
|
||||
|
||||
The background section covers the general boot sequence: Power-On Self-Test, firmware handoff, BIOS and UEFI differences, bootloader responsibilities, early i386 memory layout, modern operating-system boot chains, and the role of virtualization. This gives context for why the project can rely on a bootloader, why the kernel starts from a constrained early machine state, and why QEMU is useful for testing this kind of low-level work.
|
||||
|
||||
The implementation itself is intentionally low-level. Most of the kernel interacts directly with x86 processor structures and hardware interfaces, including the Global Descriptor Table, Interrupt Descriptor Table, Programmable Interrupt Controller, Programmable Interval Timer, VGA text output, keyboard controller, and PC speaker. Because the kernel runs in a freestanding environment, basic facilities that are normally provided by an operating system or standard library also had to be implemented inside the project.
|
||||
|
||||
After the boot background, the report follows the same order as the implementation work. The first implementation part covers the early boot path and processor setup, with particular focus on the Global Descriptor Table and the need to reload segment registers after installing it. The second part describes interrupt handling, including CPU exceptions, hardware IRQs, PIC remapping, and keyboard input. The third part introduces memory management, paging, PIT-based timing, sleep functions, and PC speaker music playback. The final part shows how these individual subsystems were combined into a small menu-driven application environment with a music player, an interactive Snake game and a piano application.
|
||||
|
||||
The overall goal was not to build a complete general-purpose operating system, but to demonstrate how core operating-system mechanisms fit together. Each stage adds a specific capability, and later stages reuse earlier ones: the PIT depends on interrupt handling, the music player depends on PIT timing and PC speaker output, Snake depends on memory allocation, keyboard input, terminal drawing, timing, and sound feedback, and Piano app depends on keyboard input, terminal drawing, PIT timing and memory allocation. By the end of the project, the kernel has moved from static startup output to an event-driven system that can run simple interactive applications.
|
||||
41
AdvOpsys/notes/report/problems.tex
Normal file
41
AdvOpsys/notes/report/problems.tex
Normal file
@@ -0,0 +1,41 @@
|
||||
\section{Problems and Challenges}
|
||||
|
||||
Several issues appeared during the project because the kernel runs in a freestanding environment where there is little separation between build configuration, CPU state, hardware programming, and application behaviour. Most problems were not isolated to one source file; they came from interactions between the boot image, low-level initialization, interrupts, timing, and terminal output.
|
||||
|
||||
\subsection{Processor State and Verification}
|
||||
The GDT implementation required careful verification because a mistake in this stage can prevent the kernel from continuing at all. One important detail was that loading the GDT with \texttt{lgdt} does not automatically update the cached segment registers. The implementation therefore needed a far jump to reload \texttt{cs}, followed by explicit reloads of \texttt{ds}, \texttt{es}, \texttt{fs}, \texttt{gs}, and \texttt{ss}.
|
||||
|
||||
This was verified both statically and at runtime. Static inspection confirmed that the kernel binary contained the expected GDT load sequence, far jump, and data segment reloads. Runtime debugging in QEMU with \texttt{gdb-multiarch} confirmed that \texttt{cs} became \texttt{0x08} and that the data segment registers became \texttt{0x10}. This was necessary because the code could compile correctly while still failing if the processor did not actually enter the expected segment state.
|
||||
|
||||
\subsection{Build and Boot Image Mismatch}
|
||||
A major integration problem appeared during the PIT work. Rebuilding only the kernel target updated \texttt{kernel.bin}, but did not update the bootable ISO image. As a result, QEMU could still boot an older image even after the source code and kernel binary had changed.
|
||||
|
||||
This made the runtime output misleading. The newer \texttt{kernel.bin} contained PIT and memory-management strings, while the older \texttt{kernel.iso} still showed the earlier \texttt{Hello, World!} output. The issue was resolved by rebuilding the bootable image target so the ISO contained the updated kernel.
|
||||
|
||||
This problem showed that kernel development requires verifying not only the source code, but also the exact artifact being booted. When the build system produces multiple outputs, it is possible to test the wrong one without noticing immediately.
|
||||
|
||||
\subsection{Freestanding C and Header Consistency}
|
||||
Because the kernel does not run with a normal hosted C environment, ordinary library assumptions do not always apply. During the PIT integration, \texttt{printf()} calls had to be removed from test code and replaced with the kernel's terminal output functions. The terminal interface also needed to expose \texttt{TerminalWriteHex()} through the header because the function was implemented in \texttt{terminal.c} but not declared for other modules.
|
||||
|
||||
Inline assembly syntax also caused a build issue. An incorrect form of \texttt{\_\_asm\_\_volatile} had to be corrected to \texttt{\_\_asm\_\_ volatile}. This was a small syntax problem, but in low-level code it blocked the PIT sleep implementation because \texttt{sti; hlt} was part of the interrupt-based delay loop.
|
||||
|
||||
\subsection{Interrupt Dependencies}
|
||||
Several later features depended on interrupt handling being correct. The PIT relies on IRQ0, keyboard input relies on IRQ1, and both the music player and Snake depend on timer and keyboard behaviour. This meant that interrupt setup was not only an isolated assignment task; it became part of the foundation for the rest of the project.
|
||||
|
||||
The PIC also had to be remapped so hardware IRQs did not overlap with CPU exception vectors. Without this separation, hardware events and processor exceptions would be harder to distinguish, and debugging later features would become much less reliable.
|
||||
|
||||
\subsection{Terminal Output Limitations}
|
||||
The terminal output implementation was sufficient for early debugging and application display, but repeated output exposed a limitation. During the PIT sleep test, once the VGA text buffer wrapped back to the top of the screen, new text overwrote old text directly. This made long-running debug output harder to read.
|
||||
|
||||
The later application framework reduced this issue by clearing the terminal before drawing menus and game frames. However, the underlying limitation remains: the terminal would benefit from proper scrolling or a cleaner screen-management model for continuous logs.
|
||||
|
||||
\subsection{PC Speaker and QEMU Limitations}
|
||||
The music player used the PC speaker and PIT channel 2 to generate square wave tones. The implementation worked, but QEMU's PC speaker support limited the clarity of playback. Fast note changes could sound unclear, and songs degraded when played at the original speed.
|
||||
|
||||
The practical adjustment was to double note durations during playback. This slowed down the music and made individual notes easier to distinguish. The limitation was therefore not mainly in the note representation or PIT timing logic, but in the quality and behaviour of the emulated sound output.
|
||||
|
||||
\subsection{Subsystem Integration}
|
||||
The final Snake application exposed the main integration challenge of the project. Snake uses dynamic memory allocation for game state, keyboard interrupts for input, PIT timing for movement speed, terminal output for drawing, and PC speaker output for feedback. A problem in any of these lower-level systems can appear as an application-level bug.
|
||||
|
||||
This made the final stage useful as more than a demonstration. It acted as an integration test for the kernel. The menu and Snake game showed that the separate subsystems could cooperate through a single control flow, but they also made clear that kernel features must be built in a stable order: processor setup first, then interrupts, then memory and timing, then application behaviour.
|
||||
|
||||
220
AdvOpsys/notes/report/references.tex
Normal file
220
AdvOpsys/notes/report/references.tex
Normal file
@@ -0,0 +1,220 @@
|
||||
%Referansene listet her kan bli brukt med \cite{name}
|
||||
%De dukker bare opp om de refereres til minst en gang.
|
||||
%OBS! om man har mer enn 3 forfattere er det mulig referansen ikke vil fungere, skriv da heller de to første og så et. al.
|
||||
|
||||
@misc{techtargetPOST,
|
||||
author = {Robert Sheldon},
|
||||
title = {{POST (Power-On Self-Test)}},
|
||||
year = {2022},
|
||||
month = {august},
|
||||
day = {2},
|
||||
howpublished = {\url{https://www.techtarget.com/whatis/definition/POST-Power-On-Self-Test}},
|
||||
note = {Accessed: 2026-04-28}
|
||||
}
|
||||
|
||||
@misc{osdevBootloader,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{Bootloader}},
|
||||
year = {2023},
|
||||
month = {july},
|
||||
day = {9},
|
||||
howpublished = {\url{https://wiki.osdev.org/Bootloader}},
|
||||
note = {Accessed: 2026-04-28}
|
||||
}
|
||||
|
||||
@misc{wikipediaLimine,
|
||||
author = {{Wikipedia contributors}},
|
||||
title = {{Limine (bootloader)}},
|
||||
year = {2026},
|
||||
month = {april},
|
||||
day = {9},
|
||||
howpublished = {\url{https://en.wikipedia.org/wiki/Limine_(bootloader)}},
|
||||
note = {Accessed: 2026-04-28}
|
||||
}
|
||||
@misc{ionosBootloader,
|
||||
author = {{IONOS}},
|
||||
title = {{What is a bootloader?}},
|
||||
year = {2022},
|
||||
month = {november},
|
||||
day = {5},
|
||||
howpublished = {\url{https://www.ionos.com/digitalguide/server/configuration/what-is-a-bootloader/}},
|
||||
note = {Accessed: 2026-04-28}
|
||||
}
|
||||
|
||||
@misc{cyberraidenUEFIBoot,
|
||||
author = {{Raiden}},
|
||||
title = {{The Windows Operating System Boot Process in UEFI Mode}},
|
||||
year = {2025},
|
||||
month = {july},
|
||||
day = {24},
|
||||
howpublished = {\url{https://cyberraiden.wordpress.com/2025/07/24/the-windows-operating-system-boot-process-in-uefi-mode/}},
|
||||
note = {Accessed: 2026-04-28}
|
||||
}
|
||||
|
||||
@misc{wikipediaPOST,
|
||||
author = {{Wikipedia}},
|
||||
title = {{Power-on self-test}},
|
||||
year = {2026},
|
||||
month = {april},
|
||||
day = {25},
|
||||
howpublished = {\url{https://en.wikipedia.org/wiki/Power-on_self-test}},
|
||||
note = {Accessed: 2026-04-28}
|
||||
}
|
||||
|
||||
@misc{littleosbook,
|
||||
author = {Erik Helin and Adam Renberg},
|
||||
title = {The Little Book About OS Development},
|
||||
year = {2015},
|
||||
howpublished = {\url{https://littleosbook.github.io/}},
|
||||
note = {Accessed: 2026-04-28}
|
||||
}
|
||||
|
||||
@misc{manybutfinite,
|
||||
author = {Gustavo Duarte},
|
||||
title = {How Computers Boot Up},
|
||||
year = {2011},
|
||||
howpublished = {\url{https://manybutfinite.com/post/how-computers-boot-up/}},
|
||||
note = {Accessed: 2026-04-28}
|
||||
}
|
||||
|
||||
@misc{osdevInterruptsTutorial,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{Interrupts Tutorial}},
|
||||
howpublished = {\url{https://wiki.osdev.org/Interrupts_Tutorial}},
|
||||
note = {Accessed 2026-04-16}
|
||||
}
|
||||
|
||||
@misc{fenollosaOsTutorial,
|
||||
author = {Carlos Fenollosa},
|
||||
title = {{os-tutorial}},
|
||||
howpublished = {\url{https://github.com/cfenollosa/os-tutorial/tree/master}},
|
||||
note = {Accessed 2026-04-16}
|
||||
}
|
||||
|
||||
@misc{assignmentFiles,
|
||||
author = {{Turgay Celik}},
|
||||
title = {{assignment\_files.zip}},
|
||||
note = {Source code and assignment material provided by course lecturer}
|
||||
}
|
||||
|
||||
@misc{osdevBootSequence,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{Boot Sequence}},
|
||||
howpublished = {\url{https://wiki.osdev.org/Boot_Sequence}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{osdevMemoryMapX86,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{Memory Map (x86)}},
|
||||
howpublished = {\url{https://wiki.osdev.org/Memory_Map_(x86)}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{osdevGdtTutorial,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{GDT Tutorial}},
|
||||
howpublished = {\url{https://wiki.osdev.org/GDT_Tutorial}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{osdev8259Pic,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{8259 PIC}},
|
||||
howpublished = {\url{https://wiki.osdev.org/8259_PIC}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{osdevPs2Controller,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{I8042 PS/2 Controller}},
|
||||
howpublished = {\url{https://wiki.osdev.org/I8042_PS/2_Controller}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{osdevMemoryAllocation,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{Memory Allocation}},
|
||||
howpublished = {\url{https://wiki.osdev.org/Memory_Allocation}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{osdevSettingUpPaging,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{Setting Up Paging}},
|
||||
howpublished = {\url{https://wiki.osdev.org/Setting_Up_Paging}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{osdevPit,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{Programmable Interval Timer}},
|
||||
howpublished = {\url{https://wiki.osdev.org/Programmable_Interval_Timer}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{osdevPcSpeaker,
|
||||
author = {{OSDev Wiki}},
|
||||
title = {{PC Speaker}},
|
||||
howpublished = {\url{https://wiki.osdev.org/PC_Speaker}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{uefiSpecifications,
|
||||
author = {{UEFI Forum}},
|
||||
title = {{UEFI Specifications}},
|
||||
howpublished = {\url{https://uefi.org/specifications}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{limineProtocol,
|
||||
author = {{Limine Bootloader Project}},
|
||||
title = {{Limine Boot Protocol}},
|
||||
howpublished = {\url{https://github.com/limine-bootloader/limine-protocol}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{linuxX86BootProtocol,
|
||||
author = {{Linux Kernel Documentation}},
|
||||
title = {{The Linux/x86 Boot Protocol}},
|
||||
howpublished = {\url{https://docs.kernel.org/arch/x86/boot.html}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{microsoftWindowsBootOptions,
|
||||
author = {{Microsoft}},
|
||||
title = {{Configure and edit boot options in Windows for driver development}},
|
||||
howpublished = {\url{https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/boot-options-in-windows}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{appleSiliconBootProcess,
|
||||
author = {{Apple}},
|
||||
title = {{Boot process for a Mac with Apple silicon}},
|
||||
howpublished = {\url{https://support.apple.com/guide/security/secac71d5623/web}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{appleIntelBootProcess,
|
||||
author = {{Apple}},
|
||||
title = {{Boot process for an Intel-based Mac}},
|
||||
howpublished = {\url{https://support.apple.com/guide/security/sec5d0fab7c6/web}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{qemuPcMachine,
|
||||
author = {{QEMU Project}},
|
||||
title = {{i440fx PC (pc-i440fx, pc)}},
|
||||
howpublished = {\url{https://qemu.readthedocs.io/en/v8.1.5/system/i386/pc.html}},
|
||||
note = {Accessed 2026-04-20}
|
||||
}
|
||||
|
||||
@misc{hashnodeVMboot,
|
||||
author = {{Logeshwaran N}},
|
||||
title = {{Virtual Machine Boot Process Explained – Easy Guide}},
|
||||
year = {2024},
|
||||
month = {october},
|
||||
day = {12},
|
||||
howpublished = {\url{https://logeshwrites.hashnode.dev/virtual-machine-boot-process-explained-easy-guide}},
|
||||
note = {Accessed: 2026-04-28}
|
||||
}
|
||||
9
AdvOpsys/notes/report/work_distribution.tex
Normal file
9
AdvOpsys/notes/report/work_distribution.tex
Normal file
@@ -0,0 +1,9 @@
|
||||
\section{Work Distribution}
|
||||
|
||||
The project work was divided primarily by assignment stage. Teodor was mainly responsible for assignments 2, 4, and 5, and shared assignment 6. I was mainly responsible for assignments 1 and 3, and also shared assignment 6. If only the implementation assignments are considered, this gives an approximate 60/40 split in Teodor's favour.
|
||||
|
||||
The report writing was distributed somewhat differently. I wrote approximately 60\% of the report text, which balanced much of the earlier implementation difference. Taken together, the implementation work and the report writing therefore gave a contribution split that was close to 50/50 overall, which we consider a reasonable balance for a group project of this type.
|
||||
|
||||
We also worked deliberately to maximize learning from the parts of the project that each of us had not implemented directly. After each work session, we wrote short notes describing what had been done, what design choices had been made, and what problems had appeared. This made it possible for the other person to read the notes afterward and then write that part into the report with a clear understanding of both the implementation and the reasoning behind it.
|
||||
|
||||
In practice, this meant that when Teodor implemented an assignment task, I would usually write the corresponding report section, and the same principle also applied in the opposite direction. This gave both of us a reason to study and explain the other person's work instead of only documenting our own. In that way, the report became part of the learning process rather than a duplicate writing pass over the same task.
|
||||
101
AdvOpsys/notes/work-summary-2026-04-10.md
Normal file
101
AdvOpsys/notes/work-summary-2026-04-10.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Summary of Work Done on April 10, 2026
|
||||
|
||||
## Overview
|
||||
|
||||
Today, work was done on the `OSDev_18` project to improve the kernel startup sequence and to make sure the kernel still boots correctly and prints output to the screen. The main focus was to add a minimal Global Descriptor Table (GDT) setup for the i386 version of the kernel and verify that it works as expected.
|
||||
|
||||
The project is set up to use a dev container, so the build and test steps were carried out in that environment. This was necessary because the container already includes the required cross-compilation and debugging tools.
|
||||
|
||||
## GDT Implementation
|
||||
|
||||
A minimal GDT was added for 32-bit protected mode. The implementation uses three entries:
|
||||
|
||||
1. A null descriptor
|
||||
2. A kernel code segment descriptor
|
||||
3. A kernel data segment descriptor
|
||||
|
||||
The code and data segments were configured with:
|
||||
|
||||
- base address `0x00000000`
|
||||
- effective 4 GiB address space
|
||||
- 4 KiB granularity
|
||||
- ring 0 privilege level
|
||||
|
||||
The GDT setup code builds these descriptors in memory and prepares a GDT descriptor that can be loaded by the CPU.
|
||||
|
||||
After that, an assembly routine loads the new GDT with `lgdt`, performs a far jump to reload the code segment register, and then reloads the remaining segment registers for data access. This step is important because loading the GDT alone does not automatically update the segment registers already cached by the processor.
|
||||
|
||||
## Kernel Startup Changes
|
||||
|
||||
The kernel startup code was updated so that the GDT is initialized before the terminal is set up. After the GDT setup is finished, the terminal initialization still runs and the kernel prints `Hello, World!` to the VGA text buffer.
|
||||
|
||||
This means the boot flow now works roughly like this:
|
||||
|
||||
1. The bootloader transfers control to the kernel entry point.
|
||||
2. The kernel starts execution in C code.
|
||||
3. The GDT is initialized and loaded.
|
||||
4. Segment registers are reloaded.
|
||||
5. The VGA terminal is initialized.
|
||||
6. `Hello, World!` is printed.
|
||||
7. The kernel enters an infinite halt loop.
|
||||
|
||||
## Build System Changes
|
||||
|
||||
The build configuration was updated so the kernel now includes both the new C source for the GDT logic and the new assembly source for the reload routine. This ensures that the GDT implementation is compiled and linked into the kernel binary together with the rest of the startup code.
|
||||
|
||||
New build artifacts were generated today, including an updated kernel binary and bootable ISO image.
|
||||
|
||||
## Documentation Work
|
||||
|
||||
The project documentation was also improved today. Short explanations were added to describe:
|
||||
|
||||
- the general kernel boot flow
|
||||
- where the GDT setup happens
|
||||
- why `lgdt` is not enough on its own
|
||||
- why a far jump and segment-register reload are required after loading the GDT
|
||||
|
||||
This was done to make the code easier to follow and to connect the implementation more clearly to the assignment requirements.
|
||||
|
||||
## Verification and Testing
|
||||
|
||||
The GDT implementation was tested in two ways.
|
||||
|
||||
### 1. Static inspection of the kernel binary
|
||||
|
||||
The built kernel binary was inspected to confirm that the expected GDT-related symbols were present and that the generated machine code actually contained:
|
||||
|
||||
- a call to load the GDT
|
||||
- a far jump to selector `0x08`
|
||||
- reloads of the data segment registers using selector `0x10`
|
||||
|
||||
This showed that the expected GDT setup sequence had been compiled into the kernel.
|
||||
|
||||
### 2. Runtime debugging in QEMU with GDB
|
||||
|
||||
The kernel was then started in QEMU and examined with `gdb-multiarch`. During this session, execution was stopped inside the GDT initialization code and inside the assembly reload routine.
|
||||
|
||||
The following results were observed:
|
||||
|
||||
- the GDT descriptor size was `0x17`, which matches three 8-byte entries minus 1
|
||||
- the code segment entry was populated with the expected values
|
||||
- after the far jump, the `cs` register changed to `0x08`
|
||||
- after stepping through the segment reload instructions, `ds`, `es`, `fs`, `gs`, and `ss` all became `0x10`
|
||||
|
||||
These checks show that the processor did not just compile the code correctly, but also used the new GDT correctly at runtime.
|
||||
|
||||
## Current State at the End of the Day
|
||||
|
||||
At the end of today’s work, the `OSDev_18` kernel:
|
||||
|
||||
- builds inside the dev container
|
||||
- includes a minimal i386 GDT implementation
|
||||
- loads the GDT during kernel startup
|
||||
- reloads the code and data segment registers correctly
|
||||
- still initializes the VGA terminal
|
||||
- still prints `Hello, World!` after boot
|
||||
|
||||
Based on the testing that was done today, the GDT implementation appears to be working correctly for this minimal protected-mode kernel setup.
|
||||
|
||||
## Later Integration Note
|
||||
|
||||
Later project stages changed the visible startup order so `TerminalInitialize()` runs before `GdtInitialize()` in the final `kernel.c`. This allows later initialization code to print diagnostics through the terminal. The GDT implementation itself remains part of the early startup path and is still initialized before the interrupt, memory, timing, and application layers are used.
|
||||
43
AdvOpsys/notes/work-summary-2026-04-11-to-14.md
Normal file
43
AdvOpsys/notes/work-summary-2026-04-11-to-14.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Summary of Work Done on Interrupt System
|
||||
|
||||
## Overview
|
||||
|
||||
The last 4 days, work was done on implementing interrupt support for the OSDev_18 project. The goal was to allow the kernel to handle both CPU exceptions and hardware interrupts.
|
||||
|
||||
This included setting up the Interrupt Descriptor Table (IDT), implementing basic Interrupt Service Routines (ISRs), adding support for hardware interrupts (IRQs), and handling keyboard input. After completing these steps, the system is now able to respond to interrupts and display output when they occur.
|
||||
|
||||
## IDT Implementation
|
||||
|
||||
The Interrupt Descriptor Table (IDT) was implemented to define how the CPU should respond to interrupts.
|
||||
|
||||
A structure for IDT entries was created, storing the handler address, segment selector, and flags. An IDT pointer structure was also defined to hold the base and size of the table.
|
||||
|
||||
The IDT is initialized in IdtInitialize() and loaded using the lidt instruction. A helper function is used to set up each descriptor.
|
||||
|
||||
## ISR Implementation
|
||||
|
||||
Interrupt Service Routines (ISRs) were implemented to handle CPU exceptions.
|
||||
|
||||
A Registers structure was created to store the CPU state during an interrupt. The IsrHandler() function uses this information to determine which interrupt occurred and prints a message to the terminal.
|
||||
|
||||
Interrupts can also be triggered manually for testing purposes.
|
||||
|
||||
## IRQ Implementation
|
||||
|
||||
Support for hardware interrupts (IRQs) was added for IRQ0 to IRQ15.
|
||||
|
||||
The Programmable Interrupt Controller (PIC) was remapped so that IRQs do not overlap with CPU exceptions. An IrqHandler() function was implemented to handle incoming hardware interrupts and dispatch them to registered handlers.
|
||||
|
||||
End-of-interrupt signals are sent to the PIC after handling each interrupt.
|
||||
|
||||
## Keyboard Implementation
|
||||
|
||||
Keyboard input was implemented using IRQ1.
|
||||
|
||||
A keyboard handler reads scancodes from port 0x60 and translates key presses into ASCII characters using a lookup table. Raw scancodes are kept in a small buffer, while the latest translated character is stored for higher-level terminal and application code to consume.
|
||||
|
||||
This allows the system to respond to user input from the keyboard.
|
||||
|
||||
## Conclusion
|
||||
|
||||
The system now supports basic interrupt handling, including CPU exceptions and hardware interrupts. This is an important step towards building a more advanced and interactive operating system.
|
||||
164
AdvOpsys/notes/work-summary-2026-04-14.md
Normal file
164
AdvOpsys/notes/work-summary-2026-04-14.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# Assignment 4 – Part 1: Memory Management
|
||||
|
||||
## Overview
|
||||
In this part of the assignment, a basic memory management system was implemented for a simple OS kernel. The goal was to initialize kernel memory, enable paging, and implement dynamic memory allocation using `malloc()` and `free()`.
|
||||
|
||||
---
|
||||
|
||||
## Implemented Features
|
||||
|
||||
### Kernel Memory Initialization
|
||||
- Used:
|
||||
```c
|
||||
extern uint32_t end;
|
||||
```
|
||||
to get the end address of the kernel from the linker.
|
||||
- Implemented:
|
||||
```c
|
||||
InitKernelMemory(&end);
|
||||
```
|
||||
- Set up:
|
||||
- `heap_begin`
|
||||
- `heap_end`
|
||||
- `pheap_begin` (page-aligned heap)
|
||||
- `pheap_end`
|
||||
|
||||
---
|
||||
|
||||
### Paging
|
||||
- Implemented:
|
||||
```c
|
||||
InitPaging();
|
||||
```
|
||||
- Set up:
|
||||
- Page directory at `0x400000`
|
||||
- Page tables starting at `0x404000`
|
||||
- Identity-mapped:
|
||||
- `0x00000000 → 0x00000000`
|
||||
- `0x400000 → 0x400000`
|
||||
- Enabled paging by modifying `cr0` and `cr3`.
|
||||
|
||||
---
|
||||
|
||||
### Dynamic Memory Allocation
|
||||
|
||||
#### malloc()
|
||||
- Implemented a simple heap allocator.
|
||||
- Stores metadata using:
|
||||
```c
|
||||
typedef struct {
|
||||
uint8_t status;
|
||||
uint32_t size;
|
||||
} alloc_t;
|
||||
```
|
||||
- Supports:
|
||||
- Sequential allocation
|
||||
- Reuse of freed blocks
|
||||
- Zeroes allocated memory using `memset`.
|
||||
|
||||
---
|
||||
|
||||
#### free()
|
||||
- Frees memory by marking the block as unused.
|
||||
- Prevents errors by:
|
||||
```c
|
||||
if (!mem) return;
|
||||
```
|
||||
- Correctly updates `memory_used`.
|
||||
|
||||
---
|
||||
|
||||
#### Block Reuse
|
||||
- When a block is freed, it can be reused by future `malloc()` calls.
|
||||
- Verified by:
|
||||
- Freeing a block
|
||||
- Allocating a smaller block
|
||||
- Observing same address reused
|
||||
|
||||
---
|
||||
|
||||
### Page-Aligned Allocation (pmalloc)
|
||||
- Implemented `pmalloc()` and `pfree()`:
|
||||
- Allocates memory in 4KB pages
|
||||
- Uses a descriptor array (`pheap_desc`)
|
||||
- Located in upper memory region near `0x400000`
|
||||
|
||||
---
|
||||
|
||||
### Memory Utility Functions
|
||||
Implemented:
|
||||
- `memcpy()`
|
||||
- `memset()`
|
||||
- `memset16()`
|
||||
|
||||
These replace standard library functions in kernel space.
|
||||
|
||||
---
|
||||
|
||||
### Debug Output
|
||||
|
||||
#### Memory Layout
|
||||
- Implemented:
|
||||
```c
|
||||
PrintMemoryLayout();
|
||||
```
|
||||
- Displays:
|
||||
- Memory used
|
||||
- Memory free
|
||||
- Heap size
|
||||
- Heap start/end
|
||||
- Page heap start/end
|
||||
|
||||
#### Allocation Logs
|
||||
- Outputs allocation details:
|
||||
```
|
||||
Allocated X bytes from 0x... to 0x...
|
||||
Re-allocated X bytes from 0x... to 0x...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Hex Output for Addresses
|
||||
- Implemented:
|
||||
```c
|
||||
TerminalWriteHex(uint32_t num);
|
||||
```
|
||||
- Ensures correct formatting of memory addresses (base 16)
|
||||
- Avoids confusion from decimal output
|
||||
|
||||
---
|
||||
|
||||
## Testing & Verification
|
||||
|
||||
### Allocation Test
|
||||
```c
|
||||
void* memory1 = malloc(48261);
|
||||
void* memory2 = malloc(27261);
|
||||
void* memory3 = malloc(12617);
|
||||
```
|
||||
|
||||
### Free and Reuse Test
|
||||
```c
|
||||
free(memory2);
|
||||
void* memory4 = malloc(1000);
|
||||
```
|
||||
|
||||
### Result
|
||||
- `memory4` reused the same address as `memory2`
|
||||
- Confirms:
|
||||
- `free()` works
|
||||
- allocator reuses memory correctly
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
- Successfully implemented a basic kernel memory manager
|
||||
- Paging is enabled and functioning
|
||||
- `malloc()` and `free()` work correctly
|
||||
- Memory reuse is verified
|
||||
- Debug output is clear and correctly formatted
|
||||
|
||||
---
|
||||
|
||||
## Next Step
|
||||
Proceed to **Part 2: PIT and Sleep Functions**
|
||||
81
AdvOpsys/notes/work-summary-2026-04-15-v2.md
Normal file
81
AdvOpsys/notes/work-summary-2026-04-15-v2.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Assignment 5
|
||||
|
||||
## #Overview
|
||||
This assignment implements a simple music player using the **PC speaker (PCSPK)** and the **Programmable Interval Timer (PIT)**.
|
||||
Songs are played by generating frequencies and controlling timing between notes.
|
||||
|
||||
---
|
||||
|
||||
## #HowItWorks
|
||||
|
||||
### #PCSpeaker
|
||||
The PC speaker is controlled through **port `0x61`**:
|
||||
- Bits 0 and 1 enable/disable sound output
|
||||
|
||||
---
|
||||
|
||||
### #PIT
|
||||
The PIT is used for:
|
||||
|
||||
#### #SoundGeneration
|
||||
- Channel 2 generates square wave audio
|
||||
- Frequency is set using:
|
||||
|
||||
divisor = PIT_BASE_FREQ / frequency
|
||||
|
||||
|
||||
#### #Timing
|
||||
- Channel 0 runs at ~1000 Hz
|
||||
- A tick counter is incremented via interrupts
|
||||
- `SleepInterrupt()` is used for accurate note timing
|
||||
|
||||
---
|
||||
|
||||
## #Implementation
|
||||
|
||||
### #Playback
|
||||
Each note contains:
|
||||
- Frequency (Hz)
|
||||
- Duration (ms)
|
||||
|
||||
Playback works by:
|
||||
1. Setting frequency with `PlaySound()`
|
||||
2. Waiting using `SleepInterrupt()`
|
||||
3. Stopping sound with `StopSound()`
|
||||
|
||||
Rests are handled using `R = 0`.
|
||||
|
||||
---
|
||||
|
||||
## #Challenges
|
||||
|
||||
### #Timing
|
||||
Busy-wait delays caused incorrect timing.
|
||||
This was solved by using interrupt-based sleeping.
|
||||
|
||||
### #QEMU
|
||||
QEMU has limited PC speaker support:
|
||||
- Fast note changes sound unclear
|
||||
- Songs degrade at high speed
|
||||
|
||||
---
|
||||
|
||||
## #Adjustments
|
||||
|
||||
To improve playback clarity:
|
||||
|
||||
duration = original_duration * 2
|
||||
|
||||
|
||||
This slows down the songs and makes notes more distinguishable.
|
||||
|
||||
---
|
||||
|
||||
## #Conclusion
|
||||
|
||||
The system successfully:
|
||||
- Generates sound using the PC speaker
|
||||
- Uses PIT for timing and frequency control
|
||||
- Plays songs with multiple notes
|
||||
|
||||
Limitations in QEMU affect sound quality, but the implementation itself works as intended.
|
||||
221
AdvOpsys/notes/work-summary-2026-04-15.md
Normal file
221
AdvOpsys/notes/work-summary-2026-04-15.md
Normal file
@@ -0,0 +1,221 @@
|
||||
# Assignment 4 – Part 2: Programmable Interval Timer
|
||||
|
||||
## Overview
|
||||
In this part of the assignment, work was done on adding a basic driver for the Programmable Interval Timer (PIT) to the `OSDev_18` kernel. The goal was to initialize the PIT, count timer ticks using IRQ0, and use those ticks to support both busy-wait sleeping and interrupt-based sleeping.
|
||||
|
||||
The PIT was configured to use channel 0 and generate regular timer interrupts at `1000 Hz`. This gives a simple timing model where one timer tick is approximately equal to one millisecond.
|
||||
|
||||
---
|
||||
|
||||
## Implemented Features
|
||||
|
||||
### PIT Header and Constants
|
||||
- Added:
|
||||
```c
|
||||
include/kernel/pit.h
|
||||
```
|
||||
- Defined constants for:
|
||||
- PIT command port
|
||||
- PIT channel 0 port
|
||||
- PIT base frequency
|
||||
- target frequency
|
||||
- calculated divider
|
||||
- ticks-per-millisecond conversion
|
||||
|
||||
This provides a single place for PIT-related configuration and function declarations.
|
||||
|
||||
---
|
||||
|
||||
### PIT Initialization
|
||||
- Implemented:
|
||||
```c
|
||||
void PitInitialize(void);
|
||||
```
|
||||
- Configures the PIT using:
|
||||
```c
|
||||
OutPortByte(PIT_CMD_PORT, 0x36);
|
||||
```
|
||||
- Loads the PIT divisor into channel 0:
|
||||
- low byte first
|
||||
- high byte second
|
||||
- Registers the PIT interrupt handler on:
|
||||
```c
|
||||
IRQ0
|
||||
```
|
||||
|
||||
This allows the kernel to start receiving regular timer interrupts from the PIT.
|
||||
|
||||
---
|
||||
|
||||
### Tick Counter
|
||||
- Added a global tick counter:
|
||||
```c
|
||||
static volatile uint32_t pit_ticks = 0;
|
||||
```
|
||||
- Implemented a helper function:
|
||||
```c
|
||||
uint32_t GetCurrentTick(void);
|
||||
```
|
||||
|
||||
The tick counter is incremented every time IRQ0 is triggered. Since the PIT is configured to `1000 Hz`, this gives a simple way to measure elapsed time in milliseconds.
|
||||
|
||||
---
|
||||
|
||||
### PIT IRQ Handler
|
||||
- Implemented:
|
||||
```c
|
||||
static void PitIrqHandler(struct Registers* regs);
|
||||
```
|
||||
- The handler:
|
||||
- ignores the register argument
|
||||
- increments `pit_ticks`
|
||||
|
||||
This keeps the timer interrupt path minimal and lets the rest of the kernel measure time by reading the current tick count.
|
||||
|
||||
---
|
||||
|
||||
### Busy-Wait Sleep
|
||||
- Implemented:
|
||||
```c
|
||||
void SleepBusy(uint32_t milliseconds);
|
||||
```
|
||||
- Uses the current tick count to wait until enough time has passed
|
||||
- Continuously checks the tick counter in a loop
|
||||
|
||||
This version works by actively spinning on the CPU, so it is simple but inefficient.
|
||||
|
||||
---
|
||||
|
||||
### Interrupt-Based Sleep
|
||||
- Implemented:
|
||||
```c
|
||||
void SleepInterrupt(uint32_t ticks);
|
||||
```
|
||||
- Uses:
|
||||
```c
|
||||
__asm__ volatile ("sti; hlt");
|
||||
```
|
||||
inside a loop
|
||||
- Re-checks the current tick count after each wake-up
|
||||
|
||||
This version is more efficient than busy waiting because the CPU is halted between interrupts instead of constantly polling in a tight loop.
|
||||
|
||||
---
|
||||
|
||||
### Kernel Integration
|
||||
- Added:
|
||||
```c
|
||||
#include <kernel/pit.h>
|
||||
```
|
||||
to `kernel.c`
|
||||
- Added:
|
||||
```c
|
||||
PitInitialize();
|
||||
```
|
||||
during kernel startup
|
||||
- Added:
|
||||
```c
|
||||
SleepTest();
|
||||
```
|
||||
during the PIT verification stage. In the final application-oriented kernel flow, this standalone test loop is no longer called from `main()`.
|
||||
|
||||
This connects the PIT driver to the boot flow so the timer logic is available after interrupt setup is complete.
|
||||
|
||||
---
|
||||
|
||||
### Build System Update
|
||||
- Added:
|
||||
```c
|
||||
src/pit.c
|
||||
```
|
||||
to `CMakeLists.txt`
|
||||
|
||||
This ensures the PIT implementation is compiled and linked into the kernel binary together with the rest of the source files.
|
||||
|
||||
---
|
||||
|
||||
### Terminal Output Fix
|
||||
- Added the missing declaration for:
|
||||
```c
|
||||
void TerminalWriteHex(uint32_t num);
|
||||
```
|
||||
to `terminal.h`
|
||||
|
||||
This was necessary because `TerminalWriteHex()` was already implemented in `terminal.c` and used in several source files, but it was not exposed through the header.
|
||||
|
||||
---
|
||||
|
||||
## Testing & Verification
|
||||
|
||||
### Build Issues Resolved
|
||||
During integration, several issues were found and corrected:
|
||||
|
||||
- incorrect inline assembly syntax:
|
||||
```c
|
||||
__asm__volatile
|
||||
```
|
||||
was corrected to:
|
||||
```c
|
||||
__asm__ volatile
|
||||
```
|
||||
- `printf()` calls were removed from PIT test code and replaced with terminal output functions
|
||||
- the missing `TerminalWriteHex()` declaration in `terminal.h` was added
|
||||
|
||||
These changes allowed the kernel to build again with the PIT source included.
|
||||
|
||||
---
|
||||
|
||||
### Boot Image Verification
|
||||
- Verified that rebuilding only:
|
||||
```c
|
||||
uiaos-kernel
|
||||
```
|
||||
updates `kernel.bin`, but does not update the bootable ISO image
|
||||
- Confirmed that:
|
||||
- the new `kernel.bin` contained the new PIT- and memory-related strings
|
||||
- the older `kernel.iso` still contained `Hello, World!`
|
||||
- Rebuilt the image using:
|
||||
```c
|
||||
uiaos-create-image
|
||||
```
|
||||
so QEMU would boot the updated kernel
|
||||
|
||||
This explained why old output was still appearing even after the source code had changed.
|
||||
|
||||
---
|
||||
|
||||
### Runtime Result
|
||||
After rebuilding the bootable image, the kernel successfully booted into the updated code and displayed:
|
||||
- paging setup output
|
||||
- heap and allocation debug output
|
||||
- the `memory1`, `memory2`, `memory3`, and `memory4` address prints
|
||||
|
||||
This confirms that the updated kernel binary is now being booted correctly.
|
||||
|
||||
The PIT sleep test loop was also verified at runtime. The kernel repeatedly prints the expected sleep messages for both:
|
||||
- busy-wait sleeping
|
||||
- interrupt-based sleeping
|
||||
|
||||
This shows that the PIT tick counter is advancing and that both sleep functions return as expected during execution.
|
||||
|
||||
During this test, one remaining issue was observed in the terminal output: once the VGA text buffer wraps back to the top of the screen, new text overwrites old text directly. The later menu and game flow reduces this by clearing the screen before redrawing, but the terminal still does not implement proper scrolling.
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
- Added a PIT driver skeleton to the kernel
|
||||
- Configured PIT channel 0 for periodic timer interrupts
|
||||
- Registered an IRQ0 handler and added a global tick counter
|
||||
- Implemented both busy-wait and interrupt-based sleep functions
|
||||
- Integrated PIT support into `kernel.c`
|
||||
- Added `pit.c` to the build system
|
||||
- Fixed the missing `TerminalWriteHex()` declaration
|
||||
- Verified that the updated kernel is now booting from the rebuilt ISO image
|
||||
- Verified that the PIT sleep test loop runs at runtime
|
||||
|
||||
This means the code structure for Part 2 is present in the kernel and has been tested at runtime. The remaining terminal limitation is that continuous logs still wrap instead of scrolling.
|
||||
|
||||
---
|
||||
|
||||
## Later Integration Note
|
||||
The final application framework clears the screen before menu and game redraws, so the wraparound problem is less visible during normal use. Proper terminal scrolling would still be a useful future improvement for debug output.
|
||||
7
AdvOpsys/notes/work-summary-2026-04-16-to-18.md
Normal file
7
AdvOpsys/notes/work-summary-2026-04-16-to-18.md
Normal file
@@ -0,0 +1,7 @@
|
||||
I developed a simple application framework inside my OS by creating a menu system that lets the user switch between applications, currently music and Snake. I implemented a basic terminal input method using keyboard interrupts so the user can select an application from the menu and return back to it after use. This demonstrates integration between screen output, keyboard handling, and overall OS control flow.
|
||||
|
||||
For the Snake application, I designed a full game loop with its own game state, including the snake, food, score, and board representation. I used dynamic memory allocation to create and destroy the game state, which shows practical use of memory management in my OS. The game uses PIT/timing to control movement speed and game pacing, while keyboard input is used in real time to change direction, restart, or quit.
|
||||
|
||||
I also integrated sound through the PC speaker to give feedback for events such as eating food, dying, and winning. Overall, this task shows integration of multiple OS components, real-time system programming, practical application of operating system concepts, and creative problem-solving through building an interactive menu-driven game environment.
|
||||
|
||||
Finally everything is printed to the screen using existing TerminalWrite...(); infrastructure.
|
||||
210
AdvOpsys/notes/work-summary-2026-04-27.md
Normal file
210
AdvOpsys/notes/work-summary-2026-04-27.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# Summary of Work Done on April 27, 2026
|
||||
|
||||
## Overview
|
||||
|
||||
Today, work was done on the `OSDev_18` project to add a new piano application to the kernel. The main change was the introduction of a small PC-speaker piano mode that lets the user play notes from the keyboard, record note sequences, and replay recorded songs.
|
||||
|
||||
This work also required changes to the application menu in `kernel.c`, the build system, terminal input handling, and a few small adjustments to existing sound and game code so the new feature could fit into the current menu-driven OS flow.
|
||||
|
||||
## Git Evidence
|
||||
|
||||
The changes described in this note come from the newest local commit:
|
||||
|
||||
```text
|
||||
7686d3e added piano feature
|
||||
```
|
||||
|
||||
This commit is local work that has not been pushed yet.
|
||||
|
||||
## Piano Application
|
||||
|
||||
The largest part of the work was the addition of a new piano application:
|
||||
|
||||
- Added:
|
||||
```c
|
||||
include/pianoApp/piano.h
|
||||
include/pianoApp/frequencies.h
|
||||
src/piano.c
|
||||
```
|
||||
|
||||
The new piano code introduces:
|
||||
|
||||
- note frequency constants from `C4` up to `C5`
|
||||
- a `Note` structure storing frequency and duration
|
||||
- a `Song` structure storing a recorded note sequence
|
||||
- a `SongLibrary` structure for keeping multiple saved songs
|
||||
- a `PianoAppState` structure for tracking recording state, active note, and timing between notes
|
||||
|
||||
This gives the kernel a dedicated application module instead of placing all piano logic directly inside `kernel.c`.
|
||||
|
||||
## How the Piano Works
|
||||
|
||||
The piano uses the existing PIT and PC-speaker infrastructure already present in the kernel:
|
||||
|
||||
- `PianoPlaySound()` configures PIT channel 2 to generate the selected note frequency
|
||||
- `PianoEnableSpeaker()` and `PianoDisableSpeaker()` control whether the sound is actually sent to the PC speaker
|
||||
- `SleepInterrupt()` is used to control note timing
|
||||
|
||||
The key layout maps keyboard keys such as `z`, `x`, `c`, `v`, `b`, `n`, `m`, and `,` to white keys, while keys such as `s`, `d`, `g`, `h`, and `j` act as black keys.
|
||||
|
||||
The application also supports:
|
||||
|
||||
- `r` to start and stop recording
|
||||
- `p` to play back a stored song
|
||||
- `q` to quit the piano application
|
||||
|
||||
While recording, the code stores both notes and rests. Rests are detected by comparing the current PIT tick count with the time when the previous note ended. This means the playback system can preserve simple pauses between notes instead of only storing the frequencies themselves.
|
||||
|
||||
## Kernel and Menu Integration
|
||||
|
||||
The kernel menu in `src/kernel.c` was expanded so the user can launch the new application.
|
||||
|
||||
The menu now shows:
|
||||
|
||||
- `0. Play Music`
|
||||
- `1. Play Snake`
|
||||
- `2. Play Piano`
|
||||
|
||||
The kernel now includes `piano.h` and calls `PlayPiano()` when the user selects option `2`.
|
||||
|
||||
The menu flow was also cleaned up slightly:
|
||||
|
||||
- the terminal is cleared before entering each application
|
||||
- the terminal is cleared again after returning from an application
|
||||
- invalid input now prints an error, waits briefly using `SleepInterrupt(1000)`, and then clears the screen
|
||||
|
||||
This makes the menu loop more suitable for switching between multiple interactive kernel applications.
|
||||
|
||||
## Terminal and Input Support
|
||||
|
||||
To support the piano playback menu, terminal input handling was extended:
|
||||
|
||||
- Added to `terminal.h`:
|
||||
```c
|
||||
int TerminalGetUInt(uint32_t *number);
|
||||
```
|
||||
|
||||
- Implemented in `terminal.c`:
|
||||
```c
|
||||
int TerminalGetUInt(uint32_t *number);
|
||||
```
|
||||
|
||||
This helper reads a numeric value from keyboard input and converts it into an unsigned integer while checking for invalid characters and overflow.
|
||||
|
||||
It is used when the piano application asks which recorded song should be replayed.
|
||||
|
||||
Another visible input-related change was made in `keyboard.c`:
|
||||
|
||||
- `TerminalPutChar(ascii);`
|
||||
|
||||
was added inside the keyboard handler so typed characters are echoed to the terminal when key presses are processed.
|
||||
|
||||
## Related Adjustments to Existing Code
|
||||
|
||||
Some smaller changes were also made outside the new piano module:
|
||||
|
||||
- `src/songPlayer.c` no longer doubles note duration during playback, so it now uses the exact stored duration values
|
||||
- `src/snake.c` now prints `Press q to exit` on the game screen
|
||||
- `include/libc/limits.h` now defines `UINT32_MAX`, which is needed by `TerminalGetUInt()`
|
||||
|
||||
These are small changes, but they support the new interaction model and make the applications more consistent.
|
||||
|
||||
## Build System Update
|
||||
|
||||
The build configuration was updated so the new piano source is compiled into the kernel:
|
||||
|
||||
- Added to `CMakeLists.txt`:
|
||||
```c
|
||||
src/piano.c
|
||||
```
|
||||
|
||||
Without this change, the new application code would exist in the source tree but would not be linked into the final kernel binary.
|
||||
|
||||
## Current State
|
||||
|
||||
At the end of this work, the source tree contains a third kernel application alongside the existing music player and Snake game. The new code introduces:
|
||||
|
||||
- a keyboard-playable piano
|
||||
- support for recording note sequences
|
||||
- support for replaying saved songs
|
||||
- a menu option for launching the piano from the main kernel loop
|
||||
|
||||
This note reflects the code changes present in the newest local commit. It describes the implementation work, but it does not by itself prove runtime verification in QEMU during this session.
|
||||
|
||||
## Later Debug and Audio Setup Work
|
||||
|
||||
After the piano feature was added, more work was done on the development setup so the new PC-speaker functionality could be tested through the existing VS Code and devcontainer workflow.
|
||||
|
||||
The first step was to compare a new QEMU command snippet against the current `AdvOpsys` setup. That check showed that the suggested command did not fit this machine and repository unchanged.
|
||||
|
||||
This was treated as a local environment compatibility issue, not as a problem in the `OSDev_18` source tree itself.
|
||||
|
||||
The main problems were:
|
||||
|
||||
- the suggested PulseAudio path `/mnt/wslg/PulseServer` is for WSLg, while this machine uses a Linux host Pulse socket instead
|
||||
- the suggested `-s` flag exposes GDB on the default port `1234`, but the repository debug setup already uses port `26000`
|
||||
- the repository boot flow expects `kernel.iso` as the CD image and `disk.iso` as a separate drive, so using `-hda` for `kernel.iso` does not match the current image layout
|
||||
|
||||
## VS Code Debug Script Changes
|
||||
|
||||
The existing QEMU launcher in `.vscode/qemu-debug.sh` was updated so it can choose an audio backend more carefully.
|
||||
|
||||
The script now:
|
||||
|
||||
- prefers PulseAudio when a usable Pulse socket or `PULSE_SERVER` value is available
|
||||
- falls back to SDL audio when PulseAudio is not available
|
||||
- keeps the existing `127.0.0.1:26000` GDB server path used by the VS Code debugger
|
||||
|
||||
This preserves the original debug flow while making the QEMU launch logic less tied to one audio backend.
|
||||
|
||||
## Devcontainer Mount Attempt and Revert
|
||||
|
||||
An attempt was also made to forward the Linux PulseAudio socket into the devcontainer through `.devcontainer/devcontainer.json`.
|
||||
|
||||
That change did not work with the current Docker Desktop `desktop-linux` environment. Rebuilding the container failed because Docker could not bind-mount the expected Pulse runtime directory:
|
||||
|
||||
```text
|
||||
/run/user/1000/pulse
|
||||
```
|
||||
|
||||
As a result, the PulseAudio mount and related container environment variable were removed again so the devcontainer could build and start normally.
|
||||
|
||||
This failure was specific to the local container/runtime setup on this machine. It did not indicate that the kernel project, the piano code, or the normal image build process were broken.
|
||||
|
||||
This means:
|
||||
|
||||
- the devcontainer build and debug workflow works again
|
||||
- audio support cannot be relied on from the container itself in the same way
|
||||
|
||||
## Host-Side QEMU Workaround
|
||||
|
||||
Because the devcontainer could build and debug the kernel but still had no sound, a separate host-side QEMU workflow was added.
|
||||
|
||||
Two files were updated for this:
|
||||
|
||||
- `.vscode/launch.json`
|
||||
- `.vscode/qemu-debug-host.sh`
|
||||
|
||||
The new host-side setup works by splitting the responsibilities:
|
||||
|
||||
- the kernel is still built from the existing VS Code/devcontainer environment
|
||||
- QEMU is launched on the host, where it can access the real PulseAudio socket
|
||||
- the debugger inside VS Code attaches to that host-side QEMU instance on port `26000`
|
||||
|
||||
This gives a more realistic path for testing PC-speaker audio on this machine, because the emulator process that generates sound now runs in the same environment as the actual audio server.
|
||||
|
||||
In other words, the remaining sound problem was due to where QEMU was running relative to the local audio server, not due to a fault in the repository code.
|
||||
|
||||
## Updated Current State
|
||||
|
||||
At the end of the day, the work on April 27 consisted of both feature development and environment debugging:
|
||||
|
||||
- a new piano application was added to the kernel
|
||||
- the application menu and terminal input code were extended to support it
|
||||
- the QEMU debug setup was reviewed against a new sound-enabled launch command
|
||||
- the container-based PulseAudio mount attempt was tested and then rolled back because it broke devcontainer startup
|
||||
- a host-side QEMU launch script and matching VS Code attach configuration were added as the current workaround for testing sound
|
||||
|
||||
So the codebase now contains the piano implementation itself, and the surrounding debug setup has also been adjusted to better support testing that feature on this specific machine.
|
||||
|
||||
The important distinction is that the main issue here was local system integration between Docker Desktop, the devcontainer, QEMU, and the host audio stack. It was not a core project issue in `AdvOpsys`.
|
||||
Reference in New Issue
Block a user