[임베디드/C] 유저 어플리케이션 작성하기

2 분 소요

유저 어플리케이션

2020 군장병 SW 역량 강화 교육 “C언어로 배우는 IoT 프로그래밍(유명환)” 강좌의 내용입니다.

우리가 앞서 만들었던 디바이스 드라이버를 실제로 사용하게 만드는 유저 어플리케이션을 살펴보자.

소스코드 전문


/**
 **     SKELETON User Application Example for Raspberry Pi
 **
 **/

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

static unsigned short flag;

int main(void)
{
	int fd,i;
	int key;
	char *buffer1 = "EFGH";
	char buffer2[256];

	fd = open("/dev/SKELETON", O_RDWR);

	if (fd < 0)
	{
    		printf("USERAPP: /dev/SKELETON isn't opened! \n");
		//exit(1);
		return 1;
	}
    	else
	    	printf("USERAPP: /dev/SKELETON is opened! \n");

    	while((key = main_menu()) != 0)
	{
		switch(key)
		{
	   		case '1':	
				printf("USERAPP: Keyboard [1] is clicked! \n");
				ioctl(fd, 1, flag);
				break;
           		case '2':
                		printf("USERAPP: Keyboard [2] is clicked! \n");
				ioctl(fd, 2, flag);
                		break;
           		case '3':
                		printf("USERAPP: Keyboard [3] is clicked! \n");
				ioctl(fd, 3, flag);
				break;
           		case '4':
                		printf("USERAPP: Keyboard [4] is clicked! \n");
				ioctl(fd, 4, flag);
                		break;
           		case 'w':
                		printf("USERAPP: write() is called! \n");
				write(fd, buffer1, 5);
                		break;
           		case 'r':
                		printf("USERAPP: read() is called! \n");
				read(fd, buffer2, 5);
				printf("USERAPP: Device read data = %s \n", buffer2);
                		break;
           		case 'q':
                		printf("USERAPP: User Application is closed! \n");
				close(fd);
				//exit(-1);
				return 0;
                		break;
                }
        }

        return 0;
} 


int main_menu(void)
{
        int key;
        char input[2];
        printf("\n\n");
        printf("********** [USERAPP] Menu ***********\n");
        printf("* 1.  USERAPP: Keyboard [1]         *\n");
        printf("* 2.  USERAPP: Keyboard [2]         *\n");
        printf("* 3.  USERAPP: Keyboard [3]         *\n");
        printf("* 4.  USERAPP: Keyboard [4]         *\n");
        printf("* w.  USERAPP: write() execute      *\n");
        printf("* r.  USERAPP: read() execute       *\n");
	printf("*                                   *\n");
        printf("* q.  USERAPP: Quit                 *\n");
        printf("*************************************\n");
        printf("\n\n");

	printf("USERAPP: Select the command number : ");
	key = getchar();
	getchar();
	printf("\n\n");

        return key;
}

buffer1, buffer2

char *buffer1 = "EFGH";
char buffer2[256];

첫 번째 버퍼의 경우 디바이스에 전달하고자 하는 데이타, 두 번째의 경우 디바이스로부터 전달 받은 데이타를 담을 공간이다.

open()

fd = open("/dev/SKELETON", O_RDWR);

c 프로그래밍을 할 때 fopen()함수를 자주 사용했지만, stdio.h파일을 사용할 수 없는 low level programming의 경우 시스템 콜을 더 자주 쓴다. 이 함수 이전에 이미 /dev/SKELETON파일이 생성되있어야 한다.

이외의 것들

이후부터는 메인메뉴를 호출하는 내용이다. 숫자를 누르는 경우에는 단순히 io control함수를, 읽거나 쓰는 경우, read나 write를 사용한다.

 while((key = main_menu()) != 0)
{
	switch(key)
	{
	  	case '1':	
			printf("USERAPP: Keyboard [1] is clicked! \n");
			ioctl(fd, 1, flag);
			break;
           	case '2':
                	printf("USERAPP: Keyboard [2] is clicked! \n");
			ioctl(fd, 2, flag);
                	break;
           	case '3':
                	printf("USERAPP: Keyboard [3] is clicked! \n");
			ioctl(fd, 3, flag);
			break;
           	case '4':
                	printf("USERAPP: Keyboard [4] is clicked! \n");
			ioctl(fd, 4, flag);
                	break;
           	case 'w':
                	printf("USERAPP: write() is called! \n");
			write(fd, buffer1, 5);
                	break;
           	case 'r':
                	printf("USERAPP: read() is called! \n");
			read(fd, buffer2, 5);
			printf("USERAPP: Device read data = %s \n", buffer2);
                	break;
           	case 'q':
                	printf("USERAPP: User Application is closed! \n");
			close(fd);
			//exit(-1);
			return 0;
                	break;
            }
    }

    return 0;
} 

이전에도 말했지만, 유저 어플리케이션의 함수들은 직접 디바이스 드라이버에 접근할 수 없으므로 모두 디바이스 드라이버를 통해 데이타를 전달받는다.

실행

sudo insmod skeleton.ko make로 만든 커널 모듈을 삽입시킨 후,

sudo mknod /dev/SKELETON[sort of device] [major number] [minor number]

디바이스의 종류는 캐릭터 디바이스. dmesg를 통해 메이저 넘버를 확인하고, 마이너 넘버는 이때까지 겹치는 디바이스가 없을 테니 0이다. 이를 통해 디바이스파일, 노드를 만들었다. 이후 dir /dev/SKELETON을 해보면 맨 왼쪽에 c가 적혀있는 것을 볼 수 있는데, 이는 inode값이다. 파일이면 f, Block Device면 b가 적힐 것이다. 쭉 읽어보면 메이저 번호와 마이너 번호도 확인할 수 있을 것이다.

sudo ./userapp

접근권한은 디바이스를 만져야하니 루트권한이어야 한다. 정상적으로 실행되는 것을 확인할 수 있다.

실제 개발에는?

커널 소스 전부를 재컴파일하기가 매번 힘들기 때문에 리눅스의 커널모듈을 이용하여 디바이스 드라이버를 만들었다. 디버깅이 끝나면, 이 디바이스 드라이버는 커널 소스에 포함시켜서 포팅한다. 부팅할때, 메모리에 상주시키는 것이다.

댓글남기기