대개 응용의 crash가 날 경우, core파일을 통해 디버깅할 수 있는데, 

간혹 생성되지 않을 경우가 있다. 

 웬만한 것을 건들지 않는다는 가정하에 간단한 해결책은 

ulimit -a 로 core dump에 대한 size가 너무 적거나 0이 되어 있는지 확인하고 이를, 

ulimit -c unlimited로 해주는 것이 가장 속편하다. 


core는 프로그램의 비정상적인 종료(세그먼트 폴트 등)가 발생하는 경우
커널에서 해당 프로세스와 관련된 메모리를 덤프시킨 파일이다.

아래와 같이 리눅스는 기본적으로 코어덤프를 하지 않도록 되어있다.
# ulimit -aS 또는 ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
file size               (blocks, -f) unlimited
pending signals                 (-i) 1024
max locked memory       (kbytes, -l) 32
max memory size         (kbytes, -m) unlimited
open files                      (-n) 2048
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
stack size              (kbytes, -s) 10240
cpu time               (seconds, -t) unlimited
max user processes              (-u) 2047
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited 


코어덤프를 하도록 설정하려면 위 부분에서
core file size          (blocks, -c) 0 이부분이 값(KB)나 unlimited로 되어있어야만 한다.

- core dump를 하도록 설정하는 방법
# ulimit -c unlimited
# ulimit -c 1048576 (KB) - 1G 제한을 두는 경우

- 간단하게 dump된 core 파일에서 문자열(string)만을 보려면
# strings core | more

- 자세한 debugging을 원할 경우에는 gdb라는 툴을 사용하면 된다.(gdb 맨페이지 참고)


예전에는 단순히 core가 dump될 때 단순히 core라는 이름으로 생성이 되었는데
이 경우 어떤 프로세스에서 core가 발생했는지 확인하는 문제가 있었다.
하지만 요 근래의 Red Hat Linux를 보면 core.PID로 생성이 된다.
core와 관련된 커널 파라미터 설정은 다음과 같다.

# sysctl -a | grep core
kernel.core_pattern = core
kernel.core_uses_pid = 1

# cat /proc/sys/kernel/core_pattern
core

# cat /proc/sys/kernel/core_uses_pid
1  (0일 경우 pid를 출력하지 않음)


- core 덤프가 발생할 때 프로세스 실행파일명으로 core 덤프를 떨어뜨리는 방법
1) sysctl -w kernel.core_pattern = core.%e  (reboot후 초기화 된다.)

2) echo "core.%e" > /proc/sys/kernel/core_pattern

3) /etc/sysctl.conf 파일에 아래 내용을 등록해 준다.
kernel.core_pattern = core.%e

- 강제로 특정 프로세스에 대한 core를 발생시키는 방법
디버깅을 위해 특정 프로세스에 대해 강제로 core dump를 발생시켜야 하는 경우
kill 커맨드를 이용할 수 있다.

# kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL
 5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE
 9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2
13) SIGPIPE     14) SIGALRM     15) SIGTERM     17) SIGCHLD
18) SIGCONT     19) SIGSTOP     20) SIGTSTP     21) SIGTTIN
22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO
30) SIGPWR      31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1
36) SIGRTMIN+2  37) SIGRTMIN+3  38) SIGRTMIN+4  39) SIGRTMIN+5
40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8  43) SIGRTMIN+9
44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13
52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9
56) SIGRTMAX-8  57) SIGRTMAX-7  58) SIGRTMAX-6  59) SIGRTMAX-5
60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2  63) SIGRTMAX-1
64) SIGRTMAX

위의 시그널 중에서 8)SIGFPE 또는 3)SIGQUIT 를 통해 core를 dump시킬 수 있다.
# top (top 실행)
# ps -ef | grep -i top (top 프로세스 PID 확인, 다른 터미널에서)
root     31775 29667  0 14:30 pts/3    00:00:00 top

# kill -SIGFPE 31775

위와 같이 하면 top을 실행한 디렉토리에 core.top.31775가 생성되어있음을 확인할 수 있다.


- gcore를 이용한 core dump 발생
디버깅을 위해 현재 실행중인 프로세스를 강제로 core dump를 생성할 필요가 있는 경우 gcore를 이용할 수 있다.

# top (top 실행)
# ps -ef | grep -i top (top 프로세스 PID 확인, 다른 터미널에서)
root     31775 29667  0 14:30 pts/3    00:00:00 top

# gcore 31775
gcore: core.31775 dumped

참고 : gcore 바이너리는 gdb 패키지에 포함돼 있다. (gdb-6.3.0.0-1.143.el4.i386.rpm)


=========================================================================================================================================

[ 참고 자료]

아래는 각각의 OS 플랫폼에서 core 파일 정보를 확인하는 결과이다.
각각의 플랫폼에서는 다음과 같은 결과가 나왔다 :

$ uname -a;file core                                     
SunOS v880 5.8 Generic_117350-51 sun4u sparc SUNW,Sun-Fire-880
core:           ELF 32-bit MSB core file SPARC Version 1, from 'a.out'

$ uname -a; file core
AIX aix5 3 5 0001D01F4C00
core: AIX core file 32-bit, a.out

% uname -a; file core
HP-UX hp1123 B.11.23 U 9000/800 190494686 ??????-????? ???̼???
core:           core ???? - 'a.out' - SIGBUS ????

$ uname -a; file core.528 
Darwin castepo.local 9.3.0 Darwin Kernel Version 9.3.0: Fri May 23 00:49:16 PDT 2008; root:xnu-1228.5.18~1/RELEASE_I386 i386
core.528: Mach-O core i386
때때로 여러개의 프로세스를 마구 띄우는 daemon 을 작업하다 보면 뜬 프로세스들 중 하나가 죽을 경우에 생기는 core 파일이 도대체 어느 프로그램이 죽어서 생긴 것인지 알 수 없을 경우가 있다. 물론, 하나씩 디버거로 읽어서 뭔가 좀 말이 되는 콜스택을 보여준다든지 하는 녀석을 대충 지레짐작해서 얘이거니 하고 분석할 수도 있지만, 기분 나쁘다.

리눅스 맨페이지에 따르면 커널 버젼 2.6 이후, 그리고 2.4.21 이후의 커널에 다음의 기능이 추가되어 있다고 한다 :
$ man core | col -b 
.
.
.
   Naming of core dump files
       By default, a core dump file is    named  core,  but  the    /proc/sys/ker-
       nel/core_pattern file (since Linux 2.6 and 2.4.21) can be set to define
       a template that is used to name core dump files.  The template can con-
       tain  % specifiers which are substituted by the following values when a
       core file is created:

     %%  A single % character
     %p  PID of dumped process
     %u  real UID of dumped process
     %g  real GID of dumped process
     %s  number of signal causing dump
     %t  time of dump (seconds since 0:00h, 1 Jan 1970)
     %h  hostname (same as 'nodename' returned by uname(2))
     %e  executable filename


말인 즉슨, 다음과 같이 하면 core file 이 생성될 때 그 파일의 이름을 어떻게 지을 것인가 하는 문제를 사용자가 설정할 수 있다는 이야기인데, cut to the chase, 결론부터 말하면, 다음과 같이 하면 실행파일의 이름을 가지고 core 파일의 이름을 만들 수 있다 :

# cat > /proc/sys/kernel/core_pattern
core.%e
^D
# 
저와 같이 해 두고 아까 컴파일한 파일을 실행시켜 보면,
$ ./a.out 
hello
Segmentation fault (core dumped)
$ ls
a.c  a.out*  core.a.out
$

참고 및 인용사이트 : http://altibase.tistory.com/241

Posted by 라판

보통 core파일은 응용 프로그램가 crash날 때, 사용되곤 한다. 

그런데 만약 커널 패닉이 발생하는 상황은 어덯게 대처해야 할까? 

커널 패닉의 경우에도 core 파일이 생기며, vmcore라는 이름으로 아래 경로에

생기는 듯 하다. 

/var/crash/xxxx-xx-xx-xx:xx:xx/vmcore


그렇다면 이를 어떻게 분석할 수 있을까?

아래와 같이 crash 명령어를 사용하여 분석할 수 있다. 


출처 - http://joonlinux.blogspot.kr/2011/02/blog-post.html

리눅스 커널 코어 덤프 분석하기 


1. 커널 코어 분석 유틸리티 설치하기

 yum install --enablerepo=rhel-debuginfo crash kernel-debuginfo

2.커널 코어덤프 파일 분석 시작 하기

crash /usr/lib/debug/lib/modules/<커널 릴리즈 버전>/vmlinux \
/var/crash/xxxx-xx-xx-xx:xx:xx/vmcore

3.커널 메세지 버퍼내용 확인하기
crash > log

4.커널 satck trace  정보 확인하기
crash > bt

5. 프로세서 상태 확인하기
crash > ps

6. virtual memory 정보 확인하기
crash > vm

7. open file 확인하기
crash > files

- crash 명령 소개 

sys - 시스템의 일반적인 정보를 출력해 준다.
bt - Backtrace 명령. 스택의 내용들을 순차적으로 출력해준다.
ps - Process list 출력.
free - Memory 및 스왑 상태 출력.
mount - 마운트 상태 출력
irq - 각 장치의 ( irq ) 상태를 출력.
kmem - 메모리 상태 출력 ( kmalloc, valloc 등 메모리 할당 상태도 보여줌 )
log - dmesg 의 내용을 출력.
mod - 로딩된 모듈 리스트 출력.
net - Network 상태 출력.
runq - 실행중인 task 리스트 출력.
task - 작업목록 출력.
rd - 메모리 번지수에 대한 상세정보 출력.
foreach - 모든 task, process 등 디버깅 정보에 대한 상세한 출력이 가능함.
set - 설정된 주소 및 PID 등을 기본 컨텍스트로 설정.
struct - 구조화된 메모리 내부의 변수들을 출력해 준다.
files - task 가 열고있는 파일디스크립터들을 출력해준다.

또는, 아래의 문서를 참고하면 좋을 듯 하다 
출처 - http://cafe.daum.net/redhat/COsR/29?docid=1HjQ8COsR2920090904142417

 Kernel_Dump_Analysis.pdf

Posted by 라판

안드로이드 EventHub를 살펴보다, 초반 device 검색 시, poll 매커니즘을 이용하는 것을 알았다. 

이에 대해 poll 매커니즘에 대한 간단한 설명을 아래 스크랩하엿다. 
이와 더불어 inotify도 사용하는 데 이에 대한 정리도 필요함 

출처 - http://www.joinc.co.kr/modules/moniwiki/wiki.php/Site/Network_Programing/Documents/Poll

poll을 이용한 입출력 다중화

poll은 select 와 마찬가지로 다중입출력 을 구현하기 위한 방법으로 사용되며, 동시에 여러개의 클라이언트를 다루는 서버를 제작하기 위한방법으로 흔히 사용된다. 

select 의 경우 입출력 이벤트가 발생했을 때 넘겨주는 정보가 너무 적음으로써, 프로그래밍시 여기에 신경을 써줘야 하는데 poll 을 이용하면 이러한 제한을 극복할수 있다.
select 에 대한 자세한 내용은 select 를 통한 입출력 다중화 와 다중연결서버 만들기 (2) 를 참조하기 바란다. 

poll
다음은 poll의 함수원형이다.
int poll(struct poolfd *ufds, unsigned int nfds, int timeout);
poll이 여러개의 파일을 다루는 방법은 select 와 마찬가지로 파일지시자의 이벤트를 기다리다가 이벤트가 발생하면, poll 에서의 block 이 해제되고, 다음 루틴에서 어떤 파일지시자에 이벤트가 발생했는지 검사하는 방식을 사용하게 된다.
우선 poll 함수의 첫번째 인자인 pollfd 구조체에 대해서 알아보도록 하겠다. poolfd 구조체만 알만 poll 의 대부분을 다 이해한것이나 마찬가지이니, 주의 깊게 읽어 바란다.
struct pollfd
{
	int fd;         // 관심있어하는 파일지시자
	short events;   // 발생된 이벤트
	short revents;  // 돌려받은 이벤트
};
pollfd 구조체는 3개의 멤버변수가 있는데, 이 구조체에 우리가 관심있어하는 파일지시자를 세팅하고(fd), 관심있어 하는 파일지시자가 어떤 이벤트가 발생하는걸 기다릴것인지(events)를 지정하게 된다. 그럼 poll 은 해당 fd 에 해당 events 가 발생하는지를 검사하게 되고, 해당 events 가 발생하면 revents 를 채워서 돌려주게 된다. 
revents 는 events 가 발생했을때 커널에서 이 events 에 어떻게 반응 했는지에 대한 반응 값이다.
후에 revent 값을 조사함으로써, 해당 파일지시자에 어떠한 event 가 최해지고 커널에서 그 event를 어떻게 처리했는지 (입력/출력이 제대로 이루어졌는지, 아니면 에러가 발생했는지)를 알아내서 적절한 조취(읽을 데이타가 있으면 읽거나 하는등의 일)를 취할수 있게 된다.

그럼 events 에 세팅할수 있는 events 에 대해서 알아보도록 하겠다. 이 값들은 <sys/poll.h> 에 디파인 되어 있다.
    #define POLLIN      0x0001  // 읽을 데이타가 있다.
    #define POLLPRI     0x0002  // 긴급한 읽을 데이타가 있다.
    #define POLLOUT     0x0004  // 쓰기가 봉쇄(block)가 아니다. 
    #define POLLERR     0x0008  // 에러발생
    #define POLLHUP     0x0010  // 연결이 끊겼음
    #define POLLNVAL    0x0020  // 파일지시자가 열리지 않은것같은
                                // Invalid request (잘못된 요청)
2번째 인자인 nfds 는 pollfd 의 배열의 크기 즉 우리가 조사할 파일지시자의 크기(네트웍프로그래밍측면에서 보자면 받아들일수 있는 클라이언트의 크기) 로, 보통 프로그래밍 할때 그크기를 지정해준다. 
마지막 아규먼트인 timeout 는 select 의 time 와 같은 역할을 한다.
  • 값을 지정하지 않을경우 이벤트가 발생하기 전까지 영원히 기다린다.
  • 0일경우는 기다리지 않고 곧바로 다음 루틴을 진행하고자
  • 0보다 큰 양의 정수일 경우에는 해당 시간만큼을 기다리게 된다. 해당 시간내에 어떤 이벤트가 발생하면 즉시 되돌려 주며, 시간을 초과하게 될경우 0을 return 한다.

