콘텐츠로 바로가기

Device Drivers & Module Programming

커널의 기능을 동적으로 확장하고 하드웨어와 소프트웨어 사이의 최종 통번역가 역할을 수행하는 디바이스 드라이버의 물리적 결합과 프로그래밍을 다루는 학습 노드입니다.

Article
M

Me

hyunyoun's Blog

operating-systems-system-mechanicsoperating-systemssystem-mechanicsstoragei-o-mechanicsdevice-driversmodule-programmingos-storage9 min read

1. Overview

디바이스 드라이버와 모듈 프로그래밍(Device Drivers & Module Programming)은 USB 키보드를 컴퓨터에 꽂는 순간, OS 커널이 해당 장치와 통신할 인터페이스를 찾아 사용자의 키 입력을 화면 텍스트로 변환하는 하드웨어-소프트웨어 중개(Driver) 공학입니다.

학습자는 커널 안으로 동적으로 적재/해제되는 리눅스 커널 모듈(LKM, Loadable Kernel Module)의 수명 사이클(init_module / cleanup_module)을 살펴봅니다. 나아가 드라이버가 하드웨어 레지스터를 MMIO로 접근하는 방식, 하드웨어가 이벤트를 CPU에게 IRQ/Interrupt로 알리는 통신 계약, 장치 파일(/dev/)을 통해 복잡한 하드웨어를 read()/write() 인터페이스로 다루는 문자/블록 디바이스 드라이버 추상화를 익힙니다.

2. Scope & Boundaries

In-Scope

  • 커널 모듈 생명 주기 (LKM Lifecycle): insmod, rmmod, modprobe, lsmod, init/exit 엔트리 포인트, 커널 심볼 테이블 링킹(EXPORT_SYMBOL).
  • 디바이스 파일 추상화 (Device File Abstraction): 주 번호(Major Number)와 부 번호(Minor Number), char_dev, blk_dev, register_chrdev_region.
  • 하드웨어 통신 물리 (Hardware Comms): MMIO(Memory-Mapped I/O), I/O 포트(Port), IRQ(Interrupt Request) 등록, request_irq().
  • 드라이버 프레임워크 (Driver Frameworks): 리눅스 드라이버 모델(kobject/sysfs), PCI/USB 드라이버 바인딩(Probe/Remove), udev 장치 규칙.

Out-of-Scope

  • OS 커널 스케줄러 소스코드 수정: 드라이버를 넘어 커널의 CFS 스케줄러 알고리즘 자체를 바꾸는 작업 \rightarrow 03-02-03. CPU Scheduling Algorithms 영역.
  • 펌웨어(Firmware) 자체 작성: 하드웨어(MCU, GPU) 칩 안에 구워 넣는 바이너리 코드 자체의 설계 \rightarrow 02-03. Firmware Engineering 영역.

Boundaries

  • Driver vs. Application (Ring 0 vs Ring 3): 일반 유저 앱(Ring 3)은 하드웨어 레지스터(I/O Port) 주소에 직접 접근할 권한이 없어 ioctl() 같은 커널 인터페이스를 통해야 합니다. 반면 드라이버(Ring 0)는 inb(port), outb(val, port) 어셈블리로 하드웨어 레지스터에 직접 접근할 수 있지만, 작은 버그도 커널 패닉(Kernel Panic)으로 이어질 수 있으므로 엄격한 제약과 검증이 필요합니다.

