본문 바로가기

2012
임베디드월드

글: 라영호 | ratharn@naver.com / 2012-07-06


[연재 차례]

1. 안드로이드 시스템의 역사 및 동향
2. 안드로이드 시스템과 리눅스
3. 안드로이드 플랫폼의 이해
4. 안드로이드 바인더(Binder)의 이해
5. 안드로이드 서비스
6. 안드로이드 SurfaceFlinger와 프레임버퍼 드라이버
7. 안드로이드 User Interface와 ADK2012
8. Linux Sound Device와 안드로이드 사운드 시스템
9. 안드로이드 카메라 시스템
10. 안드로이드 카메라와 멀티미디어 프레임워크
11. 안드로이드 카메라와 멀티미디어 프레임워크 ②
12. 안드로이드 시스템 디버깅 및 기타



안드로이드 시스템 개발자를 위한 안드로이드 시스템의 분석 및 이해 ⑥
안드로이드 SurfaceFlinger와 프레임버퍼 드라이버


7월호에서는 안드로이드에서 화면을 관리하는 SurfaceFliger와 프레임버퍼(FrameBuffer) 드라이버에 관하여 살펴 보도록 하겠다. 지금까지는 안드로이드의 전체적인 운영 방법에 대해 살펴 봤고 이제부터 각각의 모듈이 실제 어떻게 운영되는지 확인해 보는 시간이다. 안드로이드 응용 프로그램에서 만든 메뉴나 다이얼로그 박스, 사용자 인터페이스 구성 요소들은 SurfaceFlinger를 통하여 리눅스 커널에 위치한 LCD 디스플레이 드라이버인 프레임 버퍼 드라이버를 통해 그림을 그리게 된다. 이러한 응용 프로그램과 SurfaceFlinger, 프레임 버퍼 드라이버 사이의 관계 및 구조에 대해 살펴 보도록 하자.

SurfaceFlinger
SurfaceFlinger의 전체적인 역할은 [그림1]과 같이 표현할 수 있다. 사용자의 프로세스 혹은 응용 프로그램에서 생성한 화면(원래는 하나의 Surface)을 합성하여 LCD 화면에 그림을 출력할 수 있도록 관장하는 역할을 한다. 따라서 SurfaceFlinger의 역할은 크게 2가지로 볼 수 있다.


(1) 사용자 프로세스나 앱에서 생성한 화면을 관리 하는 역할·화면의 위치, 표시 순서(Display Order), 색상 등을 관리하는 역할
(2) 커널에 위치한 프레임 버퍼 드라이버와 연동하여 생성된 최종 이미지를 프레임 버퍼 드라이버를 통해 LCD에 출력할 수 있도록 프레임 버퍼와 연동하는 역할
이와 같이 SurfaceFlinger의 역할을 분류해 볼 수 있다.
하지만 게임과 같은 응용 프로그램이나 카메라를 통한 프리뷰 동작을 위해서는 고속의 화면 처리가 필요한 응용 프로그램에서는 SurfaceFlinger를 사용하여 처리하기에는 속도 면에서 부적절 하기 때문에 하드웨어 오버레이(Overlay) 사용하여 출력하는 방법이 사용된다.

프레임버퍼 드라이버(Frame Buffer Driver)
다음 소스는 프레임 버퍼 드라이버를 직접 오픈 하여 프레임 버퍼에 대한 핸들을 얻어내고, 프레임 버퍼에 대한 포인터를 얻어 내어 RGB 이미지 데이터를 직접 출력하는 소스의 일부분이다.

int fbfd;

fbfd = open("/dev/graphics/fb0", O_RDWR);
// 플레임 버퍼 드라이버 오픈
pfbmap = (unsigned short *)
mmap(0, COLS*ROWS*2,PROT_READ|PROT_ WRITE, MAP_SHARED, fbfd, 0);

if((unsigned)pfbmap == (unsigned)-1)
{
perror("fbdev mmap");
exit(1);
}

//중략

for(j=0;j<rows;j++){
k = j*cols*3;
for(i=0;i<cols;i++)
{
b = *(data + (k + i*3));
g = *(data + (k + i*3+1));
r = *(data + (k + i*3+2));
pixel = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
bmpdata1[(rows-1-j)+120*0][i+160*0] = pixel;
}
}


[리스트1] 프레임버퍼 예제


