Include the 100 lines kernel in CI

This commit is contained in:
Jianfeng Jiang 2024-06-20 02:50:23 +00:00 committed by Tate, Hongliang Tian
parent cd2b305fa8
commit 72e726295f
7 changed files with 242 additions and 152 deletions

View File

@ -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. Our new kernel will be able to run the following Hello World program.
```s ```s
.global _start # entry point {{#include ../../../osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/hello.S}}
.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:
``` ```
The assembly program above can be compiled with the following command. 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. to highlight how the APIs of Asterinas OSTD enable safe kernel development.
```rust ```rust
#![no_std] {{#include ../../../osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/lib.rs}}
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<UserSpace>) -> Arc<Task> {
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!(),
}
}
``` ```

View File

@ -158,7 +158,9 @@ fn install_setup_with_arch(
cmd.arg("--force"); cmd.arg("--force");
cmd.arg("--root").arg(install_dir.as_ref()); cmd.arg("--root").arg(install_dir.as_ref());
cmd.arg("--git").arg(crate::util::ASTER_GIT_LINK); 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 { cmd.arg("--target").arg(match arch {
SetupInstallArch::X86_64 => "x86_64-unknown-none", SetupInstallArch::X86_64 => "x86_64-unknown-none",
SetupInstallArch::Other(path) => path.to_str().unwrap(), SetupInstallArch::Other(path) => path.to_str().unwrap(),

View File

@ -5,3 +5,4 @@
mod create_os_projects; mod create_os_projects;
mod test_and_run_projects; mod test_and_run_projects;
mod work_in_workspace; mod work_in_workspace;
mod write_a_kernel_in_100_lines;

View File

@ -1,7 +1,6 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use std::{ use std::{
env,
fs::{self, OpenOptions}, fs::{self, OpenOptions},
io::Write, io::Write,
path::PathBuf, path::PathBuf,
@ -21,7 +20,6 @@ fn work_in_workspace() {
} }
fs::create_dir_all(&workspace_dir).unwrap(); 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"); let workspace_toml = include_str!("work_in_workspace_templates/Cargo.toml");
fs::write(workspace_dir.join("Cargo.toml"), workspace_toml).unwrap(); 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 // Create a kernel project and a library project
let kernel = "myos"; let kernel = "myos";
let module = "mylib"; let module = "mylib";
cargo_osdk(&["new", "--kernel", kernel]).ok().unwrap(); cargo_osdk(&["new", "--kernel", kernel])
cargo_osdk(&["new", module]).ok().unwrap(); .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 // Add a test function to mylib/src/lib.rs
let module_src_path = workspace_dir.join(module).join("src").join("lib.rs"); let module_src_path = workspace_dir.join(module).join("src").join("lib.rs");
@ -75,13 +79,22 @@ fn work_in_workspace() {
.unwrap(); .unwrap();
// Run subcommand build & run // Run subcommand build & run
cargo_osdk(&["build"]).ok().unwrap(); cargo_osdk(&["build"])
let output = cargo_osdk(&["run"]).output().unwrap(); .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(); let stdout = String::from_utf8_lossy(&output.stdout).to_string();
assert!(stdout.contains("The available memory is")); assert!(stdout.contains("The available memory is"));
// Run subcommand test // Run subcommand test
cargo_osdk(&["test"]).ok().unwrap(); cargo_osdk(&["test"])
.current_dir(&workspace_dir)
.ok()
.unwrap();
// Remove the directory // Remove the directory
fs::remove_dir_all(&workspace_dir).unwrap(); fs::remove_dir_all(&workspace_dir).unwrap();

View File

@ -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();
}

View File

@ -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:

View File

@ -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<UserSpace>) -> Arc<Task> {
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!(),
}
}