위의 3가지 아규먼트를 채워넣음으로써 poll을 사용할수 있다. poll 함수의 return 값은 int 형인데, 에러일경우 -1 이 리턴되고, 그렇지 않을경우 revent 가 발생한 pollfd 구조체의 숫자를 돌려주게 된다. 

이제 poll 버젼의 우편주소 프로그램의 서버를 작성해 보도록 하자. 

예제 : zipcode_poll.c
#include <sys/time.h> 
#include <sys/socket.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <unistd.h> 
#include <stdlib.h> 
#include <stdio.h> 
#include <string.h> 
#include <netinet/in.h> 
#include <arpa/inet.h> 
#include <sys/poll.h> 

// 받아들일수 있는 클라이언트의 크기
#define OPEN_MAX    600 

int main(int argc, char **argv)
{

    int server_sockfd, client_sockfd, sockfd;

    int i, maxi;
    int nread;
    int state = 0;

    socklen_t clilen;

    struct sockaddr_in clientaddr, serveraddr;

    char buf[255];
    char line[255];

    FILE *fp;

    struct pollfd client[OPEN_MAX];

    if (argc != 2)
    {
        printf("Usage : ./zipcode_poll [port]\n");
        printf("예    : ./zipcode_poll 4444\n");
        exit(0);
    }


    if ((fp = fopen("zipcode.txt", "r")) == NULL)
    {
        perror("file open error : ");
        exit(0);
    }

    if ((server_sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("socket error : ");
        exit(0);
    }
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serveraddr.sin_port = htons(atoi(argv[1]));

    state = bind(server_sockfd, (struct sockaddr *)&serveraddr, 
                sizeof(serveraddr));

    if (state == -1)
    {
        perror("bind error : ");
        exit(0);
    }
    state = listen(server_sockfd, 5);
    if (state == -1)
    {
        perror("listen error : ");
        exit(0);
    }

    // pollfd  구조체에 
    // 소켓지시자를 할당한다.  
    // 소켓에 쓰기 events (POLLIN)에 대해서 
    // 반응하도록 세팅한다. 
    client[0].fd = server_sockfd;
    client[0].events = POLLIN;

    // pollfd 구조체의 모든 fd 를 -1 로 초기화 한다.  
    // fd 가 -1 이면 파일지시자가 세팅되어있지 않다는 뜻이다. 
    for (i = 1; i < OPEN_MAX; i++)
    {
        client[i].fd = -1;
    }
    maxi = 0;

    // POLLING 시작
    for (;;)
    {
        nread = poll(client, maxi + i, 1000);

        // 만약 POLLIN 이벤트에 대해서 
        // 되돌려준 이벤트가(revents) POLLIN
        // 이라면 accept 한다. 
        if (client[0].revents & POLLIN)
        {
            clilen=sizeof(clientaddr);
            client_sockfd = accept(server_sockfd, 
                            (struct sockaddr *)&clientaddr, 
                            &clilen);
            for (i = 1; i < OPEN_MAX; i++)
            {
                if (client[i].fd < 0)
                {
                    client[i].fd = client_sockfd;
                    break;
                }
            }

            if (i == OPEN_MAX)
            {
                perror("too many clients : ");
                exit(0);
            }

            client[i].events = POLLIN;

            if (i > maxi)
            {
                maxi = i;
            }

            if (--nread <= 0)
                continue;
        }

        // 현재 파일지시자의 총갯수 만큼 루프를 돌면서 
        // 각 파일지시자에 POLLIN revent 가 발생했는지를 조사하고 
        // POLLIN이 발생했다면, 해당 파일지시자에서 데이타를 읽어들이고, 
        // 주소정보를 돌려준다. 
        // 만약 "quit" 를 읽었다면, 소켓연결을 끊는다. 
        for (i = 1; i <= maxi; i++)
        {
            if ((sockfd = client[i].fd) < 0)
                continue;
            if (client[i].revents & (POLLIN | POLLERR))
            {
                rewind(fp);
                memset(buf, 0x00, 255);
                if (read(sockfd, buf, 255) <= 0)
                {
                    close(client[i].fd);
                    client[i].fd = -1;
                }
                else
                {
                    if (strncmp(buf, "quit", 4) == 0)
                    {
                        write(sockfd, "byebye\n", 7);
                        close(client[i].fd);
                        client[i].fd = -1;
                        break;
                    }
                    while(fgets(line, 255, fp) != NULL)
                    {
                        if (strstr(line, buf) != NULL)
                            write(sockfd, line, 255);
                        memset(line, 0x00, 255);
                    }
                }
            }
        }
    }
}
select 버젼인 다중연결서버 만들기(2)와 비교해서 보기 바란다. 
코딩 분위기가 select 와 매우 비슷하다는걸 알수 있을것이다. 

pollfd 에 입력된 파일지시자의 event 에 입력event 가 발생하면, 커널은 입력event 에 대한 결과를 되돌려줄것이다. 
이결과는 입력 event 가 제대로 처리되었다면 POLLIN 을 되돌려줄것이고, 어딘가에서 에러가 발생했다면 POLLERR 을 되돌려주게 될것이다. 
그러므로 우리는 revent 를 검사함으로써, 해당 파일지시자에 읽을 데이타가 있다는걸 알게 되고, 데이타를 읽어서 적당한 행동(여기에서는 주소를 돌려주는)을 할수 있다. 
위의 프로그램은 이러한 일련의 과정을 보여준다.
select 버젼과 별차이가 없으므로 select 버젼의 쏘쓰를 이해했다면 위의 쏘쓰를 이해하는데 별 어려움이 없을것이다. 

poll 은 보통 select 에 비해서 해당파일지시자에 대해서 보다 많은 정보를 되돌려줌으로, 보통 select 보다 선호되는 추세이다.
select 버젼과 마찬가지로 polling 중간에 파일 I/O 가 들어갈경우, 파일 I/O 작업에서의 block 때문에 짧은시간에 다수의 메시지를 처리할경우 문제가 될 소지가 있다. 
그러므로 되도록이면 polling 중간에 파일 I/O 가 일어나지 않도록 해주어야 한다.
위의 쏘쓰의 경우도 주소정보를 미리 메모리 상에 올려놓고 쓰는게 더욱 좋은 방법이 될것이다.

Posted by 라판

출처 : http://taehyo.egloos.com/4131598

태효형의 깔끔한 정리? ㅋ 

왜 커널의 많은 #defined은 do { ... } while(0)을 많이 사용할까?
다음과 같은 이유가 있다:

(from Dave Miller) 빈 구문(empty statements)은 컴파일러가 경고를 낸다.
따라서 #define FOO do { } while(0) 와 같은  구문을 사용한다.

(from Dave Miller) 지역 변수를 선언할 수 있는 기본 구역(basic block)을 마련해 준다.

(from Ben Collins) 조건문을 포함한 코드에서 복잡한 형태의 매크로를 사용할 수 있도록 해준다.
다음과 같이 몇몇 줄로 이루어진 매크로를 고려해보자:

#define FOO(x) \
        printf("arg is %s\n", x); \
        do_something_useful(x);

이제 이 코드를 다음과 같이 사용한다고 생각해보자:

if (blah == 2)
        FOO(blah);

이 코드는 다음과 같이 해석된다:

if (blah == 2)
        printf("arg is %s\n", blah);
        do_something_useful(blah);;

위에서 볼 수 있듯이 원했던 결과와는 달리 if의 범위는 prinft() 만을 포함하게 되고

do_something_useful()는 조건과 관계없이 불린다(if의 범위에 포함되지 않기 때문에).

따라서 do { ... } while(0)를 사용하여 다음과 같은 결과를 얻을 수 있다:

if (blah == 2)
        do {
                printf("arg is %s\n", blah);
                do_something_useful(blah);
        } while (0);

이것이 기대했던 결과이다.


(from Per Persson) Miller와 Collins가 지적했듯이 여러 줄의 코드를 쓰거나

지역 변수를 선언하기 위해 블록 구문(block statement)을 사용하고자 할 수도 있을 것이다.

하지만 예를 들어 아래와 같이 일반적인 블록 구문을 쓴다고 하자:

#define exch(x,y) { int tmp; tmp=x; x=y; y=tmp; }

하지만 어떤 경우에는 이 코드는 동작하지 않을 수 없다.

아래의 코드는 두개의 브랜치를 가지는 if문을 의도한 것이다:

if (x > y)
        exch(x,y);             // Branch 1
else
        do_something();     // Branch 2

하지만 이 코드는 단지 하나의 브랜치만 갖는 if문으로 해석된다.

if (x > y) {                // 단일 브랜치를 갖는 if문!!!
        int tmp;            // 블록으로 구성된
        tmp = x;           // 단 하나의 브랜치
        x = y;
        y = tmp;
}
;                             // 빈 구문
else                        // 에러!!! "parse error before else"
        do_something();

문제는 블록 바로 다음에 나오는 세미콜론(;)이다. 해결책은 블록을 do, while(0) 사이에 위치시키는 것이다.

그러면 컴파일러가 블록 구문이라고 인식하지 않는 블록의 역할을 하는 하나의 구문을 만들 수 있다.

이제 변경된 if문은 다음과 같다:

if (x > y)
        do {
                int tmp;
                tmp = x;
                x = y;
                y = tmp;
        } while(0);
else
        do_something();

(from Bart Trojanowski) gcc는 do-while-0 구문을 대체할 수 있는 구문 표현을 추가하였다.

이것은 위에 언급한 모든 이점을 갖는 동시에 좀 더 읽기 쉽다.

#define FOO(arg) ({         \
           typeof(arg) lcl;     \
           lcl = bar(arg);       \
           lcl;                      \
    })
