Rust OS 개발일지1

Layfort·2024년 1월 8일

Rust OS 개발

목록 보기
1/1

First Day: A Freestanding Rust Binary

  • 당연하겠지만, 우리는 커널 개발에 stdandard library를 사용할 수 없다. -> std lib은 system call을 wrapping하는 함수들이기 때문에
  • 그렇기 때문에우리는 stdandard library를 사용하지 않는다. -> freestanding binary

Introduction

  • 운영체제를 만들기 위해서는 운영체제에 의존하지 않는 코드를 만들어야 한다.

    • Thread
    • File
    • Dynamic Memory Allocation
    • Random Number Generator
    • Standard Library
    • etc...
  • 그럼에도 불구하고 rust의 언어 자체 기능은 사용이 가능하다.(Iterator, Option, Result, Pattern Matching, etc...)

  • 이렇게, 운영체제에 의존하지 않는 코드를 만드는 것을 freestanding binary(또는 bare-metal binary)라고 한다.

Unlinking std

  • std를 사용하지 않기 위해서는 rustc에게 말해야 한다. -> #![no_std]라는 attribute를 사용한다.
#![no_std]

fn main() {
    println!("Hello, world!");  // ERROR!
}
  • 위의 에제 코드는 작동하지 않는다. -> std의 println! macro를 사용하고 있기 때문에 \
  • 그렇다면 println! macro를 지우고 다시 빌드해보자.
#![no_std]

fn main() {}
  • 놀랍게도 위의 코드 역시 빌드되지 않는다!
  • 에러 메세지를 확인해보면 다음과 같은 문제가 있음을 알 수 있다.
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`
  • 메세지를 확인해보면, 2가지 문제가 있다.
    • panic handler가 없다.
    • eh_personality가 없다.

Panic Handler

  • panic handler는 panic이 발생했을 때, panic을 처리하는 함수이다.

    • 복잡한 시스템에서는 이를 잘 처리하는 것이 중요하겠지만... 우리는 그럴 여지가 없다. → 실제로 linux kernel의 panic handler는 그 자체만으로도 대략 700줄에 달하는 코드로 구성되어 있다.
  • 하지만 우리의 코드는 그 정도로 복자하지 않다. 그래서 매우 단순하게 panic handler를 만들어보자.

// in main.rs

use core::panic::PanicInfo;

/// 패닉이 일어날 경우, 이 함수가 호출됩니다.
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}
  • 위의 코드는 panic이 발생했을 때, 무한 루프를 돌게 된다. → 절대 반환해서는 안되기 때문에, -> !를 사용하여(never type 이라고 한다.) 반환하지 않음을 명시해준다.
  • PanicInfo Type은 panic이 발생했을 때, panic에 대한 정보를 담고 있다. 이를 이용하여 원래는 적절한 handeling을 해주는 것이 좋지만, 우리는 그럴 여지가 없다.

eh_personality Language Item

  • Language Items은 rust의 언어 자체 기능을 구현하는데 사용되는 특별한 함수+변수들이다.
    • ㄴㅇ
  • eh_personality는 rust의 panic을 처리하는데 사용되는 함수이다.
    • 정확하게는 스택 되감기(stack unwinding)를 구현하는 함수를 가르키는 Language Item이다.
    • 이 함수를 이용하여, rust는(또한 대부분의 언어들은) panic이 발생했을 때, stack을 되감아가며, 자식과 부모 thread의 모든 stack frame을 제거한다.(이를 통해, 메모리 누수를 방지한다.)
    • 하지만 이는 굉장히 복잡하는 기능이다.(예: Linux는 libunwind, Windows는 structured exception handling) -> 우리는 이를 구현할 필요가 없다. -> rustc에게 말해야 한다.

Disabling eh_personality

  • rustc에게 eh_personality를 사용하지 않는다고 말해보자. → Cargo.toml에 다음의 코드를 추가하면 된다.
[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"
  • 자 이제 다시 빌드해보자.
warning: unused import: `panic::panic`
 --> src/main.rs:5:5
  |
5 | use panic::panic;
  |     ^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: crate-level attribute should be in the root module
 --> src/panic/mod.rs:1:1
  |
1 | #![no_std]
  | ^^^^^^^^^^
  |
  = note: `#[warn(unused_attributes)]` on by default