3. Counterexample

  • 인터럽트 핸들러 안에서의 sleep 호출 (Sleeping in ISR): IRQ 핸들러(ISR, Interrupt Service Routine) 안에서 sleep(), msleep(), 또는 세마포어(Semaphore) 대기를 호출하면 심각한 문제가 생깁니다. 인터럽트 핸들러는 인터럽트가 제한된 원자적(Atomic) 컨텍스트에서 실행되므로, 이 상태에서 잠들면 락 해제와 스케줄링이 막혀 Hard Lockup이 발생할 수 있습니다. 오래 걸리는 처리는 반드시 워크큐(Workqueue)나 태스크렛(Tasklet) 같은 하단부(Bottom Half)로 미뤄야 합니다.
  • 모듈 언로드(rmmod) 시의 해제 망각 (Resource Leak on Unload): 드라이버 모듈이 init 시 IRQ request_irq(45, my_handler)로 인터럽트 채널을 예약하고, cleanup_module()에서 free_irq(45) 해제를 빠뜨리면 문제가 됩니다. rmmod로 모듈을 제거하면 드라이버 코드는 커널 메모리에서 사라지지만, IRQ 45번 채널은 여전히 사라진 주소(my_handler)를 가리키는 Dangling Pointer로 남을 수 있습니다. 이후 인터럽트가 발생하면 CPU가 유효하지 않은 주소를 실행하려다 커널 패닉이 발생합니다.

4. Prerequisites

  • 인터럽트와 예외 (Basic): 하드웨어가 CPU에 신호를 보내 실행 흐름을 전환하는 인터럽트(IRQ)의 기작을 알아야 드라이버의 핵심을 이해할 수 있습니다. (03-01-03 Interrupts & Exceptions)
  • 메모리 맵 I/O (Recommended): 하드웨어 레지스터 주소를 가상 메모리로 맵핑(ioremap)하는 구조를 이해해야 드라이버 코드를 쓸 수 있습니다. (03-04-02 HCI & I/O Bus)

5. Learning Map

Sequence Core Cluster Objective & Description Evidence (BoK)
1 LKM Lifecycle OS를 재부팅하지 않고 모듈(.ko)을 적재하고 해제하는 LKM의 init/exit 수명 주기를 이해합니다. P1
2 Device File Abstraction /dev/sda1 같은 장치 파일을 통해 주/부 번호와 write() 호출이 드라이버로 연결되는 추상화를 살펴봅니다. P5
3 IRQ & Register Access 하드웨어 인터럽트(IRQ) 채널을 예약(request_irq)하고, 하드웨어 레지스터를 MMIO로 접근하는 통신 방식을 이해합니다. Industry
4 PCI/USB Probe Model 장치를 연결하는 순간 OS가 드라이버를 찾아 probe()를 호출하고, 제거 시 remove()를 호출하는 핫플러그 바인딩 모델을 익힙니다. Industry

6. Learning Topics

Basic

Core Topic 01: 커널 모듈 적재와 해제, LKM 수명 사이클 (Loadable Kernel Module)

  • Why to Learn: 리눅스 커널 전체를 재컴파일하고 재부팅하지 않아도, 필요한 드라이버 코드(.ko 파일)를 동적으로 적재하고 해제할 수 있는 모듈식 커널 아키텍처를 이해하기 위해서입니다.
  • What to Learn:
    • Concepts: LKM(Loadable Kernel Module), module_init(), module_exit(), MODULE_LICENSE("GPL"), 커널 심볼 EXPORT.
    • Skills: 크로스 컴파일(Cross-compile) 세팅, printk() 커널 로그 디버깅.
    • Tools: 리눅스 insmod, rmmod, modprobe, dmesg.
    • Trade-offs: LKM 방식은 필요한 기능만 골라 동적으로 커널에 추가할 수 있어 기본 커널 이미지(vmlinuz)를 작게 유지하지만, 해당 커널 버전(5.15.x)에 맞게 컴파일된 모듈만 삽입할 수 있습니다. 커널 버전이 바뀌면 insmod: ERROR: could not insert module 같은 호환성 문제가 발생할 수 있습니다.
  • How to Learn:
    • 1단계: C 언어로 단순한 hello_kernel.c를 작성합니다. module_init(my_hello_init) 함수 안에서 printk(KERN_INFO "Kernel Loaded!\n")을 출력하고, module_exit(my_hello_exit) 에서 printk(KERN_INFO "Kernel Unloaded!\n")을 출력하는 커널 모듈의 적재/해제 수명 주기를 확인합니다.
    • 2단계: make 빌드로 hello_kernel.ko 파일을 생성하고, sudo insmod hello_kernel.ko 로 커널 메모리 공간에 적재한 뒤, dmesg | tail로 커널 링 버퍼에서 "Kernel Loaded!" 메시지를 확인합니다. sudo rmmod hello_kernel로 제거하며 종료 메시지를 확인합니다.
  • Implement: 최소 기능 LKM 작성 가이드. 실제로 컴파일하고 삽입하기 어려운 환경이라면, 파이썬 Plugin 관리자 모사: PluginManager 클래스가 런타임에 importlib.import_module("my_driver") 로 모듈을 동적 로드하고 .init() 호출, 삭제 시 .cleanup()del로 메모리에서 완전히 제거하는 LKM 탈착 생명주기 모사 코드.