Posted by 라판

    이 글은 http://www.kernel.org/doc/Documentation/input/input-programming.txt을 바탕으로 작성되었습니다. 

  1. 간단한 예제

    간단한 입력 디바이스 드라이버 예제를 만들어 보자.  디바이스는 BTN_0이라는 한 가지 버튼만을 가지고 있고 버튼은 BUTTON_PORT로 정의된 i/o 포트에서 접근할 수 있다고 가정하자. 버튼을 누르거나 뗄 때, BUTTON_IRQ가 발생한다. 이러한 디바이스에 대한 드라이버는 다음과 같다. 



    #include <linux/input.h>

    #include <linux/module.h>
    #include <linux/init.h>
    
    #include <asm/irq.h>
    #include <asm/io.h>
    
    static struct input_dev *button_dev;
    
    static irqreturn_t button_interrupt(int irq, void *dummy)
    {
    	input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1);
    	input_sync(button_dev);
    	return IRQ_HANDLED;
    }
    
    static int __init button_init(void)
    {
    	int error;
    
    	if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) {
                    printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_irq);
                    return -EBUSY;
            }
    
    	button_dev = input_allocate_device();
    	if (!button_dev) {
    		printk(KERN_ERR "button.c: Not enough memory\n");
    		error = -ENOMEM;
    		goto err_free_irq;
    	}
    
    	button_dev->evbit[0] = BIT_MASK(EV_KEY);
    	button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);
    
    	error = input_register_device(button_dev);
    	if (error) {
    		printk(KERN_ERR "button.c: Failed to register device\n");
    		goto err_free_dev;
    	}
    
    	return 0;
    
     err_free_dev:
    	input_free_device(button_dev);
     err_free_irq:
    	free_irq(BUTTON_IRQ, button_interrupt);
    	return error;
    }
    
    static void __exit button_exit(void)
    {
            input_unregister_device(button_dev);
    	free_irq(BUTTON_IRQ, button_interrupt);
    }
    
    module_init(button_init);
    module_exit(button_exit); 




  2.  예제 설명 
     
    일반적으로 리눅스 드라이버는 모듈 프로그래밍으로 구현된다. 구현된 드라이버 모듈은 커널에 컴파일 시 포함되거나, 커널 부팅 후 관리자에 의해 로드된다.

    위 예제를 도식화 하면 다음과 같다. 



    여기서 커널이 어떻게 드라이버 모듈을 로드/해제 하는지는 차후 알아보기로 하고 위 도식에 따른 각각의 동작에 대한 설명에 집중하기로 한다. 


    모듈 로드 

    모듈이 로드되면 커널은 module_init 매크로로 명시한 함수를 호출하게 되는 데, 이는 드라이버 초기화 진입점이 된다. 
    해당 함수의 호출 시점은 드라이버의 형태에 따라 다르다. 만약 드라이버가 빌트인이라면, 커널 부팅 중 do_initcalls()에서, 적재가능한 모듈 형태라면 모듈이 적재될 때 해당 함수가 불려지게 될 것이다. 

    모듈 당 단 한 개의 module_init 함수가 존재하게 된다. 

    참고 : http://lxr.free-electrons.com/source/include/linux/init.h#L259

    module_init(button_init)

    module_init 매크로에 의해 button_init 함수가 호출된다. 
    module_init에 의해 불려진 함수에서 하는 일은 크게 2가지이다. 
    드라이버 구동에 필요한 자원 할당과 드라이버를 커널에 등록하는 일이다.

    필요한 자원은 드라이버의 속성에 따라 다르다. i2c로 통신하는 디바이스는 i2c 관련 자원을 확보하고 ADC값을 다루는 디바이스는 ADC와 관련 자원을 확보해야할 것이며 인터럽트를 사용하는 디바이스는 디바이스에 대한 IRQ를 확보하고 인터럽트 핸들러를 등록해야 할 것이다.  
      예제는 interrupt driven 방식이므로 BUTTON_IRQ라는 irq 넘버에 대한 인터럽트 핸들러를 등록해야 한다.

          request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)

    필요한 자원을 확보했으면, 이제 드라이버를 등록해야 한다.
    input 드라이버를 등록하기 위해선 input 디바이스 정보를 가진 자료구조가 필요한데 그것이 input_dev이다.
    input 드라이버의 등록은 input_dev에 대한 조작 과정이며, input_dev에 대한 메모리 할당, input_dev 비트필드 설정, 드라이버 등록 순으로 이루어진다. 




    * input_dev 메모리 할당    . 
        input_allocate_device()를 호출하면 커널영역의 메모리를 할당하여 input_dev를 만들고 이를 반환한다.     

    더보기


    * input 비트필드 설정


      input_dev는 input 디바이스에 대한 다양한 정보를 포함하고 있다. 그러나 그 중에서 가장 중요한 것은 어떤 이벤트를 핸들링하느냐이다.  evbit는 디바이스가 지원하는 이벤트의 타입을 포함하고 있으며, 대표적으로 EV_KEY(일반적인 키 혹은 버튼), EV_REL(상대적 좌표), EV_ABS(절대 좌표) 등의 이벤트를 선언할 수 있다. 
     

      그리고 각 이벤트타입마다 추가적인 비트필드가 존재한다. 키 이벤트일 경우, 어떤 키를 핸들링하는 지에 대한 정보가 필요하고, 절대 혹은 상대 좌표 이벤트일 경우, 어떤 축의 좌표를 핸들링할 것인지, 그 최소/최대값은 얼마인지 등에 대한 정보가 필요하기 때문이다. 자세한 사항은 http://lxr.free-electrons.com/source/include/linux/input.h#L1219를 참고하라. 

    예제에서는 버튼을 사용하고 있으므로 EV_KEY를 가지게 되며, BTN_0 키를 핸들링하므로 keybit에 BTN_0을 설정해준다. 

    * input_dev 등록 
        이제 input_dev 자료구조에는 동작에 필요한 모든 정보를 가지고 있으므로 이를 커널에 등록해야 한다. 이는 input_register_device(struct input_dev)를 호출함으로써 이루어진다. 


    드라이버 동작 

    드라이버가 동작하는 방식에는 크게 interrupt driven 방식과 polling 방식이 있다. 예제에서는 interrupt driven 방식으로 되어 있으므로 이 글에서는 interrupt driven 방식만을 다루기로 한다. 

    더보기


      BUTTON_IRQ가 발생하면, 커널은 BUTTON_IRQ에 대해 등록된 인터럽트 핸들러인 button_interrupt()를 호출한다. 

     

    static irqreturn_t button_interrupt(int irq, void *dummy)

    {
    	input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1);
    	input_sync(button_dev);
    	return IRQ_HANDLED;
    }

    매번의 인터럽트마다 button_interrupt는 BUTTON_0의 버튼 상태(PRESS/RELEASE)를 체크하여 커널에 보고해야 한다.
     input_report_key()함수를 통해 BUTTON_0의 상태를 보고할 수 있다.

    그러나 만약, 동시에 여러 개의 입력이 발생할 경우에는 어떻게 처리해야 할까? 가령 마우스와 같이 동시에 X,Y 좌표를 전달해야 할 때에, 드라이버는 커널에 X,Y 좌표 변화가 동시에 발생하였다는 것을 알려야 한다. 이렇게 복수 개의 입력이 하나의 이벤트로 발생하였다는 것을 알리기 위해 input_sync()를 호출한다.

     

    모듈 해제

    모듈 로드 시와 유사하게 모듈 해제 시, 커널은 module_exit 매크로로 명시한 함수를 호출하게 되며 
    이는 드라이버 종료 진입점이 된다. 예제에서는 button_exit 함수가 호출된다.

    해당 함수의 호출 여부는 드라이버의 형태에 따라 다르다. 드라이버가 모듈 형태일 경우, 사용자가 rmmod로 모듈을 해제할 시, cleanup_module()함수에 의해 불려지지만 드라이버가 빌트인 형태라면, 모듈을 해지하는 경우가 없기에 해당 함수는 불려지지 않게 된다. 

    참고 : http://lxr.free-electrons.com/source/include/linux/init.h#L268

    module_exit(button_exit)

    module_exit 매크로에 의해 button_exit 함수가 호출하게 된다. 

    module_exit 매크로로 명시된 함수는 드라이버의 구동 중에 사용한 자원을 반환해야 한다. 
    예제에서는 IRQ를 사용하였으므로 IRQ에 대한 자원을 해지해야 한다. 

    그리고 커널에 등록한 드라이버를 해지하고 메모리를 반환해야 한다. 경우에 따라 두 가지 함수가 쓰이는  데 다음과 같다. 
       * input_unregister_device() - input_register_device()에 의해 등록되었을 경우, 드라이버를 해지하고 메모리를 반환                                               한다.
       * input_free_device() -  input_register_device()에 등록되지 않는 input_dev에 대한 메모리를 반환한다. 주로 초기화                                       도중 에러가 생겼을 때 메모리 반환을 위해 쓰인다. 
      

Posted by 라판

출저 : http://blog.naver.com/nugu99/70004648008

유영창| FA 리눅스(주)

 

터치스크린은 마우스와 유사한 포인터 입력 장치이지만 똑같은 방식으로 동작하는 장치는 아니다. 이번 컬럼에서는 개발자가 임베디드 리눅스에서 터치스크린을 다루어야 하는 경우 알아야 할 디바이스 드라이버와 관련한 내용을 커널 2.6 기준으로 살펴보고자 한다.

 

PDA장치는 필수적으로 GUI 기능이 구현되어야 한다. PDA 장비에 가장 많이 사용되는 입력 장치로는 터치스크린이 있는데, PC 시스템이라면 마우스나 키보드를 사용하기 때문에 공장자동화에 쓰이는 모니터링 시스템이나, 제어장치나 물류 시스템에 사용되는 POS 시스템과 같은 특수한 경우를 제외하면 일반인이 보기 힘든 것이 터치스크린이다. 반면에 작은 그래픽 화면에서 동작하는 조건에서 키보드와 같은 부가적인 입력 장치를 달기 곤란한 PDA류의 임베디드 제품들에서는 터치스크린은 흔히 볼 수 있는 입력 장치이다.

 

터치스크린은 기본적으로 절대 좌표계를 사용한다는 점과 좌표 입력이 발생하려면 접촉되어야 한다는 점이 마우스 입력 장치와 가장 큰 차이점이다. 임베디드 리눅스는 PC 리눅스와 동일한 시스템을 사용하기 때문에 터치스크린 역시 마우스와 같은 장치로 봐야 하고 처리돼야 하지만 그 입력 처리 방식의 차이점 때문에 조금 다른 관점에서 처리되어야 한다. 특히 디바이스 드라이버 입장에서는 하드웨어 구조가 다르기 때문에 기존 마우스 처리와 다른 형태를 가지고 있다.

 

터치스크린

 

PDA와 같은 휴대용 장치에 사용되는 터치스크린은 사용자의 입력을 좌표 값으로 바꿔주는 시스템이다. 일반적으로 터치스크린은 LCD와 같은 화면표시 장치와 연동돼서 움직인다. 입력 방식은 손으로 직접 위치를 지정하거나 스타일러스 펜과 같은 전용 입력 도구로 지정한다. 화면 크기가 작은 PDA 시스템은 손가락으로 입력이 가능하지만 접촉 범위가 커지므로 스타일러스 펜을 제공하여 입력 위치를 세밀하게 처리하고 있다. 스타일러스 펜은 끝이 둥그렇게 처리되었는데 이는 터치스크린의 손상을 방지하기 위한 목적도 있다. 터치스크린에 의해서 입력된 내용은 내부 장치에 의해서 좌표로 변환된다. 이렇게 변환된 좌표는 LCD상에 커서로 표시하여 사용자가 입력된 위치를 인식하도록 도와준다. 하지만 PDA의 경우에는 입력 위치가 곧 표출 위치이기 때문에 입력된 위치를 커서 형태로 굳이 표현하지 않는 경우가 많다. 어떤 시스템은 터치스크린의 크기가 LCD 크기를 벗어나서 필기체 입력을 처리하도록 도와주는 영역을 두기도 한다.

 

터치스크린의 종류

 

터치스크린의 종류는 매우 다양하다. 보통 처리 방식에 따라서 다음과 같은 방식으로 나눠진다.

 

◆ 매트릭스 스위치(Matrix Switch) 방식
- 광센서(적외선) 매트릭스 방식
- 도전 필름 방식 - 저항막 방식
- 용량변화 방식 - 정전 용량 방식
- 금속세선매립 방식

◆ 아날로그 방식
- 도전 필름 방식(Analog)
- 하중분압(압력 센서) 방식
- 표면파(초음파) 반사 방식
- 금속세선매립 방식(감압 방식)

 

이중 PDA와 같은 휴대용 기기에 가장 많이 사용되는 방식은 저항막 방식이고 POS와 같은 시스템에서는 정전 용량 방식도 사용된다. 이 연재에서는 주로 저항막 방식에 대해서 설명할 것이다. 참고로 정전 용량 방식 역시 간략하게 소개하겠다.

 

저항막 방식

유리나 투명한 플라스틱 판 위에 저항성분의 물질을 입히고 그 위에 폴리에스틸렌 필름을 덮어씌운 형태로 되어 있으며, 두 면이 서로 닿지 않도록 일정한 간격으로 절연층(Dot Spacer)이 설치되어 있다.

 

동작 상태에서는 저항막의 양단에서 일정한 전류를 흘려주면 저항막이 저항 성분을 갖는 저항체와 같이 작용하기 때문에 양단에 전압이 걸리게 된다. 손으로 접촉을 하게 되면 위쪽 표면의 폴리에스틸렌 필름이 휘어지면서 두 면이 접속하게 된다. 이때 두 면의 저항 성분 때문에 저항의 병렬접속과 같은 형태가 되고 저항 값의 변화가 일어나게 된다. 이때 양단에 흐르는 전류에 의하여 전압의 변화도 일어나게 되는데, 바로 이러한 전압의 변화 정도로써 접촉된 손의 위치를 알 수 있다. 이 방식은 다음과 같은 특징을 가진다.

 

◆ 원리의 특성상 두 면이 닿아야 인식된다.
◆ 볼펜이나 기타 물질에 의한 압력으로도 인식된다.
◆ 뾰쪽한 물체에 의한 손상이나 사용빈도에 따라 표면에 긁힘이 날 수 있다.
◆ 수명이 거의 영구적이다(한 점당 200만 터치 ; 일반적으로 약 10년 간 사용)
◆ 터치시 반응속도가 빠르다(초당 약 150번 이상)
◆ 오차율이 적다(2%).
◆ 투명도가 좋음(폴리에스틸렌 필림의 종류에 따라 88~92%의 투과율)

 

정전 용량 방식

유리 또는 플라스틱, 필름과 같은 양면에 투명한 특수전도성 금속(TAO)을 코팅되어지는 구성을 갖는다.

 

스크린의 네 귀퉁이에 전압을 걸어준다. 구석에서 발생되는 고주파가 센서 전면에 퍼지게 된다. 이때 손가락으로 스크린을 접촉하게 되면 수신부에서는 변형된 파형이 감지된다. 이를 가지고 컨트롤러에서는 위치를 계산하게 된다. 이 방식은 다음과 같은 특징을 가진다.

 

◆ 미세한 정전압에도 반응하므로 살짝만 접촉되어도 감지한다.
◆ 높은 분해 능력을 가진다(1024×1024).
◆ 강화 처리된 유리에 특수 금속 코팅을 했기 때문에 견고성이 좋다.
◆ 수명이 길다(1 포인트 당 2000만 터치).
◆ 빛 투과율이 높아 원 화상의 색상을 그대로 살릴 수 있다.
◆ 터치 시 반응속도가 매우 빠르다(초당 약 270번 이상). 
◆ 오차율이 매우 적어 정확하다(오차율 : 1%).

 

<그림 1> 저항막 방식

 

<그림 2> 정전 용량 방식

 

<그림 3> 터치스크린 라인 층

 

<그림 4> 터치스크린의 저항 특성

 

<그림 5> 접촉 검출 구조

 

<그림 6> 등가 회로

 

터치스크린 연결 방식

터치스크린과 컨트롤러에 입력되는 라인 수에 따라 크게 4선식과 5선식으로 나누어진다. 4선식은 저항막 방식에서 사용되는 업게 표준 방식이다. 5선식은 4선식의 신호에 사용자 입력의 압력 감도를 받을 수 있는 방식으로 그리 흔하게 사용되는 방식은 아니다.

 

POS 시스템의 경우라면 터치스크린을 연결하는 경우 RS-232나 USB와 같은 버스 방식으로 연결될 수 있다. 하지만 이 경우는 업계마다 그 연결 방식이 다르고 터치스크린을 처리하는 컨트롤러가 독립적으로 존재하는 경우이기 때문에 이번 연재에서는 다루지 않는다.

 

터치스크린의 하드웨어 구성과 동작 원리

 

저항식 터치스크린은 일반적으로 4선식으로 구성되어 있다. 이 4선은 터치스크린의 층에 연결되어 <그림 3>과 같은 신호라인을 갖는다.

 

업체마다 각 층의 구성은 다르지만 보편적으로 가장 상층이 X축 검출을 위한 층이고, 그 다음은 ITO라고 불리는 절연층, 그 다음은 Y축 검출을 위한 층, 마지막으로 터치스크린 지지를 위한 유리나 플라스틱의 고형물이 된다. 각 층은 저항 피막으로 코팅되어 있으므로 논리적으로는 하나의 저항으로 봐야 한다.

 

ITO 절연층은 터치가 되는 순간에 상층의 X축 검출을 위한 코팅 층과 바로 밑의 Y축 검출을 위한 코딩층이 연결된다. 만약 눌린 순간을 검출하는 것을 그림으로 표현하면 <그림 5>와 같은 형식이 된다. 이를 회로적으로 검출하기 위한 회로상 표현은 <그림 6>이 된다. 즉 R2에 걸리는 전압을 측정하고 이 전압과 인가된 VCC의 비율이 접촉된 X의 위치가 되는 것이다. Y의 위치를 측정하기 위해서는 이와 반대로 Y(+)에 VCC를 인가하고 Y(-)에 GND를 연결하고 X(+)에서 측정된 전압 값을 측정하면 된다.

 

일반적으로 ADC 컨트롤러의 분해능이 측정 정밀도를 결정하는데 입력 위치의 검출을 위해서 사용되는 ADC는 축차 비교형을 사용하기 때문에 일정한 주기를 가져야 한다.

 

