Compare commits

...

42 Commits

Author SHA1 Message Date
Philipp Oppermann
9f1a69cafa Create a new AreaFrameAllocator and allocate maximum number of frames 2017-11-19 10:39:13 +01:00
Philipp Oppermann
edb2e693da Create an AreaFrameAllocator 2017-11-19 10:39:13 +01:00
Philipp Oppermann
84c337e6f0 Create a memory module with a Frame struct and FrameAllocator trait 2017-11-19 10:39:13 +01:00
Philipp Oppermann
98d97703f4 Calculate start and end of kernel and multiboot struct 2017-11-19 10:39:13 +01:00
Philipp Oppermann
402ec77bc0 Reduce number of ELF section by merging .text.* (etc.) sections together 2017-11-19 10:39:13 +01:00
Philipp Oppermann
9a86f60835 Print kernel ELF sections 2017-11-19 10:39:13 +01:00
Philipp Oppermann
cbc9e112bd Print a proper message on panics 2017-11-19 10:39:13 +01:00
Philipp Oppermann
f231f2c7f2 Print available memory areas 2017-11-19 10:39:13 +01:00
Philipp Oppermann
5f65e1d31f Add dependency on multiboot2 crate 2017-11-19 10:39:13 +01:00
Philipp Oppermann
28ce5310c8 Pass address of multiboot info structure to rust_main 2017-11-19 10:39:13 +01:00
Philipp Oppermann
08a4e795a4 Increase stack size to 16k 2017-11-19 10:39:13 +01:00
Philipp Oppermann
8040f8d565 Update Readme for “Allocating Frames” post 2017-11-19 10:39:13 +01:00
Philipp Oppermann
9f448fbe0e Avoid deadlock on nested print! invokation 2017-11-19 10:39:05 +01:00
Philipp Oppermann
59b8133396 Add print! and println! macros and a clear_screen function 2017-11-19 10:39:05 +01:00
Philipp Oppermann
40aed4fa0f Create a static WRITER protected by a spinlock 2017-11-19 10:39:05 +01:00
Philipp Oppermann
f24c7bc322 Implement the new_line method 2017-11-19 10:31:00 +01:00
Philipp Oppermann
5e0ccd5aa5 Implement the fmt::Write trait and print something with the write! macro 2017-11-19 10:31:00 +01:00
Philipp Oppermann
578717a9b8 Add a write_str method and print “Hello!” 2017-11-19 10:31:00 +01:00
Philipp Oppermann
0ed21fb943 Use volatile writes for printing to screen 2017-11-19 10:31:00 +01:00
Philipp Oppermann
6aa3f67331 Add a print_something function to print an H in the lower left 2017-11-19 10:30:48 +01:00
Philipp Oppermann
46d47f8d2e Create a Writer struct with a write_byte function 2017-11-19 10:30:48 +01:00
Philipp Oppermann
afc2c26a9d Create a vga_buffer module 2017-11-19 10:30:48 +01:00
Philipp Oppermann
db9a19b38a Update Readme for “Printing to Screen” post 2017-11-19 10:30:48 +01:00
Philipp Oppermann
bef5f13560 Print a “Hello World!” in Rust 2017-11-19 10:30:36 +01:00
Philipp Oppermann
a1743eb3dd Set the panic strategy to abort to fix _Unwind_Resume errors 2017-11-19 10:30:36 +01:00
Philipp Oppermann
13cffc3319 Add target directory to .gitignore 2017-11-19 10:30:36 +01:00
Philipp Oppermann
ff623a90e3 Use --gc-sections to remove unused program sections 2017-11-19 10:30:36 +01:00
Philipp Oppermann
5d8758df59 Add a dependency on rlibc 2017-11-18 11:10:17 +01:00
Philipp Oppermann
ca2ccc31aa Call the rust_main function from assembly 2017-11-18 11:10:17 +01:00
Philipp Oppermann
8dd179a6f4 Adjust the Makefile to build and link Rust code 2017-11-18 11:10:17 +01:00
Philipp Oppermann
9f578640d8 Add a target spcification that disables SSE and the red zone 2017-11-18 11:10:17 +01:00
Philipp Oppermann
8e5a85ece4 Create a new no_std cargo project 2017-05-12 21:31:28 +02:00
Philipp Oppermann
1c88c7f945 Update Readme for “Set Up Rust” post 2017-05-12 21:31:28 +02:00
Philipp Oppermann
9136a7b66f Zero all segment registers 2017-05-12 21:31:28 +02:00
Philipp Oppermann
415d27814f Do a far jump to long mode and print OKAY 2017-05-12 21:31:28 +02:00
Philipp Oppermann
e684bfd262 Create and load a 64-bit GDT 2017-05-12 21:31:28 +02:00
Philipp Oppermann
936e2073a9 Enable paging 2017-05-12 21:31:24 +02:00
Philipp Oppermann
b199b02578 Set up page tables for identity mapping 2017-05-12 21:31:24 +02:00
Philipp Oppermann
90e716827a Check multiboot magic number and for CPUID/long mode support 2017-05-12 21:31:24 +02:00
Philipp Oppermann
9819e17a28 Create and load a stack 2017-05-12 21:31:24 +02:00
Philipp Oppermann
5ebdd89ffb Add an error function 2017-05-12 21:31:24 +02:00
Philipp Oppermann
78e86c2312 Update Readme for “Entering Longmode” post 2017-05-12 21:31:24 +02:00
12 changed files with 556 additions and 9 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
build
target

