실시간 운영체제
고친 사람 github-actions[bot]
실시간 운영체제는 맡은 일을 정해진 시간 안에 끝내도록 약속해 주는 운영체제입니다. 목표는 빨리 끝내는 것이 아니라 늦지 않는 것입니다. 에어백처럼 늦은 반응이 곧 고장인 기계에 들어갑니다.
쉽고 빠른 이해
실시간 운영체제는 일이 언제까지 끝날지를 약속하는 운영체제입니다. 차가 부딪힌 것을 감지하면 에어백은 정해진 짧은 시간 안에 터져야 합니다.
서버나 노트북에 깔리는 범용 운영체제는 대개 빠르지만 가끔 늦습니다. 서버라면 느린 요청 하나로 끝납니다. 에어백은 한 번만 늦어도 사고입니다.
어떻게 도나:
- 일마다 급한 정도를 매깁니다
- 더 급한 일이 생기면 하던 일을 곧바로 멈추고 그 일부터 돌립니다
- 운영체제가 하는 동작마다 걸리는 시간에 상한이 있습니다. 그래서 가장 느린 경우에도 늦지 않는지 미리 따질 수 있습니다
대가도 있습니다. 전체 처리량을 조금 내주고 기능도 적습니다. 만드는 사람은 일마다 가장 오래 걸리는 시간을 따져 둬야 합니다.
상세
화물 열차는 정해진 시각에 떠납니다. 짐을 일찍 싣는다고 열차가 더 빨리 가지는 않습니다. 일 분만 늦어도 짐은 역에 남습니다. 짐을 싣는 사람에게 필요한 것은 빠른 손이 아니라 출발 시각을 한 번도 넘기지 않는 습관입니다.
늦은 답은 틀린 답
「실시간」은 빠르다는 뜻이 아닙니다. 정해진 시각 안에 반응한다는 뜻입니다. 그 정해진 시각이 마감 시간입니다. 데드라인이라고도 합니다.
평범한 프로그램은 계산 결과가 맞으면 맞은 것입니다. 실시간으로 도는 프로그램은 결과가 맞아도 마감 시간을 넘기면 틀린 것입니다. 엔진에 불꽃을 튀길 시각을 계산하는 제어기가 그렇습니다. 계산이 맞아도 피스톤이 지나간 뒤에 불꽃이 튀면 쓸모가 없습니다.
이렇게 결과가 맞아도 늦게 나오면 틀린 것으로 치는 시스템이 실시간 시스템입니다. 실시간 운영체제는 그런 시스템의 바탕에 깔려, 그 위의 프로그램이 마감 시간을 지킬 수 있게 받쳐 줍니다. 영어로는 RTOS(Real-Time Operating System)라고 줄여 부릅니다.
실시간 운영체제와 맞세우는 것은 서버와 노트북에 깔리는 운영체제입니다. 여러 프로그램을 두루 돌리도록 만든 운영체제라서 범용 운영체제라고 부릅니다.
평균이 아니라 최악
서버의 빠르기는 흔히 평균이나 백분위수로 잽니다. 요청 백 개 가운데 가장 느린 한 개를 뺀 나머지가 모두 드는 시간을 p99 라고 부르는 식입니다. 드물게 느린 요청 몇 개는 감수하고 전체가 대체로 빠른지를 봅니다.
실시간 운영체제가 보는 값은 그 끝, 가장 느린 한 번입니다. 에어백은 백만 번 가운데 한 번만 늦어도 사고이기 때문입니다. 그래서 「대체로 빠르다」로는 부족하고 「아무리 느려도 이 시간은 넘지 않는다」를 말할 수 있어야 합니다.
한 작업이 가장 오래 걸릴 때의 시간이 최악 실행 시간입니다. 이 값을 알아야 마감 시간을 지킬 수 있는지 돌리기 전에 따질 수 있습니다. 평균이 아무리 짧아도 최악을 모르면 약속을 할 수 없습니다.
같은 일을 되풀이할 때 걸리는 시간이 매번 조금씩 흔들리는 폭을 지터라고 합니다. 지터가 크면 최악이 평균에서 멀어집니다. 실시간 운영체제는 이 폭을 작게 둡니다.
언제 돌려도 걸리는 시간이 거의 같고 상한을 넘지 않는 성질을 결정성이라고 부릅니다. 결정성이 있어야 최악 실행 시간을 미리 따진 값이 믿을 만해집니다.
하드 실시간과 소프트 실시간
마감 시간을 놓쳤을 때 얼마나 나쁜지는 기계마다 다릅니다. 그 정도에 따라 실시간을 둘로 가릅니다.
| 갈래 | 마감을 놓치면 | 흔히 드는 예 |
|---|---|---|
| 하드 실시간 | 시스템이 실패한 것입니다. 사람이 다치거나 기계가 부서질 수 있습니다 | 에어백 · 브레이크 제어 · 심장 박동기 |
| 소프트 실시간 | 품질이 떨어집니다. 늦은 결과에도 쓸모가 조금 남습니다 | 영상 통화 · 음악 재생 · 게임 화면 |
표에서 두 갈래를 가르는 것은 늦었을 때의 결과입니다. 실시간 운영체제라고 하면 대개 하드 실시간까지 받칠 수 있는 운영체제를 가리킵니다. 소프트 실시간은 범용 운영체제에 우선순위 설정을 더해 얻기도 합니다.
태스크와 우선순위
실시간 운영체제에서는 따로 도는 실행 흐름 하나하나를 태스크라고 부릅니다. 범용 운영체제의 스레드와 비슷한 단위입니다. 에어백 제어기라면 충돌 센서를 읽는 태스크, 경고등을 켜는 태스크, 기록을 남기는 태스크가 따로 돕니다.
태스크마다 우선순위를 매깁니다. 늦으면 위험한 일일수록 높은 우선순위를 받습니다. 충돌에 반응하는 태스크가 기록 태스크보다 높은 식입니다.
태스크는 언제나 세 상태 가운데 하나에 있습니다. CPU(Central Processing Unit, 중앙 처리 장치)에서 지금 도는 실행, 돌 수 있지만 차례를 기다리는 준비, 센서 값이나 타이머처럼 무언가를 기다리는 대기입니다.
stateDiagram-v2
state "준비" as Ready
state "실행" as Running
state "대기" as Blocked
[*] --> Ready
Ready --> Running: 가장 높은 우선순위라 뽑힘
Running --> Ready: 더 높은 태스크가 준비됨
Running --> Blocked: 센서 값이나 타이머를 기다림
Blocked --> Ready: 기다리던 것이 옴
그림에서 실행에서 준비로 돌아가는 화살표가 눈여겨볼 곳입니다. 태스크가 스스로 멈추지 않았는데도 운영체제가 끼어들어 멈춥니다. 다음 소절이 이 끼어들기를 풉니다.
선점과 인터럽트
스케줄러는 준비 상태의 태스크 가운데 누구에게 CPU 를 줄지 정하는 부품입니다. 운영체제의 핵심부인 커널 안에 있습니다. 실시간 운영체제의 스케줄러는 규칙이 단순합니다. 준비된 태스크 가운데 우선순위가 가장 높은 것이 언제나 돕니다.
더 높은 태스크가 준비되면 지금 돌던 태스크를 곧바로 멈춥니다. 이것을 선점이라고 합니다.
멈춘 태스크가 어디까지 했는지 저장하고 새 태스크의 것을 불러오는 일을 문맥 교환이라고 부릅니다. 멈췄던 태스크는 나중에 저장해 둔 곳부터 이어서 돕니다.
급한 일이 생겼다는 소식은 대개 인터럽트로 옵니다. 인터럽트는 장치가 CPU 에게 하던 일을 멈추고 자기를 봐 달라고 끼어드는 신호입니다. 신호를 받으면 짧은 처리 코드가 먼저 돕니다. 이 코드가 기다리던 태스크를 준비 상태로 깨웁니다.
sequenceDiagram
participant 센서 as 충돌 센서
participant 커널
participant 에어백 as 에어백 태스크
participant 기록 as 기록 태스크
Note over 기록: 낮은 우선순위로 돌던 중
센서->>커널: 인터럽트
Note over 커널: 짧은 처리 코드가 돈다
커널->>기록: 멈춘다
커널->>에어백: 깨워서 돌린다
Note over 센서,에어백: 신호부터 에어백 태스크가 돌기까지 걸린 시간
에어백->>커널: 일을 마치고 다시 기다린다
커널->>기록: 멈춘 데부터 이어서 돌린다
충돌 센서가 신호를 보낸 뒤 에어백 태스크가 돌기 시작할 때까지 시간이 조금 걸립니다. 그림에서 묶어 둔 이 구간이 인터럽트 지연입니다. 실시간 운영체제는 이 시간을 짧게 둡니다. 무엇보다 상한을 넘지 않게 만듭니다.
걸리는 시간에 상한이 있는 커널
스케줄러의 규칙만으로는 부족합니다. 커널이 하는 동작 하나하나가 오래 걸리면 그동안 급한 태스크도 기다려야 합니다. 그래서 실시간 운영체제는 커널 동작마다 걸리는 시간에 상한을 둡니다. 기다리는 태스크가 몇 개든 다음 태스크를 고르는 시간이 늘지 않게 짜는 식입니다.
메모리를 다루는 방식도 여기에 맞춥니다. 프로그램이 돌다가 필요할 때 메모리를 빌리는 동적 메모리 할당은 빈 곳을 찾는 시간이 그때그때 다릅니다. 크기가 제각각인 빈 곳 사이에서 알맞은 것을 찾아 훑어야 하기 때문입니다.
그래서 필요한 메모리를 시작할 때 모두 잡아 둡니다. 아니면 메모리 풀에서 꺼내 씁니다.
메모리 풀은 같은 크기 조각을 미리 쌓아 둔 것입니다. 빈 조각은 목록으로 묶어 둡니다. 목록 맨 앞의 조각 하나를 꺼내면 되므로 꺼내는 시간이 늘 같습니다.
flowchart TD
subgraph 동적["동적 메모리 할당"]
direction TB
D1["사용 중 · 큰 조각"] --- D2["빈 곳 · 작은 조각"]
D2 --- D3["사용 중 · 작은 조각"]
D3 --- D4["빈 곳 · 큰 조각"]
end
subgraph 풀["메모리 풀"]
direction TB
L["빈 조각 목록"] --> P3["빈 조각"]
P3 --> P4["빈 조각"]
P1["사용 중 조각"]
P2["사용 중 조각"]
end
동적 ~~~ 풀
그림에서 위쪽 조각은 크기가 제각각입니다. 아래쪽 조각은 크기가 모두 같습니다.
범용 운영체제는 메모리가 모자라면 당장 안 쓰는 내용을 디스크로 내렸다가 필요할 때 다시 올립니다. 그동안 프로그램은 디스크를 기다리며 멈춥니다. 이 멈춤이 언제 올지는 미리 알 수 없습니다. 실시간 운영체제는 대개 이 기능이 없거나 끌 수 있게 둡니다.
우선순위를 매기는 방법
우선순위를 아무렇게나 매기면 스케줄러가 규칙대로 돌아도 마감 시간을 놓칩니다. 그래서 우선순위를 정하는 방법이 따로 있습니다. 대표적인 방법은 둘입니다.
많은 태스크는 같은 일을 일정한 간격으로 되풀이합니다. 충돌 센서를 읽는 태스크가 그렇습니다. 이 간격을 주기라고 부릅니다.
주기가 짧은 태스크일수록 높은 우선순위를 미리 정해 주는 방법이 비율 단조 스케줄링입니다. 자주 도는 태스크일수록 다음 마감 시간이 빨리 오기 때문입니다. 우선순위가 돌기 전에 정해져 바뀌지 않으므로 고정 우선순위 방식이라고도 합니다.
다른 하나는 돌면서 우선순위를 바꿉니다. 그 순간 마감 시간이 가장 가까운 태스크를 먼저 돌립니다. 이 방법이 최단 마감 우선 스케줄링입니다.
어느 방법을 쓰든 만드는 사람은 미리 계산해 봅니다. 태스크마다 최악 실행 시간과 주기를 적어 둡니다. 그 값으로 모든 태스크가 마감 시간을 지킬 수 있는지 따집니다. 이 계산을 스케줄 가능성 분석이라고 부릅니다.
우선순위 역전
우선순위를 지켜도 높은 태스크가 낮은 태스크를 기다리게 되는 경우가 있습니다. 여러 태스크가 같은 자원을 나눠 쓸 때 생깁니다. 한 번에 한 태스크만 그 자원을 쓰도록 잠그는 도구가 뮤텍스입니다.
앞의 에어백 제어기로 보겠습니다. 에어백 태스크와 기록 태스크가 통신 버스 하나를 나눠 씁니다. 통신 버스는 기계 안의 부품끼리 데이터를 주고받는 통로입니다.
경고등 태스크는 이 버스를 안 씁니다. 우선순위는 에어백과 기록 사이입니다. 셋을 줄여 높은 태스크(에어백) · 중간 태스크(경고등) · 낮은 태스크(기록)라고 부르겠습니다.
낮은 태스크가 버스의 뮤텍스를 쥐고 있습니다. 이때 높은 태스크가 같은 뮤텍스를 원하면 기다립니다. 여기까지는 잠깐입니다.
그런데 그 사이 중간 태스크가 준비되면 낮은 태스크를 선점합니다. 낮은 태스크가 못 도니 뮤텍스가 안 풀립니다. 높은 태스크는 중간 태스크가 끝날 때까지 기다립니다. 중간 태스크는 그 뮤텍스와 상관이 없는데도 높은 태스크를 사실상 앞지릅니다.
sequenceDiagram
participant 낮은 as 낮은 태스크
participant 뮤텍스
participant 높은 as 높은 태스크
participant 중간 as 중간 태스크
낮은->>뮤텍스: 잠근다
높은->>뮤텍스: 잠그려 한다
Note over 높은: 막혀서 기다린다
중간->>낮은: 선점한다
Note over 낮은: 못 돌아서 뮤텍스를 못 푼다
Note over 높은: 중간 태스크가 끝날 때까지 못 돈다
Note over 중간: 할 일을 끝낸다
낮은->>뮤텍스: 푼다
뮤텍스->>높은: 넘겨준다
Note over 높은: 이제야 돈다
이것이 우선순위 역전입니다. 높은 태스크의 기다림이 뮤텍스와 상관없는 중간 태스크에 달리게 됩니다. 중간 태스크가 오래 돌수록 기다림도 길어집니다. 그래서 높은 태스크가 얼마나 기다릴지 미리 알 수 없습니다.
흔한 해법은 우선순위 상속입니다. 뮤텍스를 쥔 태스크의 우선순위를 그 뮤텍스를 기다리는 가장 높은 태스크만큼 잠시 올립니다. 그러면 중간 태스크가 끼어들지 못합니다. 낮은 태스크가 빨리 끝내고 뮤텍스를 놓습니다.
sequenceDiagram
participant 낮은 as 낮은 태스크
participant 뮤텍스
participant 높은 as 높은 태스크
participant 중간 as 중간 태스크
낮은->>뮤텍스: 잠근다
높은->>뮤텍스: 잠그려 한다
Note over 낮은: 높은 태스크만큼 우선순위를 올린다
Note over 중간: 준비됐지만 선점하지 못한다
낮은->>뮤텍스: 풀고 원래 우선순위로 돌아간다
뮤텍스->>높은: 넘겨준다
Note over 높은: 바로 돈다
Note over 중간: 높은 태스크 다음에 돈다
두 그림에서 달라진 것은 하나입니다. 중간 태스크가 낮은 태스크를 선점하지 못합니다. 그래서 높은 태스크는 낮은 태스크가 뮤텍스를 쥐고 있는 동안만 기다립니다.
화성 탐사선 패스파인더가 이 문제로 되풀이해 재시동됐습니다. 우선순위 상속을 켜는 설정 하나로 고쳤습니다.
범용 운영체제와 가르는 선
범용 운영체제와 실시간 운영체제는 먼저 챙기는 것이 다릅니다. 그래서 여러 곳에서 다르게 짜입니다. 아래 표가 그 차이를 줄마다 보입니다.
| 범용 운영체제 | 실시간 운영체제 | |
|---|---|---|
| 먼저 챙기는 것 | 평균 처리량과 공평함 | 가장 느린 응답의 상한 |
| 스케줄러 | 여러 프로그램에 시간을 고루 나눕니다 | 우선순위가 가장 높은 태스크가 언제나 먼저 돕니다 |
| 메모리 | 모자라면 디스크로 내렸다가 올립니다 | 미리 잡아 두고 디스크로 안 내립니다 |
| 크기와 기능 | 크고 기능이 많습니다 | 작고 기능이 적습니다 |
| 흔히 도는 곳 | 서버 · 노트북 · 스마트폰 | 자동차 제어기 · 의료 기기 · 공장 설비 |
표의 모든 줄은 평균 처리량과 최악 응답의 상한 가운데 무엇을 먼저 챙기느냐에서 나옵니다. 유닉스 계열 운영체제는 실행 중인 프로그램마다 시간을 나눠 줍니다. 하지만 언제까지 끝난다고 약속하지는 않습니다.
서버에서는 이쪽이 맞는 선택입니다. 드물게 늦는 요청 하나보다 전체가 얼마나 많이 처리하는지가 더 중요하기 때문입니다.
범용 운영체제도 설정을 바꿔 실시간 성질을 높일 수 있습니다. 리눅스는 PREEMPT_RT를 켜면 커널 안의 거의 모든 곳에서 선점이 가능해집니다. 그래도 커널이 크고 기능이 많습니다. 그래서 모든 동작의 최악을 따지기는 전용 실시간 운영체제보다 어렵습니다.
작은 기계 안의 실시간 운영체제
실시간 운영체제는 흔히 마이크로컨트롤러에서 돕니다. 마이크로컨트롤러는 CPU 와 메모리, 입출력 장치를 칩 하나에 담은 작은 컴퓨터입니다. 메모리가 작아서 운영체제도 작아야 합니다.
이런 기계에서는 커널과 응용 프로그램을 실행 파일 하나로 묶어 펌웨어로 굽는 경우가 많습니다. 펌웨어는 기계의 저장 장치에 한 번 써 넣어 두고 켤 때마다 도는 프로그램입니다. 「굽는다」는 그 저장 장치에 써 넣는다는 뜻입니다.
범용 운영체제는 프로그램끼리, 그리고 프로그램과 커널 사이에 벽을 둡니다. 한 프로그램이 잘못돼도 다른 프로그램이나 커널의 메모리를 못 건드리게 하려는 것입니다. 목적이 하나로 정해진 기계에서는 서로 떼어 놓을 프로그램이 적습니다. 그래서 이 벽을 두지 않는 실시간 운영체제도 흔합니다.
flowchart TD
subgraph 범용["범용 운영체제"]
direction TB
A["프로그램 A"] --> W["벽"]
B["프로그램 B"] --> W
W --> K1["커널"]
end
subgraph 펌["실시간 운영체제 펌웨어 · 실행 파일 하나"]
direction TB
T1["태스크 1"] --> K2["커널"]
T2["태스크 2"] --> K2
T3["태스크 3"] --> K2
end
범용 ~~~ 펌
위는 프로그램마다 따로 떨어져 벽을 거쳐야 커널에 닿습니다. 아래는 태스크와 커널이 한 덩어리 안에 있습니다. 사이에 벽이 없습니다.
같은 까닭으로 범용 운영체제가 갖춘 인터페이스를 전부 갖추지는 않습니다. 유닉스 계열이 따르는 표준 인터페이스인 POSIX(Portable Operating System Interface, 이식 가능 운영체제 인터페이스)도 일부만 갖추기도 합니다. FreeRTOS · Zephyr · VxWorks 가 널리 쓰이는 실시간 운영체제입니다.
치르는 값
실시간 운영체제를 쓰면 세 가지를 내줍니다.
- 처리량. 급한 일을 먼저 돌리고 자주 선점하므로, 같은 시간에 해내는 일의 총량은 범용 운영체제보다 적을 수 있습니다
- 기능. 커널을 작고 예측 가능하게 두려고 파일 시스템이나 네트워크 같은 기능을 빼거나 골라 넣게 합니다
- 설계 부담. 만드는 사람이 태스크마다 최악 실행 시간을 따지고 우선순위를 매겨야 합니다. 우선순위를 잘못 매기면 낮은 태스크가 끝내 차례를 못 받는 기아가 생깁니다
관련 항목
실시간 운영체제가 속하는 상위 분류
운영체제 · 실시간 시스템 · 임베디드 · 펌웨어 · 하드 실시간 · 소프트 실시간
실시간 운영체제를 이루는 구성 요소
커널 · 태스크 · 스케줄러 · 인터럽트 · 인터럽트 서비스 루틴 · 타이머 · 시스템 틱 · 메모리 풀 · 동적 메모리 할당
실시간 운영체제가 쓰는 스케줄링 방식
스케줄링 · 선점 · 문맥 교환 · 우선순위 기반 스케줄링 · 비율 단조 스케줄링 · 최단 마감 우선 스케줄링 · 라운드 로빈 · 스케줄 가능성 분석
실시간 운영체제가 태스크 사이에 두는 동기화 도구
뮤텍스 · 세마포어 · 메시지 큐 · 이벤트 플래그 · 우선순위 상속 · 우선순위 천장 프로토콜
실시간 운영체제에서 자주 터지는 문제
우선순위 역전 · 기아 · 데드락 · 마감 시간 초과 · 스택 오버플로
실시간 운영체제의 응답을 재는 지표
마감 시간 · 최악 실행 시간 · 인터럽트 지연 · 지터 · 결정성 · 응답 시간
실시간 운영체제를 구현한 제품
FreeRTOS · Zephyr · VxWorks · QNX · RTEMS · ThreadX · PREEMPT_RT
실시간 운영체제와 맞세워지는 범용 운영체제
유닉스 · 리눅스 · Windows · 범용 운영체제 · 시분할 · 가상 메모리
실시간 운영체제가 올라가는 하드웨어와 안전장치
다른 이름: RTOS · real-time operating system · 실시간 OS