error: requires `start` lang_item

start Language Item

  • 처음 프로그래밍을 접하는(그리고 그다지 low-level programming을 하지 않는) 사람들은 대부분의 언어의 main 함수가 프로그램의 시작점이라고 생각할 수 있다.

    • 하지만 이는 사실이 아니다. → main 함수는 언어가 어떤 프로그램을 실행할 준비를 마쳤을 때 실행되는 함수이지, 프로그램의 시작점이 아니다.
    • 이러한, 프로그램의 실행 환경을 초기화하는 작업을 런타임 시스템이라고 하고, 이는 언어에 따라서 다르다.
    • 러스트는 기본적으로 C 기반 언어이기 때문에, C의 런타임 시스템을 사용한다. -> 이는 crt0라고 불리는 C의 런타임 시스템이다.
  • 이렇게 런타임 시스템이 호출이 되고 나면, 런타임 시스템은 main 함수를 호출한다. -> 이는 rust의 start Language Item이다.

    • 하지만 우리는 런타임 시스템을 사용할 수 없다... -> 우리는 운영체제를 만들고 있기 때문에, 운영체제에 의존하지 않는 코드를 만들어야 한다.
    • 그렇기 때문에, rustc에게 말해야 한다. -> #![no_main] attribute를 사용한다.
// in main.rs

#![no_std]
#![no_main]

use core::panic::PanicInfo;

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

#[no_mangle]
pub extern "C" fn _start() -> ! {
    loop {}
}
  • 위의 코드를 잘 보면 main이 사라졌다는 사실을 알 수 있다.

    • 대신 _start라는 함수가 생겼다. → 이는 프로그램의 새로운 시작 지점을 제공하는 함수이다.
      • 이는 rust의 start Language Item을 구현한 것이 아니다. start Language Item은 main 함수를 호출하는 역할을 하는데, 이 함수는 crt0에서 호출되는 것이다. 따라서 지금 상황과 같이 런타임 시스템이 없는 경우에는 custom start Language Item을 구현한다 하더라도 아무런 의미가 없다.
    • #[no_mangle] attribute는 rustc에게 말해주는 것이다. -> 이 함수의 이름을 _start로 해야 한다는 것을 말해주는 것이다.
      • 일반적으로, 대부분의 언어는 함수의 이름을 그대로 사용하지 않는다. -> 이는 name mangling이라고 불리는 기법을 사용하기 때문이다.
      • 하지만, 이 함수의 경우에는 프로그램의 새로운 시작지점이 되는 함수이기 때문에... 이름을 그대로 사용해야할 필요가 있다. -> 그렇기 때문에 #[no_mangle] attribute를 사용하여 rustc에게 이름을 변경하지 말라고 말해준다.
    • extern "C"의 경우에는 일반적인 rust의 호출규약이 아니라 C 함수 호출 규약을 사용하겠음을 알리는 코드이다.
    • 마찬가지로 이 함수도 -> !를 사용하여 반환하지 않음을 명시해준다.
  • 이제 다시 빌드해보자.

    • 그리고... 이제 정말 끔찍한 링크 에러를 마주하게 될 것이다.