안드로이드에서 "/dev/graphics/fb0" 형태로 프레임 버퍼 드라이버를 오픈 하여 mmap() 함수를 사용하여 프레임 버퍼에 대한 직접적인 제어를 통해 LCD를 제어할 수 있도록 하고 있다.
프레임 버퍼 드라이버는 리눅스 시스템에서 사용자 레벨의 응용 프로그램에서 제어할 수 있도록 만들어진 디바이스 드라이버이다. 또한 위의 예제에서 볼 수 있듯이 open, mmap과 같은 표준 인터페이스 함수를 통해 이용할 수 있는 구조로 구성되었다.


[그림 2]

[그림2]에서는 프레임 버퍼 드라이버의 동작 모습을 사용자의 응용 프로그램 관점에서 보여준다. 사용자의 응용 프로그램에서 작성한 화면은 프레임 버퍼 드라이버를 거쳐, 프로세서 내부에 있는 LCD 컨트롤러를 통해 LCD에 표시되게 된다. 물론 안드로이드 시스템에서는 [리스트1]과 같은 프로그램을 작성하여 운영할 수 있지만, 앱이나 사용자의 프로세스에서 만들어진 모든 동작들은 SurfaceFlinger를 통해 운영이 된다. 즉, 사용자의 응용 프로그램과 프레임 버퍼 드라이버상에 위치하여 관리하게 된다. 따라서 프레임 버퍼에 대한 윈리의 이해, SurfaceFlinger에 대한 정확한 동작의 이해가 필요한 이유다.

프레임 버퍼 드라이버의 일반적인 사항
● 디바이스 드라이버로써 이식성을 높이기 위해 사용됨 표시장치의 종류가 많고, 이러한 여러 하드웨어를 고려하지 않고 표준화된 인터페이스를 제공하기 위함
● 통상 "/dev/fb0, dev/graphics/fb0" 형태로 장치 드라이버가 구성되어 있고 드라이버가 할당한 메모리를 사용자 응용 프로그램에서 사용할 수 있도록 메모리 매핑하여 사용함
● 사용자 응용 프로그램에서 전송한 프레임 버퍼 데이터를 LCD 드라이버가 수신하여 LCD 컨트롤러를 통해 TFT LCD나 다른 표시 장치에 출력하는 역할을 함

안드로이드 시스템에서 프레임 버퍼 드라이버의 특이사항
● 기존 리눅스 프레임 버퍼 드라이버에 struct fp_ops와 fb_ pan_display 함수가 추가로 구현되어야 함
● 실제 프레임 버퍼 크기보다 두 배의 메모리 할당 필요 해당 함수는 프레임 버퍼의 디스플레이 포인터를 옮기는 기능을 제공한다. 즉, 실제 화면보다 넓은 가상 디스플레이를 구현 가능하도록 함
● 안드로이드 시스템의 프레임 버퍼 드라이버는 고속 처리를 위해 반드시 더블 버퍼링(Double Buffering)을 사용하도록 구성됨

[리스트2]는 프레임 버퍼 드라이버를 구성하는 주요 함수의 엔트리 포인트를 보여주는 소스의 일부분이다. 본 소스는 삼성 계열의 소스에서 발취한 소스라 "s3cfb_" 형태로 내부 함수가 구성되었다. 다른 프레임 버퍼 드라이버의 구조도 이와 유사하다.

[kernel/drivers/video/samsung/s3cfb_main.c]

struct fb_ops s3cfb_ops = {
.owner = THIS_MODULE,
.fb_open = s3cfb_open,
.fb_release = s3cfb_release,
.fb_check_var = s3cfb_check_var,
.fb_set_par = s3cfb_set_par,
.fb_setcolreg = s3cfb_setcolreg,
.fb_blank = s3cfb_blank,
.fb_pan_display = s3cfb_pan_display,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
.fb_cursor = s3cfb_cursor,
.fb_ioctl = s3cfb_ioctl,
};


[리스트2] 프레임 버퍼 드라이버의 내부 함수 테이블

SurfaceFlinger
SurfaceFlinger의 역할은 앞에서 설명 했듯이 앱이나 사용자의 응용 프로그램에서 생성한 화면(여기에서는 Surface라는 용어를 사용함) 합성 관리하는 역할이다. "Surface+Flinger"의 합성이다. 즉, 생성된 여러 개의 Surface를 하나의 Surface로 만들고 이 만든 화면을 프레임 버퍼 드라이버와 연계하여 프레임 버퍼로 만들고, LCD 화면에 표시하는 역할을 하는 것이다.