Core Topic 02: 장치 파일과 주/부 번호 체계 (Device Files)

  • Why to Learn: python3 > /dev/ttyS0처럼 표준 파일 인터페이스를 통해 직렬 포트로 데이터를 보낼 수 있는 리눅스 장치 파일 추상화를 이해하기 위해서입니다.
  • What to Learn:
    • Concepts: 주 번호(Major Number), 부 번호(Minor Number), 문자 디바이스(char_dev), 블록 디바이스(blk_dev), mknod.
    • Skills: register_chrdev_region(), alloc_chrdev_region(), file_operations 구조체 바인딩.
    • Tools: 리눅스 ls -l /dev/, mknod, /proc/devices.
    • Trade-offs: 모든 장치를 /dev/ 하나의 이름 공간(Namespace)으로 통일하면 스크립트에서 일관된 파일 인터페이스로 하드웨어를 다룰 수 있습니다. 다만 echo "FullSpeed" > /dev/fan_control_module 같은 잘못된 입력이 장치 제어 레지스터에 그대로 전달될 수 있어 타입 안전성이 낮습니다.
  • How to Learn:
    • 1단계: 커널이 /dev/sda 뒤에 "주 번호(Major): 8번은 SCSI/SATA 담당 드라이버, 부 번호(Minor): 0번은 1번째 디스크 전체"라는 매핑을 두고, open("/dev/sda") 호출 시 이 정보를 바탕으로 관련 file_operations 구조체를 찾아 Dispatch하는 흐름을 분석합니다.
    • 2단계: 드라이버가 register_chrdev(42, "my_kbd", &my_fops) 를 호출하면 커널 장치 테이블에 "42번은 my_fops 구조체"라는 매핑이 등록됩니다. 사용자가 mknod /dev/my_kbd c 42 0으로 장치 파일을 만들면 echo "data" > /dev/my_kbdmy_fops.write() 함수 호출로 이어집니다.
  • Implement: 파이썬 Device_Registry 딕셔너리(Major → Driver_Object) 가상 OS. driver_kbd.register(major=42)가 호출되면 레지스트리에 추가. 유저 레이어에서 open('/dev/kbd') 시 레지스트리 42번 엔트리를 찾아 .write(data) 다형성 호출하여, 'kbd 드라이버'의 echo 로직이 실행되는 가상 VFS 디스패치 증명.

Practical