13
Cargo.toml Normal file
View File

@@ -0,0 +1,13 @@
[package]
name = "blog_os"
version = "0.1.0"
authors = ["Philipp Oppermann <dev@phil-opp.com>"]
[lib]
crate-type = ["staticlib"]
[dependencies]
rlibc = "1.0"
volatile = "0.1.0"
spin = "0.4.5"
multiboot2 = "0.1.0"

View File

@@ -1,6 +1,8 @@
arch ?= x86_64
kernel := build/kernel-$(arch).bin
iso := build/os-$(arch).iso
target ?= $(arch)-blog_os
rust_os := target/$(target)/debug/libblog_os.a
linker_script := src/arch/$(arch)/linker.ld
grub_cfg := src/arch/$(arch)/grub.cfg
@@ -8,7 +10,7 @@ assembly_source_files := $(wildcard src/arch/$(arch)/*.asm)
assembly_object_files := $(patsubst src/arch/$(arch)/%.asm, \
build/arch/$(arch)/%.o, $(assembly_source_files))
.PHONY: all clean run iso
.PHONY: all clean run iso kernel
all: $(kernel)
@@ -27,8 +29,12 @@ $(iso): $(kernel) $(grub_cfg)
@grub-mkrescue -o $(iso) build/isofiles 2> /dev/null
@rm -r build/isofiles
$(kernel): $(assembly_object_files) $(linker_script)
@ld -n -T $(linker_script) -o $(kernel) $(assembly_object_files)
$(kernel): kernel $(rust_os) $(assembly_object_files) $(linker_script)
@ld -n --gc-sections -T $(linker_script) -o $(kernel) \
$(assembly_object_files) $(rust_os)
kernel:
@xargo build --target $(target)
# compile assembly files
build/arch/$(arch)/%.o: src/arch/$(arch)/%.asm

View File

@@ -1,12 +1,12 @@
# Blog OS (A Minimal x86 Kernel)
[![Build Status](https://travis-ci.org/phil-opp/blog_os.svg?branch=post_1)](https://travis-ci.org/phil-opp/blog_os/branches)
# Blog OS (Allocating Frames)
[![Build Status](https://travis-ci.org/phil-opp/blog_os.svg?branch=post_5)](https://travis-ci.org/phil-opp/blog_os/branches)
This repository contains the source code for the [A Minimal x86 Kernel](http://os.phil-opp.com/multiboot-kernel.html) post of the [Writing an OS in Rust](http://os.phil-opp.com) series.
This repository contains the source code for the [Allocating Frames](http://os.phil-opp.com/allocating-frames.html) post of the [Writing an OS in Rust](http://os.phil-opp.com) series.
**Check out the [master branch](https://github.com/phil-opp/blog_os) for more information.**
## Building
You need to have `nasm`, `grub-mkrescue`, `xorriso`, and `qemu` installed. Then you can run it using `make run`.
You need to have `nasm`, `grub-mkrescue`, `xorriso`, `qemu`, and a nightly Rust compiler installed. Then you can run it using `make run`.
Please file an issue if you have any problems.

View File

@@ -1,8 +1,165 @@
global start
extern long_mode_start
section .text
bits 32
start:
mov esp, stack_top
mov edi, ebx ; move Multiboot info pointer to edi
call check_multiboot
call check_cpuid
call check_long_mode
call set_up_page_tables
call enable_paging
; load the 64-bit GDT
lgdt [gdt64.pointer]
jmp gdt64.code:long_mode_start
; print `OK` to screen
mov dword [0xb8000], 0x2f4b2f4f
hlt
check_multiboot:
cmp eax, 0x36d76289
jne .no_multiboot
ret
.no_multiboot:
mov al, "0"
jmp error
check_cpuid:
; Check if CPUID is supported by attempting to flip the ID bit (bit 21)
; in the FLAGS register. If we can flip it, CPUID is available.
; Copy FLAGS in to EAX via stack
pushfd
pop eax
; Copy to ECX as well for comparing later on
mov ecx, eax
; Flip the ID bit
xor eax, 1 << 21
; Copy EAX to FLAGS via the stack
push eax
popfd
; Copy FLAGS back to EAX (with the flipped bit if CPUID is supported)
pushfd
pop eax
; Restore FLAGS from the old version stored in ECX (i.e. flipping the
; ID bit back if it was ever flipped).
push ecx
popfd
; Compare EAX and ECX. If they are equal then that means the bit
; wasn't flipped, and CPUID isn't supported.
cmp eax, ecx
je .no_cpuid
ret
.no_cpuid:
mov al, "1"
jmp error
check_long_mode:
; test if extended processor info in available
mov eax, 0x80000000 ; implicit argument for cpuid
cpuid ; get highest supported argument
cmp eax, 0x80000001 ; it needs to be at least 0x80000001
jb .no_long_mode ; if it's less, the CPU is too old for long mode
; use extended info to test if long mode is available
mov eax, 0x80000001 ; argument for extended processor info
cpuid ; returns various feature bits in ecx and edx
test edx, 1 << 29 ; test if the LM-bit is set in the D-register
jz .no_long_mode ; If it's not set, there is no long mode
ret
.no_long_mode:
mov al, "2"
jmp error
set_up_page_tables:
; map first P4 entry to P3 table
mov eax, p3_table
or eax, 0b11 ; present + writable
mov [p4_table], eax
; map first P3 entry to P2 table
mov eax, p2_table
or eax, 0b11 ; present + writable
mov [p3_table], eax
; map each P2 entry to a huge 2MiB page
mov ecx, 0 ; counter variable
.map_p2_table:
; map ecx-th P2 entry to a huge page that starts at address 2MiB*ecx
mov eax, 0x200000 ; 2MiB
mul ecx ; start address of ecx-th page
or eax, 0b10000011 ; present + writable + huge
mov [p2_table + ecx * 8], eax ; map ecx-th entry
inc ecx ; increase counter
cmp ecx, 512 ; if counter == 512, the whole P2 table is mapped
jne .map_p2_table ; else map the next entry
ret
enable_paging:
; load P4 to cr3 register (cpu uses this to access the P4 table)
mov eax, p4_table
mov cr3, eax
; enable PAE-flag in cr4 (Physical Address Extension)
mov eax, cr4
or eax, 1 << 5
mov cr4, eax
; set the long mode bit in the EFER MSR (model specific register)
mov ecx, 0xC0000080
rdmsr
or eax, 1 << 8
wrmsr
; enable paging in the cr0 register
mov eax, cr0
or eax, 1 << 31
mov cr0, eax
ret
; Prints `ERR: ` and the given error code to screen and hangs.
; parameter: error code (in ascii) in al
error:
mov dword [0xb8000], 0x4f524f45
mov dword [0xb8004], 0x4f3a4f52
mov dword [0xb8008], 0x4f204f20
mov byte [0xb800a], al
hlt
section .bss
align 4096
p4_table:
resb 4096
p3_table:
resb 4096
p2_table:
resb 4096
stack_bottom:
resb 4096 * 4
stack_top:
section .rodata
gdt64:
dq 0 ; zero entry
.code: equ $ - gdt64 ; new
dq (1<<43) | (1<<44) | (1<<47) | (1<<53) ; code segment
.pointer:
dw $ - gdt64 - 1
dq gdt64

View File

@@ -6,11 +6,19 @@ SECTIONS {
.boot :
{
/* ensure that the multiboot header is at the beginning */
*(.multiboot_header)
KEEP(*(.multiboot_header))
}
.text :
{
*(.text)
*(.text .text.*)
}
.rodata : {
*(.rodata .rodata.*)
}
.data.rel.ro : {
*(.data.rel.ro.local*) *(.data.rel.ro .data.rel.ro.*)
}
}

