본문 바로가기

IT/임베디드 IoT

[Buildroot] 임베디드 리눅스 구조의 이해

반응형

서  론

빌드루트, Buildroot(혹은 하드웨어 스케일에 따라서는 Yocto)라는 툴을 다루는 것은 임베디드 리눅스의 영역, 그것도 BSP(Board Support Package)의 영역이다.

 

분명 학부 때는 전기공학과를 다니며, 펌웨어 코딩과 자동제어를 주로 프로그래밍했었는데,

어느 새 취업을 하고 보니 임베디드 리눅스의 영역도 다루게 되고 있다.

 

Buildroot에 대해 다루기 전에, 임베디드 리눅스의 구조에 대해 알아볼 필요가 있다.

Buildroot라는 툴을 다루려면 최소한 그 안에서 어떤 동작을 하는지, 그 구조는 어떻게 되는지에 대해서는 알아야 하기 때문이다.

 

임베디드 리눅스를 구성하는 가장 중요한 요소들은 무엇일까?

그것에 대해 매우 간략히 정리해보도록 하겠다.


리눅스, Linux

Embedded Linux

  • 임베디드 리눅스를 구성하는 가장 중요한 4가지 요소는 아래와 같다.
    • 툴체인
    • 부트로더
    • 커널
    • 루트 파일시스템
  • Buildroot는 위 4개의 요소를 구현하기 쉽도록 도와주는 도구이다.

툴체인

  • 툴체인은 소스 코드를 타깃 장치에서 실행할 수 있는 실행 파일로, 컴파일러, 링커, 런타임 라이브러리를 포함하는, 컴파일 도구의 집합이다.
    • 리눅스 시스템의 나머지 3요소(부트로더, 커널, 루트파일시스템)를 빌드하기 위해 툴체인이 필요하다.
  • 표준 GNU 툴체인
  • 툴체인의 종류
    • 네이티브: 툴체인이 만들어내는 프로그램과 같은 종류의 시스템, 때로는 실제로 같은 시스템에서 실행된다. 데스크톱과 서버에서는 일반적인 경우
    • 크로스: 툴체인이 타깃 기계와 다른 종류의 시스템에서 실행된다. 빠른 데스크톱 PC에서 개발한 다음 임베디드 장치에 로드해서 테스트.
  • CPU Architecture
    • CPU Architecture: ARM, MIPS, x86_64
    • Endian: Big Endian, Little Endian
    • Fixed Float Point Support
    • ABI: OABI → EABI → EABIHF

부트로더

  • 부트로더는 시스템을 Basic Level로 초기화하고 커널을 로드하는 두 가지 주요 작업을 실행한다.
  • 부트 순서
    1. 롬 코드
      • 리셋이나 전원을 켠 직후에 실행되는 코드는 SoC의 칩상에 저장돼야 한다.
      • 이는 롬 코드(ROM Code)라고 불린다.
      • 롬 코드는 제조 시에 칩에 프로그램되므로, 비공개이며 오픈소스 대용품으로는 대체할 수 없다.
      • DRAM 구성은 장치 별로 다르기에, 메모리 컨트롤러를 초기화하는 코드는 보통 담고 있지 않으므로, 메모리 컨트롤러가 필요 없는 SRAM만 사용할 수 있다.
      • 1단계가 끝날 때면, SPL이 SRAM에 존재하고 롬 코드는 SPL 코드의 시작으로 점프한다.
    2. Secondary Program Loader (SPL)
      • 메모리 컨트롤러와 기타 Tertiary Program Loader(TPL)을 DRAM에 로드하기 위해 필요한 시스템의 필수적인 부분들을 시작해야 한다.
      • SPL의 기능은 크기로 인해 제한된다.
      • 롬 코드처럼 다시 한 번 사전에 프로그램된 플래시 저장 장치 시작부터의 오프셋을 이용해 일련의 저장 장치로부터 프로그램을 읽을 수 있다.
      • SPL에 파일시스템 드라이버가 내장되어 있다면, 디스크 파티션에서 u-boot.img처럼 잘 알려진 파일 이름을 읽을 수 있다.
      • SPL은 제조사가 바이너리로 제공하는 비공개 코드인 것이 일반적. (TI, Atmel 제외)
      • 2단계가 끝날 때면, 3단계 로더가 DRAM에 존재하고 SPL은 그 영역으로 점프할 수 있다.
    3. Tertiary Program Loader (TPL)
      • U-Boot나 Barebox 같은 완전한 부트로더 실행
      • 새로운 부트, 커널 이미지를 플래시 저장소에 로드하고, 커널을 로드하고 부팅하는 등의 유지보수 작업을 수행할 수 있게 하는 간단한 CLI가 있고, 사용자 개입없이 커널을 자동으로 로드하는 방법도 있음.
      • 3단계가 끝날 때면, 커널이 메모리에서 시작되길 기다리고 있다.
      • 임베디드 부트로더는 보통 일단 커널이 실행되면 메모리에서 사라지고 시스템의 동작에 더 이상 관여하지 않는다.
  • 예시) UEFI 펌웨어로 부팅
    • 부트 순서
      1. 프로세서가 플래시 메모리로부터 플랫폼 초기화 펌웨어를 로드한다.
      2. 플랫폼 초기화 펌웨어는 SPL의 역할을 수행한다. 로컬 디스크상의 EFI System Partition(ESP)나 PXE 부트를 통해 네트워크 서버에서 EFI 부트 관리자를 로드할 수 있도록 DRAM 컨트롤러와 기타 인터페이스를 초기화한다.
      3. UEFI 부트 관리자가 TPL이다. 이 경우 TPL은 리눅스 커널과 램 디스크를 메모리에 로드할 수 있는 부트로더여야 한다. 일반적으로 다음 둘 중 하나를 선택한다.
  • 부트로더가 제어를 커널로 넘길 때 넘겨야 하는 정보
    • 장치 트리가 지원되지 않는 파워PC와 ARM 플랫폼에서 SoC의 종류를 식별하기 위해 사용하는 기계 번호 (machine number)
    • 이제까지 발견된 하드웨어의 기본적인 세부사항 (최소한 물리적인 램의 크기와 위치, CPU 클럭 속도 등을 포함)
    • 커널 커맨드라인
    • 장치 트리 바이너리의 위치와 크기
    • 초기 램 디스크(initramfs 초기 램 파일시스템)의 위치와 크기

