리눅스의 커널은 Monolithic
리눅스의 커널은 기본적으로 Monolithic 구조이다. 그러나 유연성을 위해서 커널 모듈을 적극적으로 사용하고 있다.
ㅤㅤ

리눅스가 사용하는 Monolithic 커널이란 다음의 특징을 가진다.
- 하나의 거대한 커널 공간에 모든 핵심적인 서비스가 포함되어 실행된다.
- 커널 컴포넌트간에 직접 서로 함수를 호출할 수 있어, 오버헤드가 적어 좋은 성능을 가진다.
- 단, 커널의 크기가 매우 크고 복잡하다. (기존에는) 새로운 기능이 추가되거나 기능 수정을 위해서는 전체 커널을 새로 컴파일해서 사용해야했다.
ㅤㅤ

Monolithic 구조의 단점들을 해결하기 위해서 리눅스는 모듈식 설계(Loadable Kernel Module, LKM)를 적용한다.
- 장치 드라이버, 파일 시스템같은 기능들은 필요할 때에만 메모리에 올려서 사용하고, 불필요하면 내린다.
- 커널 대부분의 기능을 모듈 단위로 분리하여, 간편하게 떼고 붙일 수 있다.
- 커널 전체를 다시 컴파일하지 않아도 새로운 모듈 코드로 변경할 수 있어 마이크로 커널의 유연성을 챙길 수 있다. (커널 실행중에 모듈을 교체하더라도, 커널 입장에서는 모듈이 잠시 메모리에서 내려갔다가 올라오는거랑 같다.)
ㅤㅤ
커널의 주요 서브시스템
리눅스 커널은 부가적인 다양한 서브시스템으로 구현된다.
여기에서 커널의 운용에 항상 필요한 핵심적인 시스템들은 커널의 이미지에 내장되어서 구워지기에 모듈로 분리되지 않는다. (프로세스 관리, 메모리 관리, 기본 IPC, VFS 등) 그러나 디바이스 드라이버, 네트워크 스택, 파일 시스템 처럼 항상 필요하지 않은 시스템들은 앞서 언급했던 모듈로 분리되어 유연성을 높인다..
Linux Kernel
├─ Process Scheduler (프로세스 스케줄러)
├─ Memory Management (메모리 관리)
├─ Virtual File System (VFS)
├─ Network Stack (네트워크 스택)
├─ Device Drivers (디바이스 드라이버)
├─ Interrupt Handlers (인터럽트 핸들러)
└─ System Call Interface (시스템 콜 인터페이스)
ㅤㅤ
사실 여기 나오는 토픽 하나하나가 아티클 최소 2-3개 감이라서 최대한 간단하게 키워드만 적어뒀다. 앞으로 아티클로 계속해서 접할 내용들… 시간이 된다면..!
ㅤㅤ
프로세스 스케줄러
스케줄러도 커널의 서브 시스템에 속한다.
ㅤㅤ
이전에 공부했던 FreeRTOS와 동일하게, 리눅스는 기본적으로 시분할 + 우선순위 기반 선점형 스케줄링 방식을 사용한다. 여기에다가 추가로 공정한 시간 분배를 위한 CFS와 nice 값을 기반으로한 가중치 방식을 추가로 사용한다.
ㅤㅤ
또 FreeRTOS와 유사하게 총 5가지 상태로 구성된다.
- Running : 현재 CPU를 점유하고 실행 중
- Ready : 스케줄러의 선택을 기다리는 중
- Sleeping :
- Stopped
- Zombie
여기에 나오는 스케줄링, 프로세스 상태와 관련된 내용들은 Embedded Linux 시리즈의 ‘프로세스’ 편에서 만나볼 수 있다.
ㅤㅤ
메모리 관리
Task의 주소로 실제 메모리 주소를 사용하는 Cortex-M의 FreeRTOS 방식과 다르게, 리눅스에서는 가상 메모리를 사용한다. (이는 Cortex-A 에만 가상 메모리를 지원하는 MMU가 있기 때문임)
ㅤㅤ
이 방식으로 프로세스끼리 서로의 메모리 공간을 함부로 침범하지 못하고, 프로세스 데이터의 ‘논리적인 주소’와 ‘실제 주소’를 분리해 메모리 공간을 효율적으로 사용할 수 있다.
ㅤㅤ
가상 파일 시스템
리눅스는 모든 것이 파일이다. 파일과 프로그램, 주변장치, 프로세스까지 모든 것들을 파일처럼 관리하고 접근하는 방식을 사용한다.
ㅤㅤ
실제로 주변장치를 제어하는 파일들 중에서 GPIO의 제어를 위해 사용하는 여러 파일들을 살펴보면 가상 파일로 되어있다. (실제 파일이 아님! sysfs)
pi07@pi07:/sys/class/gpio $ ls -al
total 0
drwxrwxr-x 2 root gpio 0 Nov 25 18:11 .
drwxr-xr-x 66 root root 0 Jan 1 1970 ..
--w--w---- 1 root gpio 4096 Nov 25 18:11 export
lrwxrwxrwx 1 root gpio 0 Nov 25 18:11 gpiochip512 -> ../../devices/platform/soc/fe200000.gpio/gpio/gpiochip512
lrwxrwxrwx 1 root gpio 0 Nov 25 18:11 gpiochip570 -> ../../devices/platform/soc/soc:firmware/soc:firmware:gpio/gpio/gpiochip570
--w--w---- 1 root gpio 4096 Nov 25 18:11 unexport
ㅤㅤ
네트워크 스택
리눅스 (UNIX) 에서는 네트워크 스택을 제공한다. 특히 BSD 계열의 유닉스에서 이 TCP를 이용한 소켓 통신을 널리 퍼트리는데에 큰 공을 세웠다. 기본적으로 리눅스에서도 네트워크를 위해서 계층 구조를 사용한다.
Application (User Space)
↓ socket(), send(), recv()
Socket Layer
↓
Transport Layer (TCP/UDP)
↓
Network Layer (IP)
↓
Link Layer (Ethernet)
↓
Device Driver (Kernel Space)
↓
Hardware (NIC)
ㅤㅤ
디바이스 드라이버
커널과 하드웨어의 중간을 제어하는 시스템.
ㅤㅤ
프로세스(유저 공간)에서 HW의 제어를 요청하면 → 시스템 콜 → 커널에게 제어 넘어옴 → VFS → 디바이스 드라이버 (커널 모듈)을 거쳐 → HW 제어로 흐름이 넘어온다.
ㅤㅤ
디바이스 드라이버에는 아래에도 또 세부 분류가 있는데,
- 문자 디바이스 (파일 종류가
c: character device) - 시리얼 포트 - 블럭 디바이스 (파일 종류가
b: block device) - 하드 디스크 - 네트워크 디바이스 - 이더넷
ㅤㅤ
커널 공간과 사용자 공간의 분리
이전에 사용해봤던 (나름 OS인) FreeRTOS 와 Linux의 차이를 한 번 살펴보자.