Core Topic 03: IRQ와 MMIO 레지스터 기반 하드웨어 통신 (Hardware Communication)

  • Why to Learn: 드라이버 작성은 하드웨어 데이터시트에서 레지스터 오프셋(예: 제어 레지스터 0x04)을 읽고, 이를 MMIO 포인터로 접근해 장치를 제어하는 작업입니다. 마이크로컨트롤러(MCU/RPI)와 같은 장치 제어의 기본 원리를 익히기 위해서입니다.
  • What to Learn:
    • Concepts: 레지스터 맵(Register Map), ioremap(phys_addr, size), ioread32(), iowrite32(), request_irq(irq, handler, flags, name).
    • Skills: 인터럽트 상단부(ISR) vs 하단부(Tasklet/Workqueue) 분리 설계.
    • Tools: 리눅스 /proc/iomem, /proc/interrupts.
    • Trade-offs: 인터럽트(IRQ) 방식은 CPU가 다른 일을 하다가 장치 준비 신호가 오면 깨어나 처리하므로 효율적입니다. 하지만 IRQ 핸들러는 원자적 컨텍스트 제약과 sleep 금지 규칙을 지켜야 합니다. 폴링(Polling)은 구현이 쉽고 지연을 예측하기 좋지만 CPU 점유율이 높아질 수 있습니다.
  • How to Learn:
    • 1단계: MMIO 레지스터 접근: SoC(시스템 온 칩)의 GPIO(범용 입출력) 데이터시트에서 "LED 제어 레지스터 물리 주소: 0x20200010"을 읽고, 드라이버 안에서 ioremap(0x20200010, 4) 로 커널 가상 주소를 받아온 뒤 iowrite32(1, gpio_reg) 로 LED를 켜는 과정을 분석합니다.
    • 2단계: IRQ 채널 예약: 버튼을 누르면 GPIO 핀 신호가 CPU IRQ로 전달된다고 가정합니다. request_irq(BUTTON_IRQ, my_button_handler, IRQF_TRIGGER_FALLING, "my_button") 을 등록해두면 버튼 이벤트 시 my_button_handler() 함수로 Dispatch되는 하드웨어 이벤트 바인딩 흐름을 살펴봅니다.
  • Implement: 파이썬 GPIO_Simulator 클래스. ioremap(base=0x200, size=8) 메서드가 딕셔너리를 반환하고, iowrite32(1, offset=0) 으로 딕셔너리 키값 0x2001로 세팅하면 내부적으로 print("GPIO LED ON") 트리거. 별도 스레드가 while True: if reg[0x204] == 1: callback()을 500ms마다 폴링하여 "버튼 눌림 감지" 인터럽트 에뮬레이션 흐름 로그.

Advanced

Core Topic 04: 핫플러그와 PCI/USB 바인딩 모델 (Probe/Remove Pattern)

  • Why to Learn: USB 마우스를 연결하면 드라이버가 자동 바인딩되고, 제거하면 해제되는 Plug-and-Play 흐름이 커널의 probe, remove 함수로 어떻게 구현되는지 이해하기 위해서입니다.
  • What to Learn:
    • Concepts: struct pci_driver, struct usb_driver, probe(), remove(), id_table(Vendor/Product ID), udev 장치 이벤트.
    • Skills: 장치 트리(Device Tree) 기반 바인딩(임베디드), kobjectsysfs 링크.
    • Tools: 리눅스 udevadm monitor, lsusb -v, lspci -vv.
    • Trade-offs: 드라이버의 id_table에 지원하는 Vendor(제조사)와 Product(제품) ID 쌍을 명시하면 해당 기기가 연결될 때만 드라이버가 자동 활성화됩니다. 다만 새로운 하드웨어 리비전이 나오면 드라이버 소스코드의 id_table을 갱신해야 하는 유지보수 비용이 생깁니다.
  • How to Learn:
    • 1단계: 드라이버를 로드할 때 커널이 probe() 함수 포인터와 지원 장치 목록(id_table: [{vendor:0x046D, product:0xC077}])을 드라이버 레지스트리에 등록합니다. USB를 꽂으면 udev가 장치 ID를 읽어 레지스트리를 탐색하고, 매칭 ID를 찾으면 해당 드라이버의 probe(dev) 함수를 자동 호출하는 핫플러그 매칭 흐름을 분석합니다.
    • 2단계: probe()가 불리면 드라이버는 해당 장치에 IRQ를 등록하고 메모리를 할당합니다. 장치를 뽑으면 remove(dev)가 불리어 IRQ를 해제하고(free_irq) 메모리를 해방(kfree)하는 리소스 정리 사이클을 확인합니다.
  • Implement: 파이썬 플러그인 레지스트리 시스템. Driver_Registry 딕셔너리에 {(VendorID, ProductID): DriverClass} 형태로 드라이버 등록. PlugDevice(vendor=0x046D, product=0xC077) 호출 시 레지스트리에서 매칭 드라이버를 찾아 .probe(device_info) 자동 호출. UnplugDevice().remove() 자동 호출하여 Resources=[irq=45, mem=0x5000] 정리 로그를 출력하는 Plug-and-Play 생명주기 시뮬레이터.

