본문 바로가기
공부하는 하스씨/Graphics

Frame Buffer 이야기 (4)

by 박하스. 2008. 11. 26.
728x90
반응형

출처: KELP 이규명님의 강좌 http://kelp.or.kr/korweblog/stories.php?story=02/11/09/8557035&topic=29

== 시작하기에 앞서
이 글에 있는 모든 내용은 글쓴이가 가지고 있는 ATI mach 64(2MB) 그래픽 카드가 달려있고 RedHat 8.0이 설치된 PC(또는 Permedia2(8MB) 그래픽 카드가 달려 있는 RedHat 7.3이 설치된 PC)에서 제대로 동작하지만 이 글에 있는 모든 내용이 정확하다고 말씀 드릴 수 없습니다. 이 글에 있는 내용을 따라 했을 때 혹 생길 지 모르는 모든 문제에 대해서 글쓴이는 책임을 지지 않습니다. 글의 내용 중에 잘못된 내용이 있거나 질문할 것이 있으면 위쪽의 “holelee”를 누르고 메일을 보내기 바랍니다.

== 시작
저번 글에서는 다소 무식한(?) pixel 찍기를 해 보았습니다. 왜 무식한지는 나중에 따지도록 하고 전형적인 보통 파일 쓰기의 방법에 기초한 방법이었습니다. open하고 lseek으로 offset 조정하고 write로 값을 써 넣었습니다. 그런데 보통의 파일 쓰기의 경우 그냥 주르륵 써 넣기만 하지 쓸 때마다 매번 lseek으로 offset을 조정하거나 하는 것은 매우 드믄 경우가 됩니다. 이렇게 매번 lseek으로 offset을 조정하는 것은 상당히 프로그래밍을 하기가 피곤합니다. 좀더 좋은 방법이 없을까요? mmap 시스템 호출이라는 좋은 방법이 있습니다. 이번 글에서는 일반적인 mmap 시스템 호출에 대해서 알아보고 다음 글에서는 mmap을 frame buffer에 이용하는 법을 알아보도록 하겠습니다.

== mmap 시스템 호출
역사적 배경이야 잘 모르겠고, “파일 읽기 쓰기를 메모리 읽기 쓰기와 동일하게 할 수 없을까?”라는 소박한(?) 요구에 부응하는 것이 mmap 시스템 호출입니다. mmap 시스템 호출은 file descriptor로 표현(?)되는 object(C++도 아니니 객체로 번역하기도 껄끄럽고 그렇다고 컴파일할 때 나오는 object 코드를 이야기하는 것도 아닙니다. 그냥 개체)를 application의 virtual address상에 매핑(mapping)시켜주는 시스템 호출입니다. 그냥 쉽게 생각해서 파일을 열어서 mmap하면 read/write를 포인터로 할 수 있다고 알아두면 쉽습니다. 이러한 시스템 호출은 linux상에도 있고 Windows도 비슷한 일을 하는 시스템 호출이 있는 것으로 보아 많은 현대적인 OS에 존재하는 것 같습니다. 사용법을 알아보죠.
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
이것이 mmap의 man page에 나오는 mmap 시스템 호출의 prototype입니다. 우선 그냥 특별한 경우가 아니면 start는 0을 넣는다고 외우세요.(즉 일반적인 경우 크게 의미가 없는 argument입니다.) length는 메모리로 매핑할 크기가 됩니다. prot, flags는 우선 무시하고 fd는 매핑할 file descriptor입니다. 그리고 offset은 fd로 표현되는 object상의 offset입니다. 리턴 값은 어드레스구요. 제가 한마디로, 그러나 어설프게 mmap이 무엇을 해주는 가를 말해보도록 하겠습니다. object 대신 파일이라는 말을 쓰면 이해하기 쉬우니 파일을 써서 하겠습니다.
“mmap 시스템 호출은 fd로 지정된 파일에서 offset을 시작으로 length바이트만큼을 메모리에 매핑하고 그 어드레스를 리턴한다.”(사실 한글 man page에서 적당히 베껴서 편집한 말입니다.)
argument prot은 메모리 매핑이 읽기/쓰기를 어떤 형식으로 허용할지를 나타내고 flags는 이 메모리와 파일 사이의 관계(?)를 나타내는 flag값인 것 같습니다.
모든 자세한 사항은 man page를 참고로 하시기 바랍니다.

예제로 무엇을 할까 많은 고민 끝에 가장 간단하면서도 아무 쓸모가 없는 것으로 정했습니다.(마땅한 예제가 없네요.) 우선 mmap 예제를 돌리기에 앞서서 test file을 만들어 내는 프로그램 소스코드를 먼저 올립니다.
===============================
/*
* tvector.c : test vector generator for mmaptest
*
* Copyright(C) 2002 holelee
*
*/

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

int main()
{
FILE *fstream;
int i;

fstream = fopen("mmaptest.tst", "w+b");
if(fstream == NULL)
{
fprintf(stderr, "Cannot open mmaptest.tst\n");
exit(1);
}

for(i = 0; i < 1024; i++)
fwrite(&i, 4, 1, fstream);

fclose(fstream);
exit(0);
return 0;
}
===============================
그냥 C standard library에 있는 함수로 작성해 봤습니다. 내용은 간단하죠. 그냥 mmaptest.tst라는 파일 안에 0부터 1023까지 정수 값을 저장하는 프로그램입니다.(총 32비트 machine에서는 총 4096바이트에 해당하겠죠.)
이제 mmaptest.c 파일을 살펴보도록 하겠습니다.
===============================
/*
* mmaptest.c : mmap example
*
* Copyright(C) 2002 holelee
*
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

int main()
{
int fd;
int *pmmap;
int i;

fd = open("mmaptest.tst", O_RDWR);
if(fd < 0)
{
perror("open");
exit(1);
}

pmmap = (int *)mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if((unsigned)pmmap == (unsigned)-1)
{
perror("mmap");
exit(1);
}

for(i = 0; i < 1024; i++)
{
printf("%d\n", pmmap[i]); /* 읽어서 출력 */
pmmap[i] = pmmap[i]+1024; /* 값을 바꿈 */
}