ㅤㅤ
FreeRTOS에서도 Task의 메모리 공간을 따로 잡고, 커널의 메모리 공간도 따로 잡아서 요소들이 별도로 동작하도록 구성할 수 있었다. 그러나 돌이켜 생각해보면 다음의 특징을 가진다.
- 모든 코드가 동일한 권한을 가진다.
- 모든 코드가 HW에 직접 접근해서 제어할 수 있다.
- Task의 메모리를 보호하지 않는다. (Task A가 Task B, Task C의 메모리에 접근 가능)
- Cortex-M 환경에서 MMU 없이 동작한다.
ㅤㅤ
그러나 리눅스 쪽의 구조를 보면 FreeRTOS와 같은 듯 하면서 다르게 설계되었다.
- Application 들이 올라가는 사용자 공간, Kernel이 올라가는 커널 공간이 아예 분리되어있다.
- App 에서 HW를 직접 제어하지 못한다.
- App 끼리 격리되어있어, 서로의 메모리를 함부로 접근하지 못한다.
- MMU가 있어야 동작한다. Cortex-A 필수.
ㅤㅤ
이렇게 쪼개둔 목적이 뭘까
1. 안정성
FreeRTOS에서는 시스템이 돌다가 TaskA 에서 문제가 생겼거나 버그가 발생하면 전체 시스템이 뻗어버릴 수 있다. TaskA 가 실수로 이상한 메모리에 접근해 값을 변경해버렸는데, 그게 하필이면 Kernel의 스케줄링을 위한 currentTCB를 가리키는 주소라서 Context-Switch가 정상적으로 이루어지지 않는다면? 그러면 모든 스케줄링이 갑자기 엉켜버리면서 시스템이 뻗어버리게 될 수 있다.
ㅤㅤ
Linux 에서는 App 간의 공간도 격리가 되어있고, 커널과의 공간 또한 분리가 되어있기 때문에, 함부로 이상한 메모리 주소를 참조하려고 하면 Segmentation Fault 를 발생시켜 이상한 동작의 수행을 막을 수 있다. 이 경우 App만 크래시가 나고, 나머지 커널과 다른 실행중이던 App에는 아무런 영향이 없도록 설계가 되어있다.
ㅤㅤ
2. 보안
만약 위 예시가 다른 App의 중요한 값을 몰래 읽는 코드였다면? 갑자기 내 통장에 있던 1조원이 한 순간에 사라지는 상황이 찾아올 수도 있다. 메모리 공간을 나누어, 특정 Task에서 다른 Task의 공간을 함부로 접근할 수 없도록 하는 방식으로 보호할 수 있다.
ㅤㅤㅤㅤ
3. 멀티 사용자
FreeRTOS에서는 단일 사용자 라는 개념이 없이, 모든 Task가 동등한 권한을 가졌다.
ㅤㅤ
UNIX는 이와 달리, 기본적으로 여러 사용자가 동시에 로그인 할 수 있는 구조이다. 그래서 사용자별로 공간을 격리해줄 필요가 있고, 관리자와 일반 사용자간의 권한 구분 또한 필요하다.
ㅤㅤ
윈도우는 처음부터 PC(Personal Computer)를 위해 만들어진 OS였기에 한 명의 사용자가 사용하다가 로그아웃하면 다른 사용자가 사용하는 구조.
이에 반해 Linux(UNIX)는 서버용 OS 로 출발했기에 여러 사용자가 동시에 로그인을 하고 사용할 수 있는 환경이다. “여러 사용자의 동시 로그인”이라는게 이런 차이를 말한다는걸 생각하면서 접근하자.
ㅤㅤ
CPU의 특권 레벨
CPU 자체에서 이런 사용자 별 OS 권한 분리를 위해 기능들이 만들어져있다. 혹시 사용자가 들어와서 시스템의 설정들을 변경해버리면서 문제를 일으킨다거나, 함부로 HW에 접근해 멋대로 제어하는 (핵폭탄을 발사해버린다는 등) 문제를 막기 위해서 일반 모드와 커널 모드의 권한을 분리해두었다.
ㅤㅤ
시스템의 root 사용자는 커널모드를 사용해 시스템 값들을 설정할 수 있지만, 일반 사용자 또는 프로그램에서는 이를 수행하지 못하게 막아둔다.
ㅤㅤ
EL0 (Exception Level 0) - User Mode
- 애플리케이션 실행
- 제한된 명령어만 실행 가능
- 하드웨어 직접 접근 불가
ㅤㅤ
EL1 (Exception Level 1) - Kernel Mode
- 커널 실행
- 모든 명령어 실행 가능
- 하드웨어 직접 제어
- MMU 설정
ㅤㅤ
EL2 (Exception Level 2) - Hypervisor
- 가상화 (VM)
ㅤㅤ
EL3 (Exception Level 3) - Secure Monitor
- TrustZone
ㅤㅤ
시스템 콜
사용자 프로그램에서 HW를 제어할 필요가 있을 때는 그러면 어떻게 하냐? 에 대한 답이 바로 시스템 콜이다. 시스템 콜이란 사용자 프로그램이 커널 기능을 요청하는 방법이라 볼 수 있다. 파일 읽기/쓰기, 네트워크 통신, 새로운 프로세스의 생성, 메모리 할당 등의 동작을 수행하기 위해서는 커널에게 작업을 요청해야하며, 이때 시스템 콜을 호출해 작업을 요청한다.
ㅤㅤ
시스템 콜이 호출되면 아래에서는 무슨 일이 일어나나?
사용자 쪽에서 아래 코드로 작성된 프로그램을 실행했다고 하자. 그럼 어떤 과정을 거쳐서 이게 실행될까?
ㅤㅤ
에 대해서 궁금해서, 직접 내가 사용하고 있는 라이브러리인 glibc 와 라즈베리파이 커널쪽 코드를 타고 들어가면서 실제로 어떻게 동작이 되고있는지 파악해봤다. 생각보다 토픽의 내용이 길어져서, 이건 시스템콜 함수가 호출되면 아래에선 무슨일이 일어나나 라는 글에서 따로 정리해두었다!
ㅤㅤ
요점만 말해보자면, 사용자 프로세스에서 호출한 함수는 아래 그림의 구조를 위에서부터 차례대로 한 칸씩 내려가면서 호출하는 추상화 구조를 가지고 있다.

