====== 디바이스 드라이버의 기초 - LED 제어하기 ======
여기서는 리눅스 디바이스 드라이버의 가장 기본이라고 할 수 있는 LED 제어를 해보려고 한다.
우선은 커널 2.4 대에서 해보고, 나중에 커널 2.6 대에서 시도해 볼 것이다. 여기서 나오는 소스 파일을 분석해보면 디바이스 드라이버의 어느정도 감을 잡을 수 있지 않을까 생각한다.
====== 준비물 ======
참고로 나의 환경은 다음과 같다. 주인공이라고 할 수 있는 LED는 반드시 있어야 한다! 또 한가지를 꼽자면, 프린터 포트이다. 왠만한 컴퓨터에 프린터 포트는 있기 때문에 별도로 명시하지 않았다.
| CPU | AMD |
| OS | 와우 리눅스 7.1 |
| 커널 | 2.4.2 |
| 그 외 | LED 1개 |
위의 준비물을 갖추었으면 본격적으로 시작해보자! 다시 말하지만 굳이 나의 환경에 맞출 필요는 없다. 한가지 당부하고 싶은 것은 커널을 2.4 대로 한다. 참고로 커널 2.4.26 으로 테스트해본 결과 정상적으로 동작하였다.
====== 시작하기 ======
우선 디바이스 드라이버를 만들 파일을 만들어야 한다.
#vi prnioport.c
언뜻 보기에는 복잡해 보이지만 알고보면 간단하다.
#include
#include
#include
#include
#include
#define PRNIOPORT_MAJOR 88 // 프린터 IO를 위한 메이저 번호
#define PRNIOPORT_NAME "PRINT IO PORT" // 프린터 장치명 이름
#define PRNIOPORT_MODULE_VERSION "PRINT IO PORT V0.2" // 프린터 장치명 이름 및 버전 번호
#define PRNIOPORT_ADDRESS 0x0378 // 대부분의 PC에서 LPT1은 이 주소를 사용한다.
#define PRNIOPORT_ADDRESS_RANGE 3 // 대부분의 PC에서 LPT1은 이 주소 범위를 사용한다.
//
// 광역 변수 정의
//
//*******************************************************************
static int prnioport_usage=0; // 디바이스 오픈 상태 1 이면 사용 / 0 이면 미사용 상태
//*******************************************************************
//
// 함수 정의
//
//*******************************************************************
//-------------------------------------------------------------------
// 설명 : 어플리 케이션에서 디바이스를 open 했을 때 호출되는 함수
// 매계 : 없음
// 반환 : 정상이면 0을 반환한다.
// 주의 : 이 함수의 구성은 오렐리가 가장 싫어하는 형태다.
// 즉 디바이스를 오픈할 수 있는 기회는 단 한번으로 제약을 가하고 있다.
//
//
// 즉 한 어플리케이션에서 오픈을 하여 점유하면 다른 어플리케이션은 점유하지 못한다.
//
//-------------------------------------------------------------------
int prnioport_open(struct inode *minode, struct file *mfile)
{
// 이미 사용중이면 사용중이라는 값을 되돌린다.
if( prnioport_usage != 0 ) return -EBUSY;
MOD_INC_USE_COUNT; // 모듈 사용 증가 카운터 매크로
prnioport_usage = 1; // 사용 중
printk("PRN IOPORT DRIVE OPEN");
return(0);
}
//-------------------------------------------------------------------
//
// 설명 : 어플리케이션에서 디바이스를 close를 하였을 때 호출되는 함수
// 매계 : 없음
// 반환 : 정상이면 0을 반환한다.
// 주의 : 이 함수의 구성은 오렐리가 가장 싫어하는 형태다.
// 즉 디바이스를 오픈 할 수 있는 기회는 단 한번으로 제약을 가하고 있다.
// 즉 한 어플리케이션에서 오픈을 하여 점유하면 다른 어플리케이션은 점유하지 못한다.
//
//
//-------------------------------------------------------------------
int prnioport_release(struct inode *minode, struct file *mfile)
{
MOD_DEC_USE_COUNT; // 모듈 사용 횟수를 감소시킨다.
prnioport_usage = 0; // 사용하지 않음을 표시
printk("PRN IOPORT DRIVE CLOSE");
return 0;
}
//-------------------------------------------------------------------
//
// 설명 : 어플리케이션에서 디바이스를 write 를 하였을 때 호출되는 함수
// 매계 : gdata : 어플리케이션 영역의 버퍼주소
// length : 버퍼크기
// off_what : ?
// 반환 : 정상이면 처리된 바이트 수를 반환한다.
// 주의 : 없음
//-------------------------------------------------------------------
ssize_t prnioport_write_byte(struct file *inode, const char *gdata,
size_t length, loff_t *off_what)
{
int i;
const char *data;
char c;
data=gdata;
for (i=0; i
이제는 컴파일할 Makefile 을 만들 차례다. Makefile 을 만드는 이유는 여러가지가 있겠지만, 내가 생각하기에는, 다음과 같다.
- 일일이 컴파일 옵션을 적어주는 번거로움을 덜 수 있다.
- 복수의 수정된 파일을 컴파일 할때, 바뀐 파일만 컴파일 할 수 있다.
또한 앞으로 맞닥들이게 될 프로그램들은 하나의 파일로 이루어진 것보다는 여러개의 모듈로 구성된 프로그램들이다. 여기서는 가장 쉽게 해볼 수 있는 예제를 든 것이지만...
여러개의 모듈의 프로그램을 컴파일 할 때 Makefile 의 중요성은 커진다. 서문이 너무 길었다. 만들어 보자!
#vi Makefile
Makefile 을 만들 때 주의할 점이 있다. 나 또한 그랬지만, 직접 타이핑하지 않고 복사&붙여넣기를 하게되면, 나중에 뒤에서 컴파일이 십중팔구 안 될 것이다. 이것은 바로 Makefile 의 특징을 잘 몰라서 저지르는 일반적인 실수이다.
Makefile 을 만들 때는 라벨(Label)을 제외한 모든 줄의 처음에 반드시 탭(Tab)를 눌러서 칸을 띄어 주어야 한다. 그렇지 않으면 구문 에러가 날 것이다.
# 디바이스 드라이버를 디버깅하기 위한 디버그 코드가 삽입되게 컴파일하기 위해서는
# 아래 라인을 살리면 된다.
#DEBUG = y
# 헤더 파일 디렉토리를 여기서 지정하거나 "make"의 명령라인에서 수정한다.
INCLUDEDIR = /usr/src/linux/include
# 디버그를 설정했을때의 컴파일 환경을 처리하는 부분
ifeq ($(DEBUG),y)
DEBFLAGS = -O -g -DJIT_DEBUG -DJIQ_DEBUG -DALL_DEBUG
else
DEBFLAGS = -O2
endif
CFLAGS = -D__KERNEL__ -DMODULE -Wall $(DEBFLAGS)
CFLAGS += -I$(INCLUDEDIR)
OBJS = prnioport.o
all: $(OBJS)
$(CC) $(CFLAGS) -c $^ -o $@
clean:
rm -f *.o *.~*
이제 기다리던 직접 컴파일을 해보자!
#make
에러없이 수행되었다면, 컴파일에 성공한 것이다. 또한 디렉토리를 보면 *.o 오프젝트 파일이 생성되었을 것이다. 이것은 바로 커널 모듈이다. 어렵게 만든 모듈을 커널에 직접 올려보자!!
#insmod prnioport.o
#lsmod
모듈 목록에 방금 전에 올린 모듈이 있을 것이다. 뭔가 허전하지 않은가? 그렇다. User Application 을 만들지 않았다. -_-; 어서 만들어 주자!
#vi test.c
다음을 적어준다.
//
//******************************************************************************
//
// 헤더 정의
//
//******************************************************************************
#include
#include
#include
#include
#include
#include
//******************************************************************************
//
// 함수 정의
//
//******************************************************************************
//------------------------------------------------------------------------------
// 설명 : 디바이스 드라이버를 열고 닫고 쓰는 시험을 한다.
// 매계 : 없음
// 반환 : 정상이면 0을 반환한다.
// 주의 : 없음
//------------------------------------------------------------------------------
int main(int argc, char **argv)
{
int dev;
int loop;
char buff[2];
// 화일을 연다.
dev = open("/dev/prnioport", O_WRONLY|O_NDELAY );
if (dev != -1)
{
printf( "PRNIOPORT OPEN OK");
for( loop = 0; loop < 10; loop++ )
{
sleep(1); // 1 초 대기 한다.
buff[0] = 0x00; // LED를 끈다.
write(dev,buff,1); // 프린터 포트에 쓴다.
sleep(1); // 1 초 대기 한다.
buff[0] = 0xff; // LED를 켠다.
write(dev,buff,1); // 프린터 포트에 쓴다.
}
close(dev);
printf( "PRNIOPORT CLOSE
");
}
else
{
// 화일 열기 실패
printf( "PRNIOPORT OPEN FAIL");
exit(-1);
}
return(0);
}
곧바로 컴파일 하자! 이것은 User Application 이기 때문에 바로 해주면 된다.
#gcc -o test test.c
여기서 잠깐 실행하기 전에 해주어야 하는 것이있다. 그것은 바로 디바이스 파일을 만들어주어야 하는데, 눈치 빠른 사람이라면 벌써 만들었는지도 모르겠다. 위의 소스를 보면, /dev/prnioport 파일을 open 하고 있다. 당연히 해당 파일이 없다면, 에러가 날 것이다. 그래서 디바이스 파일을 만들어 주어야 한다.
#mknod /dev/prnioport c 88 0
====== 실행하기 ======
이제야 처음에 준비한 LED를 써먹을 때가 왔다. LED를 보면 다리가 한쪽이 긴 것과 짧은 것이 있다. 긴쪽은 (+)이고, 짧은 쪽은 (-)이다.
긴쪽을 프린터 포트 2번 구멍에 꼽고 짧은 쪽을 18번과 25번 사이에 꼽아 보자!
그리고 방금 위에서 컴파일한 test 파일을 실행해보자!!
#./test
어떤가? LED가 깜박깜박 거리지 않는가!!
이번에는 2.6 대 커널에서 한번 해보자~
----
{{indexmenu>:#1|skipns=/^(wiki|etc|diary|playground)$/ skipfile=/^(todays|about|guestbook)$/ nsort rsort}}
----