From 72e726295f0eadee4d1f7c7f7532984234582038 Mon Sep 17 00:00:00 2001 From: Jianfeng Jiang Date: Thu, 20 Jun 2024 02:50:23 +0000 Subject: [PATCH] Include the 100 lines kernel in CI --- docs/src/ostd/a-100-line-kernel.md | 146 +----------------- osdk/src/commands/build/bin.rs | 4 +- osdk/tests/examples_in_book/mod.rs | 1 + .../examples_in_book/work_in_workspace.rs | 27 +++- .../write_a_kernel_in_100_lines.rs | 65 ++++++++ .../hello.S | 19 +++ .../lib.rs | 132 ++++++++++++++++ 7 files changed, 242 insertions(+), 152 deletions(-) create mode 100644 osdk/tests/examples_in_book/write_a_kernel_in_100_lines.rs create mode 100644 osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/hello.S create mode 100644 osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/lib.rs diff --git a/docs/src/ostd/a-100-line-kernel.md b/docs/src/ostd/a-100-line-kernel.md index 3962907a..7cfa3bed 100644 --- a/docs/src/ostd/a-100-line-kernel.md +++ b/docs/src/ostd/a-100-line-kernel.md @@ -7,23 +7,7 @@ we will show a new kernel in about 100 lines of safe Rust. Our new kernel will be able to run the following Hello World program. ```s -.global _start # entry point -.section .text # code section -_start: - mov $1, %rax # syscall number of write - mov $1, %rdi # stdout - mov $message, %rsi # address of message - mov $message_end, %rdx - sub %rsi, %rdx # calculate message len - syscall - mov $60, %rax # syscall number of exit, move it to rax - mov $0, %rdi # exit code, move it to rdi - syscall - -.section .rodata # read only data section -message: - .ascii "Hello, world\n" -message_end: +{{#include ../../../osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/hello.S}} ``` The assembly program above can be compiled with the following command. @@ -42,131 +26,5 @@ Comments are added to highlight how the APIs of Asterinas OSTD enable safe kernel development. ```rust -#![no_std] - -extern crate alloc; - -use align_ext::AlignExt; -use core::str; - -use alloc::sync::Arc; -use alloc::vec; - -use ostd::cpu::UserContext; -use ostd::prelude::*; -use ostd::task::{Task, TaskOptions}; -use ostd::user::{ReturnReason, UserMode, UserSpace}; -use ostd::mm::{PageFlags, PAGE_SIZE, Vaddr, FrameAllocOptions, VmIo, VmMapOptions, VmSpace}; - -/// The kernel's boot and initialization process is managed by Asterinas OSTD. -/// After the process is done, the kernel's execution environment -/// (e.g., stack, heap, tasks) will be ready for use and the entry function -/// labeled as `#[ostd::main]` will be called. -#[ostd::main] -pub fn main() { - let program_binary = include_bytes!("../hello_world"); - let user_space = create_user_space(program_binary); - let user_task = create_user_task(Arc::new(user_space)); - user_task.run(); -} - -fn create_user_space(program: &[u8]) -> UserSpace { - let user_pages = { - let nframes = program.len().align_up(PAGE_SIZE) / PAGE_SIZE; - let vm_frames = FrameAllocOptions::new(nframes).alloc().unwrap(); - // Phyiscal memory pages can be only accessed - // via the Frame abstraction. - vm_frames.write_bytes(0, program).unwrap(); - vm_frames - }; - let user_address_space = { - const MAP_ADDR: Vaddr = 0x0040_0000; // The map addr for statically-linked executable - - // The page table of the user space can be - // created and manipulated safely through - // the VmSpace abstraction. - let vm_space = VmSpace::new(); - let mut options = VmMapOptions::new(); - options.addr(Some(MAP_ADDR)).flags(PageFlags::RWX); - vm_space.map(user_pages, &options).unwrap(); - vm_space - }; - let user_cpu_state = { - const ENTRY_POINT: Vaddr = 0x0040_1000; // The entry point for statically-linked executable - - // The user-space CPU states can be initialized - // to arbitrary values via the UserContext - // abstraction. - let mut user_cpu_state = UserContext::default(); - user_cpu_state.set_rip(ENTRY_POINT); - user_cpu_state - }; - UserSpace::new(user_address_space, user_cpu_state) -} - -fn create_user_task(user_space: Arc) -> Arc { - fn user_task() { - let current = Task::current(); - // Switching between user-kernel space is - // performed via the UserMode abstraction. - let mut user_mode = { - let user_space = current.user_space().unwrap(); - UserMode::new(user_space) - }; - - loop { - // The execute method returns when system - // calls or CPU exceptions occur or some - // events specified by the kernel occur. - let return_reason = user_mode.execute(|| false); - - // The CPU registers of the user space - // can be accessed and manipulated via - // the `UserContext` abstraction. - let user_context = user_mode.context_mut(); - if ReturnReason::UserSyscall == return_reason { - handle_syscall(user_context, current.user_space().unwrap()); - } - } - } - - // Kernel tasks are managed by OSTD, - // while scheduling algorithms for them can be - // determined by the users of OSTD. - TaskOptions::new(user_task) - .user_space(Some(user_space)) - .data(0) - .build() - .unwrap() -} - -fn handle_syscall(user_context: &mut UserContext, user_space: &UserSpace) { - const SYS_WRITE: usize = 1; - const SYS_EXIT: usize = 60; - - match user_context.rax() { - SYS_WRITE => { - // Access the user-space CPU registers safely. - let (_, buf_addr, buf_len) = - (user_context.rdi(), user_context.rsi(), user_context.rdx()); - let buf = { - let mut buf = vec![0u8; buf_len]; - // Copy data from the user space without - // unsafe pointer dereferencing. - user_space - .vm_space() - .read_bytes(buf_addr, &mut buf) - .unwrap(); - buf - }; - // Use the console for output safely. - println!("{}", str::from_utf8(&buf).unwrap()); - // Manipulate the user-space CPU registers safely. - user_context.set_rax(buf_len); - } - SYS_EXIT => Task::current().exit(), - _ => unimplemented!(), - } -} +{{#include ../../../osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/lib.rs}} ``` - diff --git a/osdk/src/commands/build/bin.rs b/osdk/src/commands/build/bin.rs index 4c969855..977ade06 100644 --- a/osdk/src/commands/build/bin.rs +++ b/osdk/src/commands/build/bin.rs @@ -158,7 +158,9 @@ fn install_setup_with_arch( cmd.arg("--force"); cmd.arg("--root").arg(install_dir.as_ref()); cmd.arg("--git").arg(crate::util::ASTER_GIT_LINK); - cmd.arg("--tag").arg(crate::util::ASTER_GIT_TAG); + // FIXME: Uses a fixed tag instaed of relies on remote branch + cmd.arg("--tag").arg("v0.5.1"); + // cmd.arg("--tag").arg(crate::util::ASTER_GIT_TAG); cmd.arg("--target").arg(match arch { SetupInstallArch::X86_64 => "x86_64-unknown-none", SetupInstallArch::Other(path) => path.to_str().unwrap(), diff --git a/osdk/tests/examples_in_book/mod.rs b/osdk/tests/examples_in_book/mod.rs index 2fa4e61a..9387dc50 100644 --- a/osdk/tests/examples_in_book/mod.rs +++ b/osdk/tests/examples_in_book/mod.rs @@ -5,3 +5,4 @@ mod create_os_projects; mod test_and_run_projects; mod work_in_workspace; +mod write_a_kernel_in_100_lines; diff --git a/osdk/tests/examples_in_book/work_in_workspace.rs b/osdk/tests/examples_in_book/work_in_workspace.rs index 676c44d0..3061c071 100644 --- a/osdk/tests/examples_in_book/work_in_workspace.rs +++ b/osdk/tests/examples_in_book/work_in_workspace.rs @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 use std::{ - env, fs::{self, OpenOptions}, io::Write, path::PathBuf, @@ -21,7 +20,6 @@ fn work_in_workspace() { } fs::create_dir_all(&workspace_dir).unwrap(); - env::set_current_dir(&workspace_dir).unwrap(); let workspace_toml = include_str!("work_in_workspace_templates/Cargo.toml"); fs::write(workspace_dir.join("Cargo.toml"), workspace_toml).unwrap(); @@ -29,8 +27,14 @@ fn work_in_workspace() { // Create a kernel project and a library project let kernel = "myos"; let module = "mylib"; - cargo_osdk(&["new", "--kernel", kernel]).ok().unwrap(); - cargo_osdk(&["new", module]).ok().unwrap(); + cargo_osdk(&["new", "--kernel", kernel]) + .current_dir(&workspace_dir) + .ok() + .unwrap(); + cargo_osdk(&["new", module]) + .current_dir(&workspace_dir) + .ok() + .unwrap(); // Add a test function to mylib/src/lib.rs let module_src_path = workspace_dir.join(module).join("src").join("lib.rs"); @@ -75,13 +79,22 @@ fn work_in_workspace() { .unwrap(); // Run subcommand build & run - cargo_osdk(&["build"]).ok().unwrap(); - let output = cargo_osdk(&["run"]).output().unwrap(); + cargo_osdk(&["build"]) + .current_dir(&workspace_dir) + .ok() + .unwrap(); + let output = cargo_osdk(&["run"]) + .current_dir(&workspace_dir) + .output() + .unwrap(); let stdout = String::from_utf8_lossy(&output.stdout).to_string(); assert!(stdout.contains("The available memory is")); // Run subcommand test - cargo_osdk(&["test"]).ok().unwrap(); + cargo_osdk(&["test"]) + .current_dir(&workspace_dir) + .ok() + .unwrap(); // Remove the directory fs::remove_dir_all(&workspace_dir).unwrap(); diff --git a/osdk/tests/examples_in_book/write_a_kernel_in_100_lines.rs b/osdk/tests/examples_in_book/write_a_kernel_in_100_lines.rs new file mode 100644 index 00000000..9c41a6f9 --- /dev/null +++ b/osdk/tests/examples_in_book/write_a_kernel_in_100_lines.rs @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MPL-2.0 + +use std::{fs, path::PathBuf, process::Command}; + +use assert_cmd::output::OutputOkExt; + +use crate::util::{cargo_osdk, depends_on_local_ostd}; + +#[test] +fn write_a_kernel_in_100_lines() { + let workdir = "/tmp"; + let os_name = "kernel_in_100_lines"; + + let os_dir = PathBuf::from(workdir).join(os_name); + + if os_dir.exists() { + fs::remove_dir_all(&os_dir).unwrap() + } + + // Creates a new kernel project + cargo_osdk(&["new", "--kernel", os_name]) + .current_dir(&workdir) + .ok() + .unwrap(); + + // Depends on local OSTD + let manifest_path = os_dir.join("Cargo.toml"); + depends_on_local_ostd(manifest_path); + + // Copies the kernel content + let kernel_contents = include_str!("write_a_kernel_in_100_lines_templates/lib.rs"); + fs::write(os_dir.join("src").join("lib.rs"), kernel_contents).unwrap(); + + // Copies and compiles the user program + let user_program_contents = include_str!("write_a_kernel_in_100_lines_templates/hello.S"); + fs::write(os_dir.join("hello.S"), user_program_contents).unwrap(); + Command::new("gcc") + .args(&["-static", "-nostdlib", "hello.S", "-o", "hello"]) + .current_dir(&os_dir) + .ok() + .unwrap(); + + // Adds align ext as the dependency + let file_contents = fs::read_to_string(os_dir.join("Cargo.toml")).unwrap(); + let mut manifest: toml::Table = toml::from_str(&file_contents).unwrap(); + let dependencies = manifest + .get_mut("dependencies") + .unwrap() + .as_table_mut() + .unwrap(); + dependencies.insert( + "align_ext".to_string(), + toml::Value::String("0.1.0".to_string()), + ); + + let new_file_content = manifest.to_string(); + fs::write(os_dir.join("Cargo.toml"), new_file_content).unwrap(); + + // Runs the kernel + let output = cargo_osdk(&["run"]).current_dir(&os_dir).ok().unwrap(); + let stdout = std::str::from_utf8(&output.stdout).unwrap(); + println!("stdout = {}", stdout); + + fs::remove_dir_all(&os_dir).unwrap(); +} diff --git a/osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/hello.S b/osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/hello.S new file mode 100644 index 00000000..140432d0 --- /dev/null +++ b/osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/hello.S @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: MPL-2.0 + +.global _start # entry point +.section .text # code section +_start: + mov $1, %rax # syscall number of write + mov $1, %rdi # stdout + mov $message, %rsi # address of message + mov $message_end, %rdx + sub %rsi, %rdx # calculate message len + syscall + mov $60, %rax # syscall number of exit, move it to rax + mov $0, %rdi # exit code, move it to rdi + syscall + +.section .rodata # read only data section +message: + .ascii "Hello, world\n" +message_end: \ No newline at end of file diff --git a/osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/lib.rs b/osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/lib.rs new file mode 100644 index 00000000..889779f7 --- /dev/null +++ b/osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/lib.rs @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: MPL-2.0 + +#![no_std] + +extern crate alloc; + +use align_ext::AlignExt; +use core::str; + +use alloc::sync::Arc; +use alloc::vec; + +use ostd::arch::qemu::{exit_qemu, QemuExitCode}; +use ostd::cpu::UserContext; +use ostd::mm::{ + FrameAllocOptions, PageFlags, Vaddr, VmIo, VmMapOptions, VmSpace, VmWriter, PAGE_SIZE, +}; +use ostd::prelude::*; +use ostd::task::{Task, TaskOptions}; +use ostd::user::{ReturnReason, UserMode, UserSpace}; + +/// The kernel's boot and initialization process is managed by OSTD. +/// After the process is done, the kernel's execution environment +/// (e.g., stack, heap, tasks) will be ready for use and the entry function +/// labeled as `#[ostd::main]` will be called. +#[ostd::main] +pub fn main() { + let program_binary = include_bytes!("../hello"); + let user_space = create_user_space(program_binary); + let user_task = create_user_task(Arc::new(user_space)); + user_task.run(); +} + +fn create_user_space(program: &[u8]) -> UserSpace { + let user_pages = { + let nframes = program.len().align_up(PAGE_SIZE) / PAGE_SIZE; + let vm_frames = FrameAllocOptions::new(nframes).alloc().unwrap(); + // Phyiscal memory pages can be only accessed + // via the Frame abstraction. + vm_frames.write_bytes(0, program).unwrap(); + vm_frames + }; + let user_address_space = { + const MAP_ADDR: Vaddr = 0x0040_0000; // The map addr for statically-linked executable + + // The page table of the user space can be + // created and manipulated safely through + // the VmSpace abstraction. + let vm_space = VmSpace::new(); + let mut options = VmMapOptions::new(); + options.addr(Some(MAP_ADDR)).flags(PageFlags::RWX); + vm_space.map(user_pages, &options).unwrap(); + Arc::new(vm_space) + }; + let user_cpu_state = { + const ENTRY_POINT: Vaddr = 0x0040_1000; // The entry point for statically-linked executable + + // The user-space CPU states can be initialized + // to arbitrary values via the UserContext + // abstraction. + let mut user_cpu_state = UserContext::default(); + user_cpu_state.set_rip(ENTRY_POINT); + user_cpu_state + }; + UserSpace::new(user_address_space, user_cpu_state) +} + +fn create_user_task(user_space: Arc) -> Arc { + fn user_task() { + let current = Task::current(); + // Switching between user-kernel space is + // performed via the UserMode abstraction. + let mut user_mode = { + let user_space = current.user_space().unwrap(); + UserMode::new(user_space) + }; + + loop { + // The execute method returns when system + // calls or CPU exceptions occur or some + // events specified by the kernel occur. + let return_reason = user_mode.execute(|| false); + + // The CPU registers of the user space + // can be accessed and manipulated via + // the `UserContext` abstraction. + let user_context = user_mode.context_mut(); + if ReturnReason::UserSyscall == return_reason { + handle_syscall(user_context, current.user_space().unwrap()); + } + } + } + + // Kernel tasks are managed by the Framework, + // while scheduling algorithms for them can be + // determined by the users of the Framework. + TaskOptions::new(user_task) + .user_space(Some(user_space)) + .data(0) + .build() + .unwrap() +} + +fn handle_syscall(user_context: &mut UserContext, user_space: &UserSpace) { + const SYS_WRITE: usize = 1; + const SYS_EXIT: usize = 60; + + match user_context.rax() { + SYS_WRITE => { + // Access the user-space CPU registers safely. + let (_, buf_addr, buf_len) = + (user_context.rdi(), user_context.rsi(), user_context.rdx()); + let buf = { + let mut buf = vec![0u8; buf_len]; + // Copy data from the user space without + // unsafe pointer dereferencing. + let current_vm_space = user_space.vm_space(); + let mut reader = current_vm_space.reader(buf_addr, buf_len).unwrap(); + reader + .read_fallible(&mut VmWriter::from(&mut buf as &mut [u8])) + .unwrap(); + buf + }; + // Use the console for output safely. + println!("{}", str::from_utf8(&buf).unwrap()); + // Manipulate the user-space CPU registers safely. + user_context.set_rax(buf_len); + } + SYS_EXIT => exit_qemu(QemuExitCode::Success), + _ => unimplemented!(), + } +}