Escrevendo um sistema operacional com rust
Olá amates de low level, hoje vamos começar um longa jornada onde vou apresentar pra voces minha trajetoria escrevendo um sistema operacional simples em rust. Vou utilizar o tutorial do os.phil-opp.com para seguir essa jornada vou tentar melhorar o possivel no codigo dele lembrando que não sou um programador muito experiente em rust e nem sei tanto sobre criação de OS isso vai ser apenas um experimento meu que quero compartilhar com vcs.
1. Por onde começar
A primeira coisa que precisamos fazer para criar nosso sistema operacional e construir nosso kernel ou seja um executavel rust que não está vinculado a nenhuma biblioteca padrão. Isso fazendo possivel rodar o executavel no barel metal
Vamos começar criando o projeto:
cargo new os --bin --edition 2018
Atributo no_std
Esse é um atributo no rust que desabilita o uso da biblioteca padrão do rust.
// main.rs
#![no_std]
fn main() {
println!("Hello, world!");
}
Se executarmos cargo build
e esperado que tenhamos o seguinte erro.
italo@italo:~/os$ cargo build
Compiling os v0.1.0 (/home/italo/os)
error: cannot find macro `println` in this scope
--> src/main.rs:4:5
|
4 | println!("Hello, world!");
| ^^^^^^^
error: `#[panic_handler]` function required, but not found
error: language item required, but not found: `eh_personality`
|
= note: this can occur when a binary crate with `#![no_std]` is compiled for a target where `eh_personality` is defined in the standard library
= help: you may be able to compile for a target that doesn't need `eh_personality`, specify a target with `--target` or in `.cargo/config`
error: could not compile `os` due to 3 previous errors
A razao disso é que println é um macro da lib padrão.
// main.rs
#![no_std]
fn main() {
}
Se tentarmos buildar novamente teremos o erro.
italo@italo:~/os$ cargo build
Compiling os v0.1.0 (/home/italo/os)
error: `#[panic_handler]` function required, but not found
error: language item required, but not found: `eh_personality`
|
= note: this can occur when a binary crate with `#![no_std]` is compiled for a target where `eh_personality` is defined in the standard library
= help: you may be able to compile for a target that doesn't need `eh_personality`, specify a target with `--target` or in `.cargo/config`
error: could not compile `os` due to 2 previous errors
O panic_handler atributo define a função que o compilador deve invocar quando ocorrer um pânico . A biblioteca padrão fornece sua própria função de tratamento de pânico, mas em um no_stdambiente precisamos defini-la nós mesmos:
#![no_std]
fn main() {
}
use core::panic::PanicInfo;
/// This function is called on panic.
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
Alem disso temos que desabilitar o Unwinding.
# Cargo.toml
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
Buildando novamente:
italo@italo:~/os$ cargo build
Compiling os v0.1.0 (/home/italo/os)
error: requires `start` lang_item
error: could not compile `os` due to previous error
Atributo start
Em rust pensamos que a função main
e a primeira função que é execultada. Porem a maioria das linguagens possui um Runtime system utilizado para fazer varias coisas como coleta de lixo como no Java ou threads de software como o goroutine em Go. Esse Runtime System é executado antes do main.
Em um binario Rust que esta vinculado a lib padrão a execução começa em um lib de runtime system C chamada crt0
(“C runtime zero”) que configura o ambiente para o aplicativo C isso inclui criação de pilha e correções de argumentos nos registradores. O Runtime system invoca o ponto de entrada em tempo de execução que esta marcado como start
Como não temos acesso a crt0 pois nosso binario rust é independente vamos ter criar nosso ponto de entrada.
Adicione o atributo #![no_main]
.
#![no_std]
#![no_main]
use core::panic::PanicInfo;
#[no_mangle]
pub extern "C" fn _start() -> ! {
loop {
}
}
/// This function is called on panic.
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
Ao usar o #[no_mangle] atributo, desabilitamos a manipulação de nomes para garantir que o compilador Rust realmente produza uma função com o nome \_start
. Sem o atributo, o compilador geraria algum \_ZN3blog_os4_start7hb173fedf945531caE
símbolo enigmático para dar a cada função um nome exclusivo. O atributo é obrigatório porque precisamos informar o nome da função do ponto de entrada ao vinculador na próxima etapa.
Tambem usar Calling convention com extern "C"
para informar ou compilador que ele deve usar essa funcão como start
Quando executamos cargo buildagora, obtemos um erro feio no vinculador .
Erros do vinculador
O vinculador é um programa que combina o código gerado em um executável. Como o formato executável difere entre Linux, Windows e macOS, cada sistema possui seu próprio vinculador que gera um erro diferente. A causa fundamental dos erros é a mesma: a configuração padrão do vinculador assume que nosso programa depende do tempo de execução C, o que não acontece.
Para resolver os erros, precisamos informar ao vinculador que ele não deve incluir o tempo de execução C. Podemos fazer isso passando um determinado conjunto de argumentos para o vinculador ou construindo para um alvo bare metal.
🔗Construindo para um alvo Bare Metal
Por padrão, o Rust tenta construir um executável que seja capaz de ser executado no ambiente atual do sistema. Por exemplo, se você estiver usando o Windows no x86_64, o Rust tentará criar um .exeexecutável do Windows que use x86_64instruções. Este ambiente é chamado de sistema “host”.
Para descrever diferentes ambientes, Rust usa uma string chamada target triple . Você pode ver o triplo alvo do seu sistema host executando rustc –version –verbose:
italo@italo:~/os$ rustc --version --verbose
rustc 1.66.1 (90743e729 2023-01-10) (built from a source tarball)
binary: rustc
commit-hash: 90743e7298aca107ddaa0c202a4d3604e29bfeb6
commit-date: 2023-01-10
host: x86_64-unknown-linux-gnu
release: 1.66.1
LLVM version: 15.0.7
Ao compilar para nosso host triplo, o compilador Rust e o vinculador assumem que existe um sistema operacional subjacente, como Linux ou Windows, que usa o tempo de execução C por padrão, o que causa erros no vinculador. Portanto, para evitar erros do vinculador, podemos compilar para um ambiente diferente sem sistema operacional subjacente.
Um exemplo de ambiente bare metal é o thumbv7em-none-eabihfalvo triplo, que descreve um sistema ARM embarcado . Os detalhes não são importantes, tudo o que importa é que o triplo alvo não tenha nenhum sistema operacional subjacente, o que é indicado pelo no triplo alvo. Para poder compilar para este alvo, precisamos adicioná-lo no Rustup:none
rustup target add thumbv7em-none-eabihf
Isso baixa uma cópia da biblioteca padrão (e principal) do sistema. Agora podemos construir nosso executável independente para este alvo:
cargo build --target thumbv7em-none-eabihf
Agora sim roda de boa.
Como ficou tudo ?
src/main
:
#![no_std]
#![no_main]
use core::panic::PanicInfo;
#[no_mangle]
pub extern "C" fn _start() -> ! {
loop {}
}
/// This function is called on panic.
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
Cargo.toml
:
[package]
name = "blog_os"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
[dependencies]
Para buildar use cargo build --target thumbv7em-none-eabihf
para compilar para host especificos use:
# Linux
cargo rustc -- -C link-arg=-nostartfiles
# Windows
cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
# macOS
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
Esse foi o primeira etapa fique atento a novas postagens.