이전 강좌에서는 프레임버퍼를 사용하면서 칼라 값을 write() 와 lseek() 함수를 사용하여 쓰기를 함으로써 화면에 점을 찍었습니다. 그러나 이렇게 사용해서 코딩하는 것은 불편하죠. 또한 write() 와 lseek() 함수는 처리 속도가 느립니다.

여기서 포인터 변수 특징을 먼저 짚고 넘어 가겠습니다.

포인터 변수의 주소 변가량

참~ 제목이 마음에 안듭니다. 여하튼 제가 말씀드리고 싶은 것은 결국 포인터 변수 사용의 편리성입니다.

char    *ptr

C 에서의 변수 타입은 문자, 또는 숫자라는 개념 보다는 이 변수가 메모리에 차지하는 크기를 의미합니다. 지금 위의 ptr 은 char 포인터로 선언 되어 있고, char 의 변수 크기는 1 byte이기 때문에 ptr 의 주소를 증가 시키면 1 씩 증가됩니다.

즉,

ptr = 0x2000;      ptr 의 주소값을 0x2000으로 설정

ptr += 1;          ptr의 주소값은 0x2001 로 됨
Ptr += 1;          ptr의 주소값은 0x2002 로 됨
Ptr += 1;          ptr의 주소값은 0x2003 으로 됨

다시 short 로 설정했을 때를 보겠습니다. short 는 2 바이트 크기의 변수입니다.

ptr = 0x2000;      ptr 의 주소값을 0x2000으로 설정

ptr += 1;          ptr의 주소값은 0x2002 로 됨
Ptr += 1;          ptr의 주소값은 0x2004 로 됨
Ptr += 1;          ptr의 주소값은 0x2006 으로 됨

즉, 포인터 변수의 선언 형태에 따라 주소 계산이 이렇게 다르다는 것을 아실 수 있습니다.

포인터 변수를 이용한 프레임 버퍼 쓰기

이 말씀을 드리는 이유는 픽셀 하나가 1 바이트일 때, 또는 2바이트일 때, 위의 방법에 따라 포인터 변수를 선언하고 바로 칼라 값을 넣어 준다면 매우 편리합니다.

예로 8 비트 칼라에서는 픽섹당 1 바이트이므로 아래와 같이 사용하면 옆으로 수평선을 그을 수 있습니다.

char *ptr

*ptr++ = 0xff;    // 프레임 버퍼 주소 0
*ptr++ = 0xff;    // 프레임 버퍼 주소 1
*ptr++ = 0xff;    // 프레임 버퍼 주소 2
*ptr++ = 0xff;    // 프레임 버퍼 주소 3

16비트 칼라라면 픽셀당 2 바이트 입니다.

short *ptr

*ptr++ = 0xff;    // 프레임 버퍼 주소 0
*ptr++ = 0xff;    // 프레임 버퍼 주소 2
*ptr++ = 0xff;    // 프레임 버퍼 주소 4
*ptr++ = 0xff;    // 프레임 버퍼 주소 8

이렇게 C 에서 자동으로 픽셀당 바이트 크기 만큼 자동으로 주소를 계산해 주기 때문에 write()나 lseek()함수를 사용하는 것 보다 매우 편리합니다. 또한 (x, y) 좌료 계산도 편리하죠.

메모리 매핑 mmap()

그러면 파일로 처리되는 프레임 버퍼를 어떻게 포인터로 이용할 수 있는 메모리로 변환할 수 있을까요? 이런 필요에 의해 나온 것이 메모리 매핑(memory mapping) 입니다.

우리는 느끼지 못하지만 리눅스에서는 매우 다양하고 고급스러운 메모리 기술을 가지고 있습니다. 물리적으로는 하나의 메모리를 가지고 있지만 실행되는 여러 개의 에플리케이션은 마치 모든 메모리를 혼자 독차지한듯 작동하는 것도 바로 리눅스의 메모리 관리 때문이며, 또한 이를 위해 리눅스는 따로 메모리 관리자를 운영합니다.

메모리 매핑은 물리적으로는 메모리 장치가 아니지만 메모리(memorry) 처럼 구성(maaping)해서 마치 메모리에 접근하듯이, 일반 메모리처럼 사용할 수 있도록 해줍니다.

이 때 사용하는 것이 mmap() 함수입니다. 이 메모리 매핑은 리눅스에만 있는 것이 아닙니다. Microsoft Windows에서도 제공되는 데요, 리눅스 보다 훨씬 복잡합니다. 왜 이렇게 복잡한지는 이해가 안되지만 다 이유가 있겠죠. ^^

void * mmap(void    *start,        // 물리적 장치에 대해 메모리로 시작할 위치, 보통 0
            size_t   length,       // 물리적 장치의 크기, 즉 확보될 메모리의 크기
            int      prot ,        // 읽기/쓰기와 같은 메모리의 특성
            int      flags,        // 다른 프로세스와 공유할지 여부
            int      fd,           // 물리적 장치의 디스크립터
            off_t    offset        // 보통 0  
            );

주석으로 간단히 말씀을 드렸습니다만 몇 가지를 다시 보겠습니다.

start

인수값은 특별한 경우를 제외하고 0 을 입력합니다. 저도 아직 경험이 적어서 0 이외의 값은 사용해 본적이 없습니다.

prot