[그림 3]

[그림3]에서 볼 수 있듯이 SurfaceFlinger의 역할은 응용 프로그램과 LCD 프레임 버퍼 드라이버 사이에 위치하면서 CopyBIT 동작에 의해 LCD 프레임 버퍼에 화면은 전송하거나 하드웨어 가속을 사용할 경우 OpenGL을 통해 하드웨어 가속기인 GPU에 전달하는 역할을 하게 된다.

SurfaceFligner의 테스트
다음은 SurfaceFliger의 기능을 이용하는 간단한 예제를 보여준다.

char *buf;
int stride;

if (!surfaceflinger_init(x, y, w, h, &stride)) {
printf("failed to initialize surfaceflingern");
return;
}

buf = surfaceflinger_lock();
draw(buf, w, h, stride);
surfaceflinger_unlock();

while (1)
sleep(1);

surfaceflinger_fini();


[리스트3] SurfaceFligner 테스트 프로그램

[리스트3]은 SurfaceFlinger를 사용하여 앞의 [리스트1]과 유사한 동작을 하는 프로그램이다. [리스트3]에서는 프레임버퍼 드라이버에 대한 제어가 필요 없이 SurfaceFlinger에 대한 호출을 통해 화면 그리기 작업이 이루어진다.
통상적으로 SurfaceFlinger를 사용하여 화면을 구성하는 방법은 SurfaceFlinger 서비스에 대한 객체 생성, surfa ce 생성, 위치 및 표시 레이어 설정을 통하여 surface를 구성하고, 이 생성한 surface에 대해 그리기 작업을 하게 된다.

int surfaceflinger_init(int x, int y, int w, int h, int *stride)
{
int depth = 32;
int fmt;
android_client = new SurfaceComposerClient;
if (android_client == NULL) {
LOGE("failed to create clientn");
return 0;
}


fmt = surfaceflinger_format(depth);
if (fmt == PIXEL_FORMAT_UNKNOWN) {
LOGE("failed to find a format for depth %dn", depth);
return 0;
}

android_surfacecontrol = android_client->createSurface(getpid(), 0, w, h, fmt, 0);
if (android_surfacecontrol == NULL) {
LOGE("failed to create surfacen");
return 0;
}

android_surface = android_surfacecontrol->getSurface();

android_client->openTransaction();
android_surfacecontrol->setPosition(x, y);
android_surfacecontrol->setLayer(INT_MAX);
android_client->closeTransaction();

if (stride)
*stride = w * depth / 8;

return 1;
}


[리스트4] SurfaceFlinger의 실제 함수 호출 부분



[리스트4]는 특정한 위치에 surface에 대한 surface를 생성하는 코드를 보여 준다.

surfaceflinger_lock() 함수를 통해 생성한 surface에 대한 포인터를 얻어오게 되고, 이 포인터에 draw() 함수를 사용하여 surface에 대한 그리기 작업을 진행하게 된다. 프레임 버퍼 드라이버를 직접 접근하여 그리는 작업과 다르게 surface에 대한 각종 작업을 해야 한다는 차이점이 존재 한다는 것이다. 이런 개념으로 SurfaceFlinger에 대해 기본적인 이해를 하고 SurfaceFlinger의 내부에 대해 살펴보도록 한다.

SurfaceFlinger의 내부 구조


[그림 4]

[그림4]는 SurfaceFlinger의 전체적인 구성을 보여 준다. 통상적인 앱은 android.view.Surface 클래스를 통해 화면 제어를 하게 된다. 당연히 앱과 내부간의 통신은 JNI 인터페이스를 통해 UI와 통신하고, SurfaceFlinger와는 바인더 통신을 통해 접근하게 된다. 안드로이드 시스템에서 생성되는 일반적인 그래픽들의 처리는 gralloc와 프레임 버퍼 드라이버를 통하여 그려지게 되고, 카메라나 멀티미디어와 같은 하드웨어 가속을 쓰는 경우에는 오버레이(overlay)를 통해 관리되게 된다.

