디바이스 드라이버(Device Driver)란?
디바이스 드라이버는 컴퓨터 운영 체제와 하드웨어 장치 간의 통신을 가능하게 하는 소프트웨어라고 정의하는데, 처음 들으면 이게 무슨 의미인지 감도 안 잡히는 사람도 있다.(그게 나였다.) 그래서 오늘은 내가 신입사원 때 공부했던 디바이스 드라이버(DD)에 대해 조금 더 쉽게 풀어서 써보고자 한다.
일상생활 속에서 디바이스 드라이버의 역할을 예를 들어 보겠다. 무선 마우스를 컴퓨터에 연결해서 사용하려고 할 때, 드라이버를 설치하라는 메시지를 볼 수 있다. 마우스를 컴퓨터에 연결했을 때, 마우스 자체는 클릭하거나 포인터를 움직이는 동작을 전달할 수 있지만, 컴퓨터가 이를 이해하려면 마우스의 동작을 컴퓨터 명령어로 변환하는 과정이 필요하다. 디바이스 드라이버(DD)가 없다면 마우스의 클릭이나 움직임은 단순한 전기 신호에 불과해 컴퓨터가 이를 올바르게 해석할 수 없기 때문에 컴퓨터가 특정 장치를 동작시킬 때 어떻게 동작시켜야 하는지 정의된 프로그램을 설치해야 한다.
마찬가지로, 키보드로 'A'키를 누르면 디바이스 드라이버가 이 신호를 받아 컴퓨터에게 "사용자가 'A' 문자를 입력했다"라고 알려주는 것이다. 이제 디바이스 드라이버가 컴퓨터와 외부 장치 간의 소통을 가능하게 해주는 역할을 한다는 의미가 조금은 와닿을 것이다.
컴퓨터에 USB를 꽂는 경우도 드라이버가 이 장치를 컴퓨터에 연결하여 파일을 저장하거나 불러올 수 있도록 한다. 드라이버 없이는 컴퓨터가 USB 드라이브에 저장된 데이터를 읽거나 쓸 수 있는 방법을 알 수 없다.
디바이스 드라이버(DD) 구조

(1) 하드웨어
가장 아래의 하드웨어는 실제 물리적인 장치인데, 각 장치마다 요구되는 드라이버가 다 다르다. 장치 종류마다 다를 수 있고, 제조사, 모델마다 다를 수 있다. 예를 들면 게이밍 마우스의 경우 추가 버튼이나 사용자 정의가 가능한 기능이 더 많을 수 있고, 해당 모델에 특화된 드라이버가 필요할 수 있다. 그래서 리눅스에서는 서로 다른 다양한 파일 시스템에 공통된 인터페이스를 제공하기 위해 VFS라는 개념이 생겼다.
(2) Kernel
위 그림에서 커널은 하드웨어와 직접 상호작용하기 위한 핵심 코어로, 이 커널에 접근하기 위해서는 applications에서 system call이나 shell을 거쳐가야 한다.
- System Call Interface: 사용자 공간 애플리케이션과 커널 서비스 간의 통신 계층으로 애플리케이션에서 작업을 수행하고자 할 때 시스템 콜을 사용하고, 이는 커널에 의해 처리된다.
- VFS (가상 파일 시스템): 다양한 파일 시스템에 대한 표준 인터페이스를 제공하는 커널의 추상화 계층으로 이를 통해 같은 시스템 콜 인터페이스를 다양한 종류의 파일 시스템에 사용할 수 있다. 디바이스 드라이버도 하나의 파일이므로 VFS의 인터페이스에 따라 디바이스 드라이버가 작성되어야 할 것이다.
- Buffer Cache: 디스크에 직접적으로 수행되는 읽기 및 쓰기 작업의 수를 줄이고 성능을 개선하기 위해 데이터를 일시적으로 보관하는 메모리 공간이다.
- Network Subsystem: 네트워크에서의 데이터 송수신과 같은 모든 네트워크 작업을 처리하는 커널 부분이다.
- 캐릭터 디바이스 드라이버: 문자(바이트) 단위의 데이터를 읽고 쓰이는 캐릭터 디바이스(예: 키보드, 마우스)를 관리하는 드라이버로, buffer 또는 cache가 존재하지 않는다.
- 블록 디바이스 드라이버: 블록 단위로 읽고 쓰이는 블록 디바이스를 관리하며, 데이터를 저장할 system buffer(buffer cache)가 필요하다. 보통 파일 시스템에 의해 마운트 되어 관리되며, 해당 방식을 사용하는 하드웨어로는 hdd, cdroms, rad disks 등이 있다.
- 네트워크 디바이스 드라이버: 이더넷 어댑터나 무선 컨트롤러와 같은 네트워크 장치에서 네트워크 통신과 관련된 기능을 관리한다. ex) TCP/IP 프로토콜 스택 관리, 네트워크 인터페이스 관리 등
(3) Application
사용자 공간의 애플리케이션을 나타낸다. 이 애플리케이션들은 디바이스로부터 데이터를 읽거나 디바이스에 데이터를 쓰는 등의 작업을 커널과 상호작용하며 수행한다.
리눅스에서 디바이스 드라이버 확인하기
/dev 살펴보기
필자의 경우 Ubuntu 22.04 LTS 버전으로 사용 중이다. 아래 사진에서 c로 시작하면 character device, b로 시작하면 block device를 의미한다.