소프트웨어적으로 이런 저항식 터치스크린의 컨트롤러를 제어할 경우라면 다음과 같은 순서가 진행된다.

 

1. X(+)에 VCC가 인가하고 X(-)에 GND 단자가 연결되도록 제어한다.
2. ADC 입력을 Y(+)에 연결하고 Y(-)에는 아무런 연결이 되지 않도록 한다. 
3. ADC의 전압 측정을 시작하도록 한다. 
4. ADC의 전압 측정이 종료되면 해당 전압에 해당하는 디지털 값을 읽고 이 값을 X 좌표의 측정 값으로 저장한다. 
5. Y(+)에 VCC가 인가되고 Y(-)에 GND 단자가 연결되도록 제어한다.
6. ADC 입력을 X(+)에 연결하고 X(-)에는 아무런 연결이 되지 않도록 한다. 
7. ADC의 전압 측정을 시작하도록 한다.
8. ADC의 전압 측정이 종료되면 해당 전압에 해당하는 디지털 값을 읽고 이 값을 Y 좌표의 측정 값으로 저장한다.

 

이와 같은 과정은 터치스크린 컨트롤러에 따라서 자동으로 해주는 경우가 있는데 이때 역시 원리적으로 이와 같은 과정이 처리되는 것을 이해해야 컨트롤러 제어에 대한 프로그램 디버깅을 쉽게 할 수 있다. 보통 터치스크린이 눌린 상태가 되면 컨트롤러는 인터럽트를 발생하게 되는데 이것은 X 값이나 Y 값이 일정 전압 이하가 되는 상태를 검출하여 만들어진다.

 

터치스크린의 입력을 처리하기 위해서 터치 컨트롤러는 보통 일정한 주기로 이와 같은 처리를 반복하게 한다. 정확한 입력 위치를 얻기 위해서 샘플링 값을 평균 내어 처리하기 때문이다.

 

리눅스와 터치스크린

 

터치스크린을 리눅스 시스템에서 다루기 위해서는 리눅스에서 입력 장치를 어떻게 다루는지를 알아야 한다. 터치스크린은 PC 시스템에서 그리 흔하지 않은 시스템이기 때문에 표준적인 처리 방식은 초기 설계에 반영되어 있지 않았다. 그러나 리눅스 개발자들은 필요에 의해서 터치스크린 입력을 기존의 마우스 입력을 이용하여 처리하였다. 다행히도 마우스 입력 장치용 디바이스 드라이버는 디지타이저와 같은 타블렛 처리 장치들을 처리하기 위해서 절대 좌표를 처리하도록 하고 있었다.

 

이 특성을 이용하여 개발자들은 마우스를 에뮬레이션하도록 처리하여 터치스크린을 처리하는 방법을 개발하였다. 이것은 리눅스 디바이스 드라이버가 정식으로 터치스크린을 지원하지 않는다는 것을 의미하기도 한다. 이런 점은 커널 2.6에서 개선되었다. 임베디드 시스템에서 리눅스가 사용되고 또한 USB와 같은 다양한 입력장치를 지원하기 위해서 리눅스의 입력 시스템을 처리해야 하는 사용자 환경 변화가 발생했고 이를 해결하는 과정에서 터치스크린 문제도 자연적으로 해결되었다. 우선적으로 리눅스에서 터치스크린을 응용 프로그램에서 사용할 수 있도록 지원하는 구조를 그림으로 살펴보자.

 

<그림 7>에서 보듯 리눅스가 터치스크린을 처리하기 위해서는 크게 디바이스 드라이버를 구현하는 커널부와 터치 시스템을 이용하는 응용 프로그램 처리부로 나눌 수 있다. 응용 프로그램 처리부는 터치를 포함한 다양한 입력 장치를 다루기 위한 응용 프로그램의 드라이버 부분과 실제로 응용하는 프로그램부분으로 나눌 수 있다.

 

하드웨어

터치스크린에서 하드웨어는 터치 장치와 터치를 제어하고 좌표 값을 ADC(아날로그-디지털 변환 컨트롤러)를 이용하여 실제 눌린 위치(X, Y)와 눌림 상태를 얻어오는 컨트롤러를 포함한다. 이 컨트롤러는 커널 디바이스 드라이버 층에서 해석되어 응용 프로그램에 전달된다.

 

커널 디바이스 드라이버

터치 컨트롤러는 하드웨어 장치이기 때문에 리눅스 시스템에서는 응용 프로그램이 직접적으로 접근이 불가능하다. 응용 프로그램이 하드웨어를 제어하기 위해서 커널은 디바이스 드라이버를 제공하여야 하고 이 디바이스 드라이버를 커널이 제공하는 디바이스 파일 시스템을 이용하여 접근해야 한다. 터치스크린과 관련한 디바이스 드라이버는 커널 2.6부터 INPUT 디바이스 드라이버로 통합되어졌다. 터치 디바이스 드라이버를 사용하기 위해서는 리눅스 디바이스 드라이버의 한 형태인 INPUT 디바이스 드라이버의 이해가 필요하고 부가적으로 이벤트 핸들러 디바이스인 mouse와 event 및 tsdev 디바이스 드라이버에 대한 이해가 필요하다.

 

응용 프로그램

GUI를 구현하는 응용 프로그램으로 가장 대표적인 것을 들라면 X가 있다. 임베디드 시스템은 흔히 TinyX를 사용하는데 방대한 X의 기능 중 필요한 기능만을 구현한 간단한(?) X이다. 이 X는 여러 가지 입력 장치를 처리하기 위해서 하부 구조에 입력 모듈을 구분하여 드라이버처럼 다루고 있다. 이 드라이버 중 하나에 터치스크린을 처리하도록 하는 드라이버가 필요하다. 여기서 드라이버라는 의미는 라이브러리라는 의미도 되고 모듈의 의미도 된다.

 

<그림 7> 터치 시스템을 처리하기 위한 리눅스 구조

 

임베디드 리눅스 시스템에서 사용되는 GUI 라이브러리 중에는 QT도 포함된다. 이 QT를 우리는 GNOME과 대치되는 X의 상층부에 구현되는 매니저 시스템으로 알고 있지만 임베디드 리눅스에서는 QT 자체가 그래픽 라이브러리의 묶음 형태를 가지고 있기 때문에 X를 사용하지 않고도 그래픽 처리가 가능하다. QT 역시 X와 유사하게 입력 장치를 위한 드라이버 모듈이 필요하다. 윈도우의 API와 유사한 함수나 아주 작은 X를 구현하여 GUI를 구현하고 싶다면 마이크로윈도우(MicroWindow or NanoX)라는 라이브러리를 고려해보는 것도 좋다. 이 마이크로윈도우 역시 가장 하부 단에는 입력 드라이버를 만들어줘야 한다.

 

응용 프로그램에서 터치를 이용하기 위해서는 GUI 라이브러리나 시스템을 이용하게 된다. 이 GUI 라이브러리는 다양한 입력 장치를 모두 처리하기 위해서 각각의 라이브러리나 시스템별로 자신만의 독특한 규칙을 가지고 있다. 모든 라이브러리나 시스템에 대해서 이해할 수는 없지만 대부분의 구성이 유사하기 때문에 한두 가지만 이해하고 나면 나머지는 필요에 따라서 금방 파악할 수 있을 것이다. 이 글에서는 아쉽게도 지면관계상 이 부분의 설명이 빠지게 되었다. 독자에게 양해를 구한다.

 

INPUT 디바이스 드라이버

 

리눅스 시스템 커널에서 터치스크린을 다루는 방식을 이해하기 위해서 가장 먼저 입력(INPUT) 디바이스 드라이버가 어떻게 구성되며, 어떤 특성이 있는가에 대하여 이해해보는 것은 무척 중요하다. 임베디드 시스템에서 터치스크린용 디바이스 드라이버를 만들어야 하는 개발자라면 반드시 이 부분의 이해가 필요하다. 이런 이해 없이 디바이스 드라이버를 제작하면 절름발이 드라이버를 만들 공산이 크다. 그래서 여기서는 커널 2.6을 중심으로 입력 디바이스 드라이버를 설명하고자 한다.

 

PC 시스템에서 사용자의 입력을 처리하는 입력 장치는 크게 키보드, 마우스, 조이스틱, 터치스크린으로 나누어 볼 수 있다. 초기 PC 시스템은 이 네 가지 입력장치에 해당하는 장치가 많지 않았기 때문에 표준적인 처리 방법을 따로 구현하지 않았다. 필요에 의해서 그때그때 만들어져 갔다는 것이 더 맞을지도 모른다. 키보드라면 IBM 키보드였고 마우스라면 마이크로소프트 마우스, 조이스틱은 우리가 흔히 아는 조이스틱 정도였다. 그런데 PC 시스템이 발달하고 USB라는 버스가 생기면서 입력 장치는 여러 가지 형태로 구현되기 시작했다.

 

<그림 8> INPUT 디바이스 드라이버 구성과 응용 프로그램

 

더구나 터치스크린을 사용하거나 타블렛 또는 디지타이저와 같은 입력장치가 등장하면서 리눅스 디바이스 드라이버 개발자들은 그때그때 필요한 입력 장치를 만들어갔다. 이것은 나중에 GUI 응용 프로그램이 각각의 입력장치에 대한 처리 루틴을 따로따로 만들어 가는 문제를 야기하고 USB 입력 장치에 대응하는 것이 점점 힘들어지는 요소가 되었다. 그나마 다행인 것은 드라이버 제작자들은 기존의 디바이스 드라이버의 입출력 처리 규칙을 지키고자 노력해서 큰 혼란이 생기지 않았다는 정도이다.

 

리눅스 커널 개발자들은 이런 다양한 입력 장치를 통합하고자 노력했고 그 결과물로 입력 디바이스 드라이버라는 형태로 발전한다. 이것은 기존의 PS/2나 시리얼을 이용하는 단순한 입력 장치뿐만 아니라 USB나 기타 버스를 이용하는 입력 장치에도 대응될 수 있도록 디바이스 드라이버 구성을 표준화한 것이다. INPUT 디바이스 드라이버가 만들어진 가장 큰 공로자는 역시 USB 버스의 출연이다.

 

입력(INPUT) 디바이스 드라이버의 역할은 크게 두 가지로 볼 수 있다. 첫 번째는 시스템에 연결된 입력 장치의 정보를 파악하여 응용 프로그램이 대응할 수 있도록 한 것과 이벤트 핸들러 디바이스 드라이버들의 등록 처리 방식에 대한 표준적인 절차를 마련한다는 것이다. 입력 디바이스 드라이버는 INPUT 디바이스 드라이버를 중심으로 다음과 같은 이벤트 핸들러 디바이스 드라이버로 구성된다.

 

◆ 키보드 디바이스 드라이버(kbd)
◆ 마우스 디바이스 드라이버(mousedev,mice)
◆ 조이스틱 디바이스 드라이버(joydev)
◆ 이벤트 디바이스 드라이버(evdev)
◆ 터치스크린 디바이스 드라이버(tsdev)

 

이중 이벤트 디바이스 드라이버와 터치스크린 디바이스 드라이버는 현재 진행형이다. 즉 아직도 개발이 완료된 것이 아니고 계속 개발 중이라는 것이다. 그렇다고 불안전한 것이라는 의미는 아니다. 현재로도 충분히 쓸만하지만 개발자가 아직 종료되었다고 선언하지 않았다는 의미이다.

 

INPUT 디바이스 드라이버가 입력 디바이스 드라이버를 총체적으로 관리하는 디바이스 드라이버라면 앞서 열거한 디바이스 드라이버는 응용 프로그램에서 실제로 접근하여 입력 값을 읽어 올 수 있는 디바이스 드라이버이다. 우리가 터치스크린 디바이스 드라이버를 만든다면 반드시 INPUT 디바이스 드라이버에서 제공하는 API를 이용하여 등록해야 하고 앞서 열거한 이벤트 핸들러 디바이스 드라이버 중 하나 이상에 소속되도록 해야 한다. 이것을 도식적으로 표현하면 <그림 8>과 같다.

 

입력 디바이스 드라이버는 앞서 언급했듯이 크게 두 가지 기능을 제공한다. 첫 번째는 등록된 이벤트 핸들러의 정보와 입력 디바이스 정보를 제공하며, 두 번째는 이벤트 핸들러 디바이스 드라이버를 관리하고 실제로 동작되는 입력 디바이스 드라이버를 이벤트 핸들러 디바이스와 연결하는 역할과 전달된 입력 상태를 각각의 이벤트 핸들러에 전달하는 역활을 담당한다.

 

입력 디바이스 드라이버가 등록된 이벤트 핸들러 정보와 디바이스 정보를 표현하는 방법은 proc 파일 시스템을 이용한다. 가장 상위 디렉토리는 다음과 같은 위치에 존재하며 다음과 같은 파일을 가진다.

 

[root@falinux drivers]$ cd /proc/bus/input/ [root@falinux input]$ ls -al dr-xr-xr-x 2 root root 0 Jan 1 00:03 . dr-xr-xr-x 4 root root 0 Jan 1 00:00 .. -r--r--r-- 1 root root 0 Jan 1 00:03 devices -r--r--r-- 1 root root 0 Jan 1 00:03 handlers /proc/bus/input/devices

 

devices는 현재 시스템에서 동작하는 디바이스를 나타내는데 예를 들어 키보드, 터치스크린, USB 마우스가 시스템에 연결되어 있다면 devices 파일은 다음과 같은 내용을 포함한다.

 

[root@falinux input]$ cat devices I : Bus=0003 Vendor=055d Product=0001 Version=0001 N : Name=”Samsung Samsung Combo Mini Keyboard” P : Phys=usb-amba-1/input0 H : Handlers=kbd B : EV=120013 B : KEY=10000 7 ff800000 7ff febeffdf ffefffff ffffffff fffffffe B : MSC=10 B : LED=1f I : Bus=0003 Vendor=045e Product=0040 Version=0121 N : Name=”Microsoft Microsoft Wheel Mouse Optical□ P : Phys=usb-amba-2/input0 H : Handlers=mouse0 ts0 B : EV=17 B : KEY=70000 0 0 0 0 0 0 0 0 B : REL=103 B : MSC=10 I : Bus=0019 Vendor=1013 Product=9300 Version=0000 N : Name=”Cirrus Logic EP93xx Touchscreen” P : Phys=ep93xx_ts/input0 H : Handlers=mouse1 ts1 B : EV=b B : KEY=20 0 0 0 0 0 0 0 0 0 0 B : ABS=11000003

 