munmap(pmmap, 4096);
close(fd);
exit(0);
return 0;
}
===============================
내용을 보면 동일한 파일(mmaptest.tst)를 open하고 offset 0부터 4096byte만큼을 READ/WRITE가 가능하도록 mmap합니다. mmap이 리턴한 address는 integer 포인터 pmmap에 assign하고 1024개 만큼 그 포인터에서 읽어서 출력하면서 포인터로 지정된 어드레스에 있는 값을 바꾸는 프로그램입니다. 값을 바꾸었으므로 당연히 파일에 저장된 값이 바뀔겁니다. 그 다음 munmap이라고 하여 mmap의 역함수(?)와 같은 함수를 부릅니다. munmap에 대해서 자세히 알고 싶으면 man page를 보길 바랍니다. 암튼 이제 동작을 살펴봐야 겠죠.
우선 tvector를 수행하여 파일을 만듭니다. 그 다음 mmaptest를 수행시키면 0부터 1023까지의 정수를 차례로 출력할겁니다.(눈깜짝할 사이에 지나가 버리겠지만요.) 다시 mmaptest를 수행하면 이제는 1024부터 2047까지의 값을 찍게 됩니다. 전에 수행시켰던 mmaptest가 pmmap 포인터를 이용하여 파일에 저장된 내용을 바꾸었으니까 당연한 것이죠. 계속 수행해 보면 1024씩 증가합니다.
암튼 파일 내용을 write 시스템 호출을 이용하지 않고 포인터를 이용하여 바꾸는데 성공하였습니다.
** 여기서 잠깐
mmap이 리턴하는 어드레스는 virtual address입니다. 그 address 값 자체는 큰 의미를 지니지 못합니다. CPU의 MMU가 그것을 physical address로 바꿀 때 어떤 일이 일어나겠지만요.

== file descriptor로 지정된 object
file descriptor로 지정될 수 있는 object는 상당히 많이 있습니다. 당연히 일반적인 파일이 지정될 수 있죠. 또한 socket도 file descriptor로 지정될 수 있고, IPC(Inter Process Communication)에서 사용하는 pipe, fifo 등도 역시 file descriptor로 지정될 수 있습니다. 또한 device special file(mknod로 만들어진)을 open하면 device도 지정될 수 있겠죠. 사실 file descriptor로 지정된 모든 것이 mmap을 지원하는 것은 아닙니다. 예를 들어 serial port device와 같은 경우는 모든 데이터가 순서대로 읽어지고 써지는 구조를 가지고 있는데 이런 경우에는 lseek도 의미가 없고 mmap도 되지 않을 겁니다. Serial device를 mmap하여 접근하는 것 자체가 우스운 일이죠.(혹 가능하면 글쓴이만 우스워지므로 가능하다는 사실을 알고 있으면 조용히 메일을 보내줄 것.) mmap을 지원하지 않은 object라면 mmap 시스템 호출에 -1를 리턴하고 errno를 적당한 값으로 채워줄 겁니다.

== 왜 mmap 이야기를 꺼냈는가?
Frame buffer 이야기를 하다가 왜 일반적인 mmap 이야기가 나왔을까요? 이쯤 되면 짐작이 되겠지만 frame buffer device도 file descriptor로 지정될 수 있고 일반적으로 mmap을 지원합니다. 따라서 lseek/write 시스템 호출에서 벗어나서 그냥 포인터로 접근을 해서 화면에 출력될 pixel 값을 바꾼다던지 화면에 출력된 pixel 값을 읽어 올 수 있습니다. lseek/write 시스템 호출을 사용하는 것은 나중에 살펴보겠지만 엄청 무식한 방법입니다. 단, 모든 frame buffer가 mmap을 지원하지는 않는 것 같습니다. 커널 소스의 Documentation/fb/tgafb.txt를 보면 DECChip 21030 칩 기반 그래픽 카드는 mmap을 지원하지 않는 것으로 나와 있습니다. 아마도 시스템의 구조상 버퍼 메모리를 CPU의 virtual address로 매핑할 수 없기 때문일 것으로 생각됩니다. 하지만 일반적인 frame buffer라면 대부분 mmap을 지원하고 mmap을 해서 사용하는 것이 일반적입니다. StrongARM의 LCD도 당연히 mmap을 지원할 겁니다. 왜냐하면 시스템 메모리의 일부를 LCD 버퍼로 이용하므로 virtual address로의 매핑이 쉽게 가능하기 때문이죠.

== 마치며
이번 글에서는 일반적인 mmap 시스템 호출에 대해서 알아보았습니다. 다음 글에서는 좀 유식한(?) mmap을 이용한 pixel 찍는 방법에 대해서 알아보겠습니다. 그리고 이번 글부터는 소스코드에 nl을 이용하여 줄 숫자를 적는다거나 하는 작업을 하지 않을 겁니다. 어짜피 KELP에서 잘 보이지 않네요. 잘 알아서 보길 바랍니다.

 

728x90
반응형

'공부하는 하스씨 > Graphics' 카테고리의 다른 글

Frame Buffer 이야기 (6)  (0) 2008.11.26
Frame Buffer 이야기 (5)  (0) 2008.11.26
Frame Buffer 이야기 (3)  (0) 2008.11.26
Frame Buffer 이야기 (2)  (0) 2008.11.26
Frame Buffer 이야기 (1)  (0) 2008.11.26