위 그림에서 노란색으로 표시한 '10, 235'는 각각 major number(주 번호), minor number(부 번호)를 나타낸다. major는 디바이스 종류 구분을 위해, minor는 같은 종류 중에서도 실제 제어해야 할 디바이스를 구분하기 위함이다.
ls -al /dev와 cat /proc/devices 명령어를 통해 현재 등록된 디바이스의 종류를 볼 수 있으며 같은 주번호가 캐릭터, 블록 디바이스에 부여될 수 있다.


/dev/block 살펴보기

- /dev/sr0: SCSI CD-ROM 디바이스를 나타낸다. sr은 SCSI optical drive를 의미한다.
- /dev/dm-0: 디바이스 매퍼(Device Mapper). 일반적으로 LVM(Logical Volume Manager)이나 암호화된 볼륨을 위해 사용된다.
- /dev/loop0부터 /dev/loop7: 루프백 디바이스. 루프백 디바이스는 파일을 마치 블록 디바이스인 것처럼 접근할 수 있게 해준다. 이를 통해 ISO 이미지와 같은 파일을 마운트하고, 마치 실제 물리 디바이스인 것처럼 사용할 수 있다.
- /dev/sda: 이것은 첫 번째 SCSI/SATA 하드 디스크를 나타낸다.
- /dev/sda1, /dev/sda2, /dev/sda3: 이 파일들은 /dev/sda 디스크의 첫 번째, 두 번째, 세 번째 파티션을 나타낸다. 파티션은 디스크 상에 구분된 공간으로, 각기 다른 파일 시스템을 가질 수 있다.
디바이스 드라이버 생성하기

- 소스코드를 작성하고 컴파일을 한 뒤 insmod로 커널에 생성한 모듈을 적재한다.
- 그다음, 생성할 커널 모듈을 위한 디바이스 파일을 만들어야 하기 때문에 mknod 명령어를 이용한다.
- 해당 명령어로 디바이스 파일을 만들었으면, 이제 응용 프로그램에서 open, read 등을 이용하여 디바이스 파일에 접근한다.
- Char Device Driver 모듈 제작
- 커널 모듈이 초기에 등록될 때 init 과정을 거치는데, 이 안에서 char device를 등록하는 로직을 넣어줘야 한다. 이는 register_chrdev() 함수를 이용하면 된다.
- register_chrdev("major number", "device name", "file_operations 구조체 변수") char device 등록 함수는 위와 같이 3개의 인수를 가진다.
- 첫 번째는 major 번호, 두 번째는 디바이스 이름, 마지막은 file_operations 구조체이다.
- file_operations 구조체는 Char Device, Block Device 드라이버와 일반 응용 프로그램 간의 통신을 위해 제공되는 인터페이스라고 보면 된다.
- read, write, open, release 등의 함수 포인터를 사용하여 디바이스 드라이버를 제작하면 된다.

- 유저 공간에서 open, close, read, write 같은 함수들은 트랩에 의해 커널에게 system call을 통해 처리가 된다.
- sys_...( ) 함수 내부에선 실제 VFS 내부의 file_operations 구조체의 함수 포인터를 참조하고, 거기에 등록된 디바이스 드라이버 함수가 실제로 호출되는 과정을 나타낸다.
디바이스 드라이버 생성 예제
[test_device.c 작성]
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h> // 문자열 관련 함수 사용을 위해 추가
int main() {
int dev;
char buff[1024] = {0}; // 버퍼를 0으로 초기화
printf("Device driver test.\n");
dev = open("/dev/test_device", O_RDWR);
if (dev < 0) {
perror("Failed to open the device"); // 에러 메시지 출력
return errno;
}
printf("dev = %d\n", dev);
const char* msg = "덮어쓸 메시지"; // 쓰려는 문자열을 변수에 저장
ssize_t bytes_written = write(dev, msg, strlen(msg)); // strlen을 사용하여 정확한 길이 전달
if (bytes_written < 0) {
perror("Failed to write to the device");
return errno;
}
ssize_t bytes_read = read(dev, buff, sizeof(buff)); // 버퍼 크기만큼 읽기
if (bytes_read < 0) {
perror("Failed to read from the device");
return errno;
}
printf("read from device: %s\n", buff);
close(dev);
exit(EXIT_SUCCESS);
}
[make 후 lsmod를 통해 현재 커널에 로드된 모듈 확인]
make
make install
lsmod | grep test

응용 프로그램을 실행하여 디바이스 드라이버가 정상적으로 실행됐는지 확인한다. 나는 내 이름으로 덮어쓰기 해서 내용은 가렸다.
[dmesg 확인]

'Opensource > Linux' 카테고리의 다른 글
Ubuntu TACACS+ 서버 구축 및 systemd 서비스 생성하기 (0) | 2024.09.25 |
---|---|
리눅스 커널 메모리 할당과 GFP Flags (kmalloc, GFP_KERNEL, GFP_ATOMIC) (3) | 2024.09.20 |
소켓(Socket) 통신 개념과 c언어 예제 (5) | 2024.08.28 |
리눅스 커널 dump_stack() 함수 사용법 및 동작 (0) | 2024.08.11 |
[Linux] GDB를 이용해 C 디버깅하기 (Debugging 예제) (0) | 2024.08.06 |