장치 트리

  • 장치 트리는 컴퓨터 시스템의 하드웨어 요소를 정의하는 유연한 방법이다.
  • 부트로더가 로드해서 커널에 넘기는 것
  • 장치 트리를 따로 로드할 수 없는 부트로더를 위해 커널 이미지 자체에 포함시킬 수도 있긴 함

부트로더 선택

Name Supported Architecture
Das U-Boot ARC, ARM, Blackfin, Microblaze, MIPS, Nios2, OpenRiec, 파워PC, SH
Barebox ARM, Blackfin, MIPS, Nios2, 파워PC
GRUB 2 x86, x86_64
Little Kernel ARM
RedBoot ARM, MIPS, 파워PC, SH
CFE Broadcom MIPS
YAMON MIPS

커널

  • 커널은 자원 관리와 하드웨어 인터페이스를 담당하므로 최종 소프트웨어 빌드의 거의 모든 측면에 영향을 주는 요소다.
  • 커널은 특정 하드웨어 구성에 맞춰지는데, 장치 트리를 이용하면, 일반적인 커널을 만들고 장치 트리의 내용을 통해 특정하드웨어에 맞춰지도록 할 수도 있다.
  • 커널은 자원을 관리하고, 하드웨어와 인터페이스하고, 사용자 공간 프로그램에게 유용한 수준의 추상화를 제공하는 API를 제공한다.
  • Kconfig: 구성 메커니즘을 Kconfig라 하며, Kconfig에 통합된 빌드 시스템을 Kbuild라 한다.

커널 소스 청소

  • clean: 오브젝트 파일과 대부분의 중간 파일을 제거한다.
  • mrproper: 모든 중간 파일(.config 파일 포함)을 제거한다. 이 타깃을 이용하면 소스 트리를 클론하거나 압축 해제한 직후의 상태로 되돌릴 수 있다.
  • distclean: mrproper와 같지만 편집기 백업 파일과 패치 파일, 기타 소프트웨어 개발 부산물들도 지운다.

커널 작동 순서

  • 커널 초기화에서 사용자 공간으로 이행하기 위해, 커널은 루트 파일시스템을 마운트하고, 루트 파일시스템에 있는 프로그램을 실행한다. 이는 램디스크를 통하거나 블록 장치상의 실제 파일시스템을 마운트함으로써 이뤄진다.
  • 위를 처리하는 모든 코드는 init/main.c에 있고, 함수 rest_init()에서 시작한다.
  • rest_init()함수는 PID 1인 첫 번째 thread를 만들고, kernel_init() 코드를 실행한다.
  • 램디스크가 있다면, 프로그램 /init을 실행하려고 할 것이고, 이 프로그램은 계속해서 사용자 공간을 설정하는 작업을 수행한다.
    • /init을 찾아서 실행하는 데에 실패하면, init/do_mounts.c 안의 함수 prepare_namespace()를 불러 파일시스템을 마운트하려고 할 것이다.
    • 이를 위해서는 다음과 같은 root= 커맨드라인을 통해 마운트할 때 쓸 블록 장치의 이름을 제공해야 한다.
      • root=/dev/<disk_name><partition_number>
      • root=/dev/<disk_name>p<partition_number>
      • 예시) root=/dev/mmcblk0p1
      • 성공한다면, /sbin/init → /etc/init → bin/init → /bin/sh를 성공할 때까지 차례로 실행을 시도한다.
    • 램디스크의 경우 rdinit=을, 파일시스템의 경우 init=을 사용한다.

