====== 디바이스 드라이버의 기초 - 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}} ----