이 파일을 보면 현재 3개의 디바이스가 연결되어 있음을 알 수 있다. 각각의 레코드는 다음과 같은 의미들을 갖는다.

 

◆ I는 디바이스의 정보 중 연결된 버스 번호, 생산자 ID, 제품 ID, 버전을 보여준다. 
◆ N는 디바이스 모델명을 알려준다. 
◆ P는 내부적으로 표현되는 물리적인 장치 명을 가리키는데 이것은 디바이스 드라이버가 표현하는 값이다. 
◆ H는 이 디바이스 드라이버와 연결되어 동작하는 이벤트 핸들러가 어떤 것인지를 표현한다. 응용 프로그램은 이곳에 표시된 이벤트 핸들러 중 하나를 열어서 입력 값을 얻을 수 있다.
◆ 그 외에는 제어와 관련된 값으로 그냥 참고용으로 생각해도 무방하다. 물론 디바이스 드라이버를 디버그하는 단계에서는 이 값은 중요한 의미를 가진다.

 

/proc/bus/input/handlers

 

handlers는 동작하고 있는 디바이스와 연결될 수 있는 모든 핸들러의 정보를 표현한다.

 

[root@falinux input]$ cat handlers N : Number=0 Name=kbd N : Number=1 Name=mousedev Minor=32 N : Number=2 Name=tsdev Minor=128

 

이 출력 결과는 현재 커널에서 동작되고 있는 핸들러에 kbd, mousedev, tsdev 세 가지 핸들러가 있다는 의미가 되며 Minor는 해당 핸들러 디바이스 파일의 부번호 시작 번호에 대한 정보가 된다.

 

이벤트 핸들러 디바이스 드라이버

 

이벤트 핸들러 디바이스 드라이버는 응용 프로그램이 입력 장치들에 입력 값들을 읽기 위한 실제 디바이스 드라이버 파일로 두 가지 기능을 수행한다. 첫 번째는 입력 장치에 의해서 전달된 입력 값들을 내부 버퍼에 적절한 변환을 거쳐서 저장하는 기능을 수행한다. 두 번째는 저장된 입력 값들을 응용 프로그램에서 읽을 수 있도록 파일 입출력 처리를 담당한다. 현재 배포중인 커널 2.6.16 커널에는 다음과 같은 이벤트 핸들러 디바이스 드라이버가 포함되어 있다.

 

◆ 키보드 디바이스 드라이버(kbd)
◆ 마우스 디바이스 드라이버(mousedev,mice)
◆ 조이스틱 디바이스 드라이버(joydev)
◆ 이벤트 디바이스 드라이버(evdev)
◆ 터치스크린 디바이스 드라이버(tsdev)

 

이렇게 5가지로 나누어지는 것은 각각의 입력 장치마다 특성이 다르기 때문에 이를 응용 프로그램에서 선택적으로 각각의 특성에 맞게 읽어 들일 수 있도록 처리한 것이다. 이 중 터치스크린과 관련이 있는 이벤트 디바이스 드라이버와 터치스크린 디바이스 드라이버를 제외한 것은 간략하게 소개 정도만 설명하겠다.

 

키보드 이벤트 핸들러 디바이스 드라이버

 

키보드 이벤트 핸들러 디바이스 드라이버는 커널 소스 상에 linux/ drivers/char/keyboard.c 에 구현되어 있는데 이 이벤트 핸들러는 다른 이벤트 핸들러와 다르게 직접적으로 응용 프로그램에서 열 수 없다. 내부적으로 가상 콘솔 디바이스 드라이버와 연결되어 있기 때문이다. 스위치 입력을 특정 키보드 입력으로 처리하거나 USB 키보드 장치를 다루고자 한다면 키보드 이벤트 핸들러와 연결되도록 하여야 한다. USB의 경우는 표준 키보드 클래스가 구현된 경우라면 USB 디바이스 드라이버에 의해서 적절히 연결되기 때문에 특별히 고려할 것은 없다. 이 키보드 이벤트 핸들러에 전달되는 키보드 값은 로우 키 값들이다. 이렇게 전달된 값들은 가상 콘솔의 키보드 입력으로 맵핑되는 과정을 거쳐 응용 프로그램에서 사용할 수 있는 적절히 형태로 변환되어 전달된다.

 

마우스 이벤트 핸들러 디바이스 드라이버

 

마우스는 사용된 역사만큼이나 다양한 종류가 존재한다. 리눅스 커널은 이 모든 것들을 모두 수용하여 구연하고 있다. 아직까지도 이전 방식으로 마우스의 입력을 처리하는 응용 프로그램이 많기 때문에 마우스 디바이스 드라이버 역시 이전 방식으로 처리될 수 있도록 구현하고 있다. 이런 다양성의 통일화를 위해서 마우스 이벤트 핸들러라는 방식으로 통합되어 가고 있으며 USB 마우스 장치는 반드시 마우스 이벤트 핸들러 디바이스 드라이버와 연결된다.

 

마우스 이벤트 핸들러는 linux/drivers/input/mousedev.c에 구현되어 있다. 이 핸들러를 사용하는 마우스 디바이스 드라이버들은 보통 linux/drivers/input/mouse 디렉토리 하부에 구현하고 있다. 시스템에 마우스의 장치가 연결되면 응용 프로그램은 다음과 같은 디바이스 파일을 열어서 입력 값을 전달받을 수 있다. 이 디바이스 파일들은 /dev/input/ 하부 디렉토리에 다음과 같은 주 번호와 부 번호를 할당하여 문자형 디바이스 형식으로 mknod와 같은 유틸리티를 사용하여 수동으로 생성시켜 주어야 한다.

 

crw-r--r-- 1 root root 13, 32 Mar 28 22:45 mouse0 crw-r--r-- 1 root root 13, 33 Mar 29 00:41 mouse1 crw-r--r-- 1 root root 13, 34 Mar 29 00:41 mouse2 crw-r--r-- 1 root root 13, 35 Apr 1 10:50 mouse3 ... ... crw-r--r-- 1 root root 13, 62 Apr 1 10:50 mouse30 crw-r--r-- 1 root root 13, 63 Apr 1 10:50 mice

 

주 번호는 13이며 부 번호는 32부터 63까지 할당할 수 있다. 여기서 주의해서 봐야 할 것은 mice 디바이스 파일이다. mouse라는 이름이 붙은 디바이스 파일은 실제 마우스 장치마다 하나씩 대응된다. 예를 들어 USB 키보드와 일반 마우스를 연결했다면 각각은 mouse0 와 mouse1에 해당하게 된다. 하지만 사용자 입장에서 보면 두 개의 마우스는 어떤 것을 사용하였던 간에 동일한 영역의 화면의 위치를 지정하기 때문에 이와 관련된 처리가 필요하다. 그래서 부 번호가 63인 mice 디바이스 파일은 다르게 처리되도록 되어 있다. mice 디바이스 파일은 부 번호가 63이므로 이 디바이스 파일을 연 응용 프로그램은 실제로 연결된 모든 마우스 장치가 발생한 위치 변화 값을 받을 수 있다. 그래서 X 시스템과 같은 응용 프로그램에서 마우스 장치를 지정하려면 /dev/input/mice를 지정해야 한다. 만약 /dev/input/mouse0 만을 지정한다면 USB 마우스와 같이 추가된 마우스 장치는 입력을 받을 수 없게 되기 때문이다.

 

마우스와 이외의 장치도 이 디바이스 파일을 통하여 값을 읽어 들일 수 있다. 터치스크린이나 디지타이저 역시 이 디바이스 파일을 이용하여 좌표 값을 읽어 들일 수 있다. 왜냐하면 마우스 이벤트 핸들러 디바이스는 내부적으로 크게 3가지 형식으로 값을 전달받기 때문이다. 첫 번째는 전통적인 마우스 이동의 변화 값을 처리하는 방식이다. 이것은 커널 소스 상에서 mousedev_rel_event 함수를 통하여 구현되고 있다. 두 번째는 디지타이저와 같은 형식의 입력인데 좌표의 절대 값을 읽어 들인다. 이것은 커널 소스 상에서 mousedev_abs_event 함수를 통하여 구현된다. 세 번째는 터치패드와 형식의 입력인데 이것은 커널 소스 상에서 mousedev_ touchpad_event 함수로 구현된다. 터치패드는 노트북에 조그맣게 장착된 것을 말한다. 터치스크린과 혼동하지 말기 바란다. 터치패드는 이전 좌표와 연동하여 변화 값을 전달하기 때문에 첫 번째와 같은 동작 방식을 응용 프로그램은 전달 받는다.

 

여기서 문제가 되는 것은 절대 값을 처리하는 두 번째 방식이다. 이 방식으로 전달된 이벤트 값은 응용 프로그램이 절대 좌표를 처리되도록 구현되어 있어야 한다. X의 경우라면 절대 값을 처리하도록 구현되어 있지만 QT의 시스템은 mice로 열어서 읽을 수 있지만 절대 값 처리가 되어 있지 않기 때문에 처리되지 않는다. 마이크로윈도우 역시 마우스 형식으로 읽어 들이면 문제가 발생한다. 이런 경우는 해당 소스를 직접 패치하여 절대 값을 처리하도록 수정할 필요가 있다.

 

그러나 터치스크린을 이용하는 시스템일 경우 입력 장치가 마우스 이벤트 핸들러와 연결되어 동작하도록 한다. 즉 응용 프로그램이 터치스크린을 다루기 위해서 마우스 디바이스 파일과 다른 이벤트 핸들러 디바이스 파일을 동시에 열어서 처리하지 않는다는 의미이다. 또한 실질적인 사용상의 이유로 마우스 시스템과 터치스크린 시스템을 동시에 사용하도록 해야 하는 경우는 거의 없기 때문에 필자 역시 입력 장치 처리와 관련된 시스템 구성에서 이와 관련된 작업은 고려하지 않는다.

 

조이스틱 이벤트 핸들러 디바이스 드라이버

 

게임과 같은 응용 프로그램은 조이스틱 입력 장치를 다룰 필요가 있다. 조이스틱 입력 장치의 입력 값을 읽어 들이기 위해서는 조이스틱 이벤트 핸들러 디바이스 드라이버를 읽어 들여야 한다. 조이스틱 이벤트 핸들러는 linux/drivers/input/joydev.c에 구현되어 있다. 이 핸들러를 사용하는 마우스 디바이스 드라이버들은 보통 linux/ drivers/input/joystick 디렉토리 하부에 구현하고 있다.

 

시스템에 조이스틱 장치가 연결되면 응용 프로그램은 다음과 같은 디바이스 파일을 열어서 입력 값을 전달받을 수 있다. 이 디바이스 파일들은 /dev/input/ 하부 디렉토리에 다음과 같은 주 번호와 부 번호를 할당하여 문자형 디바이스 형식으로 mknod와 같은 유틸리티를 사용하여 수동으로 생성시켜 주어야 한다.

 

crw-r--r-- 1 root root 13, 0 Apr 1 10:50 js0 crw-r--r-- 1 root root 13, 1 Apr 1 10:50 js1 crw-r--r-- 1 root root 13, 2 Apr 1 10:50 js2 crw-r--r-- 1 root root 13, 3 Apr 1 10:50 js3 ... crw-r--r-- 1 root root 13, 31 Apr 1 10:50 js31

 

이벤트 이벤트 핸들러 디바이스 드라이버

 

이벤트 이벤트 핸들러 디바이스 드라이버는 장치 독립적인 범용적인 형식으로 입력 값을 받아들이기 위한 디바이스 드라이버이다. 이것은 입력 장치마다 하나씩 생성된다. 이 디바이스 파일을 모든 장치 즉 키보드, 마우스, 조이스틱, 터치스크린과 같은 장치의 값을 읽어 들일 수 있다. 아마도 장래에는 이 형식으로 리눅스 입력 장치들은 통합될 것이다. 독자 여러분이 응용 프로그램이 만약 터치스크린을 다루도록 할 예정이라며 이 이벤트 디바이스 파일을 열어서 처리하도록 설계하는 것이 미래 지향적일 것이다. 그러나 앞에서도 언급했듯이 이 핸들러 디바이스 드라이버는 진행형이다. 그렇다고 크기 바뀔 만한 내용은 필자가 보기에 발견되지 않았다.

 

이벤트 이벤트 핸들러는 linux/drivers/input/evdev.c에 구현되어 있다. 시스템에 입력장치가 연결되면 응용 프로그램은 다음과 같은 디바이스 파일을 열어서 입력 값을 전달받을 수 있다. 이 디바이스 파일들은 /dev/input/ 하부 디렉토리에 다음과 같은 주 번호와 부 번호를 할당하여 문자형 디바이스 형식으로 mknod와 같은 유틸리티를 사용하여 수동으로 생성시켜 주어야 한다.

 

crw-r--r-- 1 root root 13, 64 Apr 1 10:49 event0 crw-r--r-- 1 root root 13, 65 Apr 1 10:50 event1 crw-r--r-- 1 root root 13, 66 Apr 1 10:50 event2 crw-r--r-- 1 root root 13, 67 Apr 1 10:50 event3 ... crw-r--r-- 1 root root 13, 95 Apr 1 10:50 event31

 

응용 프로그램은 이 디바이스 파일을 읽어 들이기 위해서는 read 함수를 이용하여 읽을 수 있는데 이때 읽어 들이는 버퍼의 형식은 다음과 같아야 한다.

 

struct input_event { struct timeval time; unsigned short type; unsigned short code; unsigned int value; }; time

 

이 필드는 이벤트가 발생한 시간을 의미한다.

 

type, code, value

 

이 필드는 code와 value가 나타내는 값의 형식을 의미한다. type과 code는 리눅스 커널 소스의 include/linux/input.h 에 정의되어 있는데 터치스크린과 관련된 대표적인 값은 다음과 같다.

 

#define EV_SYN 0x00 // 동기를 맞추어야 한다. #define EV_KEY 0x01 // 키 또는 버튼이다. #define EV_ABS 0x03 // 절대 좌표 값이다. * type == EV_KEY

 

