
본격적인 드라이버 관련 내용이 시작되었다.
정리할 것도 많고 배울 것도 많아서 머리가 터질 것 같음!
그래도 할 건 해야겠죠
운영체제(OS) 분류
Desktop OS
일반 사용자용으로, GUI 중심
Windows, macOS 등
Server OS
네트워크 서비스 및 데이터 처리용으로, 안정성 중심
Linux (RHEL, CentOS, Ubuntu Server), Windows Server
Embedded OS
특정 하드웨어를 제어하거나 제한된 자원을 관리하는 용도
RTOS (Real-Time OS): FreeRTOS, uC/OS (엄격한 시간 제어 필요 시)
Embedded Linux: Android, Raspberry Pi OS, Yocto Project
임베디드 리눅스 부팅 프로세스
| 단계 | 구성 요소 | 역할 |
|---|---|---|
| 1 | Bootloader | 하드웨어 초기화 및 커널 로딩 (예: U-Boot) |
| 2 | Device Tree | 하드웨어 정보(핀 맵, 주소 등)를 커널에 전달 (.dtb, dtoverlay) |
| 3 | Kernel | OS의 핵심. 메모리/프로세스 관리 및 드라이버 로딩 (zImage) |
| 4 | Root Filesystem | 사용자 공간. 실행 파일 및 설정 파일 저장소 (/bin, /etc, Buildroot 등으로 생성) |
개발 환경 (Dev Env)
임베디드 개발을 위해 필요한 도구
GCC, IDE(Eclipse, VS Code 등)처럼 작업을 도움
Application Area (User Space)
User Code : 사용자가 작성한 프로세스들
C Library : 커널 기능(open/read/write/close)을 쉽게 사용할 수 있도록 감싼 함수들
Kernel area (Kernel Space)
Process Manager (Scheduler)
CPU 자원을 누구에게 얼마나 줄지 결정 (스케줄링)
Memory Manager
가상 메모리 관리, 페이지 할당/해제 담당
Filesystem Manager (VFS)
VFS (Virtual File System) : 모든 파일시스템을 공통된 인터페이스로 관리
지원 포맷 : ext4 (리눅스 표준), ntfs/fat (윈도우 호환), procfs/sysfs (가상 파일시스템)
Network Manager
TCP/IP 스택 및 네트워크 패킷 처리
Device Manager (Device Drivers)
하드웨어 제어를 담당하는 드라이버들이 모여 있는 곳
Hardware
CPU, Memory (RAM), Console (UART), Disk (SD Card), Network Interface (NIC) 등 실제 물리적인 장치들