error: linking with `cc` failed: exit status: 1
  |
  = note: LC_ALL="C" PATH="/home/bae/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/bin:/home/bae/anaconda3/bin:/home/bae/anaconda3/condabin:/home/bae/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/snap/bin:/home/bae/.local/share/JetBrains/Toolbox/scripts:/opt/jdk/current/bin:/usr/bin/java:/opt/jdk/current/bin:/usr/bin/java" VSLANG="1033" "cc" "-m64" "/tmp/rustcSpk25h/symbols.o" "/home/bae/Github/rust_os/target/debug/deps/rust_os-cfd2a4d760122e45.3lka4t0njt4elio9.rcgu.o" "/home/bae/Github/rust_os/target/debug/deps/rust_os-cfd2a4d760122e45.46oixk3kh2rypau3.rcgu.o" "-Wl,--as-needed" "-L" "/home/bae/Github/rust_os/target/debug/deps" "-L" "/home/bae/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-Wl,-Bstatic" "/home/bae/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_core-914eb40be05d8663.rlib" "/home/bae/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-27094fcca7e14863.rlib" "/home/bae/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-919e055b306699ae.rlib" "-Wl,-Bdynamic" "-Wl,--eh-frame-hdr" "-Wl,-z,noexecstack" "-L" "/home/bae/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-o" "/home/bae/Github/rust_os/target/debug/deps/rust_os-cfd2a4d760122e45" "-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs"
  = note: /usr/bin/ld: /home/bae/Github/rust_os/target/debug/deps/rust_os-cfd2a4d760122e45.3lka4t0njt4elio9.rcgu.o: in function `_start':
          /home/bae/Github/rust_os/src/main.rs:8: multiple definition of `_start'; /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/Scrt1.o:(.text+0x0): first defined here
          /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/Scrt1.o: in function `_start':
          (.text+0x1b): undefined reference to `main'
          /usr/bin/ld: (.text+0x21): undefined reference to `__libc_start_main'
          collect2: error: ld returned 1 exit status
          
  = note: some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified
  = note: use the `-l` flag to specify native libraries to link
  = note: use the `cargo:rustc-link-lib` directive to specify the native libraries to link with Cargo (see https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorustc-link-libkindname)

error: could not compile `rust_os` (bin "rust_os") due to previous error

Linker Errors

  • 링커는 여러 개의 executable file(컴파일러가 컴파일한 파일)들을 한개의 실행 파일로 변경해주는 역할을 하는 프로그램이다.

    • 요류가 나는 근본적인 원인은... 결국 이 링커 역시도 C의 런타임 시스템의 일부이기 때문이다. → 하지만 우리는 이미 C의 런타임 시스템을 사용하지 않기로 했다.
    • 그렇기 때문에, 우리는 링커에게 C 런타임 시스템을 사용하지 않도록 말해야 한다.
  • 이를 위해서는 링커에게 말해야 한다.

    1. 링커에 특정 인자를 전달한다.
    2. Crate compile target을 bare-metal로 설정한다.

Bare-Metal System Target

  • 일반적으로 rust는 현재 시스템 환셩(일반적으로는 현재 운영체제)에서 실행이 가능한 파일을 생성하고자 한다.
    • 만을 윈도우라면 .exe 파일을 생성할 것이다.
  • 그렇기 때문에, rust는 자체적으로 target triple이라는 문자열을 사용하여 시스템 환경을 나타낸다.
(base) bae@bae-Legion-Slim-5-16IRH8:~/Github/rust_os$ rustc --version --verbose
rustc 1.75.0 (82e1608df 2023-12-21)
binary: rustc
commit-hash: 82e1608dfa6e0b5569232559e3d385fea5a93112
commit-date: 2023-12-21
host: x86_64-unknown-linux-gnu
release: 1.75.0
LLVM version: 17.0.6
  • 위의 코드는 rustc --version --verbose의 결과로, 여기서 host에 현재 시스템 환경을 나타내는 문자열이 있다.

    • x86_64-unknown-linux-gnu라는 문자열은 CPU 아키텍처-제조사-운영체제-ABI를 나타낸다.
    • 이를 통해, rust는 현재 시스템 환경을 알 수 있다.
  • 우리가 목표로 하는 시스템 환경은 운영체제가 없는 시스템이다.

    • 그러한 시스템 환경의 예시로 thumbv7em-none-eabihf target triple이 있다.
    • 이를 사용하기 위해서 rustup에 해당 시스템 환경을 추가하자
      • rustup target add thumbv7em-none-eabihf
    • 그리고 해당 환경에서 빌드를 하기 위해서 --target flag를 사용하자.
      • cargo build --target thumbv7em-none-eabihf

Linker Arguments

  • 나중에... 이거 굉장히 복잡하다.
profile
물리와 컴퓨터

0개의 댓글