SW개발/Linux

리눅스 커널의 러스트 공식 문서 (번역)

초코쨔응 2023. 4. 19. 01:22

리눅스 커널 6.1 버전부터 러스트가 공식적으로 포함되었습니다. [참고] 기본으로는 비활성화되어있기 때문에 리눅스 커널을 개발할 때는 이전과 동일한 방식으로 개발해도 됩니다. 그래도 사용 방법과 관련하여 추가되어있는 공식 문서의 내용을 파악하고자 번역해보았습니다.

 


러스트 (Rust)

커널 내의 러스트와 관련된 문서 목록. 커널에서 러스트를 사용하려면, "빠르게 시작하기" 가이드를 읽어보세요.

  • 빠르게 시작하기 (Quick Start)
  • 일반적인 정보 (General Information)
  • 코딩 지침 (Coding Guidelines)
  • 아키텍처 지원 (Arch Support)

 

원본 주소: https://www.kernel.org/doc/html/latest/rust/index.html


빠르게 시작하기 (Quick Start)

이 문서는 러스트로 커널 개발을 어떻게 시작하면 되는지를 소개합니다.

 

요구사항: 빌드 (Requirements: Building)

본 소제목에서는 빌드를 위한 툴을 어떻게 가져오는지를 설명합니다.

이 요구사항들 중 일부는 리눅스 배포판 내의 rustc, rust-src, rust-bindgen 등등의 이름으로 이미 사용 가능할 것입니다. 그러나, 본 글을 작성하는 시점에는, 배포판이 최신 릴리즈를 따라가지 않는한 아직 충분하지 않습니다.

요구사항이 충족되었는지 쉽게 확인하기 위해서 아래와 같이 입력합니다:

make LLVM=1 rustavailable

이 명령어는 Kconfig 가 동작하는 로직과 같이 'RUST_IS_AVAILABLE' 의 활성화가 필요한지를 확인합니다. 활성화가 필요 없는 경우, 왜 그런지도 설명해줍니다.

 

rustc

특정 버전의 러스트 컴파일러가 필요합니다. 지금은 커널이 러스트의 일부 불안정한 기능에 의존하고 있기 때문에, 더 최신버전은 동작할 수도 있고 아닐 수도 있습니다.

만약 rustup 을 사용하고 있다면, 소스코드 경로에서 아래를 실행하세요:

rustup override set $(scripts/min-tool-version.sh rustc)

그렇지 않다면, standalone installer 를 가져오거나 아래에서 rustup 을 설치하세요:

https://www.rust-lang.org

 

러스트 표준 라이브러리 소스 (Rust standard library source)

빌드 시스템은 core 와 alloc 을 여러 플랫폼용으로 컴파일 (cross-compile) 되도록 할수 있기 때문에 러스트 표준 라이브러리 소스가 필요합니다.

만약 rustup 을 사용하고 있다면, 아래를 실행하세요:

rustup component add rust-src

이 컴포넌트는 툴체인마다 설치됩니다. 따라서 러스트 컴파일러를 더 최신버전으로 업그레이드한다면 컴포넌트를 다시 추가해야합니다.

만일 rustup 을 사용하는 대신 standalone installer 를 사용하고 있다면, 러스트 레포지토리를 툴체인 설치 폴더로 복사할 수 있습니다.

git clone --recurse-submodules \
        --branch $(scripts/min-tool-version.sh rustc) \
        https://github.com/rust-lang/rust \
        $(rustc --print sysroot)/lib/rustlib/src/rust

이 경우에는 러스트 컴파일러 버전을 업그레이드할 때 수동으로 위의 git clone 도 업데이트해야합니다.

 

libclang

(LLVM 의 일부인) libclang 은 커널에서 C 코드를 이해하기 위한 목적으로 bindgen 에서 사용합니다. 그러므로 LLVM 도 설치가 필요합니다. CC=clang 이나 LLVM=1 로 설정한 상태로 커널을 컴파일할 때와 같습니다.

리눅스 배포판은 가능한 버전을 갖고 있을 확률이 높으므로, 먼저 확인해보는 것이 좋습니다.

일부 종류의 시스템이나 아키텍처를 위한 LLVM 바이너리는 아래에 업로드되어있습니다:

https://releases.llvm.org/download.html

여기에 해당하지 않는다면, LLVM 을 빌드하는 것은 시간이 좀 걸리지만 그렇게 복잡하지는 않습니다:

https://llvm.org/docs/GettingStarted.html#getting-the-source-code-and-building-llvm

빌드된 결과물이 포함된 릴리즈나 배포 패키지를 가져오는 더 많은 정보들이 필요하다면 "리눅스를 Clang/LLVM 으로 빌드하기" 문서를 확인해주세요.

 

bindgen

커널의 C 코드들과 연동하는 것은 bindgen 툴을 사용하여 빌드 시에 할 수 있습니다. 특정 버전이 필요합니다.

아래를 이용하여 설치하세요. (아래 명령어는 소스로부터 툴을 다운로드하고 빌드하는 것입니다.)

cargo install --locked --version $(scripts/min-tool-version.sh bindgen) bindgen

 

요구사항: 개발 (Requirements: Developing)

본 소제목에서는 개발에 필요한 툴을 어떻게 가져오는지를 설명합니다. 즉, 이것들은 단순히 커널을 빌드하기만 할 때는 필요하지 않습니다.

 

rustfmt

rustfmt 툴은 일반적인 C 연동을 비롯하여 모든 러스트 커널 코드를 자동으로 포맷팅하는데 사용합니다. (더 자세하게 알고 싶다면, 코딩 지침을 확인해주세요.)

만약 rustup 을 사용하고 있다면, 기본 설정 (profile) 이 이미 이 툴을 설치했을 것이기 때문에 추가적으로 할 일은 없습니다. 다른 설정 (profile) 을 사용하고 있다면 수동으로 아래와 같이 컴포넌트를 설치해야합니다:

rustup component add rustfmt

standalone installer 는 rustfmt 를 함께 설치합니다.

 

clippy