View File

@@ -0,0 +1,22 @@
global long_mode_start
extern rust_main
section .text
bits 64
long_mode_start:
; load 0 into all data segment registers
mov ax, 0
mov ss, ax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; call the rust main
extern rust_main
call rust_main
; print `OKAY` to screen
mov rax, 0x2f592f412f4b2f4f
mov qword [0xb8000], rax
hlt

71
src/lib.rs Normal file
View File

@@ -0,0 +1,71 @@
#![feature(lang_items)]
#![feature(const_fn)]
#![feature(const_unique_new)]
#![feature(unique)]
#![no_std]
extern crate rlibc;
extern crate volatile;
extern crate spin;
extern crate multiboot2;
#[macro_use]
mod vga_buffer;
mod memory;
#[no_mangle]
pub extern fn rust_main(multiboot_information_address: usize) {
use memory::FrameAllocator;
vga_buffer::clear_screen();
println!("Hello World{}", "!");
let boot_info = unsafe{ multiboot2::load(multiboot_information_address) };
let memory_map_tag = boot_info.memory_map_tag()
.expect("Memory map tag required");
println!("memory areas:");
for area in memory_map_tag.memory_areas() {
println!(" start: 0x{:x}, length: 0x{:x}",
area.base_addr, area.length);
}
let elf_sections_tag = boot_info.elf_sections_tag()
.expect("Elf-sections tag required");
println!("kernel sections:");
for section in elf_sections_tag.sections() {
println!(" addr: 0x{:x}, size: 0x{:x}, flags: 0x{:x}",
section.addr, section.size, section.flags);
}
let kernel_start = elf_sections_tag.sections().map(|s| s.addr)
.min().unwrap();
let kernel_end = elf_sections_tag.sections().map(|s| s.addr + s.size)
.max().unwrap();
let multiboot_start = multiboot_information_address;
let multiboot_end = multiboot_start + (boot_info.total_size as usize);
let mut frame_allocator = memory::AreaFrameAllocator::new(
kernel_start as usize, kernel_end as usize, multiboot_start,
multiboot_end, memory_map_tag.memory_areas());
for i in 0.. {
if let None = frame_allocator.allocate_frame() {
println!("allocated {} frames", i);
break;
}
}
loop{}
}
#[lang = "eh_personality"] extern fn eh_personality() {}
#[lang = "panic_fmt"]
#[no_mangle]
pub extern fn panic_fmt(fmt: core::fmt::Arguments, file: &'static str, line: u32) -> ! {
println!("\n\nPANIC in {} at line {}:", file, line);
println!(" {}", fmt);
loop{}
}