SurfaceFlinger에 대한 초기화 과정은 threadLoop가 동작하기 전에 실행되는 readyToRun() 함수에서 담당한다. 여기에서 안드로이드에서 사용하는 EGL의 초기 과정이 진행된다. EGL은 OpenVG나 OpenGL을 다루기 위한 인터페이스 부분이라고 보면 되겠다.

status_t SurfaceFlinger::readyToRun()
{
......

// initialize the main display
GraphicPlane& plane(graphicPlane(dpy));
DisplayHardware* const hw = new DisplayHardware(this, dpy);
plane.setDisplayHardware(hw);
……
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, NULL, NULL);
eglGetConfigs(display, NULL, 0, &numConfigs);


[리스트5] SurfaceFlinger 초기화 과정


[그림 5]

[그림5]는 DisplayHardware 클래스가 생성되면서 이루어지는 작업에 대한 그림이다. Init() 함수에 의해 내부 변수들이나 버퍼들이 초기화 되고, 프레임 버퍼에 대한 메모리가 할당되고, 실제 프레임 버퍼 드라이버가 오픈 되면서 초기화 작업을 진행하게 된다.

bool SurfaceFlinger::threadLoop()
{

const DisplayHardware& hw(graphicPlane(0).displayHardware());
if (LIKELY(hw.canDraw() && !isFrozen())) {
// repaint the framebuffer (if needed)
handleRepaint();

postFramebuffer();
} else {

}
return true;


[리스트6] threadLoop의 함수 내부

[리스트6]은 threadLoop() 함수내의 동작을 보여준다. 이 함수의 역할은 생성된 surface의 위치 및 영역을 확인하여 프레임 버퍼에 쓰는 역할을 한다. 즉, SurfaceFlinger의 핵심적인 역할로 화면에 생성된 surface들을 관리하고 프레임 버퍼에 그려질 데이터를 전달하는 역할을 하게 되는 것이다.


void SurfaceFlinger::handleRepaint()
{
 …
 // compose all surfaces
 composeSurfaces(mDirtyRegion);
 …
}
void SurfaceFlinger::composeSurfaces(const Region& dirty)
{
 …
 for (size_t i=0 ; i<count ; ++i) {
 const sp<LayerBase>& layer = layers[i];
 const Region& visibleRegion(layer->visibleRegionScreen);
 if (!visibleRegion.isEmpty()) {
 const Region clip(dirty.intersect(visibleRegion));
 if (!clip.isEmpty()) {
 layer->draw(clip);
 }
 }
 }
}


[리스트7] handleRepaint 의 함수 내부

void SurfaceFlinger::postFramebuffer()
{
 if (!mInvalidRegion.isEmpty()) {
 const DisplayHardware& hw(graphicPlane(0).displayHardware());
 const nsecs_t now = systemTime();
 mDebugInSwapBuffers = now;
 hw.flip(mInvalidRegion);
 mLastSwapBufferTime = systemTime() - now;
 mDebugInSwapBuffers = 0;
 mInvalidRegion.clear();
 }
}


[리스트7] postFramebuffer 의 함수 내부

[리스트7]은 handleRepaint() 에서는 composeSurface() 를 호출해서 flags 에 따라서 업데이트할 방법이나 영역을 결정하고 surface 들을 합성한다.


[그림 6]

postFrameBuffer() 함수에서 실제 프레임버퍼에 쓰는 작업을 한다.

switch (flags & eFXSurfaceMask) {
 case eFXSurfaceNormal:
 if (UNLIKELY(flags & ePushBuffers)) {
 layer = createPushBuffersSurfaceLocked(client, d, id, w, h, flags);
 } else {
 layer = createNormalSurfaceLocked(client, d, id, w, h, flags, format);
 }
 break;
 case eFXSurfaceBlur:
 layer = createBlurSurfaceLocked(client, d, id, w, h, flags);
 break;
 case eFXSurfaceDim:
 layer = createDimSurfaceLocked(client, d, id, w, h, flags);
 break;


[리스트8] createSurface 함수 내부


SurfaceFlinger의 Layer와 LayerBuffer
SurfaceFlinger 는 createSurface() 가 호출되면 Surface 클래스를 바로 생성 하는 것이 아니고 Layer 클래스를 생성한다. createSurface() 함수에 넘어온 인자에 따라 어떤 Layer 클래스를 생성할지를 선택한다. Layer 클래스는 각각 다른 형태로 출력을 하며 Surface 클래스를 포함하고 있다. SurfaceFlinger 클래스는 createSurface() 함수 요청이 있을 때마다 LayerXXX 형태의 클래스를 만들지만 리턴은 LayerXXX 클래스 내부의 getSurface() 함수를 통해서 Surface 형을 돌려주게 된다.

결국 클라이언트는 Surface 로 컨트롤 하고 SurfaceFlin ger 에서는 Layer 로 컨트롤 하게 된다.
[리스트8]은 createSurface() 함수의 일부분이다. surface를 생성할 때 사용하는 flag에 따라 생성하는 surface 함수가 달라짐을 볼 수 있다.

sp<LayerBaseClient> SurfaceFlinger::createNormalSurfaceLocked(
 const sp<Client>& client, DisplayID display,
 int32_t id, uint32_t w, uint32_t h, uint32_t flags,
 PixelFormat& format)
{
 …
 sp<Layer> layer = new Layer(this, display, client, id);
 status_t err = layer->setBuffers(w, h, format, flags);
 if (LIKELY(err == NO_ERROR)) {
 layer->initStates(w, h, flags);
 addLayer_l(layer);
 } else {
 LOGE("createNormalSurfaceLocked() failed (%s)", strerror(-err));
 layer.clear();
 }
 return layer;
}


[리스트9] createNormalSurfaceLocked 함수 내부

[리스트9]는 Layer 를 생성하는 createNormalSurface Locked() 함수다.

sp<LayerBaseClient> SurfaceFlinger::createPushBuffersSurfaceLocked(
 const sp<Client>& client, DisplayID display,
 int32_t id, uint32_t w, uint32_t h, uint32_t flags)
{
 sp<LayerBuffer> layer = new LayerBuffer(this, display, client, id);
 layer->initStates(w, h, flags);
 addLayer_l(layer);
 return layer;
}


[리스트10] createPushBuffersSurfaceLocked 함수 내부

[리스트10]은 LayerBuffer 를 생성하는 createPushBuf fersSurfaceLocked() 함수의 소스다.

위와 같이 createSurface() 함수에서 호출되는 모든 createXXX 함수들은 해당 Layer 클래스를 생성하고 SurfaceFlinger 가 관리하는 LayerVector 에 추가하는 일을 한다. 이렇게 Layer 를 나눠놓은 이유는 각각 출력하는 방식을 다르게 하여서 용도에 맞게 쓰도록 하기 위해서다. Layer 클래스는 기본적인 화면출력을 담당하고 framebuffer에 출력한다. LayerBuffer 클래스는 동영상과 같은 고속 처리를 담당하고 overlay 에 출력한다. 나머지 LayerBlur 과 LayerDim 은 쓰이지 않기 때문에 생략하도록 하겠다.

끝으로
지금까지 안드로이드 시스템에서 화면을 관장하는 SurfaceFlinger 및 프레임 버퍼 드라이버에 대해 살펴봤다. 프레임 버퍼 드라이버는 안드로이드 시스템이 동작하기에 필수적인 드라이버이며 SurfaceFlinger 역시 안드로이드 시스템의 사용자 인터페이스 화면을 관장하기 위해 필요한 서비스다. 이와 같은 구조를 자세히 이해 한다면 향후 멀티미디어나 카메라와 같은 서비스나 응용 프로그램을 개발하는데 있어 좀더 많은 이해를 할 수 있을 것이다.




/필/자/소/개/

필자

라영호

국내 스마트폰의 초창기인 Cellvic에서부터 스마트폰을 개발하였고 윈도 모바일 및 다양한 임베디드 시스템을 개발하고 있다. 현재는 안드로이드 시스템 포팅 및 임베디드 시스템 개발, 컨설팅, 교육 등을 진행하는 회사를 운영하고 있다. 개인 블로그(www.embeddedce.com)를 통해 임베디드 시스템개발에 대한 다양한 생각과 방법론을 함께 생각해 보고자 노력 중이다.

※ 본 내용은 (주)테크월드(http://www.embeddedworld.co.kr)의 저작권 동의에 의해 공유되고 있습니다.
    Copyright ⓒ Techworld, Inc. 무단전재 및 재배포 금지

맨 위로
맨 위로