타입 값이 EV_KEY라면 터치스크린에서 버튼의 입력이 발생했음을 알리게 된다. 이때 code는 대부분의 경우 BTN_TOUCH로 표현되는 상수 값을 갖는다. value는 버튼의 상태를 표현한다. 다음 중 한 가지 상태가 된다.

 

0 : 터치가 떨어졌다. 즉 사용자가 터치스크린에서 입력을 위한 접촉을 떨어뜨렸다.
1 : 터치되었다. 즉 사용자가 위치를 지정하기 위해서 접촉했다.
2 : 터치가 연속되어 발생하고 있다. 그러나 이 값은 실제로 터치에서 잘 사용하지 않는다.

 

* type == EV_ABS

 

타입 값이 EV_ABS라면 터치스크린의 접촉된 절대 좌표 값이 변경되었음을 알리게 된다. 이때 code는 ABS_X 나 ABS_Y가 된다. value는 이때의 좌표 위치가 된다.

 

ABS_X : X 좌표 절대 값이다. 
ABS_Y : Y 좌표 절대 값이다.

* type == EV_SYN

 

타입 값이 EV_SYN는 이벤트가 최종적으로 발생되었다는 것을 알리는 값이다. code와 value는 0이 된다. 이 타입이 존재하는 이유는 이벤트 전달은 한 번에 하나씩 밖에 전달하지 못하기 때문이다. 사용자가 터치스크린을 터치하면 실제로 발생하는 이벤트는 총 3가지이거나 1가지이다.

 

1. [키가 눌렸다. X 좌표 값은 342다. Y 좌표 값은 128이다.] 
2. [키가 떨어졌다.]

 

이와 같은 두 가지 상태가 있다면 이 두 가지 경우를 실제 앞 구조체의 필드에 대입하면 다음과 같은 형태가 된다.

 

[터치가 눌린 상태] type = EV_KEY, code = BTN_TOUCH, value = 1, <-- 키가 눌렸다. type = EV_ABS, code = ABS_X , value = 342, <-- X 좌표 값은 342다. type = EV_ABS, code = ABS_Y , value = 128, <-- Y 좌표 값은 128이다. type = EV_SYN, code = 0 , value = 0, <-- 지금까지 데이터가 하나의 데이터다. [터치가 떨어진 상태] type = EV_KEY, code = BTN_TOUCH, value = 0, <-- 키가 떨어졌다. type = EV_SYN, code = 0, value = 0, <-- 지금까지 데이터가 하나의 데이터다.

 

이벤트 이벤트 핸들러는 거꾸로 응용 프로그램에서 이벤트를 집어넣을 수 있다. 방법은 읽기와 반대로 이벤트 디바이스 파일에 write 함수를 이용하여 struct input_event 구조체의 필드를 적절히 채워서 넣으면 된다.

 

터치스크린 이벤트 핸들러 디바이스 드라이버

 

이름 상으로 보면 이 이벤트 디바이스 드라이버가 터치스크린과 가장 알맞은 처리 구조를 가지고 있다. 실제로 이벤트 핸들러 디바이스 드라이버는 iPAQ H3600이라는 PDA의 터치스크린을 위해 설계되고 작성된 디바이스 드라이버이다. 터치스크린에 필요한 칼리브레이션과 관련된 파라미터를 설정할 수 있도록 ioctl이 설계되어 있기 때문에 이 디바이스 드라이버를 사용해보는 것도 좋은 방법일 수 있다. 아니면 자신의 시스템에 맞게 이 이벤트 핸들러 디바이스 드라이버를 수정하거나 또는 재작성하여 이벤트 핸들러를 등록하는 것도 좋을 것이다. 하지만 이 디바이스 드라이버는 아직까지 범용적으로 처리될 수 있는 구조로 되어 있지 않으며 응용 프로그램이 사용하는 라이브러리에 연동되어 사용되기에 몇 가지 문제점을 가지고 있다. 많은 부분이 iPAQ H3600에 맞추어져 있다. 그래서 이 연재에서는 이 터치스크린 이벤트 핸들러 대신에 이벤트 이벤트 핸들러 디바이스 드라이버를 이용하는 것을 중심으로 설명한다.

 

터치스크린 이벤트 핸들러는 linux/drivers/input/tsdev.c에 구현되어 있다. 시스템에 입력 장치가 연결되면 응용 프로그램은 다음과 같은 디바이스 파일을 열어서 입력 값을 전달받을 수 있다. 터치스크린 디바이스 드라이버들은 보통 linux/drivers/input/touch screen 디렉토리 하부에 구현하고 있다. 이곳에 만들어진 터치스크린 디바이스 드라이버들 중에 터치스크린 이벤트 핸들러를 사용한 것은 h3600_ts_input.c이다.

 

이 디바이스 파일들은 /dev/input/ 하부 디렉토리에 다음과 같은 주 번호와 부 번호를 할당하여 문자형 디바이스 형식으로 mknod와 같은 유틸리티를 사용하여 수동으로 생성시켜 주어야 한다.

 

crw-r--r-- 1 root root 13, 128 Apr 1 10:49 ts0 crw-r--r-- 1 root root 13, 129 Apr 1 10:50 ts1 crw-r--r-- 1 root root 13, 130 Apr 1 10:50 ts2 crw-r--r-- 1 root root 13, 131 Apr 1 10:50 ts3 ... crw-r--r-- 1 root root 13, 159 Apr 1 10:50 ts31

 

입력 디바이스 드라이버 구현

 

터치스크린에서 발생하는 사용자 입력을 응용 프로그램에 전달하기 위해서는 터치스크린의 제어를 담당하고 입력된 값을 처리하는 디바이스 드라이버가 필요하다. 예를 들어 EP9312나 S3C2410과 같은 프로세서는 내부적으로 터치스크린 컨트롤러를 가지고 있는데 터치 컨트롤러의 상태를 읽고 난 후 읽어 들인 눌림 상태나 좌표 값의 AD 변환 값을 적절히 절대 좌표 값과 버튼 값으로 변환하여 응용 프로그램에서 읽도록 해야 한다. 이런 과정은 다음과 같은 입력 디바이스 드라이버로 동작하기 위한 기본 구현 과정이 구현되어야 한다. 우선 터치스크린이 해야 할 동작을 살펴보자.

 

◆ 입력 디바이스 드라이버로의 등록과 이벤트 핸들러와의 연결
◆ 장치 제거시 입력 디바이스 드라이버에서 제거와 이벤트 핸들러와의 연결 종료
◆ 입력된 이벤트 상태의 보고

 

입력 디바이스 드라이버로의 등록과 이벤트 핸들러와의 연결

 

입력 디바이스 드라이버로 등록되고 이벤트 핸들러와 연결되기 위해서는 장치 특성을 기술하는 내용과 연결되어야 할 이벤트 핸들러의 특성을 기술하는 내용을 담은 struct input_dev 구조체 변수를 작성하고, 이것을 input_register_device() 함수를 이용하여 등록해야 한다. input_dev 구조체는 많은 필드를 가지고 있지만 터치스크린을 제어하고자 하는 경우에 필요한 필드들은 다음과 같다.

 

등록 정보 표현을 위해 필요한 필드들

입력 장치가 INPUT 디바이스 드라이버에 등록되면 /proc/bus/ input/devices라는 파일에 자세한 등록 정보가 표현되어 응용 프로그램에서 참고할 수 있다. 커널은 이 파일에 표현되는 정보를 터치스크린 디바이스 드라이버가 등록하는 input_dev 구조체 변수의 name,phys,id 필드의 값을 이용해서 표시한다.

 

◆ name : 이 필드에는 장치명을 구체적으로 기술한다

 

. .name = “Cirrus Logic EP93xx Touchscreen”,

 

◆ phys : 이 필드에는 장치의 연결 상태와 실제 장치명을 지정하는데 큰 의미는 없다.

 

.phys = “ep93xx_ts/input0”,

 

◆ id : 장치의 제품 정보와 관련된 필드를 지정한다. 이 필드는 다음과 같은 구조체 형식을 갖는다.

 

struct input_id { __u16 bustype; // 버스 타입 __u16 vendor; // 제작사 구분 ID __u16 product; // 제품 구분 ID __u16 version; // 버전 };

 

설정 예는 다음과 같다.

 

.id = { .bustype = BUS_HOST, .vendor = PCI_VENDOR_ID_CIRRUS, .product = 0x9300, .version = 0, },

 

이벤트 핸들러의 연결을 지정하는 필드들

터치스크린은 다음과 같은 이벤트 핸들러와 연결되어야 할 필요가 있다.

 

◆ 마우스 이벤트 디바이스 드라이버(mousedev,mice)
◆ 이벤트 이벤트 디바이스 드라이버(evdev)
◆ 터치스크린 이벤트 디바이스 드라이버(tsdev)

 

등록되는 터치스크린 디바이스 드라이버가 앞의 이벤트 핸들러와 연결되기 위해서는 다음과 같은 필드들의 설정 상태를 정확하게 지정해야 한다.

 

unsigned long evbit[NBITS(EV_MAX)]; // 이벤트 타입
unsigned long keybit[NBITS(KEY_MAX)]; // 사용되는 키 타입
unsigned long absbit[NBITS(ABS_MAX)]; // 사용되는 절대 좌표 값

 

이 필드 변수 값들이 어떻게 설정되어야 하는지 알고 싶다면 각 이벤트 핸들러 소스 상에서 struct input_device_id 구조체로 선언된 변수의 값들을 살펴보면 된다. 디바이스 드라이버가 등록될 때 이 필드와 struct input_device_id 구조체에 선언된 것과 비교하여 일치할 경우만 해당 핸들러들이 연결되기 때문이다. 터치스크린이라면 보통 다음과 같이 설정하면 mousedev와 tsdev 이벤트 핸들러에 연결된다. 이벤트 이벤트 핸들러에는 등록 조건과 상관없이 무조건 설정되기 때문에 터치 디바이스 드라이버는 신경 쓸 필요가 없다.

 

.evbit = { BIT(EV_KEY) | BIT(EV_ABS) },
.keybit = { [LONG(BTN_TOUCH)] = BIT(BTN_TOUCH) },
.absbit = { BIT(ABS_X) | BIT(ABS_Y) },

 

◆ evbit는 필드는 터치스크린이 처리해야 할 데이터 타입을 정의한다. EV_KEY는 버튼의 입력을 처리한다는 의미이고 EV_ABS는 절대 좌표 값을 처리한다는 의미이다.

 

◆ keybit는 사용되는 키 값이 어떤 것이 있는지를 표현한다.

 

◆ absbit는 이벤트 좌표 값이 형식이 된다. ABS_X와 ABS_Y는 각각 절대 좌표 형식의 X와 Y를 처리한다는 것을 표현한다.

 

여기서 사용되는 각 필드는 각 기능을 표현하는 1비트짜리 데이터를 포함할 수 있는 배열이다. BIT 매크로는 수치에 해당하는 비트를 표현하며 LONG 매크로는 비트 번호에 해당하는 배열 인덱스 값을 반환한다.

 

응용 프로그램이 장치를 열거나 닫을 때 처리해야 할 내용을 지정하는 함수 필드

응용 프로그램이 터치스크린의 입력 값을 얻기 위해서 마우스 이벤트 핸들러 디바이스 파일이나 터치 이벤트 핸들러 디바이스 파일 또는 이벤트 이벤트 핸들러 디바이스 파일을 열었을 때 터치 디바이스 드라이버 파일은 특정한 일을 수행할 필요가 있을 경우가 대부분이다. 보통 터치스크린 컨트롤러들은 터치 상태의 변화를 인터럽트를 이용하여 전달하는데 이 인터럽트는 응용 프로그램이 터치스크린을 사용하지 않는 경우라면 굳이 동작할 필요가 없다. 그래서 보통은 디바이스 파일을 응용 프로그램이 열었을 때 인터럽트 설정을 하며 닫을 때 인터럽트 설정을 해제한다. 이렇게 디바이스 파일이 열렸다는 것을 통보받거나 닫혔다는 것을 통보받기 위해서 사용되는 input_dev 구조체 필드는 open과 clode이다. 다음은 해당 사용 예를 나타낸다.

 

.open = ep93xx_ts_open,
.close = ep93xx_ts_close,

 

터치스크린 디바이스 드라이버 등록 예

이렇게 설정된 구조체 변수는 INPUT 디바이스 드라이버의 input_register_device() 함수를 사용하여 등록한다. 다음과 같은 형태가 터치스크린 디바이스를 등록하는 한 예이다.

 

static int ep93xx_ts_open(struct input_dev *dev) { } static void ep93xx_ts_close(struct input_dev *dev) { } static struct input_dev ep93xx_ts_dev = { .evbit = { BIT(EV_KEY) | BIT(EV_ABS) }, .keybit = { [LONG(BTN_TOUCH)] = BIT(BTN_TOUCH) }, .absbit = { BIT(ABS_X) | BIT(ABS_Y) }, .open = ep93xx_ts_open, .close = ep93xx_ts_close, .name = “Cirrus Logic EP93xx Touchscreen”, .phys = “ep93xx_ts/input0”, .id = { .bustype = BUS_HOST, .vendor = PCI_VENDOR_ID_CIRRUS, .product = 0x9300, .version = 0, }, }; static int __init ep93xx_ts_init(void) { input_set_abs_params(&ep93xx_ts_dev, ABS_X, TS_X_MIN, TS_X_MAX, 0, 0); input_set_abs_params(&ep93xx_ts_dev, ABS_Y, TS_Y_MIN, TS_Y_MAX, 0, 0); input_register_device(&ep93xx_ts_dev); : : }

 

INPUT 디바이스 드라이버에 터치스크린을 등록하는 위치는 보통 모듈이 초기화되는 함수에서 처리한다. 앞의 예를 보면 input_set_abs_params() 함수가 사용되는데 이것은 이벤트로 전달되는 X, Y 값을 변화하여 처리하기 위한 것이다. 터치스크린의 입력 값들은 보통 AD 변환 값이므로 이 값은 터치된 위치와 정확하게 일치하지 않는다. 그래서 이 값들은 내부적으로 적절히 변환되어 처리되어야 하고 일정한 범위를 벗어나지 않아야 한다. 이런 각 좌표 값의 최소 값과 최대 값을 input_set_abs_params() 함수를 이용하여 지정하는 것이다.

 

이제 장치 제거시 입력 디바이스 드라이버에서 제거와 이벤트 핸들러와의 연결 종료 외부에서 전달된 이벤트의 처리를 살펴보자. 터치스크린이라면 장치가 제거될 경우는 거의 없다. 그러나 USB 마우스 같은 경우에는 해당 장치가 제거될 가능성을 배제할 수 없다. 이런 경우에는 해당 디바이스 드라이버의 모듈이 제거될 수도 있으므로 이때는 INPUT 디바이스 드라이버의 관리 구조체와 연결된 이벤트 핸들러와의 연결을 끊을 필요가 있다. 이때 사용하는 함수는 input_unregister_device() 함수이다. 보통은 다음 예와 같은 형태로 모듈 제거 함수에서 처리된다.

 

static void __exit ep93xx_ts_exit(void) { input_unregister_device(&ep93xx_ts_dev); }

 

이 함수가 호출될 경우에는 input_dev 구조체의 close에 선언된 함수가 반드시 호출된다.

 

입력된 이벤트 상태의 보고

 

터치스크린에 어떤 상태 변화가 생겼다면 터치스크린 디바이스 드라이버는 이벤트 핸들러 디바이스 드라이버로 전달하여 응용 프로그램에서 해당 이벤트를 받을 수 있도록 전달해야 한다. 이때 이벤트를 전달하기 위한 가장 기초적인 함수는 input_event 이다. 이 함수는 다음과 같은 형태로 되어 있으며 사용하기 편하게 하기 위해 여러 매크로 형태로 제공하고 있다.

 

원형
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);