커널 메시지

Warning Msg Value Description
KERN_EMERG 0 시스템이 사용 불능
KERN_ALERT 1 즉시 조치를 취해야함
KERN_CRIT 2 위급 상태
KERN_ERR 3 에러 상태
KERN_WARNING 4 경고 상태
KERN_NOTICE 5 정상이지만 중요한 상태
KERN_INFO 6 정보
KERN_DEBUG 7 디버깅 메시지

루트 파일시스템

  • 커널은 부트로더로부터 포인터로 전달된 initramfs나, root= 매개변수를 통해 커널 커맨드라인에 지정된 블록 장치를 마운트함으로써 루트 파일시스템을 구한다.
  • 최소한의 루트 파일시스템을 만들기 위해서는 다음 요소들이 필요하다.
    • init: 일련의 스크립트를 실행함으로써 모든 것을 시작시키는 프로그램
    • shell: init과 기타 프로그램이 호출하는 shell 스크립트를 실행하기 위해 필요
    • daemon: daemon은 다른 프로그램에게 서비스를 제공하는 백그라운드 프로그램이다. syslogd(system log daemon)와 sshd(secure shell daemon)가 있다. init 자체도 데몬으로, 다른 데몬들을 시작하는 서비스를 제공한다.
    • 공유 라이브러리: 대부분의 프로그램들은 공유 라이브러리와 링크되는데, 이들 라이브러리는 루트 파일시스템에 존재해야한다.
    • 구성 파일: init과 기타 데몬용 구성 파일들은 일련의 텍스트 파일로, 보통 /etc 디렉토리에 저장된다.
    • 장치 노드: 다양한 장치 드라이버에 접근할 수 있게 해주는 특수 파일들
    • /proc과 /sys: 커널 자료 구조를 디렉토리와 파일의 계층구조로 나타내는 2개의 가상 파일시스템. 여러 프로그램과 라이브러리 함수들이 proc과 sys에 의존한다.
    • 커널 모듈: 커널의 일부를 모듈로 구성했다면, 루트 파일시스템(보통 /lib/modules/[kernel_version])에 설치되어야 한다.
  • 디렉토리 레이아웃
    • /bin: 모든 사용자에게 필수적인 프로그램들
    • /dev: 장치 노드와 기타 특수 파일들
    • /etc: 시스템 구성
    • /lib: 필수 공유 라이브러리 (예: C 라이브러리를 이루는 것들)-
    • /proc: proc 파일 시스템
    • /sbin: 시스템 관리자에게 필수적인 프로그램들
    • /sys: sysfs 파일시스템
    • /tmp: 임시 파일이나 휘발성 파일들을 담아두는 곳
    • /usr: /usr/bin, /usr/lib, /usr/sbin에 각각 추가 프로그램, 라이브러리, 시스템 관리 유틸리티가 담겨있다. 시스템을 부팅할 때 필요한 것을 담고 있어서는 안 된다.
    • /var: 실행 중 변경될 수도 있는 파일과 디렉토리(예: 로그 메시지, 그 중 일부는 부트 뒤에도 남아있어야함)를 담고 있다.
- 400    r - - - - - - - -    ─┐
- 200    - w - - - - - - -     ├── 소유자 권한
- 100    - - x - - - - - -    ─┘
- 040    - - - r - - - - -    ─┐ 
- 020    - - - - w - - - -     ├── 그룹 권한
- 010    - - - - - x - - -    ─┘ 
- 004    - - - - - - r - -    ─┐
- 002    - - - - - - - w -     ├── 세계 권한
- 001    - - - - - - - - x    ─┘ 
  • POSIX 파일 접근 권한
    • 이 때, r은 읽기 권한, w는 쓰기 권한, x는 실행 권한을 나타낸다.
    • 이밖에도 특별한 의미가 있는 3비트는 아래와 같다.
      • SUID(4): 파일이 실행 가능하면, 프로그램이 실행될 때 프로세스의 유효 UID를 파일 소유자의 ID로 바꾼다.
      • SGID(2): SUID와 유사하게, 파일이 실행 가능하면, 프로세스의 유효 GID를 파일의 그룹 ID로 바꾼다.
      • Sticky(1): 디렉토리에서 삭제를 제한해 다른 사용자 소유의 파일을 지울 수 없게 한다. 이는 보통 /tmp와 /var/tmp에 설정되어있다.

RYO (Roll Your Own)

  • 임베디드 리눅스 초기에 루트 파일시스템을 만드는 유일한 방법
  • 현재는 램이나 저장소의 크기가 매우 제한적이거나, 빠른 시연을 위해서나, 요구사항이 표준 빌드 시스템 도구로 충족되지 않는 모든 경우에 사용한다.

반응형