7. Terminology

Term (EN / ko, abbr) 1문장 정의 단계(기본/권장/실무/심화) 역할/맥락 관련 개념 유사/대비/함께 사용 오해 포인트 Evidence(Primary/Secondary/Industry) Flags(core)
Device Driver 운영체제가 특정 하드웨어 기기와 소통할 수 있도록 추상화된 제어 인터페이스를 제공하는 특수 소프트웨어입니다. 기본 통번역가 Kernel / Interface API 단순한 '응용 프로그램'이 아님 P1:CS2023 core
LKM (커널 모듈) 커널 전체를 재구성하지 않고도 기능을 추가/삭제할 수 있게 하는 동적 로딩 바이너리 조각입니다. 기본 확장 조각 insmod / Symbol static link 독립 실행 불가능하며 커널 컨텍스트에서 동작함 Industry/Unix core
ISR 하드웨어 인터럽트 발생 시 즉각적으로 실행되어 장치의 긴급한 요청을 처리하는 드라이버 내 특수 함수입니다. 추천 긴급 처리 Handler / IRQ Callback 일반적인 함수 호출 규약과 다름 P1:CS2023 core
ioctl 표준 파일 시스템 명령(read/write)으로 표현하기 힘든 장치 전용의 복잡한 물리 명령을 전달하는 통로입니다. 실무 특수 명령 Control / Config read/write 앱 인터페이스와 장치 동작의 핵심 Industry Std core

8. References

Primary

Secondary

  • [Linux Device Drivers] Alessandro Rubini — The driver development bible.
  • [Essential Linux Device Drivers] Venkateswaran — Comprehensive real-world guide.

Industry

  • [Kernel.org: Linux Kernel Module Programming Guide] — Official documentation.
  • [Microsoft: Developing, Testing, and Deploying Drivers (WDF)] — Windows driver framework.

9. Final Checklist

Primary

  • '사용자 공간'과 '커널 공간' 사이에서 디바이스 드라이버가 수행하는 물리적 매개 역할(Syscall 연동)을 설명 가능한가? (P1)
  • '문자 장치', '블록 장치', '네트워크 장치'의 물리적 데이터 전송 단위와 처리 방식의 근본적 차이를 입증할 수 있는가? (P1)

Secondary

  • 드라이버 코드 작성 시, 왜 전역 변수를 함부로 쓰면 안 되는지 '인터럽트 중첩(Reentrancy)' 상황을 근거로 설명할 수 있는가?
  • 하드웨어 장치를 제어할 때 Memory-mapped I/O 주소가 커널 페이지 테이블에 어떻게 물리적으로 사상되는지 그 생애 주기를 도출할 수 있는가?

Industry

  • 새로운 IoT 센서를 시스템에 붙일 때, 드라이버 성능 지표인 '인터럽트 응답 지연'을 최소화하기 위한 ISR 설계 방안을 제안할 수 있는가? (SFIA)
  • 커널 업데이트 시, 드라이버 비호환성으로 발생하는 'ABI(Application Binary Interface) 파손'의 물리적 원인과 대응 기법을 기술할 수 있는가?

OS Storage & I/O

5 / 8