매크로 
void input_report_key(struct input_dev *dev, unsigned int code, int value);
void input_report_rel(struct input_dev *dev, unsigned int code, int value);
void input_report_abs(struct input_dev *dev, unsigned int code, int value);
void input_sync(struct input_dev *dev);

 

dev는 터치스크린 디바이스 드라이버가 등록한 input_dev 구조체 변수이다. type, code, value는 앞에서 설명한 이벤트 이벤트 핸들러 디바이스 드라이버에서 설명했던 input_event 구조체의 필드와 같은 내용이다. 터치스크린에서 이벤트가 발생하는 경우는 다음과 같은 세 가지 경우로 볼 수 있다.

 

1. 터치가 눌렸다.
2. 터치가 눌린 상태로 이동한다.
3. 터치가 떨어졌다.

 

이 중 1과 2는 같은 상태로 보는 것이 좋다. 이 두 가지를 코드로 구현하면 다음과 같은 형식이 된다. 매크로를 사용했을 경우를 예로 들겠다.

 

터치가 눌렸다 / 터치가 눌린 상태로 이동한다
input_report_key(&ep93xx_ts_dev , BTN_TOUCH, 1);
input_report_abs(&ep93xx_ts_dev , ABS_X,342 );
input_report_abs(&ep93xx_ts_dev , ABS_Y,128 );
input_sync(&ep93xx_ts_dev);

터치가 떨어졌다
input_report_key(&ep93xx_ts_dev , BTN_TOUCH, 0);
input_sync(&ep93xx_ts_dev);

 

이렇게 전달된 이벤트는 연결된 이벤트 핸들러 디바이스 드라이버 모두에게 전달된다. 각 이벤트 핸들러 디바이스 드라이버는 각각의 형식에 맞게 이벤트를 변경하고 버퍼에 저장한다. 이 값은 응용 프로그램에서 형식에 맞게 읽어 들이게 된다. 그래서 어떤 이벤트 핸들러 디바이스 드라이버를 응용 프로그램이 열더라도 문제가 없게 되는 것이다.

 

INPUT 디바이스 드라이버 관련 함수 중 터치와 관련된 함수 정리

헤더 파일 : linux/include/linux/input.h

static inline void init_input_dev(struct input_dev *dev);

 

dev에 전달된 구조체를 초기화한다.

 

void input_register_device(struct input_dev *);

 

입력 장치를 등록한다.

 

void input_unregister_device(struct input_dev *);

 

입력 장치를 제거한다.

 

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);

 

이벤트를 이벤트 핸들러 디바이스 드라이버에 전달한다.

 

static inline void input_report_key(struct input_dev *dev, unsigned int code, int value);

 

내부적으로 input_event 함수를 이용하여 버튼 또는 키 이벤트를 전달한다.

 

static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value);

 

내부적으로 input_event 함수를 이용하여 이동된 크기 이벤트를 전달한다.

 

static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value);

 

내부적으로 input_event 함수를 이용하여 이동 된 절대 좌표 이벤트를 전달한다.

 

static inline void input_sync(struct input_dev *dev);

 

내부적으로 input_event 함수를 이용하여 하나의 상태에 대한 여러 이벤트가 동기되어야 할 필요가 있다는 것을 전달한다.

 

static inline void input_set_abs_params(struct input_dev *dev, int axis, int min, int max, int fuzz, int flat);

 

절대 좌표 값을 지정하는 경우 값의 최소 값과 최대 값을 지정한다.

 

커널 컴파일 옵션 설정

 

터치 디바이스 드라이버를 작성했다면 이를 포함시키고 동작할 수 있도록 커널 컴파일 옵션을 적당히 설정해야 한다. 다음은 EZ-EP9312 보드에서 사용되는 커널의 경우에 설정된 예제 화면이다. 설정해야 하는 항목은 다음과 같다.

 


<화면 1> 커널 컴파일 옵션 설정

 

1. (1024) Horizontal screen resolution 
2. (768) Vertical screen resolution 
3. <*> Touchscreen interface 
4. (1024) Horizontal screen resolution 
5. (768) Vertical screen resolution 
6. <*> Event interface 
7. <*> EP93xx Keyboard support 
8. [*] EP93xx USB Keyboard support 
9. [*] Mice 
10. [*] Touchscreens 
11. <*> EP93xx touchscreen

 

1, 2, 3, 4는 이벤트 핸들러가 좌표 값을 변환하기 위해서 사용하는 터치스크린의 해상도 값이다.

 

아쉬움을 뒤로하고

 

뭐 항상 그렇듯이 필자의 무계획성의 성격 때문에 처음 계획했던 글과 실제로 써지는 글은 항상 차이가 있다. 처음에는 입력 장치 디바이스 드라이버 구조 이외에도 해당 디바이스 드라이버를 사용하는 응용 애플리케이션에 대한 부분까지 언급하려 했으나 지면 관계상(?) 이 부분의 설명은 곤란하게 되었다. 아마도 한 번의 컬럼으로써 다루기에 너무 많은 내용이 있는 것이 아닐까 생각된다. 다음에 나머지 부분을 설명하는 것을 약속하며 마쳐야겠다.

 

제공 : DB포탈사이트 DBguide.net

Posted by 라판
    Input Driver 관련 문서[각주:1]를 살펴보다 BITS_TO_LONGS(), BIT_WORD(), BIT_MASK() 매크로를 보게 되었다. 문서에는 다음과 같이 나와 있다. 

     BITS_TO_LONGS(x) - returns the length of a bitfield array in longs for x bits
     BIT_WORD(x)  - returns the index in the array in longs for bit x
     BIT_MASK(x)  - returns the index in a long for bit x

    영어가 부족하여 무슨 의미인지 몰라 해당 매크로를 따라 가며 분석하였다.[각주:2] 일반적인 컴퓨터(1byte=8bit, long = 4byte)의 경우를 산정하였을 때 각각을 수식으로 나타내면, 
     BITS_TO_LONGS(x) = DIV_ROUND_UP(x,BITS_PER_BYTE * sizeof(long)) =  DIV_ROUND_UP(x,32) = (x+32-1)/32 
     BIT_WORD(x)  = x /BITS_PER_LONG = x/ 8
     BIT_MASK(x)  =  1 << (x% BITSPER_LONG) = 1 << (x % 32) 

 어떻게 정의되어 있는지는 알았는데 이것들은 도대체 어디에 쓰이며 무슨 의미인 걸까? 
 
   드라이버를 포함하여 커널은 메모리에 상주하여야 하는 특성상 그 크기는 가능한 작아야
좋다. 이를 위해 CPU의 레지스터를 나타내거나 정보를 저장할 때 BITMAP 형태로 저장하는 경우가 많다. 
   이 때 저장해야 되는 정보가 최대 32개라면 하나의 long 변수에 저장하고 비트 연산을 하면 되겠지만, 그 이상일 때에는 2개 이상의 long 변수, 즉 long형 배열이 필요하며 이 때의 비트 연산은 좀 더 복잡하여진다. 위 3가지 매크로는 이와 같이 비트맵 형태의 정보를 long형 배열에 저장하였을 때를 염두에 둔 매크로로 보인다. 
   즉 BITMAP 구조의 정보를 long형 배열에 저장 해 두었다고 하였을 때 각각의 의미는 다음과 같다.
    BITS_TO_LONGS(x) - 총 x개의 bit 정보를 저장하기 위해 필요한 long 자료형의 개수 
    BIT_WORD(x)  - 비트 x가 속해 있는 원소의 인덱스 
    BIT_MASK(x)  - 비트 x가 속해 있는 원소 안에서 비트 x의 비트 위치 

사용 예를 들면 다음과 같다.

   static struct input_dev *button_dev;
   .........
   button_dev = input_allocate_device();
   button_dev->evbit[0] = BIT_MASK(EV_KEY);
   button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);

  위의 드라이버는 버튼 0을 입력으로 하며, input_dev 자료구조의 keybit에 자신이 제어하는 버튼을 명시해주어야 한다. 이 때 keybit는 비트맵으로 구현되어 있기 때문에 BTN_0가 위치하는 keybit 배열 원소를 찾기 위해 BIT_WORD(BTN_0)을 쓰고, 해당 원소에서의 비트 위치를 지정하기 위해  BIT_MASK(BTN_0)을 쓰면 비트맵에서의 BTN_0 비트를 설정할 수 있게 된다. 

   
 

  1. kernel/Documentation/input/input-programming.txt [본문으로]
  2. 해당 매크로들은 kernel/include/bitops.h에 정의되어 있다. [본문으로]
Posted by 라판
1. ALSA 라이브러리를 이용한 간단한 재생프로그램
    raw sound 가공 및 컴파일 관련 옵션까지 나와 있음 
     http://blog.naver.com/pureyouth/150026426572

2.  ALSA 공식 홈페이지 
    http://www.alsa-project.org/main/index.php/Main_Page

3. ALSA 튜토리얼
    http://www.linuxjournal.com/article/6735?page=0,1
      
 
Posted by 라판

이전 글에서 journalling과 ReiserFS의 장점과 Linux 2.4 기반의 ReiserFS 시스템을 설치하는 방법을 설명하였다. 이번에는 색다른 주제를 다루려고 한다. 우선 가상 메모리 (VM) 파일시스템으로 알려져있는 tmpfs를 살펴 볼 것이다. tmpfs 는 아마도 Linux에서 사용 가능한 RAM disk와 같은 최상의 시스템일 것이다. 그리고 이것은 커널 2.4의 새로운 기능이다. 또한 그런다음 2.4의 새로운 또 하나의 기능인 "bind mounts"에 대해 살펴볼 것이다. 이것은 파일시스템을 마운트 (리마운트) 할 때 상당한 유연성을 발휘한다. 다음 회에는 devfs에 대해 집중적으로 살펴보고 ext3 파일시스템을 숙지할 것이다.

tmpfs

tmpfs를 한마디로 설명해야 한다면, ramdisk와 같으면서도 다르다고 말해야 할 것 같다. ramdisk와 비슷한 점은, tmpfs는 RAM을 사용할 수 있다는 것이고, 다른점은 저장을 위해 swap 디바이스들을 사용할 수 있다는 것이다. 기존의 ramdisk 는 블록(block) 디바이스이고, 실제로 그것을 사용하려면 mkfs 명령어가 필요하지만, 반면 tmpfs는 파일시스템이다. 블록(block) 디바이스가 아니다; 마운트하면 바로 존재하게 된다. 이러한 사실 때문에 tmpfs가 훌륭한 RAM 기반의 파일시스템이 될 수 있는 것이다.

tmpfs & VM

이제, 좀 더 재미있는 tmpfs의 기능들을 살펴보자. 앞서 언급했지만 tmpfs는 RAM과 swap 모두를 사용할 수 있다. 언뜻 듣기에는 약간 제멋대로라는 생각이 들지만 tmpfs가 "가상 메모리 파일시스템"이라는 점을 기억하라. 또한 여러분도 알고 있듯이, Linux 커널의 가상 메모리 리소스는 RAM과 swap 장치에서 나온다. 커널의 VM 서브시스템(subsystem)은 이러한 리소스들을 시스템의 다른 부분에 할당하고, 리소스들을 막후에서 관리한다. 종종 투명하게 RAM 페이지를 swap으로 또는 그 반대로 옮긴다.

tmpfs 파일시스템은 파일을 저장하기 위해서 VM 서브시스템에 페이지를 요청한다. tmpfs 자체로는 이러한 페이지가 swap 상에 있는지 또는 RAM에 있는지 알지 못한다; 그러한 결정을 하는것은 VM 서브시스템의 역할이다. tmpfs 파일시스템이 인식하고 있는 것은 이것이 특정 형식의 가상 메모리를 사용하고 있다는 것이다.

블록(block) 디바이스가 아니다!

tmpfs 파일시스템의 또 다른 재미있는 특징이 있다. ext3, ext2, XFS, JFS, ReiserFS 등의 "평범한" 파일시스템과는 달리 tmpfs는 블록 디바이스에 존재하지 않는다. tmpfs는 VM에 직접 설치되기 때문에 간단한 마운트 명령어로 tmpfs 파일시스템을 구현 할 수 있다:


# mount tmpfs /mnt/tmpfs -t tmpfs