clippy 는 러스트 린터 (Linter) 입니다. 이것을 실행하면 러스트 코드의 추가적인 경고 (warning) 를 알려줍니다. make 를 할 때 CLIPPY=1 로 설정하면 실행할 수 있습니다. (더 자세히 알고 싶다면, 일반적인 정보 문서를 확인해주세요.

만일 rustup 을 사용하고 있다면, 기본 설정으로 이미 이 툴이 설치되어서 추가적으로 할 일은 없습니다. 만일 다른 설정을 사용하고 있다면, 수동으로 아래와 같이 컴포넌트를 설치해야합니다.

rustup component add clippy

standalone installer 는 clippy 를 함께 설치합니다.

 

cargo

cargo 는 러스트 본연의 (native) 빌드 시스템입니다. 이것은 현재 테스트를 돌리기 위해서 필요합니다. 왜냐하면 이것은 커널에서 사용자 정의된 alloc 이 제공하는 기능을 포함한 사용자 정의 표준 라이브러를 빌드하는데 사용되기 때문입니다. 이 테스트는 rusttest 의 Make 를 이용하여 돌릴 수 있습니다.

만약 rustup 을 사용하고 있다면, 모든 설정들이 이 툴을 설치하므로 추가적으로 할 일은 없습니다.

standalone installer 또한 cargo 와 함께 설치됩니다.

 

rustdoc

rustdoc 은 러스트를 위한 문서화 툴입니다. 이것은 러스트 코드를 위한 멋진 HTML 문서를 생성합니다. (자세히 알기 위해서는 일반적인 정보 문서를 확인해주세요.

rustdoc 은 또한 문서화된 러스트 코드에서 제공하는 예제들을 테스트하기 위해서도 필요합니다. (이를 doctests 나 문서화 테스트라고 부릅니다) rusttest 의 Make 는 이 기능을 사용합니다.

만약 rustup 을 사용하고 있다면, 모든 설정이 이미 이 툴을 설치하므로 추가적으로 할 일은 없습니다.

standalone installer 또한 rustdoc 이 함께 설치됩니다.

 

rust-analyzer

rust-anaylzer 언어 서버는 문법 하이라이팅, 자동완성 (completion), 정의로 이동하기 등의 기능을 위해 많은 에디터들에서 사용됩니다.

rust-analyzer 는 rust-project.json 이라는 configuration 파일이 필요합니다.이 파일은 rust-analyzer Make 로 생성할 수 있습니다.

 

구성 (Configuration)

러스트 지원 (CONFIG_RUST) 는 General Setup 메뉴에서 켜야합니다. 이 옵션은 필요한 요구사항들이 만족되고, 적합한 러스트 툴체인이 있을 때만 보입니다. (위의 내용들을 확인해주세요) 이 옵션을 켜고 나면, 러스트에 의존성이 있는 다른 나머지 옵션들도 보이게 됩니다. 

이후에는 아래로 이동하세요:

Kernel hacking
    -> Sample kernel code
        -> Rust samples

그리고 샘플 모듈들을 빌트인 (built-in) 이나 loadable 로 활성화해보세요.

 

빌드 (Building)

LLVM 툴체인만으로 커널을 빌드하는 것이 현재는 가장 잘 지원되는 셋업입니다. 아래와 같이 이루어집니다:

make LLVM=1

완전한 LLVM 툴체인을 지원하지 않는 아키텍처라면 아래와 같이 사용하세요:

make CC=clang

GCC 를 사용하는 것이 일부 구성 (configurations) 에는 동작합니다. 그러나 아직 실험 단계입니다.

 

Hacking

더 깊게 탐구하기 위해서는 samples/rust 밑의 러스트 예제 코드, rust/ 밑의 러스트 지원 코드, Kernel hacking 아래에 있는 Rust hacking 메뉴를 확인해보세요.

만약 GDB/Binutils 를 사용하고 있고, 러스트 심볼이 디맹글 (demangle) 되지 않는다면, 툴체인이 러스트의 새로운 v0 mangling scheme 을 지원하지 않아서일 것입니다. 여기에는 몇 가지 해결 방법이 있습니다:

  • 새로운 릴리즈 버전을 설치합니다. (GDB >= 10.2, Binutils >= 2.36).
  • 일부 버전의 GDB (예, vanilla GDB 10.1) 는 디버그 정보 (CONFIG_DEBUG_INFO) 안에 포함된 사전에 디맹글된 이름을 사용할 수 있습니다.

 

원본 주소: https://www.kernel.org/doc/html/latest/rust/quick-start.html


일반적인 정보 (General Information)

본 문서는 커널에서 러스트를 이용하여 작업할 때 유용한 정보가 있습니다.

 

코드 문서화 (Code documentation)

러스트 커널 코드는 rustdoc 을 이용하여 문서화됩니다. 이것은 빌트인 문서화 생성기입니다.

생성된 HTML 문서는 통합 검색, 아이템 연결 (예, 자료형, 함수, 상수), 소스 코드 등등을 포함합니다. 그것은 아래의 링크에서도 읽어볼 수 있습니다.

http://kernel.org/

이 문서는 쉽게 생성되고 로컬에서 읽을 수 있습니다. 이것은 매우 빠릅니다. (코드를 컴파일하는 것과 같은 순서입니다.) 그리고 특별한 툴이나 환경이 필요하지 않습니다. 이것은 사용되는 특정한 커널 구성 (configuration) 에 맞게 수정 가능하다는 장점도 있습니다. 이를 생성하기 위해서는, 컴파일을 위해 사용되는 명령어에 rustdoc 을 사용합니다. 예:

make LLVM=1 rustdoc

여러분의 로컬 웹 브라우저에서 문서를 읽고 싶다면 아래를 실행해주세요. 예:

xdg-open rust/doc/kernel/index.html

문서를 어떻게 작성하는지 알고 싶다면, 코딩 지침 문서를 확인해주세요.

 

추가 린트 (Extra lints)

rustc 가 매우 유용한 컴파일러이기는 하지만, Rust linter 인 clippy 를 통해서 몇 가지 추가적인 린트나 분석 (analyses)이 가능합니다. 이것을 사용하기 위해서는, 컴파일 명령어에 CLIPPY=1 을 인자로 넘겨줍니다. 예:

make LLVM=1 CLIPPY=1

Clippy 는 코드 생성을 변화시킬 수 있기 때문에, 제품용 커널을 빌드할 때는 사용해서는 안 됩니다.

 

추상화 vs. 바인딩 (Abstractions vs. bindings)

추상화는 C 부분에서 커널의 기능을 감싸는 러스트 코드를 의미합니다.

C에 있는 함수와 자료형을 사용하기 위해서, 바인딩이 생성됩니다. 바인딩은 C 코드들의 그러한 함수나 자료형들의 러스트를 위한 선언 (declarations) 입니다.

예를 들어, 러스트에서 Mutex 추상화를 사용할 수 있는데, 이것은 C에 있는 struct mutex 를 감싸는 것이고 바인딩을 통해 그것의 함수를 호출해줍니다.

추상화는 모든 커널 내부의 API 와 컨셉에 가능하지는 않습니다. 하지만 이것은 시간이 갈수록 점차 영역을 확대해가려고 합니다. 말단 (Leaf) 모듈은 (예. 드라이버) 직접적으로 C 바인딩을 사용해서는 안 됩니다. 대신에 하위 시스템은 필요한만큼 안전한 추상화를 제공해야합니다.

 

조건부 컴파일 (Conditional compilation)

러스트 코드는 커널 설정 (configuration) 에 기반한 조건부 컴파일이 가능합니다.

#[cfg(CONFIG_X)]       // Enabled               (`y` or `m`)
#[cfg(CONFIG_X="y")]   // Enabled as a built-in (`y`)
#[cfg(CONFIG_X="m")]   // Enabled as a module   (`m`)
#[cfg(not(CONFIG_X))]  // Disabled

원본 주소: https://www.kernel.org/doc/html/latest/rust/general-information.html


코딩 지침 (Coding Guidelines)

이 문서는 커널에서 러스트 코드를 어떻게 작성하는지를 설명합니다.

 

스타일 & 포맷팅 (Style & formatting)

코드는 rustfmt 를 사용해서 포맷팅해야합니다. 이 방법으로 커널에 기여하려는 사람들이 추가적인 스타일 가이드를 배우고 기억해야할 필요가 없습니다. 더 중요한 것은, 리뷰어와 메인테이너들이 더 이상 스타일 이슈를 지적하는데 시간을 사용할 필요가 없고, 패치를 여러 번 고쳐야할 필요가 적어집니다.

주의할 점:
주석이나 문서화에 관련된 관례는 rustfmt 로 걸러지지 않습니다. 이것들은 여전히 신경쓸 필요가 있습니다.

rustfmt 의 기본 설정이 사용됩니다. 이는 관용적인 (idiomatic) 러스트 스타일을 따른다는 것을 의미합니다. 예를 들어, 탭 대신 스페이스 4칸을 들여쓰기 (indentation) 로 사용합니다.

저장하거나 커밋을 만들 때, 에디터나 IDE의 지침을 따르는 것이 편리합니다. 그러나 모종의 이유로 전체 커널의 러스트 코드를 재포맷팅해야한다면, 아래의 명령어를 실행하세요:

make LLVM=1 rustfmt

모든 것이 포맷팅되었는지 확인하려면 (아니면 diff 를 출력해봅니다), 예를 들어 CI 등에서 활용하기 위해서라면, 아래를 실행하세요:

make LLVM=1 rustfmtcheck

커널의 나머지 부분에 사용하는 clang-format 처럼 rustfmt 도 개별 파일에 적용할 수 있습니다. 그리고 커널 설정 (configuration) 이 필요하지 않습니다. 때때로 이것은 미완성된 코드 (broken code) 에도 동작합니다.

 

주석 (Comments)

"일반적인" 주석 (예, 코드 문서화를 위해 시작할 때 붙이는 /// 나 //! 와 달리 // 를 말한다.) 은 문서화되는 것이 아니더라도 문서화용 주석과 같은 방식으로 마크다운으로 작성합니다. 이런 방식은 일관성을 향상시키고, 규칙을 간단하게 만들고, 서로 다른 종류의 주석들의 내용을 쉽게 이동시킬 수 있게 합니다. 예를 들면 다음과 같습니다:

// `object` is ready to be handled now.
f(object);

게다가, 문서화처럼, 주석은 문장의 맨 앞은 대문자로 작성하고, 문장의 끝에는 마침표를 적습니다. (1 문장이더라도 그렇습니다) 이것은 // SAFETY: 나 // TODO 나 아래와 같은 다른 태그용 주석들을 포함합니다:

// FIXME: The error should be handled properly.

주석은 문서화 목적을 위해서 사용하면 안 됩니다: 주석은 사용자를 위한 것이 아니라 구현 디테일을 위한 것입니다. 이러한 구별은 소스 코드의 독자가 구현자이든 API 사용자이든 모두 유용합니다. 사실, 때때로 주석이랑 문서화를 동시에 사용하는 것은 유용합니다. 예를 들면, TODO 리스트를 작성할 때나, 문서화에 주석을 달 때가 있습니다. 후자의 경우, 주석은 중간에 삽입될 수도 있습니다; 즉, 문서화하는 라인에 더 가깝습니다. 다른 경우에는 주석은 문서화보다 더 아래에 작성됩니다. 예를 들면 다음과 같습니다:

/// Returns a new [`Foo`].
///
/// # Examples
///
// TODO: Find a better example.
/// ```
/// let foo = f(42);
/// ```
// FIXME: Use fallible approach.
pub fn f(x: i32) -> Foo {
    // ...
}

한가지 특별한 종류의 주석은 // SAFETY: 주석입니다. 이것은 반드시 모든 안전하지 않은 블락 (unsafe block) 전에는 작성해주어야합니다. 그리고 왜 이 블락 내의 코드가 맞는 것인지 설명해야합니다. 예를 들어, 이것의 어떤 경우에도 정의되지 않은 동작 (undefined behavior) 은 하지 않는 이유가 무엇인지를 적어야합니다.

// SAFETY: `p` is valid by the safety requirements.
unsafe { *p = 0; }

// SAFETY: 주석은 코드 문서화의 # Safety 영역과 혼동되면 안 됩니다. # Safety 영역은 호출자 (함수를 위한) 혹은 구현자 (러스트의 트레잇 traits 를 위한) 들이 따라야하는 약속을 명시합니다. // SAFETY: 주석은 왜 호출자 (함수를 위한) 와 구현자 (러스트의 트레잇을 위한) 들이 # Saftey 영역으로 지정한 사전 조건이나 언어 레퍼런스를 따라야하는지를 설명합니다.

 

코드 문서화 (Code documentation)

러스트 커널 코드는 C 커널 코드처럼 문서화되지 않습니다. (예를 들면, kernel-doc 를 통해서 하는 방식입니다.) 대신에, 러스트 코드를 문서화하기 위한 시스템을 사용합니다: 마크다운 (경량 마크업 언어) 을 사용하는 rustdoc 툴입니다.

마크다운을 학습하기 위해서는, 아래에 많은 가이드들이 있습니다. 예를 들어, 그 중 하나는 아래입니다:

https://commonmark.org/help/

잘 문서화된 러스트 함수는 아래와 같습니다:

/// Returns the contained [`Some`] value, consuming the `self` value,
/// without checking that the value is not [`None`].
///
/// # Safety
///
/// Calling this method on [`None`] is *[undefined behavior]*.
///
/// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
///
/// # Examples
///
/// ```
/// let x = Some("air");
/// assert_eq!(unsafe { x.unwrap_unchecked() }, "air");
/// ```
pub unsafe fn unwrap_unchecked(self) -> T {
    match self {
        Some(val) => val,

        // SAFETY: The safety contract must be upheld by the caller.
        None => unsafe { hint::unreachable_unchecked() },
    }
}

아래 예시는 rustdoc 의 특징이나 커널에서 따라야하는 관례들을 보여줍니다:

  • 첫번째 문단은 문서화시키려는 아이템이 무엇을 하는 건지 한 문장으로 간단히 설명해야합니다. 더 많은 설명은 추가 문단으로 해야합니다.
  • 안전하지 않은 함수들은 # Safety 영역을 사용하여 안전 사전 조건을 문서화해야합니다.
  • 만약 함수가 패닉이 날 수 있다면, # Panics 영역에 어떤 조건에서 발생할 수 있는지를 적어야합니다. 패닉을 일으키는 것은 매우 적어야하고 필요한 이유가 있을 때만 사용해야한다는 것을 기억하세요. 대부분의 경우에는 결과값에 오류를 리턴하는 방식으로 해야합니다.
  • 만일 사용 예를 제시하는 것이 독자들을 도울 수 있따면, # Examples 영역에 적어야합니다.
  • 러스트 아이템 (함수, 자료형, 상수, ...) 는 반드시 링크를 적절하게 달아야합니다. (rustdoc 은 자동으로 링크를 생성합니다.)
  • 모든 안전하지 않은 블럭은 // SAFETY: 주석 아래에 적어야합니다. 이 주석은 왜 안의 코드가 맞는지 설명해야합니다. 때때로 이 이유는 사소해보여서 필요하지 않을 수 있지만, 이 주석을 적는 것은 고려해야하는 것들을 문서화하는 좋은 방법일 뿐만 아니라 더 중요하게는, 암묵적인 제한이 더는 없다는 것을 알기 위해서 필요합니다.

러스트와 그 외 특징들을 위해서 문서화를 어떻게 하는지 더 알고 싶다면, 아래에서 rustdoc 책을 확인해보세요:

https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html

네이밍 (Naming)

러스트 커널 코드는 일반적인 러스트 네이밍 규칙을 따릅니다:

https://rust-lang.github.io/api-guidelines/naming.html

기존의 C 컨셉 (매크로, 함수, 오브젝트, ...) 을 러스트 추상화로 감쌀 때, C와 러스트 코드를 왔다갔다 할 때 헷갈리지도 않고 가독성도 높이기 위해서 최대한 C 와 비슷한 이름을 작성해야합니다. 예를 들어, C에서 pr_info 라는 매크로는 러스트에서도 같은 이름이어야합니다.

그렇지만, 대소문자 규칙은 러스트의 명명 규칙을 따라야합니다. 그리고 모듈의 네임스페이스나 자료형 이름은 아이템 이름으로 사용되면 안 됩니다. 예를 들어, 아래와 같은 상수를 감쌀 것입니다:

#define GPIO_LINE_DIRECTION_IN  0
#define GPIO_LINE_DIRECTION_OUT 1

같은 러스트 코드는 아래와 같습니다 (문서화는 무시한 상태입니다):

pub mod gpio {
    pub enum LineDirection {
        In = bindings::GPIO_LINE_DIRECTION_IN as _,
        Out = bindings::GPIO_LINE_DIRECTION_OUT as _,
    }
}

즉, GPIO_LINE_DIRECTION_IN 은 gpio::LineDirection::In 이 됩니다. 특히, gpio::gpio_line_direction::GPIO_LINE_DIRECTION_IN 으로 이름을 지으면 안 됩니다.

 

원본 주소: https://www.kernel.org/doc/html/latest/rust/coding-guidelines.html


아키텍처 지원 (Arch Support)

현재로서는 러스트 컴파일러 (rustc) 는 코드 생성을 위하여 LLVM 을 사용하기 때문에 지원하는 아키텍처에 제한이 있습니다. 이에 더하여, 커널을 LLVM/Clang 으로 빌드하도록 지원하는 것은 일정하지 않습니다. (Clang/LLVM 으로 빌드하기를 참고해주세요.) 지원을 위해서는 libclang 을 사용하는 bindgen 이 필요합니다.

 

아래는 지금 작동하는 아키텍처의 전반적인 요약입니다. 지원 정도는 'MAINTAINERS' 파일의 's' 값입니다.

아키텍처 지원 수준 제한
x86 공식 버전에서 지원 x86_64 만 지원
um 공식 버전에서 지원 x86_64 만 지원

원본 주소: https://www.kernel.org/doc/html/latest/rust/arch-support.html

 

 

재확인 필요

* LLVM/Clang 으로 구분한 부분은 LLVM 에 대한 방법과 Clang 에 대한 방법이 다르다는 의미인지 확인 필요

* source 는 source code 를 의미한 것인지

* code completion 이 자동완성의 의미가 맞는지

* 디맹글 대신 적합한 번역 단어 있는지

* printing a diff otherwise 가 문맥상 무슨 뜻인지

* broken code 의 의미

* language reference 의미

* naming 번역. 변수 이름 짓기?