Zephyr
Zephyr 는 자원이 넉넉하지 않은 작은 장치에 올리는 운영체제입니다. 환경 센서나 스마트워치 같은 물건 안에서 돕니다. 내가 짠 프로그램과 이 운영체제는 대개 한 덩어리로 빌드되어 장치에 구워집니다. 리눅스 재단이 들고 있는 오픈소스 프로젝트입니다.
쉽고 빠른 이해
자원이 넉넉하지 않은 작은 장치에 올리는 운영체제입니다. 환경 센서나 스마트워치처럼 메모리가 적은 물건 안에서 돕니다.
왜 이렇게 하나. 범용 운영체제가 갖춘 기능을 다 넣으면 이런 작은 장치에는 버겁습니다. 목적이 정해져 있고 하드웨어 자원이 적은 장치라 필요한 만큼만 갖춥니다.
어떻게 도나.
- 내가 짠 프로그램과 커널 코드를 하나의 실행 파일로 합쳐 장치에 굽습니다.
- 그 실행 파일은 하나의 메모리 공간에서 돌고, 그 안은 몇 구역으로 나뉘어 있어 설정에 따라 스레드를 구역 안에 가둘 수 있습니다.
- 빌드 도구로 이미지를 만들고 장치에 올립니다.
대가. 범용 운영체제가 주는 것 일부를 포기합니다. 여러 운영체제가 공통으로 쓰는 표준 인터페이스를 일부만 구현하고, 스레드끼리 메모리를 갈라놓는 것도 기본값이 아닙니다. 스레드가 쓸 메모리도 미리 정해 둬야 합니다.
상세
Zephyr OS 는 자원이 제한된 시스템과 임베디드 시스템에서 쓰도록 설계된 작은 커널을 바탕으로 삼습니다. 공식 문서가 대는 범위는 단순한 임베디드 환경 센서와 LED(Light Emitting Diode, 발광 다이오드) 웨어러블에서 시작합니다. 정교한 임베디드 컨트롤러와 스마트워치, 사물인터넷 무선 애플리케이션까지 이어집니다.
메모리 보호는 커널이 맡습니다. 아키텍처마다 다른 스택 오버플로 보호를 설정으로 켤 수 있습니다. 커널 오브젝트와 디바이스 드라이버의 권한도 추적합니다. 스레드는 기본으로 슈퍼바이저 모드에서 돕니다. 이 모드는 특권 CPU(Central Processing Unit, 중앙 처리 장치) 명령과 전체 메모리 주소공간, 주변장치에 접근할 수 있는 실행 모드입니다. x86·ARC·Arm 아키텍처에서는 스레드 단위 메모리 보호로 스레드를 서로 격리합니다. 유저스페이스(Zephyr 는 이것을 유저모드라고도 부릅니다)와 메모리 도메인(주소공간을 나눈 구역)도 이 격리를 이루는 수단입니다.
MMU(Memory Management Unit, 메모리 관리 장치)나 MPU(Memory Protection Unit, 메모리 보호 장치)가 없는 플랫폼과 메모리가 제한된 장치를 위한 길은 따로 있습니다. 애플리케이션 전용 코드를 맞춤 커널과 합쳐 하나의 모놀리식 이미지를 만듭니다. 그 이미지가 시스템 하드웨어에 올라가 실행됩니다. 이때 애플리케이션 코드와 커널 코드는 하나의 공유 주소공간에서 함께 돕니다.
이 자리 배치를 그림으로 보면 이렇습니다.
block-beta columns 3 bin["애플리케이션 코드 + 커널 코드 = 한 바이너리(모놀리식 이미지)"]:3 space["그 바이너리가 도는 하나의 공유 주소공간"]:3 default["기본값(CONFIG_USERSPACE 꺼짐) · 이 나눔 없음 · 전체가 슈퍼바이저 모드"]:3 kernel["커널 코드 · 커널 오브젝트<br/>슈퍼바이저 모드 · 전체 주소공간 접근"] d1["메모리 도메인 1<br/>CONFIG_USERSPACE 켜면<br/>유저모드 스레드가 갇힌다"] d2["메모리 도메인 2<br/>CONFIG_USERSPACE 켜면<br/>유저모드 스레드가 갇힌다"]
이 구역 나눔은 기본으로 강제되지 않고, 스레드는 전체 주소공간에 접근합니다. CONFIG_USERSPACE
를 켜야 유저모드 스레드가 자기 메모리 도메인 밖으로 못 나갑니다.
지원 아키텍처
공식 문서는 커널이 대는 아키텍처를 이렇게 셉니다.
| 계열 | 문서가 적은 이름 |
|---|---|
| ARC | ARCv2(EM · HS) · ARCv3(HS6X) — 공식 문서가 풀어쓰지 않고 그대로 쓰는 계열 이름 |
| Arm | ARMv6-M · ARMv7-M · ARMv8-M(Cortex-M) · ARMv7-A · ARMv8-A(Cortex-A, 32비트와 64비트) · ARMv7-R · ARMv8-R(Cortex-R, 32비트와 64비트) |
| Intel | x86(32비트와 64비트) |
| MIPS(Microprocessor without Interlocked Pipeline Stages, 파이프라인 인터록 없는 마이크로프로세서) | MIPS32 Release 1 명세 |
| OpenRISC | 32비트 |
| Renesas | RX |
| RISC-V | 32비트와 64비트 |
| SPARC | V8 |
| Tensilica | Xtensa |
주인과 라이선스
Zephyr Project 는 리눅스 재단 프로젝트입니다. 라이선스는 Apache 2.0 입니다. 레포지토리 루트의
LICENSE 파일에 Apache License Version 2.0, January 2004 가 적혀 있습니다. 공식 문서는 이
라이선스를 허용적인 것이라고 표현합니다.
포기한 것
완전한 POSIX 적합성
여러 목적을 두루 맡는 범용 운영체제는 완전한 POSIX(Portable Operating System Interface, 이식 가능 운영체제 인터페이스) 적합성을 제공합니다. Zephyr 같은 실시간 운영체제는 그 자리에서 갈립니다. 공식 문서가 스스로 이유를 적습니다. 실시간 운영체제는 대개 목적이 고정돼 있습니다. 하드웨어 자원이 제한됩니다. 사용자와 주고받는 일도 제한됩니다. 그런 시스템에서 완전한 POSIX 적합성은 실용적이지 않고 불필요할 수 있다는 것입니다.
그래서 Zephyr 는 IEEE 1003.1-2017 이 정한 표준 POSIX API(Application Programming Interface, 응용 프로그램 인터페이스)의 부분집합만 구현합니다.
프로세스별로 안 가르는 주소공간
Zephyr 는 여러 스레드를 돌릴 수 있습니다. SMP(Symmetric Multiprocessing, 대칭형 다중처리) 구성도 가능합니다. 가상 메모리와 MMU 도 지원합니다. 그런데도 Zephyr 의 코드와 데이터는 보통 하나의 공통 주소공간을 나눠 씁니다. 포기하는 것은 프로세스마다 따로 두는 독립된 주소공간입니다. 그 주소공간은 별개의 메모리 도메인으로 갈라집니다. 커널 실행 코드와 애플리케이션 실행 코드는 대개 같은 바이너리 산출물로 컴파일됩니다. 공식 문서는 그 관점에서 Zephyr 앱을 하나의 프로세스 맥락에서 도는 것으로 볼 수 있다고 적습니다. 얻은 것은 굽는 물건이 하나라는 점입니다.
켜야만 있는 스레드 격리
Zephyr 스레드는 기본으로 슈퍼바이저 모드에서 돕니다. 권한을 낮춘 실행 모드도 있습니다. Zephyr 는 그것을 유저모드라고 부릅니다. 유저모드 스레드는 축소된 권한만 갖습니다.
유저모드는 그냥 딸려 오지 않습니다. 이 실행 모드는 CONFIG_USERSPACE 옵션에 달려 있습니다.
그리고 현재 구현은 MPU 하드웨어를 갖춘 장치를 겨냥해 설계됐습니다. 이 옵션을 켜지 못하는 보드에서는
아래 격리도 없습니다.
켜서 얻는 것은 격리입니다. Zephyr 는 유저모드 스레드를 신뢰하지 않는 것으로 봅니다. 그래서 다른 유저모드 스레드로부터, 그리고 커널로부터 떼어 놓습니다. 결함이 있거나 악의적인 유저모드 스레드는 다른 스레드나 커널의 사적 데이터와 자원을 흘리거나 고칠 수 없습니다. 다른 유저모드 스레드나 커널을 간섭하거나 제어할 수도 없습니다.
미리 선언해 넘기는 스택
스레드의 주요 속성 가운데 하나가 스택 영역입니다. 스택 영역은 그 스레드의 스택으로 쓰이는 메모리 구간입니다. 크기는 그 스레드가 실제로 필요한 만큼 맞출 수 있습니다.
포기하는 것은 커널이 스레드를 만드는 시점에 즉석으로 스택 메모리를 마련해 주는 일입니다. 대신
스레드를 만드는 쪽이 스택 영역을 먼저 정의해서 넘겨야 합니다. 스레드는 스택 영역과 스레드 제어
블록을 먼저 정의한 다음, 그 스택 영역을 넘겨받으며 생성됩니다. 스택 영역은 K_THREAD_STACK_DEFINE
이나 K_KERNEL_STACK_DEFINE 으로 정적으로 할당할 수 있습니다. 얻은 것은 스택이 메모리 어디에
놓일지가 실행 전에 이미 정해진다는 점입니다.
예시
west 빌드와 플래시
공식 시작 안내서는 blinky 샘플을 west build 로 빌드합니다. west 는 Zephyr 프로젝트가 쓰는
빌드용 명령줄 도구입니다. <your-board-name> 자리에 자기 보드 이름을 넣습니다.
cd ~/zephyrproject/zephyr
west build -p always -b <your-board-name> samples/basic/blinky
-b 뒤가 대상 보드 이름입니다. 마지막 인자는 빌드할 샘플의 경로입니다. 여기서는
samples/basic/blinky 입니다.
굽는 것은 다음 한 줄입니다.
west flash
안내서는 이 명령이 방금 빌드한 애플리케이션을 연결된 보드에 프로그래밍한다고 적습니다. 샘플
README 는 reel_board 를 자기 보드 이름으로 바꿔서 빌드하고 플래시하라고 적습니다.
blinky 샘플의 설정과 소스
이 샘플이 켜는 설정은 prj.conf 에 든 한 줄입니다.
CONFIG_GPIO=y
GPIO(General-Purpose Input/Output, 범용 입출력) 지원을 켜는 항목입니다. README 는 이 샘플이 GPIO API 를 써서 LED 를 영원히 깜빡인다고 적습니다.
소스는 디바이스트리에서 핀 명세를 집어 오는 데서 시작합니다.
/* The devicetree node identifier for the "led0" alias. */
#define LED0_NODE DT_ALIAS(led0)
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
DT_ALIAS(led0) 가 디바이스트리의 led0 별칭을 가리키는 노드 식별자를 만듭니다.
GPIO_DT_SPEC_GET 이 그 노드에서 gpios 속성을 읽어 struct gpio_dt_spec 을 채웁니다.
README 는 이 샘플이 보여 주는 것을 셋으로 적습니다. 디바이스트리에서 핀 명세를
struct gpio_dt_spec 으로 얻는 것, 그 GPIO 핀을 출력으로 설정하는 것, 그 핀을 영원히 토글하는
것입니다.
실제로 도는 반복문은 이렇습니다.
while (1) {
ret = gpio_pin_toggle_dt(&led);
if (ret < 0) {
return 0;
}
led_state = !led_state;
printf("led state: %s\n", led_state ? "on" : "off");
k_msleep(SLEEP_TIME_MS);
}
SLEEP_TIME_MS 는 1000 입니다. 소스 주석이 1000 msec = 1 sec 라고 적어 둡니다. 반복이 하는
일은 셋입니다. 핀을 뒤집습니다. 뒤집힌 상태를 printf 로 찍습니다. k_msleep 으로 그만큼
쉽니다.
이 샘플을 돌리려면 조건이 둘입니다. GPIO 핀에 LED 가 연결돼 있어야 합니다. README 는 Zephyr
보드 상당수가 이런 LED 를 user LED 라고 부른다고 적습니다. 그리고 그 LED 가 led0 디바이스트리
별칭으로 설정돼 있어야 합니다.
사용처
Nordic Semiconductor 의 nRF Connect SDK(Software Development Kit, 소프트웨어 개발 키트)가 Zephyr 를 바탕으로 삼습니다. 이 SDK 의 공식 문서는 nRF Connect SDK 가 Zephyr 실시간 운영체제를 통합한다고 적습니다. 같은 문장이 이 SDK 는 Zephyr 의 애플리케이션과 샘플 대부분과 호환된다고 적습니다. 같은 문서는 nRF Connect SDK 가 Zephyr 처럼 오픈소스라고 적습니다. 검증된 오픈소스 프로젝트들을 바탕으로 한다고도 적습니다.
반대편도 맞물려 있습니다. Zephyr 공식 문서의 지원 보드·쉴드 목록은 벤더별로 나뉩니다. 그 알파벳순 벤더 인덱스에 Nordic Semiconductor 가 등재돼 있습니다. 벤더가 자기 SDK 를 Zephyr 위에 올리는 자리와, Zephyr 가 그 벤더의 보드를 공식으로 지원하는 자리가 같은 곳입니다.
관련 항목
Zephyr 커널을 이루는 개념
커널 · 스레드 · 스택 · 스택 오버플로 · 슈퍼바이저 모드 · 유저스페이스 · 유저모드 · 메모리 도메인 · 주소공간 · 모놀리식 이미지 · 격리 · 권한
Zephyr 빌드·설정을 이루는 요소
디바이스트리 · Kconfig · west · 디바이스 드라이버 · 노드
Zephyr가 지원하는 아키텍처
Arm · Cortex-M · Cortex-A · Cortex-R · x86 · Intel · RISC-V · ARC · SPARC · Xtensa · Tensilica · MIPS · OpenRISC · Renesas · RX
Zephyr가 얹히는 하드웨어 부품
MPU · GPIO · LED · 마이크로컨트롤러 · 보드 · CPU
Zephyr가 지원하는 멀티프로세싱·메모리 기능
Zephyr가 부분만 구현하는 표준 API
POSIX · IEEE 1003.1 · API
Zephyr가 속하는 상위 분류
Zephyr가 쓰이는 응용 분야
임베디드 · 사물인터넷 · 웨어러블
Zephyr가 따르는 라이선스와 소유 주체
Apache 2.0 · 오픈소스 · 리눅스 재단
Zephyr를 채택한 제품
nRF Connect SDK · Nordic Semiconductor
다른 이름: Zephyr OS · Zephyr RTOS · Zephyr Project