- User Process에서 함수 호출
- System Library 에서 인터럽트 발생으로 커널에게 제어권 넘김 (svc 또는 swi)
- 커널이 인터럽트에 대해 알맞은 핸들러 호출
- 핸들러 내에서 커널 모듈을 통해 각 모듈에 대한 동작 호출
- HW에게 명령 전달 / 자원에 대한 명령 전달
ㅤㅤ
Define에 대한 처리야 어차피 전처리기에서 처리해주는거니깐 전혀 실행 과정에서 영향이 없다. 그러나 시스템 콜을 통해 커널에게 제어권을 넘기는 과정은 단순 함수 호출보다 꽤나 헤비한 작업이다.
시스템 콜 시 [모드 전환 + 레지스터 저장/복구 + TLB 청소 + 캐시 미스 + … ] 의 다양한 동작이 수반되기 때문에, 가능한 시스템 콜을 줄이는 방식으로 구현하는 것이 좋다. (예를 들자면 파일 복사를 위해 짧은 길이를 자주 복사하기보다는 버퍼를 이용해 한 번에 크게크게 복사하기)
ㅤㅤ
'Embedded System > Embedded Linux' 카테고리의 다른 글
| [Embedded Linux] Linux 시그널 (0) | 2025.11.29 |
|---|---|
| [Embedded Linux] 리눅스의 프로세스 타파 (0) | 2025.11.27 |
| [Embedded Linux] 라즈베리파이의 부팅 (1) | 2025.11.25 |
| [Embedded Linux] 시스템콜 함수가 호출되면 아래에선 무슨일이 일어나나 (0) | 2025.11.24 |
| [Embedded Linux] UNIX, POSIX 그리고 LINUX (1) | 2025.11.23 |