이 명령어를 실행시키면 /mnt/tmpfs에 마운트 된 새로운 tmpfs 파일시스템을 갖게되는 것이다. mkfs.tmpfs를 실행시킬 필요가 없다는 것에 주목하라. 사실, 이것은 불가능하다. 그와 같은 명령어가 존재하지 않는다. mount 명령어로 파일시스템은 마운트 되어 사용할 수 있게 된다. 그리고 이것은 tmpfs 타입이다. 이것은 Linux ramdisk가 사용되는 방식과는 상당히 다르다; 표준 Linux ramdisk들은 블록 디바이스이다. 따라서 사용하기 전에 여러분이 선택한 파일시스템으로 포맷되어야 한다. 반면 tmpfs는 파일시스템이다. 따라서 마운트하여 실행시키면 된다.

tmpfs 장점

동적으로 변하는 파일시스템 사이즈

/mnt/tmpfs에 마운트 된 tmpfs 파일시스템 크기가 궁금할 것이다. /mnt/tmpfs는 처음에는 매우 작은 용량이다. 하지만 파일이 복사되고 생성되면서 tmpfs 파일시스템 드라이버는 좀 더 많은 VM을 할당하고 필요한 만큼 파일시스템 용량을 동적으로 늘린다. 그리고, 파일들이 /mnt/tmpfs에서 제거되면 tmpfs 파일시스템 드라이버는 파일시스템의 크기와 VM 리소스를 줄인다. 이렇게 VM이 동적으로 존재하면서 필요할 때마다 시스템의 다른 부분에 의해 사용될 수 있다. VM은 소중한 리소스이기 때문에 실제 필요한 것보다 더 많은 VM을 요구하는 어떤 것도 원하지 않을 것이다. tmpfs의 또 하나의 멋진 기능은 이 모든 것이 자동으로 수행된다는 것이다. 참고자료 참조.

속도

tmpfs의 장점 중 하나는 놀라운 속도이다. 일반적인 tmpfs 파일시스템은 RAM에 존재하기 때문에, 읽기 및 쓰기는 거의 동시에 이루어 질 수 있다. 비록 어느 정도의 swap이 사용되더라도, 성능은 여전히 우수하고 더 많은 VM 리소스를 사용할 수 있을 때 tmpfs 파일시스템의 일부는 RAM으로 옮겨질 것이다. VM 서브시스템이 tmpfs 파일시스템의 일부를 swap으로 자동적으로 옮기는 것은 실제로 성능에 좋은 영향을 미친다. 왜냐하면 VM 서브시스템은 이것을 필요로하는 프로세스를 위해 RAM을 남겨둘 수 있기 때문이다. 이것은 동적 크기조절 기능과 함께 전체 OS 퍼포먼스와 유연성에 기여할 수 있게 된다. 기존의 RAM 디스크를 사용하는 것보다 훨씬 낫다.

지속성이 없다!

제목 자체의 뉘앙스로는 장점을 이야기하는 것 같지 않지만 tmpfs 데이터는 재부팅 중에는 보존되지 않는다. 가상 메모리라는 것이 본질적으로 휘발성이 있기 때문이다. 여러분도 지금 tmpfs가 어떤 의미에서 "tmpfs" 로 불리게 되었는지를 생각하고 있을 것이다. 하지만 실제로 이것은 매우 좋은 것이다. 임시 파일(/tmp)과 /var 파일시스템 트리와 같은 저장하고 싶지 않은 데이터를 보존하기에는 더없이 훌륭한 파일시스템이다.

tmpfs 사용하기

tmpfs를 사용하기 위해서는 "가상 메모리 파일시스템 지원 (이전의 shm fs)"이 가능한 2.4 시리즈 커널이 필요하다; 이 옵션은 kernel 설정의 "File systems" 섹션에 존재한다. 사실상 tmpfs의 사용 여부와 관계없이 2.4 커널에 tmpfs가 지원된다는 것은 좋은 일이다. 왜냐하면 POSIX 공유 메모리를 사용하기 위해서 커널이 tmpfs를 지원해야 하기 때문이다. System V 공유 메모리는 커널에 tmpfs가 없이도 작동할 것이다. POSIX 공유 메모리를 작동시키기 위해서 tmpfs 파일시스템을 마운트하지 않아도 된다는 것에 주목하라. 커널에 지원되는 것으로 충분하다. POSIX 공유 메모리는 많이 사용되지 않는다.

low VM 문제 방지

tmpfs가 필요한 만큼 동적으로 늘어나고 줄어든다는 사실에 한가지 의문점이 생긴다: 만일 tmpfs 파일시스템이 모든 가상 메모리를 소진 할 지점까지 이르면 어떻게 하겠는가? 그리고 남아있는 RAM이나 swap도 없다면? 재미없는 상황이다. 2.4.4 커널과 같은 경우 커널이 즉시 잠긴다. 2.4.6 커널은 VM 서브시스템은 여러 방법으로 픽스된다. VM을 소진하는 것은 훌륭한 경험은 아니지만 그렇다고 해서 그것이 완전히 잘못된 것은 아니다. 2.4.6 커널이 더이상 VM을 할당할 수 없더라도 분명히 tmpfs 파일시스템에 새로운 데이터를 작성할 수 없다. 또한 시스템 상의 다른 프로세스들에 많은 메모리를 할당할 수 없을 것이다; 일반적으로 이것은 시스템이 극도로 느려지고 반응도 느려진다는 것을 의미한다. 따라서 이러한 low-VM 컨디션을 완화시키기 위해서 필요한 조치를 취하는 것이 시간 낭비일 뿐이다.

게다가, 커널은 더 이상 어떤 것도 가능하지 않을 때 메모리를 없애는 "last-ditch" 시스템이다; VM 리소스를 요구하고 있는 프로세스를 찾으면 죽인다. 불행하게도 이러한 "프로세스를 죽이는" 솔루션은 tmpfs의 증가가 VM 소진에 지대한 영향을 끼칠 때는 역작용을 한다. 여기 그 이유가 있다. tmpfs 스스로는 죽을 수가 없다. (죽어서도 안된다) 왜냐하면 이것은 커널의 일부이지 유저 프로세서가 아니기 때문이다. 그리고 커널이 어떤 프로세스가 tmpfs 파일시스템을 차지하고 있는지 알아내는 것도 쉬운 일은 아니다. 그래서 커널이 실수로 가장 큰 VM-hog 프로세스를 찾아서 공격했다면 이것은 일반적으로 X server 이다. X server가 죽으면 root는 low-VM (tmpfs)을 유발한다.

Low VM: 솔루션

다행스럽게도, tmpfs 파일시스템이 마운트 또는 리마운트 될 때 파일시스템의 최대 사이즈를 지정할 수 있다. 실제로 2.4.6 커널과 util-linux-2.11g 에서는 마운트하면서 이러한 파라미터를 설정할 수 있다. 리마운트 시에는 되지 않는다. 리마운트 할 때에도 설정할 수 있기를 기대해 본다. 최대 tmpfs 사이즈 세팅은 리소스와 Linux의 사용 패턴에 따라 다르다; 이렇게 차이를 두는 이유는 전체 tmpfs 파일시스템이 모든 가상 메모리를 다써버리지 않도록 하며, 앞서 언급한 반갑지 않은 low-VM 유발 방지를 위해서이다. 알맞은 tmpfs의 최대 한계를 찾는 좋은 방법은 'peak time' 동안에 시스템의 swap 사용을 감시하는 것이다. 그런다음 'peak time' 동안 남아있는 swap과 RAM을 합한 것 보다 근소하게 작은 tmpfs 사이즈 한계를 지정한다.

최대 크기의 tmpfs 파일시스템을 만드는 것은 쉽다. 32 MB의 새로운 tmpfs 파일시스템을 만들어보자:

이번에는 /mnt/tmpfs에 새로운 tmpfs 파일시스템을 마운트시키는 대신 /dev/shm에 마운트했다. 이것은 tmpfs 파일시스템을 위한 "공식적인" 마운트포인트가 될 수 있다. devfs를 사용 할 경우 이 디렉토리가 이미 설정되있다는 것을 알게 될 것이다.

또한 파일시스템 크기를 512 KB 또는 1 GB로 제한하고 싶다면 size=512k와 size=1g 로 각각 지정하면 된다. 사이즈 뿐만 아니라 inode (파일시스템 객체)의 수도 제한 할 수 있다. nr_inodes=x 파라미터를 지정하면 된다. nr_inodes를 사용할 경우, x 자리에 간단한 정수가 올 수 있고, 수 천, 수 백만, 수십억개(!)의 inode를 지정하고 싶다면 x 자리에 각각 kmg 가 오도록 한다.

mount tmpfs 명렁어에 해당하는 것을 여러분의 /etc/fstab 에 추가하려면 다음과 같이 한다:


tmpfs	/dev/shm	tmpfs	size=32m	0	0

기존 마운트포인트(mountpoint)에 마운트하기

2.2 커널과 같은 경우, 어떤 것이 이미 설치되어있는 마운트포인트에 마운트를 시도할 때 에러가 나타난다. 하지만, 커널 마운팅 코드가 다시 작성되었기 때문에 마운트포인트를 여러번 사용하는 것은 이제 문제가 아니다. 다음과 같은 시나리오를 설정해 보자; /tmp에 파일시스템이 마운트가 되어있다. 하지만 tmpfs를 사용하기로 결정했다. 옛날에는 /tmp 를 언마운트 하고 새로운 tmpfs /tmp 파일시스템을 그 자리에 리마운트(remount) 하는 방법 밖에 없었다:


#  umount /tmp
#  mount tmpfs /tmp -t tmpfs -o size=64m

하지만 이러한 솔루션은 아무 소용이 없다. /tmp에 'open file'을 가지고 있는 많은 실행 프로세가 있을 수도 있다; 만일 그렇다면, /tmp를 언마운트 하려고 할 때 다음과 같은 에러가 나온다:


umount: /tmp: device is busy

하지만 최근의 2.4 커널에서는, "device is busy" 에러 없이 새로운 /tmp 파일시스템을 마운트 할 수 있다:


# mount tmpfs /tmp -t tmpfs -o size=64m

단일 명령어를 사용하여 이미 마운트 된 파티션인 /tmp의 탑(top)에 새로운 tmpfs /tmp 파일시스템이 마운트된다. 여기에는 더이상 직접적으로 액세스 되지 않는다. 하지만 원래의 /tmp에 액세스 할 수 없더라도, 원래의 파일시스템상에 있었던 모든 프로세스는 지속적으로 액세스 할 수 있다. 그리고 만일 tmpfs 기반의 /tmp를 umount 하면 원래 마운트 된 /tmp 파일시스템은 다시 나타날 것이다. 사실, 같은 마운트포인트에 여러개의 파일시스템을 마운트 할 수 있다. 그리고 그러한 마운트포인트는 스택(stack)처럼 작용할 것이다; 현재의 파일시스템을 언마운트하면 최근 마운트 된 바로 밑에 있는 파일시스템이 다시 나타날 것이다.

bind mount

bind 마운트를 사용하면, 모든 것을 마운트 하거나, 이미 마운트 된 파일시스템의 일부를 다른 장소에 마운트 할 수 있고 동시에 양 마운트포인트에 파일시스템을 액세스 할 수 있다. 예를 들어, 기존의 root 파일시스템을 /home/drobbins/nifty에 마운트하기 위해서 bind 마운트를 사용해 보자:


#  mount --bind / /home/drobbins/nifty

/home/drobbins/nifty를 자세히 살펴보면, root 파일시스템(/home/drobbins/nifty/etc, /home/drobbins/nifty/opt)을 볼 수 있을 것이다. root 파일시스템 상에서 파일을 수정한다면, /home/drobbins/nifty 에서도 수정이 된다는 것을 알 수 있다. 왜냐하면 그들은 하나이고 같은 파일시스템이기 때문이다. 커널은 두 개의 다른 마운트포인트에 파일시스템을 매핑(mapping)한다. 다른 장소에 파일시스템을 마운트 할 때, bind-mounted 파일시스템 내부에 있는 마운트포인트에 마운트 된 모든 파일시스템들은 함께 움직이지 않는다. 다시 말해서, 각 파일시스템에 /usr이 있다면, 앞서 수행한 bind mount는 /home/drobbins/nifty/usr를 빈 공간으로 남겨둘 것이다. 추가적인 bind 마운트 명령어를 사용하여 /home/drobbins/nifty/usr에 있는 /usr 의 내용을 검색할 수 있다:


#  mount --bind /usr /home/drobbins/nifty/usr

파일시스템의 bind mounting

Bind mounting에는 더욱 멋진 기능이 있다. /dev/shm에 마운트 된 tmpfs 파일시스템이 있다고 하자. 현재 root 파일시스템에 있는 /tmp의 tmpfs를 사용하기로 결정했다. 새로운 tmpfs 파일시스템을 /tmp에 마운트 하지 않고 현재 마운트 된 /dev/shm 파일시스템을공유하기 위해서 새로운 /tmp를 사용하기로 결정했다. /dev/shm을 /tmp에 bind mount 할 수 있지만, /dev/shm 에는 /tmp에 나타나지 않기를 바라던 몇 개의 디렉토리가 포함되어 있다. 그렇다면 어떻게 하겠는가? 다음과 같은 방법을 보자:


# mkdir /dev/shm/tmp

# chmod 1777 /dev/shm/tmp

# mount --bind /dev/shm/tmp /tmp

예제를 보면, 우선 /dev/shm/tmp 디렉토리를 만들고, 그 다음 여기에 1777 perms를 주었다. 이것은 /tmp에 적절한 권한이다. 디렉토리가 준비되었다면 /tmp에 /dev/shm/tmp와 /dev/shm/tmp를 마운트 할 수 있다. /tmp/foo 가 /dev/shm/tmp/foo로 매핑되는 동안 /tmp에서 /dev/shm/bar 파일로 액세스 할 방법이 없다.

bind mounts는 매우 강력하고 파일시스템 레이아웃을 쉽게 수정할 수 있다. 다음에는 devfs에 대해서 살펴보도록 하자.


출저 : http://www.ibm.com/developerworks/kr/library/l-fs3.html
Posted by 라판
리눅스 커널 맵. 

 커널의 System Call을 기능별, 레벨별로 정리 해 놓았으며 시스템콜 간의 상관관계를 
지도로 표현 해 놓았다. 맵에서 특정 시스템콜을 클릭하면 해당되는 시스템콜의
레퍼런스로 이동되도록 되어 있다.

 출저 - http://www.makelinux.net/kernel_map/
Posted by 라판