prot에는 아래의 4가지 상수를 사용할 수 있습니다.

  • PROT_EXEC  페이지는 실행될 수 있다.
  • PROT_READ  읽기 가능
  • PRTO_WRITE 쓰기 가능
  • PROT_NONE  접근 불가

메모리 매핑된 메모리는 아래의 flags의 설정에 따라 다른 프로세스와 공유할 수 있으므로 프로세스의 기능에 따라 읽기/쓰기를 설정하시면 되겠습니다. 역시 나머지 PROT_EXEC와 PROT_NONE의 쓰임세는 저도 잘 모르겠네요....^^;;

flags

이 인수를 이용하여 다른 프로세스와 공유할 지의 여부를 결정할 수 있습니다.

  • MAP_FIXED   아직 무슨 옵션인지....--;
  • MAP_SHARED  다른 프로세스와 공유하며, 사용하는 모든 프로세스는 동등한 권한을 갖습니다.
                동등한 권한을 갖는 만큼 충돌이 되지 않도록 조심해야 겠지요.
  • MAP_PRIVATE 나만 쓰겠다는 것이죠.

메모리 메핑을 이용한 프레임 버퍼 사용

void	           *fb_mapped;       // 매핑된 메모리의 시작 위치를 담을 포인터
unsigned short *ptr;            // 매핑 메모리에서 칼라를 넣는 포인터

fb_mapped      = ( void *)mmap( 0,
                                line_length * screen_height,
                                PROT_READ|PROT_WRITE,
                                MAP_SHARED,
                                fb_fd,
                                0);

for ( ndx = 0; ndx < 100; ndx++)
{
   ptr   = ( unsigned short*)fb_mapped + screen_width * ndx + ndx;
  *ptr  = 0xffff;
}

이전 강좌에 올려 드린 소스와는 달리 write()와 lseek()를 사용하지 않고 바로 포인터를 사용함으로써 코딩하기도 훨씬 깔끔하고 간편해 졌죠.

주소 계산하는 것도,

lseek( fb_fd, 0, SEEK_SET);
lseek( fb_fd, ndx * line_length +ndx *(bits_per_pixel / 8), SEEK_SET);
write( fb_fd, &color, 2);

이랬던 것이 메모리 매핑을 이용해서 포인터 변수를 사용하기 때문에 아래와 같이 간단해 졌습니다.

 ptr   = ( unsigned short*)fb_mapped + screen_width * ndx + ndx;
*ptr  = 0xffff;

전체 예제 소스

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>        // open/close
#include <fcntl.h>         // O_RDWR
#include <sys/ioctl.h>     // ioctl
#include <sys/mman.h>      // mmap PROT_
#include <linux/fb.h>

int   main( int argc, char **argv)
{
   int      screen_width;
   int      screen_height;
   int      bits_per_pixel;
   int      line_length;

   int      fb_fd;
   struct   fb_var_screeninfo  fbvar;
   struct   fb_fix_screeninfo  fbfix;
   void     *fb_mapped;
   int      mem_size;
   unsigned short *ptr;

   int      ndx;

   if ( access( "/dev/fb", F_OK))
   {
      printf( "프레임 버퍼 장치가 없습니다.n");
      return 0;
   }

   if ( 0 > ( fb_fd = open( "/dev/fb", O_RDWR)))
   {
      printf( "프레임 버퍼에 접근할 수 없습니다.n");
      return 0;

   }

   if ( ioctl( fb_fd, FBIOGET_VSCREENINFO, &fbvar))
   {
      printf( "FBIOGET_VSCREENINFO를 실행하지 못했습니다.n");
      return 0;
   }
   if ( ioctl( fb_fd, FBIOGET_FSCREENINFO, &fbfix))
   {
      printf( "FBIOGET_FSCREENINFO 실행하지 못했습니다.n");
      return 0;
   }

   screen_width   = fbvar.xres;                    // 스크린의 픽셀 폭
   screen_height  = fbvar.yres;                    // 스크린의 픽셀 높이
   bits_per_pixel = fbvar.bits_per_pixel;          // 픽셀 당 비트 개수
   line_length    = fbfix.line_length;             // 한개 라인 당 바이트 개수

   mem_size       = line_length * screen_height;

   fb_mapped      = ( void *)mmap( 0,
                                   mem_size,
                                   PROT_READ|PROT_WRITE,
                                   MAP_SHARED,
                                   fb_fd,
                                   0);

   printf( "screen width   = %dn", screen_width  );
   printf( "screen height  = %dn", screen_height );
   printf( "bits per pixel = %dn", bits_per_pixel);
   printf( "line length    = %dn", line_length   );

   for ( ndx = 0; ndx < 100; ndx++)
   {
      ptr   = ( unsigned short*)fb_mapped + screen_width * ndx + ndx;
     *ptr  = 0xffff;
   }

   for ( ndx = 0; ndx < 500; ndx++)
   {
     *ptr++  = 0xffff;
   }

   munmap( fb_mapped, mem_size);
   close( fb_fd);

   return 0;
}

실행해 보시면 이전 강좌와 같은 그림이 출력됩니다.

많이 프레임 버퍼에 접근할 수 없다는 메시지가 나온다면 root 계정으로 변경하시거나 /dev/fb0 의 권한을 777 로 변경하십시오.

]$ su -
Password: ******
]# chmod 777 /dev/fb0
]# exit
logout
]$

태그: *프레임 버퍼 *그래픽