View File

@@ -0,0 +1,88 @@
use memory::{Frame, FrameAllocator};
use multiboot2::{MemoryAreaIter, MemoryArea};
pub struct AreaFrameAllocator {
next_free_frame: Frame,
current_area: Option<&'static MemoryArea>,
areas: MemoryAreaIter,
kernel_start: Frame,
kernel_end: Frame,
multiboot_start: Frame,
multiboot_end: Frame,
}
impl FrameAllocator for AreaFrameAllocator {
fn allocate_frame(&mut self) -> Option<Frame> {
if let Some(area) = self.current_area {
// "Clone" the frame to return it if it's free. Frame doesn't
// implement Clone, but we can construct an identical frame.
let frame = Frame{ number: self.next_free_frame.number };
// the last frame of the current area
let current_area_last_frame = {
let address = area.base_addr + area.length - 1;
Frame::containing_address(address as usize)
};
if frame > current_area_last_frame {
// all frames of current area are used, switch to next area
self.choose_next_area();
} else if frame >= self.kernel_start && frame <= self.kernel_end {
// `frame` is used by the kernel
self.next_free_frame = Frame {
number: self.kernel_end.number + 1
};
} else if frame >= self.multiboot_start && frame <= self.multiboot_end {
// `frame` is used by the multiboot information structure
self.next_free_frame = Frame {
number: self.multiboot_end.number + 1
};
} else {
// frame is unused, increment `next_free_frame` and return it
self.next_free_frame.number += 1;
return Some(frame);
}
// `frame` was not valid, try it again with the updated `next_free_frame`
self.allocate_frame()
} else {
None // no free frames left
}
}
fn deallocate_frame(&mut self, _frame: Frame) {
unimplemented!()
}
}
impl AreaFrameAllocator {
pub fn new(kernel_start: usize, kernel_end: usize,
multiboot_start: usize, multiboot_end: usize,
memory_areas: MemoryAreaIter) -> AreaFrameAllocator
{
let mut allocator = AreaFrameAllocator {
next_free_frame: Frame::containing_address(0),
current_area: None,
areas: memory_areas,
kernel_start: Frame::containing_address(kernel_start),
kernel_end: Frame::containing_address(kernel_end),
multiboot_start: Frame::containing_address(multiboot_start),
multiboot_end: Frame::containing_address(multiboot_end),
};
allocator.choose_next_area();
allocator
}
fn choose_next_area(&mut self) {
self.current_area = self.areas.clone().filter(|area| {
let address = area.base_addr + area.length - 1;
Frame::containing_address(address as usize) >= self.next_free_frame
}).min_by_key(|area| area.base_addr);
if let Some(area) = self.current_area {
let start_frame = Frame::containing_address(area.base_addr as usize);
if self.next_free_frame < start_frame {
self.next_free_frame = start_frame;
}
}
}
}