콘솔(Console)과 터미널(Terminal)
콘솔 (Console)
리눅스 컴퓨터 본체에 물리적으로 직접 연결된 입출력 장치 (모니터+키보드)
물리적 접근이 가능하므로 기본적으로 root 로그인이 허용됨
터미널 (Terminal)
네트워크나 GUI 창을 통해 원격으로 접속하는 가상 클라이언트 (Remote Client)
/dev/tty 나 /dev/pts 같은 가상 장치 파일이 무수히 많이 생성됨
보안상의 이유로 기본적으로 root 직접 로그인이 차단되어 있고, 일반 유저 접속 후 전환 필요
리눅스 하드웨어 자원의 종류
CPU (Processor)
Single Core / Multi-Core
Memory
Main Memory, RAM : 실행 중인 데이터가 상주하는 휘발성 메모리 (예 : D-RAM, LPDDR4, HBM, DDR-5 등)
Storage (저장 장치) : 데이터가 영구 저장되는 공간 (예 : Hard Disk, SSD, SD Card)
Console
stdin, stdout, stderr 스트림 처리
터미널 또는 UART 연결, ttyS0 등
I/O (Device/Peripherals)
연결 방식과 데이터 처리에 따라 드라이버 종류가 나뉨 (Char, Block, Network 등)
포팅(Porting)
특정 보드(Board) 하드웨어 위에서 리눅스가 동작하도록 소프트웨어를 이식하는 과정
CPU, Memory, Console, I/O 등 모든 장치에 대해 커널이 인식하도록 작업이 필요함
하드웨어 자원별 포팅 방법
CPU : 포팅 난이도가 가장 높음 → 주로 칩 제조사(SoC Vendor)가 미리 포팅해서 BSP로 제공
Memory : 데이터시트를 참조하여 Timing, 전압 등을 커널/부트로더 설정에 맞춰주는 작업
Console : 대부분 기본 제공되나 UART 드라이버 등을 통해 디버깅 출력을 확보하는 것이 최우선 과제
I/O 장치 : 나머지 센서나 주변 장치들은 개발자가 직접 드라이버를 작성하거나 Device Tree를 수정하여 포팅해야 함
현재 커널 정보 확인
터미널에서 현재 실행 중인 커널의 정보 확인
kernel.org에서 동일한 버전의 커널 소스 다운로드 가능
$ uname -a
Linux raspberrypi 6.1.21-v8+ #1642 SMP PREEMPT Mon Apr 3 17:24:16 BST 2023 aarch64 GNU/Linux
$ uname -r # 커널 릴리즈 버전만 확인
6.1.21-v8+
커널 소스 준비
sudo -s 명령어로 root 사용자 전환
다운로드한 커널 소스 압축 해제 후 해당 디렉토리로 이동
소스 코드 버전 검증
root@host:~/linux-6.1.21# make kernelversion
6.1.21
커널 소스 디렉토리 구조 (Source Tree)
| 디렉토리 | 설명 | 디렉토리 | 설명 |
|---|---|---|---|
| arch | 아키텍처 종속적 코드(CPU별 arm64, x86 등 저수준 코드) | mm | 메모리 관리 하위시스템(가상 메모리, 페이지 할당 등) |
| crypto | 암호화 API(SHA, DES, AES 등 암호화 알고리즘 구현) | net | 네트워킹 하위시스템(TCP/IP 스택, 소켓, 프로토콜 구현) |
| Documentation | 커널 소스 문서(API 설명 및 서브시스템 기술 문서) | scripts | 빌드 스크립트(make menuconfig 등 빌드 보조 도구) |
| drivers | 디바이스 드라이버(전체 소스의 60% 이상, 각종 하드웨어 제어) | security | 리눅스 보안 모듈(SELinux, AppArmor 등 보안 관련 코드) |
| fs | 파일 시스템(VFS 및 ext4, fat, procfs 등 구현) | sound | 사운드 서브 시스템(ALSA 등 오디오 드라이버 및 프레임워크) |
| include | 커널 헤더 파일들(개발에 필요한 구조체, 매크로 정의 linux/) | usr | 초기 유저 공간 코드(initramfs 생성을 위한 코드 포함) |
| init | 커널 부트와 초기화(start_kernel 등 부팅 진입점) | block | 블록 레이어(저장 장치 I/O 스케줄링 및 블록 디바이스 관리) |
| ipc | 프로세스 간 통신(메시지 큐, 세마포어, 공유 메모리) | virt | 가상화 하위시스템(KVM 등 가상화 지원 코드) |
| kernel | 커널 코어 (핵심)(스케줄러, 시그널, 타이머 등 핵심 기능) | lib | 유틸리티 루틴(커널 내부용 공통 라이브러리 함수) |
주요 디렉토리 탐색 예시
root@host:~/linux-6.1.21/arch# ls
alpha arm csky ia64 loongarch microblaze nios2 parisc riscv sh um xtensa
arc arm64 hexagon Kconfig m68k mips openrisc powerpc s390 sparc x86
root@host:~/linux-6.1.21/drivers# ls
accessibility bluetooth cpuidle eisa hid ... (매우 많음)
root@host:~/linux-6.1.21/drivers# find . -name "dht*"
./iio/humidity/dht11.c # 이미 커널에 포함되어 있음을 확인
드라이버의 실행 영역
드라이버는 커널 공간(Kernel Space)에서 실행되고, 사용자 공간(User Space)와 달리 메모리 보호 기능이 느슨함
드라이버의 작은 버그가 시스템 전체를 멈추게 하거나 데이터를 파괴하는 등 치명적인 피해가 발생할 수 있음
심볼 확인
/proc/kallsyms 명령어는 현재 커널에 로드된 모든 심볼(함수, 변수)의 주소와 이름을 확인
디버깅 시에 유용한 기능
콜백(Callback) 구조
드라이버는 주도적으로 실행되기보다는 커널이나 유저의 요청에 반응하는 수동적인 구조
File Operations : open , read , write 등의 시스템 콜이 들어왔을 때 호출될 함수 연결 필요
Interrupt Service Routine(ISR) : 하드웨어 인터럽트 발생 시 호출될 핸들러 함수 등록 필요
커널 기능 활용
메모리 할당 : malloc 대신 kmalloc , vmalloc 등을 사용
동기화 : mutex 등을 사용하여 동시성 문제(Race Condition)을 해결해야 함
인터럽트 : 하드웨어 신호 처리를 위한 인터럽트 등록 및 해제 기능을 사용
드라이버 제작 형태 결정
커널 형태와 모듈 형태 중 드라이버의 형태를 결정
소스 위치에 따른 분류(Source Location)
In-Tree(인트리)
리눅스 커널 소스 트리 내부(/drivers/…)에 코드를 위치시킴
커널 공식 드라이버들이 사용하는 방식으로, 커널 빌드 시스템(Kconfig, Makefile)을 그대로 따름
Out-of-Tree(Ex-Tree, 아웃 오브 트리)
커널 소스 외부에 코드를 위치시킴 (예 : 사용자 홈 디렉토리 /home/user/my_driver/ )
개발 단계에서 주로 사용하며, 해당 모듈만 빠르게 빌드할 수 있어 효율적
빌드 결과물에 따른 분류(Build Artifact)
커널 소스(linux-x.x.x)를 빌드하면 크게 두 가지 형태의 결과물을 얻을 수 있음
커널 이미지 포함(Built-in)
vmlinux → Image → zImage (압축된 부팅 이미지, RPi는 kernel8.img 등)을 얻음
.c → .o → 링킹되어 vmlinux에 영구적으로 포함
부팅 시 무조건 메모리에 올라가므로, 필수적인 드라이버(파일시스템, 부팅 디스크 등)는 이 방식으로 제작
명령어 : # make zImage
로드 가능한 모듈(Loadable Module)
.c → .o → .ko 로 생성된 .ko (Kernel Object) 파일을 얻음
필요할 때만 메모리에 적재(insmod)하고, 안 쓰면 제거(rmmod)할 수 있음
/lib/modules/$(uname -r)/kernel/drivers 경로에서 현재 설치된 모듈들 확인 가능
명령어 : # make modules
Device Tree Blob
커널 빌드 시 드라이버 외에 생성되는 .dtb 파일
부팅 시 하드웨어 정보를 커널에 전달하는 역할
(예 : bcm2711-rpi-4-b.dtb)
문자 디바이스 (Character Device)
데이터를 바이트(Byte)단위로, 스트림 형태로 처리
주로 순차적 접근(Sequential Access)을 하며 버퍼링 없이 즉시 입출력이 일어나는 경우 많음
키보드, 마우스, 시리얼 포트(UART), 모뎀, 비디오 장치 등 사람이 사용하는 장치
/dev 디렉토리 아래의 디바이스 노드 파일을 통해 접근 (예 : /dev/console , /dev/ttys0 )
블록 디바이스 (Block Device)
데이터를 블록 단위로 묶어서 처리
순차적 및 랜덤 접근(Random Access)이 가능하고 커널 내부의 버퍼 캐시를 사용함
/dev 아래의 노드를 통해 접근(예 : /dev/sda)하지만 주로 파일 시스템을 통해 간접적으로 사용
하드 디스크(HDD), SSD, USB 메모리, SD 카드 등 저장 장치가 대부분
리눅스 커널 소스 트리 내 드라이버
<Kernel Top Dir>/drivers/ 디렉토리 아래에 종류별로 정리되어 참고하면 좋음
제어하려는 장치와 가장 유사한 기존 드라이버를 찾고 수정하여 활용하는 것이 경제적
| 경로 | 설명 | 활용 팁 |
|---|---|---|
drivers/char/ | 문자 디바이스 드라이버 | 범용적인 문자 장치 예제 찾기 좋음 (예: mem.c) |
drivers/block/ | 블록 디바이스 드라이버 | 램디스크(brd.c) 등 저장 장치 관련 |
drivers/misc/ | 기타 잡다한 드라이버 | 분류하기 애매한 간단한 드라이버들. 초보자 참고용으로 적합 |
drivers/input/ | 입력 장치 드라이버 | 키보드, 마우스, 터치스크린 등 입력 서브시스템 관련 |
drivers/video/ | 디스플레이 관련 | 프레임버퍼(Framebuffer) 등 화면 출력 장치 |
drivers/i2c/ | I2C 버스 드라이버 | I2C 통신을 사용하는 센서 드라이버들의 기초 |
drivers/iio/ | 산업용 I/O (IIO) | ADC, 가속도 센서, 온습도 센서 등 최신 센서 드라이버 표준 |
실행 환경과 구조의 차이
| 구분 | 응용 애플리케이션 (User App) | 커널 모듈 (Kernel Module) |
|---|---|---|
| 실행 공간 | User Space (사용자 영역) | Kernel Space (커널 영역) |
| 진입점 | main() 함수에서 시작하여 절차적으로 수행 | module_init()으로 등록되고, 요청(Call)이 있을 때만 콜백 함수가 실행됨 |
| 실행 방식 | 능동적, 순차적 (Flow 중심) | 수동적, 이벤트 기반 (Event Driven: 시스템 콜, 인터럽트 등) |
| 오류 영향 | 프로세스만 죽고 시스템(OS)은 계속 동작 (Segfault 등) | 시스템 전체가 멈추거나 재부팅됨 (Kernel Panic) |
| 이식성 | 하드웨어에 비교적 독립적 (OS 위에서 동작) | 하드웨어에 매우 의존적 (레지스터 직접 제어 등) |
| 라이브러리 | 표준 C 라이브러리(glibc) 등 풍부한 라이브러리 사용 가능 | 커널 내부 함수(printk, kmalloc 등)만 사용 가능 |
기본 개념
리눅스 커널의 기능 확장을 위해 동적으로 로드되는 코드 조각
주로 .ko (Kernel Object) 확장자를 가짐
필요한 기능을 런타임에 동적으로 로드/언로드하여 사용할 수 있는 구조
디바이스 드라이버를 개발할 때 가장 많이 사용되는 형태
장점
동적 로딩/언로딩
시스템을 재부팅하지 않고도 새로운 드라이버를 메모리에 올리거나 내릴 수 있음
메모리 효율성
필요한 기능만 메모리에 올리므로 커널 메모리(zImage 크기)를 최소화
빠른 개발 사이클
커널 전체를 다시 컴파일(make zImage)할 필요 없이 해당되는 모듈 파일만 빠르게 빌드
단점
엄격한 형식
매크로, 라이선스 명시, 초기화/종료 함수 등 커널이 정한 규칙을 반드시 따라야 함
제한된 환경
오직 C언어로만 작성해야 하며 부동소수점 연산 사용 불가
디버깅 어려움
printk 로 로그를 찍고 dmesg 로 확인해야 하며, 버그 발생 시 시스템 다운으로 원인 파악 어려움
CPU의 실행 모드(Priviliege Level)
User Mode
응용 프로그램(User App)이 실행되는 제한된 영역
하드웨어 직접 제어는 불가
Kernel Mode (SVC Mode)
운영체제 커널 및 드라이버가 실행되는 영역
하드웨어 자원에 접근 가능
전환 메커니즘
응용 프로그램이 하드웨어 자원을 쓰기 위해서는 시스템 콜(System Call)을 통해 커널 모드로 전환해야 함
MMU가 가상 주소를 물리 주소로 변환하는 주소 공간 매핑 작업 수행
User Space
각 프로세스마다 독립적인 주소 공간(0x00000000 ~ 0xBFFFFFFF)을 가짐
Kernel Space
모든 프로세스가 공유하는 상위 주소 공간(0xC0000000 ~ 0xFFFFFFFF)에 매핑
참고 이미지

