[Rust OS] 독립적인 러스트 바이너리
운영체제를 만들기 위해 운영체제 종속성을 제거하는 작업을 한다.
운영체제 커널을 만들 때는 운영체제의 기능을 사용하면 안 된다.
스레드, 파일, 힙 메모리, 네트워크, 난수. 표준 입출력 등등 운영체제의 추상화나 하드웨어를 사용하는 기능을 사용할 수 없다.
그러므로 러스트 표준 라이브러리도 사용 불가
그래도 반복자, 클로저, 패턴 매칭, 옵션, Result, 문자열 포맷팅 그리고 소유권 시스템 같은 기능은 사용해도 된다.
이 기능들을 사용하면 커널 코드를 표현력 있게 작성하고 비정상적인 작동을 막고 메모리 안전성을 보장받을 수 있다.
운영체제의 기능을 사용하지 않는 독립적인 러스트 바이너리를 만드는 법을 설명한다.
표준 라이브러리 기능 끄기
러스트 표준 라이브러리는 운영체제 기능을 사용하기 위해 C 표준 라이브러리인 libc
를 사용한다.
main.rs
파일에 #![no_std]
코드 한 줄만 추가하면 표준 라이브러리를 제외하고 컴파일할 수 있다.
패닉 구현하기
표준 라이브러리 제거 시 패닉도 없어져서 패닉을 구현해야 한다.
use core::panic::PanicInfo;
/// This function is called on panic.
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
eh_personality
언어 항목
위 코드를 추가하고 실행하면 eh_personality
언어 항목이 없다고 에러가 난다.
eh_personality
는 스택 되감기를 위해 사용되는데 운영체제 기능을 사용하므로 없애는 게 낫다.
Cargo.toml
에 다음 코드를 추가한다.
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
start
속성
프로그램 시작 시 main
함수가 가장 먼저 실행되는 것은 아니다.
대부분의 언어는 런타임 시스템을 가지고 있어서 이 시스템이 가비지 컬랙팅을 수행하고(e.g. 자바), 소프트웨어 스레드(e.g. Go의 고루틴)를 실행한다.
main
함수 실행 전에 런타임이 실행되어야 한다.
일반적으로 표준 라이브러리를 사용하는 러스트 바이너리 실행 시 crt0
(C runtime zero)라는 C 런타임 라이브러리가 실행돼서 C 프로그램을 위한 환경 설정을 한다.
환경 설정은 스택을 생성하거나 인자들을 알맞은 레지스터에 배치하는 것 등등이다.
C 런타임은 Rust 런타임을 실행하는데, 이 런타임의 시작점이 바로 start
언어 항목이다.
러스트 런타임 매우 작아서, 스택 오버플로우 가드를 설정하거나 패닉 발생 시 백트레이스를 출력하는 등의 작은 일을 수행한다.
그 다음 런타임이 main
함수를 호출한다.
우리가 만들 독립 실행 파일은 러스트 런타임과 crt0
을 사용할 수 없으므로, 프로그램 시작점을 직접 정의해야 한다.
start
언어 아이템은 어차피 crt0
을 사용해야 하므로 crt0
의 시작점을 덮어쓰기로 한다.
시작점 덮어쓰기
러스트 컴파일러에 일반적인 시작점을 안 쓸거라고 #![no_main]
속성을 추가한다.
main
는 런타임이 없어서 main
함수는 지우고 운영체제 시작점인 _start
함수를 덮어쓴다.
#[no_mangle]
pub extern "C" fn _start() -> ! {
loop {}
}
링커 에러
프로그램 실행 시 링커 에러가 발생한다.
링커는 생성된 코드를 실행파일로 합쳐주는 프로그램이다.
링커는 기본 설정에 따라 프로그램이 C 런타임을 사용한다고 가정해서 에러가 발생했다.
링커에게 C 런타임을 안 쓸거라고 알려주기 위해 인자를 넘기거나 베어 메탈 타깃을 만들 수 있다.
베어 메탈 타깃 만들기
러스트는 현재 운영체제(호스트 시스템)에 따라 실행파일을 생성한다.
x86_64
윈도우라면 x86_64
명령어를 사용하는 .exe
실행파일을 만든다.
러스트는 환경 구분을 위해 타깃 트리플을 사용한다.
rustc --version --verbose
로 타깃 트리플을 확인할 수 있다.
host: x86_64-unknown-linux-gnu
에서 CPU 아키텍처(x86_64
), 판매자 (unknown
), 운영체제(linux
) 그리고 ABI (gnu
)인 타깃 트리플을 확인할 수 있다.
러스트 컴파일러와 링커는 현재 운영체제가 리눅스거나 윈도우라면 C 런타임을 기본으로 사용할 거라고 가정한다.
베어 메탈 환경을 위한 트리플 타깃의 예로 임베디드 ARM 시스템을 뜻하는 thumbv7em-none-eabihf
가 있다.
rustup target add thumbv7em-none-eabihf
로 해당 타깃용 표준 라이브러리를 다운 받는다.
이제부터 cargo build --target thumbv7em-none-eabihf
명령어로 독립 실행파일을 생성할 수 있다.
근데 이거 안 쓰고 x86_64
베어 메탈 환경용 커스텀 타깃 사용할 거임.
링커 인자
링커에 인자를 잘 넘겨서 링커 에러를 해결할 수도 있다.
# 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"
근데 이렇게 생성된 바이너리는 _stack
함수 실행 시 스택 초기화 등 기대하는 게 많아서 설정이 더 필요하다.