6.2. 샘플 디바이스 드라이버

디바이스 드라이버를 만드는 방법에 대해 알아보자. 우선 대부분의 문서에 나오는 간단한 예제를 이용해보자.

/* hello.c */
(1)
#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif

(2)
#define __NO_VERSION__
#include <linux/module.h>
#include <linux/version.h>
#include <linux/fs.h>

(3)
struct file_operations Fops = {
	NULL,	/* owener */
	NULL,	/* llseek */
	NULL,	/* read */
	NULL,	/* write */
	NULL,	/* readdir */
	NULL,	/* poll */
	NULL,	/* ioctl */
	NULL,	/* mmap */
	NULL,	/* open */
	NULL,	/* flush */
	NULL,	/* release */
	NULL,	/* fsync */
	NULL,	/* lock */
	NULL,	/* readv */
	NULL,	/* writev */
	NULL,	/* sendpage */	
	NULL	/* get_unmapped_area */
};

(4)
int init_module()
{
	if (register_chrdev(213, "hello", &Fops) < 0)
		return -EIO;

	printk("hello.o start\n");

	return 0;
}

(5)
void cleanup_module()
{
	unregister_chrdev(213, "hello");
	printk("hello.o end\n");
}

컴파일은 다음과 같이 한다.

gcc -o hello.o -c -D__KERNEL__ -DMODULE -O -Wall -I/usr/include hello.c

컴파일 할 때 컴파일러에게 커널에 해당하는 프로그램임과 이 것이 MODULE 임을 알려준다. 일반 적인 프로그램과는 달리 -c 옵션을 사용해 링킹을 하진 않아야한다.

에러 없이 컴파일 됐으면 다음과 같은 명령으로 만들어진 디바이스 드라이버를 등록해보자.

insmod hello.o

화면에 뭔가 출력되는가? 아무 것도 출려되지 않으면 dmesg 명령을 사용해 커널에서 뿌린 메시지의 마지막에 'hello.o start'가 찍혔는지 확인한다. 또 lsmod 명령으로 정상적으로 hello.o가 등록됐느지 확인해 보자. 아래 것은 필자의 리눅스 시스템에 올라간 모듈들을 본것이다. 제일 위에 hello.o가 등록된 것이 보일 것이다. 비록 아무 것도 사용하지 않는다고 나와 있지만 처음 만들어본 디바이스 드라이버가 등록된 것을 확인할 수 있다.

Module                  Size  Used by
hello                    592   0  (unused)
smbfs                  31376   4  (autoclean)
sd_mod                 10640   2  (autoclean)
vfat                    9520   1  (autoclean)
fat                    29696   0  (autoclean) [vfat]
sr_mod                 12176   0  (autoclean)
tuner                   8176   0  (autoclean) (unused)
i2c-core               13024   0  (autoclean) [tuner]
vmnet                  17984   6 
parport_pc             19648   0  (unused)
parport                14176   0  [parport_pc]
vmmon                  18784   0  (unused)
3c59x                  24960   1 
ide-scsi                7648   0 
ide-cd                 26656   0 
cdrom                  29056   0  [sr_mod ide-cd]
md                     44224   0  (unused)
snd-pcm-oss            35792   0  (unused)
snd-mixer-oss           8528   1  [snd-pcm-oss]
snd-card-fm801          7296   1 
snd-pcm                47744   0  [snd-pcm-oss snd-card-fm801]
snd-mpu401-uart         2656   0  [snd-card-fm801]
snd-rawmidi            11968   0  [snd-mpu401-uart]
snd-ac97-codec         22832   0  [snd-card-fm801]
snd-opl3                5264   0  [snd-card-fm801]
snd-timer               9584   0  [snd-pcm snd-opl3]
snd-hwdep               3376   0  [snd-opl3]
snd-seq-device          3744   0  [snd-rawmidi snd-opl3]
snd                    23632   0  [snd-pcm-oss snd-mixer-oss snd-card-fm801 snd-pcm snd-mpu401-uart snd-rawmidi snd-ac97-codec snd-opl3 snd-timer snd-hwdep snd-seq-device]
hid                    19152   0  (unused)
input                   3360   0  [hid]
usb-storage            26400   1 
scsi_mod               88400   4  [sd_mod sr_mod ide-scsi usb-storage]
usb-uhci               21408   0  (unused)
usbcore                49632   1  [hid usb-storage usb-uhci]
rtc                     5600   0  (autoclean)

이어 'rmmod hello'란 명령을 실행해 보자. insmod 때와 마찬가지로 화면에 아무 것도 출력되지 않으면 dmesg를 사용해 또 확인해 보자. 'hello.o end'란 말이 출력됐는가? 이어 lsmod를 사용해 hello.o가 해제됐는지 확인해 보기 바란다.

hello.c를 간단하게 분석해 보자.

(1)
모듈로 만들어지는 디바이스 드라이버는 __KERNEL__과 MODULE이 반드시 정의되야만 한다.
(2)
필요한 헤더를 읽어 들인다.
(3)
file_operations 라는 구조체로 모든 모듈엔 반드시 존재해야한다. hello.c는 아무 동작도 하지 않기 때문에 여기에 아무 것도 채워 넣지 않았지만 다른 디바이스 드라이버를 만들 땐 알맞는 항목을 채워 넣어 동작 하도록 해줘야한다.
(4)
init_module()은 insmod 명령 등을 이용해 디바이스 드라이버를 커널에 등록할 때 무조건 처음 실행되는 함수다. 다시 말해 모든 모듈엔 init_module()과 cleanup_module()이 존재 해야한다. 보통은 init_module()에서 디바이스 드라이버를 주/부 번호를 사용해 등록하는 함수를 실행한다.
(5)
cleanup_module()은 모듈을 제거할 때 커널에 의해 무조건 불리는 함수로 init_module()과는 반대로등록된 모듈을 해제하는 함수를 부른다.

만약 위의 예제를 X-window 상에서 insmod/rmmod 한다면 화면에 아무 것도 나오지 않을 것이다. 이는 printk의 출력이 가상 터미널 7번에 출력되기 때문이다. 그러므로 xterm에 옵션을 주지 않고 그냥 연 창엔 출력되지 않으므로 demsg를 사용해 확인해야한다. 대신 xterm -C로 연 xterm에선 바로 확인이 가능할 것이다.

예제는 그 크기가 작기 때문에 하나의 파일에 모두 들어가지만 일반적인 경우 한개의 파일에 모듈 전체의 내용이 들어가지 않을 경우엔 소스 코드 여러개에 나눠 쓰게 된다. 이런 경우엔 각각의 파일에 모듈에 필요한 정의를 하고 컴파일 후 링커를 사용해 합쳐주면 된다.

gcc -D__KERNEL__ -DMODULE -Wall -O -c -o start.o start.c
gcc -D__KERNEL__ -DMODULE -Wall -O -c -o stop.o stop.c
ld -m elf_i386 -r -o hello.o start.o stop.o 

start.c에 init_module()이 들어있고 stop.c에 cleanup_module()이 들어있다면 각각을 컴파일 한 후 ld를 사용해 하나로 묶어준다.