인터럽트 드라이버 4주차 과제, 큰 그림부터 이해하기
라즈베리파이 인터럽트 드라이버 스터디 4주차 과제를 받았는데, 처음엔 뭘 해야 하는지조차 감이 안 왔다. 과제 zip 파일을 열어보고 나서야 dts, c, Makefile, ko 같은 파일들이 각각 어떤 역할을 하는지, 그리고 이것들이 어떻게 연결되는지를 파고들게 됐다.
학습 주제
4주차 과제는 인터럽트 후반부(Bottom-Half) 처리 기법 3가지를 각각 구현하는 것이었다.
- Threaded IRQ
- Work Queue
- Tasklet
버튼(GPIO 인터럽트)이 눌리면 카운트를 증가시키고 커널 로그에 출력하는 로직을 세 가지 방식으로 각각 만들어야 하고, Device Tree Overlay도 직접 작성해서 적용해야 한다. 물리 버튼이 없으면 타이머 인터럽트로 대체해서 테스트할 수 있다는 조건도 있었다.
탐구 과정
처음엔 그냥 "과제 파일 뜯어보고 뭘 해야 하는지 파악하자"는 식으로 접근했다. zip 안에 dts, c 소스, Makefile이 섞여 있었는데, 각 파일이 뭘 하는 파일인지는 알겠는데 이것들이 서로 어떤 관계로 연결되는지가 명확하지 않았다.
특히 헷갈렸던 부분은 이거였다.
dts는 컴파일하면 dtbo가 된다는데, 그럼 이 dtbo는 어디에 두는 거지? ko 파일은 또 어디 있는 거고, insmod 하면 정확히 뭐가 일어나는 거지?
파일 종류만 나열해서 외우면 금방 잊어버릴 것 같았다. 그래서 "이 요소들이 시스템의 어느 레이어(유저 공간/커널 공간/하드웨어)에 위치하는가"와 "개발이 어떤 순서로 흘러가는가"를 축으로 잡고 다시 정리해봤다.
핵심 학습 내용
레이어 구조로 보기
시스템을 유저 공간 - 커널 공간 - 하드웨어 세 층으로 나눠서 생각하면 각 파일의 위치가 명확해진다.
- 유저 공간: dtc, make, insmod, dmesg 같은 명령어를 실행하는 곳
- 커널 공간: Device Tree(FDT)가 로드되어 있고, insmod로 올라간 .ko가 실제로 동작하는 곳
- 하드웨어: GPIO, 버튼, 타이머, 인터럽트 핀
두 개의 트랙이 합쳐지는 흐름
전체 흐름은 사실 두 개의 독립된 트랙이 진행되다가 하나로 합쳐지는 구조였다.
[하드웨어 명세 트랙]
.dts (텍스트) --dtc 컴파일--> .dtbo (바이너리) --부팅 시 적용--> 커널 메모리(FDT)
[드라이버 코드 트랙]
.c (소스) --Makefile + make--> .ko (커널 모듈) --insmod--> 실행 중인 드라이버
두 트랙이 각각 커널에 올라간 다음, 버튼을 누르는 순간 하드웨어 신호가 Device Tree에 등록된 핀 정보와 만나면서 내가 작성한 코드의 인터럽트 핸들러가 실행되는 식이다.
파일별 위치와 역할 정리
| 파일 | 형태 | 어디에 두나 | 역할 |
|---|---|---|---|
.dts |
텍스트 | 작업 디렉토리 (자유) | 하드웨어 설계도 원본 |
.dtbo |
바이너리 | /boot/firmware/overlays/ |
부팅 시 커널이 읽는 하드웨어 명세 |
.c |
텍스트 | 작업 디렉토리 | 인터럽트 핸들러 로직 |
Makefile |
텍스트 | 작업 디렉토리 | .c를 .ko로 빌드하는 레시피 |
.ko |
바이너리 | 빌드 후 파일시스템, insmod 전까지 | 커널에 주입될 실행 파일 |
Makefile이 하는 일
일반 C 프로그램처럼 stdio.h를 링크하는 게 아니라, 커널 모듈은 현재 실행 중인 커널의 빌드 시스템(/lib/modules/$(uname -r)/build)을 참조해야 한다. Makefile은 이 경로 연결과 obj-m += driver.o 같은 빌드 대상 지정을 자동화해주는 역할이었다.
insmod가 실제로 하는 일
insmod driver.ko를 치면 단순히 "실행"되는 게 아니라, 커널 시스템 콜(finit_module)을 통해 .ko 파일 내용이 커널 메모리 영역으로 복사되고, module_init()으로 지정된 초기화 함수가 즉시 실행되는 과정이었다. 일반 프로그램은 독립된 프로세스로 유저 공간에서 돌아가지만, .ko는 커널 심볼들과 결합해서 커널 자체의 일부로 확장되는 점이 달랐다.
이해한 내용
가장 크게 이해한 건, dts/dtbo와 c/Makefile/ko가 서로 다른 두 개의 준비 과정이라는 점이었다. 하나는 "하드웨어가 어디 붙어있는지 커널에 알려주는 것"이고, 다른 하나는 "인터럽트가 오면 뭘 할지 코드로 정의하는 것"이다. 이 둘이 각각 완성된 다음에야 실제 버튼 이벤트가 내가 짠 핸들러 코드로 흘러들어갈 수 있다.
또 하나는 커널 메모리의 개념이었다. 일반 앱은 이 영역에 접근할 수 없고, .ko가 insmod로 적재되는 순간 하드웨어 레지스터와 인터럽트에 제한 없이 접근할 수 있는 권한을 갖게 된다는 게 왜 커널 드라이버 개발이 신중해야 하는지를 이해하는 데 도움이 됐다.
실전 적용
이 구조를 그대로 실습 순서로 옮기면:
holy_cow_button_overlay.dts에서 GPIO 핀 번호와 트리거 방식 확인 →dtc로.dtbo컴파일 →/boot/firmware/overlays/에 복사하고config.txt에dtoverlay=등록 → 재부팅- 3가지 기법별
.c작성- Threaded IRQ:
request_threaded_irq() - Work Queue:
INIT_WORK()+schedule_work() - Tasklet:
tasklet_setup()+tasklet_schedule()
- Threaded IRQ:
make로 각각.ko빌드insmod로 로드 →lsmod로 적재 확인 → 버튼(또는 타이머) 이벤트 발생 →dmesg -w로 count 증가 및 로그 확인rmmod로 안전하게 제거
버튼이 없는 환경이라 타이머 인터럽트로 대체 테스트하는 방안도 열어둘 계획이다.
추가 학습 계획
- 세 가지 후반부 기법의 실제 코드를 직접 작성해보면서
request_threaded_irq의 전반부/후반부 분리 구조를 더 자세히 살펴볼 것 - Work Queue와 Tasklet의 스케줄링 방식 차이(워커 스레드 vs SoftIRQ)를 실제 지연 시간으로 비교해보고 싶음
- Device Tree overlay 문법(trigger type, pull 설정 등)을 좀 더 깊이 파볼 필요가 있음
- 세 기법 간 처리 타임라인을 trace log로 직접 수집해서 비교해보는 것도 시도해볼 예정