21
src/memory/mod.rs Normal file
View File

@@ -0,0 +1,21 @@
pub use self::area_frame_allocator::AreaFrameAllocator;
mod area_frame_allocator;
pub const PAGE_SIZE: usize = 4096;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Frame {
number: usize,
}
impl Frame {
fn containing_address(address: usize) -> Frame {
Frame{ number: address / PAGE_SIZE }
}
}
pub trait FrameAllocator {
fn allocate_frame(&mut self) -> Option<Frame>;
fn deallocate_frame(&mut self, frame: Frame);
}

147
src/vga_buffer.rs Normal file
View File

@@ -0,0 +1,147 @@
use core::fmt;
use core::ptr::Unique;
use spin::Mutex;
use volatile::Volatile;
pub static WRITER: Mutex<Writer> = Mutex::new(Writer {
column_position: 0,
color_code: ColorCode::new(Color::LightGreen, Color::Black),
buffer: unsafe { Unique::new_unchecked(0xb8000 as *mut _) },
});
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
pub enum Color {
Black = 0,
Blue = 1,
Green = 2,
Cyan = 3,
Red = 4,
Magenta = 5,
Brown = 6,
LightGray = 7,
DarkGray = 8,
LightBlue = 9,
LightGreen = 10,
LightCyan = 11,
LightRed = 12,
Pink = 13,
Yellow = 14,
White = 15,
}
#[derive(Debug, Clone, Copy)]
struct ColorCode(u8);
impl ColorCode {
const fn new(foreground: Color, background: Color) -> ColorCode {
ColorCode((background as u8) << 4 | (foreground as u8))
}
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
struct ScreenChar {
ascii_character: u8,
color_code: ColorCode,
}
const BUFFER_HEIGHT: usize = 25;
const BUFFER_WIDTH: usize = 80;
struct Buffer {
chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT],
}
pub struct Writer {
column_position: usize,
color_code: ColorCode,
buffer: Unique<Buffer>,
}
impl Writer {
pub fn write_byte(&mut self, byte: u8) {
match byte {
b'\n' => self.new_line(),
byte => {
if self.column_position >= BUFFER_WIDTH {
self.new_line();
}
let row = BUFFER_HEIGHT - 1;
let col = self.column_position;
let color_code = self.color_code;
self.buffer().chars[row][col].write(ScreenChar {
ascii_character: byte,
color_code: color_code,
});
self.column_position += 1;
}
}
}
pub fn write_str(&mut self, s: &str) {
for byte in s.bytes() {
self.write_byte(byte)
}
}
fn buffer(&mut self) -> &mut Buffer {
unsafe{ self.buffer.as_mut() }
}
fn new_line(&mut self) {
for row in 1..BUFFER_HEIGHT {
for col in 0..BUFFER_WIDTH {
let buffer = self.buffer();
let character = buffer.chars[row][col].read();
buffer.chars[row - 1][col].write(character);
}
}
self.clear_row(BUFFER_HEIGHT-1);
self.column_position = 0;
}
fn clear_row(&mut self, row: usize) {
let blank = ScreenChar {
ascii_character: b' ',
color_code: self.color_code,
};
for col in 0..BUFFER_WIDTH {
self.buffer().chars[row][col].write(blank);
}
}
}
impl fmt::Write for Writer {
fn write_str(&mut self, s: &str) -> fmt::Result {
for byte in s.bytes() {
self.write_byte(byte)
}
Ok(())
}
}
macro_rules! print {
($($arg:tt)*) => ({
$crate::vga_buffer::print(format_args!($($arg)*));
});
}
macro_rules! println {
($fmt:expr) => (print!(concat!($fmt, "\n")));
($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
}
pub fn print(args: fmt::Arguments) {
use core::fmt::Write;
WRITER.lock().write_fmt(args).unwrap();
}
pub fn clear_screen() {
for _ in 0..BUFFER_HEIGHT {
println!("");
}
}

13
x86_64-blog_os.json Normal file
View File

@@ -0,0 +1,13 @@
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"linker-flavor": "gcc",
"target-endian": "little",
"target-pointer-width": "64",
"target-c-int-width": "32",
"arch": "x86_64",
"os": "none",
"disable-redzone": true,
"features": "-mmx,-sse,+soft-float",
"panic-strategy": "abort"
}