API (Application Programming Interface)
응용 프로그램은 라이브러리에서 제공하는 POSIX 표준 API 함수를 사용
POSIX C Library : IEEE에서 정한 표준 인터페이스 (예 : glibc)
응용 프로그램 → C 라이브러리 → 커널
시스템 콜 수행 과정 예시
User App
printf() 호출 → C Library의 write() 함수 호출
C Library
write() 내부에서 SWI 또는 SVC 명령어 실행
CPU
인터럽트 발생을 감지하고 User→Kernel(SVC)로 모드 전환
Vector Table
CPU는 미리 정의된 벡터 테이블의 vector_swi 주소로 점프
Kernel Handler
vector_swi() → sys_write() 실행
Driver
sys_write() → VFS를 거쳐 해당 장치 드라이버의 write 함수 실행
유저 영역에서의 커널 영역 자료 참조
드라이버 개발이나 디버깅 시 유저 영억에서 커널 내부 정보를 확인하거나 설정을 변경할 때 사용하는 기능
가상 파일시스템으로, 읽고 쓰기를 통해 커널 영역과 상호작용 가능
/proc (Process Filesystem)
리눅스 커널의 프로세스 및 시스템 상태 정보를 보여주는 가상 파일시스템
실제 디스크에 저장되지 않고 메모리에만 존재함 (type proc )
root@host:/lib/modules/6.1.21-v8+/kernel/drivers# mount | grep proc
proc on /proc type proc (rw,relatime)
systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=29,pgrp=1,timeout=0,minproto=5,maxproto=5,direct)
/proc 의 주요 파일
/proc/cpuinfo : CPU 아키텍처 및 코어 정보
/proc/meminfo : 메모리 사용량 및 여유 공간
/proc/cmdline : 부팅 시 커널에 전달된 인자(Boot args)
/proc/modules : 현재 로드된 커널 모듈 목록
/sys (Sysfs)
리눅스 커널의 디바이스 모델을 계층적으로 보여주는 가상 파일시스템
드라이버가 하드웨어 정보를 보여주거나, 유저가 드라이버 설정을 실시간으로 변경할 때 사용
/sys 의 주요 경로
/sys/class/gpio : GPIO 핀 제어 인터페이스
/sys/bus : 시스템 버스(I2C, SPI, USB 등) 연결 정보
/sys/devices : 시스템의 모든 디바이스 트리 계층 구조
/sys/module : 로드된 모듈의 파라미터 정보
부팅에 필요한 4가지 요소 같은 부분은 이전에 살짝 배웠던 개념이라 이후에 나오는 내용보다는 익숙하다.
아마도 다다음 내용부터는 처음 보는 함수가 잔뜩 나오게 될 것이다.
우선 유저 프로세스와 커널 프로세스가 어떤 차이가 있는지만 잘 알고 있으면 좋을 듯!