Initial commit
This commit is contained in:
commit
233e9f6baa
8 changed files with 300 additions and 0 deletions
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
limine
|
||||
ovmf-x64
|
||||
kernel/limine.h
|
||||
*.elf
|
||||
*.iso
|
||||
*.hdd
|
||||
*.o
|
||||
*.d
|
75
GNUmakefile
Normal file
75
GNUmakefile
Normal file
|
@ -0,0 +1,75 @@
|
|||
.PHONY: all
|
||||
all: barebones.iso
|
||||
|
||||
.PHONY: all-hdd
|
||||
all-hdd: barebones.hdd
|
||||
|
||||
.PHONY: run
|
||||
run: barebones.iso
|
||||
qemu-system-x86_64 -M q35 -m 2G -cdrom barebones.iso -boot d
|
||||
|
||||
.PHONY: run-uefi
|
||||
run-uefi: ovmf-x64 barebones.iso
|
||||
qemu-system-x86_64 -M q35 -m 2G -bios ovmf-x64/OVMF.fd -cdrom barebones.iso -boot d
|
||||
|
||||
.PHONY: run-hdd
|
||||
run-hdd: barebones.hdd
|
||||
qemu-system-x86_64 -M q35 -m 2G -hda barebones.hdd
|
||||
|
||||
.PHONY: run-hdd-uefi
|
||||
run-hdd-uefi: ovmf-x64 barebones.hdd
|
||||
qemu-system-x86_64 -M q35 -m 2G -bios ovmf-x64/OVMF.fd -hda barebones.hdd
|
||||
|
||||
ovmf-x64:
|
||||
mkdir -p ovmf-x64
|
||||
cd ovmf-x64 && curl -o OVMF-X64.zip https://efi.akeo.ie/OVMF/OVMF-X64.zip && 7z x OVMF-X64.zip
|
||||
|
||||
limine:
|
||||
git clone https://github.com/limine-bootloader/limine.git --branch=v3.0-branch-binary --depth=1
|
||||
make -C limine
|
||||
|
||||
.PHONY: kernel
|
||||
kernel:
|
||||
$(MAKE) -C kernel
|
||||
|
||||
barebones.iso: limine kernel
|
||||
rm -rf iso_root
|
||||
mkdir -p iso_root
|
||||
cp kernel/kernel.elf \
|
||||
limine.cfg limine/limine.sys limine/limine-cd.bin limine/limine-eltorito-efi.bin iso_root/
|
||||
xorriso -as mkisofs -b limine-cd.bin \
|
||||
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
||||
--efi-boot limine-eltorito-efi.bin \
|
||||
-efi-boot-part --efi-boot-image --protective-msdos-label \
|
||||
iso_root -o barebones.iso
|
||||
limine/limine-s2deploy barebones.iso
|
||||
rm -rf iso_root
|
||||
|
||||
barebones.hdd: limine kernel
|
||||
rm -f barebones.hdd
|
||||
dd if=/dev/zero bs=1M count=0 seek=64 of=barebones.hdd
|
||||
parted -s barebones.hdd mklabel gpt
|
||||
parted -s barebones.hdd mkpart ESP fat32 2048s 100%
|
||||
parted -s barebones.hdd set 1 esp on
|
||||
limine/limine-s2deploy barebones.hdd
|
||||
sudo losetup -Pf --show barebones.hdd >loopback_dev
|
||||
sudo mkfs.fat -F 32 `cat loopback_dev`p1
|
||||
mkdir -p img_mount
|
||||
sudo mount `cat loopback_dev`p1 img_mount
|
||||
sudo mkdir -p img_mount/EFI/BOOT
|
||||
sudo cp -v kernel/kernel.elf limine.cfg limine/limine.sys img_mount/
|
||||
sudo cp -v limine/BOOTX64.EFI img_mount/EFI/BOOT/
|
||||
sync
|
||||
sudo umount img_mount
|
||||
sudo losetup -d `cat loopback_dev`
|
||||
rm -rf loopback_dev img_mount
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf iso_root barebones.iso barebones.hdd
|
||||
$(MAKE) -C kernel clean
|
||||
|
||||
.PHONY: distclean
|
||||
distclean: clean
|
||||
rm -rf limine ovmf-x64
|
||||
$(MAKE) -C kernel distclean
|
5
LICENSE.md
Normal file
5
LICENSE.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
Copyright 2020, 2021 mintsuki and contributors.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
43
README.md
Normal file
43
README.md
Normal file
|
@ -0,0 +1,43 @@
|
|||
# Limine Bare Bones
|
||||
|
||||
This repository will show you how to set up a simple 64-bit x86_64 Long Mode higher half kernel using Limine.
|
||||
|
||||
This project can be built using the host compiler on most Linux distros on x86_64, but it's recommended you set up an x86_64-elf [cross compiler](https://wiki.osdev.org/GCC_Cross-Compiler).
|
||||
|
||||
## Where to go from here
|
||||
|
||||
You may be asking yourself: "what now?". So here's a list of things you may want to do to get started working
|
||||
on your new kernel:
|
||||
|
||||
* Load an [IDT](https://wiki.osdev.org/Interrupt_Descriptor_Table) so that exceptions and interrupts can be handled.
|
||||
* Write a physical memory allocator, a good starting point is a bitmap allocator.
|
||||
* Write a virtual memory manager that can map, remap and unmap pages.
|
||||
* Begin parsing ACPI tables, the most important one is the MADT since it contains information about the APIC.
|
||||
* Start up the other CPUs. Limine provides a facility to make this less painful.
|
||||
* Set up an interrupt controller such as the APIC.
|
||||
* Configure a timer such as the Local APIC timer, the PIT, or the HPET.
|
||||
* Implement a scheduler to schedule threads in order make multitasking possible.
|
||||
* Design a virtual file system (VFS) and implement it. The traditional UNIX VFS works and saves headaches when porting software, but you can make your own thing too.
|
||||
* Implement a simple virtual file system like a memory-only tmpfs to avoid crippling the design of your VFS too much while implementing it alongside real storage filesystems.
|
||||
* Decide how to abstract devices. UNIX likes usually go for a `/dev` virtual filesystem containing device nodes and use `ioctl()` alongside standard FS calls to do operations on them.
|
||||
* Get a userland going by loading executables from your VFS and running them in ring 3. Set up a way to perform system calls.
|
||||
* Write a PCI driver.
|
||||
* Add support for a storage medium, the easiest and most common ones are AHCI and NVMe.
|
||||
|
||||
|
||||
At this point you should have decided what kind of interface your OS is going to provide to programs running on it, a common design that a lot of hobby operating systems use is POSIX (which derives from the UNIX design), which has both pros and cons:
|
||||
|
||||
Pros:
|
||||
|
||||
* Easier to port existing software that already runs on UNIX like operating systems like Linux.
|
||||
* The basic parts of POSIX are fairly easy to implement.
|
||||
* Pretty safe and sound design which has stood the test of time for over 40 years.
|
||||
|
||||
Cons:
|
||||
|
||||
* Restricts you to use an already existing design.
|
||||
* POSIX may get complex and has a lot of legacy cruft that software might rely on.
|
||||
|
||||
Another point to consider is that a lot of software tends to depend on Linux or glibc specific features, but a portable C library like [mlibc](https://github.com/managarm/mlibc) can be used instead of implementing your own, as it provides good compatibility with POSIX/Linux software.
|
||||
|
||||
Other options, instead of implementing POSIX in your kernel, is to add a POSIX compatibility layer on top of your native design (with the large downside of complicating the design of your OS).
|
77
kernel/GNUmakefile
Normal file
77
kernel/GNUmakefile
Normal file
|
@ -0,0 +1,77 @@
|
|||
# This is the name that our final kernel executable will have.
|
||||
# Change as needed.
|
||||
override KERNEL := kernel.elf
|
||||
|
||||
# It is highly recommended to use a custom built cross toolchain to build a kernel.
|
||||
# We are only using "cc" as a placeholder here. It may work by using
|
||||
# the host system's toolchain, but this is not guaranteed.
|
||||
ifeq ($(origin CC), default)
|
||||
CC := cc
|
||||
endif
|
||||
|
||||
# Likewise, "ld" here is just a placeholder and your mileage may vary if using the
|
||||
# host's "ld".
|
||||
ifeq ($(origin LD), default)
|
||||
LD := ld
|
||||
endif
|
||||
|
||||
# User controllable CFLAGS.
|
||||
CFLAGS ?= -O2 -g -Wall -Wextra -Wpedantic -pipe
|
||||
|
||||
# User controllable linker flags. We set none by default.
|
||||
LDFLAGS ?=
|
||||
|
||||
# Internal C flags that should not be changed by the user.
|
||||
override INTERNALCFLAGS := \
|
||||
-I. \
|
||||
-std=c11 \
|
||||
-ffreestanding \
|
||||
-fno-stack-protector \
|
||||
-fno-stack-check \
|
||||
-fno-pic \
|
||||
-mabi=sysv \
|
||||
-mno-80387 \
|
||||
-mno-mmx \
|
||||
-mno-3dnow \
|
||||
-mno-sse \
|
||||
-mno-sse2 \
|
||||
-mno-red-zone \
|
||||
-mcmodel=kernel \
|
||||
-MMD
|
||||
|
||||
# Internal linker flags that should not be changed by the user.
|
||||
override INTERNALLDFLAGS := \
|
||||
-Tlinker.ld \
|
||||
-nostdlib \
|
||||
-zmax-page-size=0x1000 \
|
||||
-static
|
||||
|
||||
# Use find to glob all *.c files in the directory and extract the object names.
|
||||
override CFILES := $(shell find ./ -type f -name '*.c')
|
||||
override OBJ := $(CFILES:.c=.o)
|
||||
override HEADER_DEPS := $(CFILES:.c=.d)
|
||||
|
||||
# Default target.
|
||||
.PHONY: all
|
||||
all: $(KERNEL)
|
||||
|
||||
limine.h:
|
||||
curl https://raw.githubusercontent.com/limine-bootloader/limine/trunk/limine.h -o $@
|
||||
|
||||
# Link rules for the final kernel executable.
|
||||
$(KERNEL): $(OBJ)
|
||||
$(LD) $(OBJ) $(LDFLAGS) $(INTERNALLDFLAGS) -o $@
|
||||
|
||||
# Compilation rules for *.c files.
|
||||
-include $(HEADER_DEPS)
|
||||
%.o: %.c limine.h
|
||||
$(CC) $(CFLAGS) $(INTERNALCFLAGS) -c $< -o $@
|
||||
|
||||
# Remove object files and the final executable.
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf $(KERNEL) $(OBJ) $(HEADER_DEPS)
|
||||
|
||||
.PHONY: distclean
|
||||
distclean: clean
|
||||
rm -f limine.h
|
33
kernel/kernel.c
Normal file
33
kernel/kernel.c
Normal file
|
@ -0,0 +1,33 @@
|
|||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <limine.h>
|
||||
|
||||
// The Limine requests can be placed anywhere, but it is important that
|
||||
// the compiler does not optimise them away, so, usually, they should
|
||||
// be made volatile or equivalent.
|
||||
|
||||
static volatile struct limine_terminal_request terminal_request = {
|
||||
.id = LIMINE_TERMINAL_REQUEST,
|
||||
.revision = 0
|
||||
};
|
||||
|
||||
static void done(void) {
|
||||
for (;;) {
|
||||
__asm__("hlt");
|
||||
}
|
||||
}
|
||||
|
||||
// The following will be our kernel's entry point.
|
||||
void _start(void) {
|
||||
// Ensure we got a terminal
|
||||
if (terminal_request.response == NULL) {
|
||||
done();
|
||||
}
|
||||
|
||||
// We should now be able to call the Limine terminal to print out
|
||||
// a simple "Hello World" to screen.
|
||||
terminal_request.response->write("Hello World", 11);
|
||||
|
||||
// We're done, just hang...
|
||||
done();
|
||||
}
|
48
kernel/linker.ld
Normal file
48
kernel/linker.ld
Normal file
|
@ -0,0 +1,48 @@
|
|||
/* Tell the linker that we want an x86_64 ELF64 output file */
|
||||
OUTPUT_FORMAT(elf64-x86-64)
|
||||
OUTPUT_ARCH(i386:x86-64)
|
||||
|
||||
/* We want the symbol _start to be our entry point */
|
||||
ENTRY(_start)
|
||||
|
||||
/* Define the program headers we want so the bootloader gives us the right */
|
||||
/* MMU permissions */
|
||||
PHDRS
|
||||
{
|
||||
null PT_NULL FLAGS(0) ; /* Null segment */
|
||||
text PT_LOAD FLAGS((1 << 0) | (1 << 2)) ; /* Execute + Read */
|
||||
rodata PT_LOAD FLAGS((1 << 2)) ; /* Read only */
|
||||
data PT_LOAD FLAGS((1 << 1) | (1 << 2)) ; /* Write + Read */
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* We wanna be placed in the topmost 2GiB of the address space, for optimisations */
|
||||
/* and because that is what the Limine spec mandates. */
|
||||
/* Any address in this region will do, but often 0xffffffff80000000 is chosen as */
|
||||
/* that is the beginning of the region. */
|
||||
. = 0xffffffff80000000;
|
||||
|
||||
.text : {
|
||||
*(.text .text.*)
|
||||
} :text
|
||||
|
||||
/* Move to the next memory page for .rodata */
|
||||
. += CONSTANT(MAXPAGESIZE);
|
||||
|
||||
.rodata : {
|
||||
*(.rodata .rodata.*)
|
||||
} :rodata
|
||||
|
||||
/* Move to the next memory page for .data */
|
||||
. += CONSTANT(MAXPAGESIZE);
|
||||
|
||||
.data : {
|
||||
*(.data .data.*)
|
||||
} :data
|
||||
|
||||
.bss : {
|
||||
*(COMMON)
|
||||
*(.bss .bss.*)
|
||||
} :data
|
||||
}
|
11
limine.cfg
Normal file
11
limine.cfg
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Timeout in seconds that Limine will use before automatically booting.
|
||||
TIMEOUT=3
|
||||
|
||||
# The entry name that will be displayed in the boot menu
|
||||
:Limine Barebones
|
||||
|
||||
# Change the protocol line depending on the used protocol.
|
||||
PROTOCOL=limine
|
||||
|
||||
# Path to the kernel to boot. boot:/// represents the partition on which limine.cfg is located.
|
||||
KERNEL_PATH=boot:///kernel.elf
|
Loading…
Add table
Reference in a new issue