임베디드기사 실기 20부작 9편
Linux System Call과 ioctl() — User Space에서 Hardware까지 가는 길
앞선 연재에서는 Device Driver, /dev Device Node, mknod, Major/Minor Number가 어떻게 연결되는지 살펴봤습니다. 이번 편에서는 그 다음 질문으로 넘어가겠습니다. 사용자가 작성한 C 프로그램이 실제로 하드웨어를 움직이기 위해서는 어떤 문을 통과해야 할까요?
핵심은 System Call입니다. User Space Application이 Kernel Space에 있는 Device Driver의 기능을 요청하는 공식 통로가 바로 System Call이며, 그중에서도 장치별 특수 명령을 전달하는 대표적인 인터페이스가 ioctl()입니다.
Linux는 안정성과 보안을 위해 실행 환경을 크게 User Space와 Kernel Space로 구분합니다. 일반 응용 프로그램은 User Space에서 실행되고, Kernel과 Device Driver는 보다 높은 권한을 가진 Kernel Space에서 동작합니다.
만약 모든 응용 프로그램이 마음대로 GPIO, UART, SPI, Motor Controller의 Register를 수정할 수 있다면 한 프로그램의 실수로 시스템 전체가 멈출 수 있습니다. 따라서 일반적인 Linux 환경에서는 Application이 직접 Register를 건드리지 않고 Kernel에 작업을 요청합니다.
Application → System Call → VFS/Device Node → Device Driver → MMIO/I/O Register → Hardware
시험에서는 이 전체 구조를 하나의 그림처럼 기억해 두는 것이 중요합니다.
System Call은 User Program이 운영체제 Kernel의 기능을 요청하기 위한 인터페이스입니다. 파일을 열거나, 데이터를 읽고 쓰거나, Process를 생성하거나, Memory를 Mapping하는 작업 역시 System Call을 통해 수행됩니다.
| System Call | 주요 역할 |
|---|---|
| open() | File 또는 Device Node 열기 |
| read() | Device 또는 File에서 Data 읽기 |
| write() | Device 또는 File로 Data 쓰기 |
| ioctl() | Device별 특수 제어 명령 전달 |
| close() | 열어 둔 File Descriptor 반환 |
| mmap() | Memory 영역을 Process 주소 공간에 Mapping |
여기서 중요한 점은 System Call은 단순한 C Library 함수 이름 암기 문제가 아니라 User Space와 Kernel Space를 연결하는 경계라는 것입니다.
Linux에서는 장치를 파일처럼 다루는 철학이 널리 사용됩니다. 예를 들어 Application이 다음과 같이 Device Node를 엽니다.
int fd = open("/dev/fpga_ctrl", O_RDWR);
open()이 성공하면 Kernel은 정수형 File Descriptor를 반환합니다. 이후 Application은 이 번호를 이용해 read, write, ioctl, close를 호출합니다.
File Descriptor는 Process가 열린 File 또는 Device를 식별하기 위해 사용하는 정수형 Handle이다.
read()와 write()는 연속적인 Data 송수신에 적합합니다. 센서 값을 읽거나, UART Buffer에서 데이터를 가져오거나, LED Pattern 또는 DAC Data를 전달하는 식의 흐름입니다.
char buf[32]; read(fd, buf, sizeof(buf)); write(fd, tx_data, tx_size);
Driver 내부에서는 보통 등록된 file_operations의 read/write Handler가 호출되고, 그 안에서 Hardware Register, FIFO 또는 DMA Buffer 등에 접근하게 됩니다.
ioctl()은 Input/Output Control의 약자로, read/write만으로 표현하기 어려운 장치별 특수 제어 명령을 전달하는 데 사용됩니다.
예를 들어 UART Baud Rate 변경, Sensor Mode 설정, Motor Enable, Trigger Mode 선택, Device Reset, Status 조회와 같이 “데이터 자체”가 아니라 “장치의 동작 방식”을 제어할 때 ioctl이 잘 어울립니다.
read/write = 데이터 송수신
ioctl = 장치 설정·특수 명령
User Application에서는 보통 다음과 같은 형태로 호출합니다.
ioctl(fd, COMMAND, &data);
여기서 fd는 Device Node를 연 File Descriptor, COMMAND는 어떤 동작을 요구하는지 나타내는 명령 번호, data는 필요할 경우 User와 Driver 사이에서 전달되는 Parameter입니다.
Kernel Driver에서는 해당 Command를 확인하고 필요한 동작을 수행합니다. 실제 Driver 구현에서는 User Pointer 검증 및 안전한 Data Copy가 중요하지만, 자격증 시험에서는 우선 “ioctl은 Device의 특수 제어를 위한 System Call”이라는 핵심 의미를 정확히 쓰는 것이 우선입니다.
예를 들어 Application에서 Motor Enable 명령을 내린다고 가정해 보겠습니다.
open("/dev/motor", O_RDWR)② File Descriptor 획득
③ ioctl(fd, MOTOR_ENABLE, ...)
④ Kernel 진입
⑤ Device Driver의 ioctl Handler 실행
⑥ Driver가 MMIO Register에 값을 기록
⑦ Motor Controller가 Enable
이 흐름을 이해하면 System Call, Device Node, Driver, MMIO가 각각 따로 떨어진 용어가 아니라 하나의 제어 경로라는 것이 보입니다.
통합 학습자료에는 고속 제어가 필요한 경우 /dev/mem과 mmap()을 이용해 Physical Memory 영역을 User Virtual Address Space에 Mapping하는 방식도 소개되어 있습니다.
이 방식은 Register Access의 Overhead를 줄일 수 있지만, 일반적인 Device Driver 방식에 비해 보호와 관리 측면에서 주의가 필요합니다. 시험에서는 다음 정도로 정리하면 충분합니다.
실기 자료에서는 파일, Process, IPC Object의 생성과 삭제도 함께 정리되어 있습니다. System Call을 단순히 장치 제어에만 연결하지 말고 운영체제 전반의 자원 관리라는 관점에서 이해해야 합니다.
| 대상 | 생성/접근 | 삭제/종료 |
|---|---|---|
| File | open(O_CREAT), creat() | unlink() |
| Directory | mkdir() | rmdir() |
| Process | fork(), clone() | exit() |
| Shared Memory | shmget(), shm_open() | IPC_RMID, shm_unlink() |
| Memory Mapping | mmap() | munmap() |
첫째, ioctl은 Driver 이름이 아닙니다. User Space에서 Kernel Driver에 특수 제어를 요청하는 System Call입니다.
둘째, mknod와 ioctl은 역할이 다릅니다. mknod는 Device Node를 만드는 명령이고, ioctl은 열린 Device에 특수 제어를 요청합니다.
셋째, open이 곧 Hardware를 직접 여는 것은 아닙니다. /dev Node를 통해 Kernel이 해당 Driver의 File Operation과 연결해 주는 것입니다.
넷째, read/write와 ioctl은 경쟁 관계가 아닙니다. 하나의 Device Driver에서 동시에 모두 사용할 수 있습니다.
[예상문제 1] Linux User Program이 Hardware를 제어하는 일반적인 경로를 순서대로 쓰시오.
[예상문제 2] ioctl()의 역할을 쓰시오.
[예상문제 3] read/write와 ioctl의 차이를 설명하시오.
[예상문제 4] mmap()의 의미를 쓰시오.
장치가 동작하지 않을 때 “Driver 문제 같다”라고 바로 결론 내리기보다 아래 순서로 계층을 좁혀가는 것이 좋습니다.
② open()이 성공하는가?
③ File Descriptor가 정상인가?
④ read/write/ioctl Return Value는 정상인가?
⑤ Driver Handler까지 진입하는가?
⑥ MMIO Register 값이 실제로 변하는가?
⑦ Hardware Signal이 변하는가?
이와 같이 User → Kernel → Driver → Register → Hardware 순으로 계층을 나누면 문제의 위치를 빠르게 좁힐 수 있습니다. 이는 자격증의 장애 대응 영역과도 매우 자연스럽게 연결됩니다.
open = Device/File 열기
read = 읽기
write = 쓰기
ioctl = 장치 고유 제어
close = 자원 반환
mmap = Memory Mapping
전체 흐름:
User App → /dev → System Call → Driver → Register → H/W
Linux에서 Application이 Hardware를 제어하는 핵심은, /dev Device Node를 연 뒤 System Call을 통해 Kernel Device Driver에 요청하고 Driver가 실제 Hardware Register를 제어하는 구조를 이해하는 것입니다.
이 구조가 머릿속에 잡히면 open, read, write, ioctl, mmap, mknod, Device Driver가 더 이상 각각의 단어가 아니라 하나의 연결된 시스템으로 보이기 시작합니다.
10편에서는 「SRAM과 DRAM, Refresh부터 Memory Management까지」를 다룹니다. SRAM과 DRAM의 구조 차이, Refresh가 필요한 이유, Cache와 Main Memory의 역할, 그리고 실기에 자주 나오는 메모리 관련 개념을 하나로 연결해 정리하겠습니다.
#임베디드기사 #임베디드기사실기 #임베디드시스템 #Linux #리눅스커널 #SystemCall #시스템콜 #ioctl #DeviceDriver #디바이스드라이버 #UserSpace #KernelSpace #FileDescriptor #open #read #write #mmap #장치제어 #임베디드리눅스 #자격증공부