본문 바로가기
오픈소스 읽기 (OLD)/데스크탑환경 - weston (wayland)

2. Weston 컴포지터 살펴보기 - 메인 플로우 (1/3)

by 커널패닉 2020. 8. 5.
반응형

Weston 컴포지터는 Weston의 동작을 준비/실행하는 핵심 컴포넌트이다. Weston 컴포넌트가 하는 일은 크게 두 가지다. 하나는 Wayland 객체, Backend 컴포넌트, Shell 컴포넌트를 생성 / 로딩하고, 실행하는 것이고, 다른 하나는 주요 Wayland Interface의 동작(= 컴포지팅)을 정의하는 일이다. Weston 컴포지터의 코드는 compositor 폴더에서 찾을 수 있으며, libweston 폴더를 참조한다. 워크플로우와 실제 소스코드를 보며, Weson compositor가 어떤식으로 동작하는지 알아보자.

(자세한 설명은 코드 주석 참조)

 

Weston compositor 워크플로우

 

// compositor/main.c
WL_EXPORT int
wet_main(int argc, char *argv[])
{
	struct wl_display *display;
	struct wl_client *primary_client;
	struct wet_compositor wet = { 0 };
	
	// (생략)
	// 인자 파싱
	// 변수 초기화
	// 로그 컨택스트 생성/설정
	
	// 1. Wayland display 생성
	// wl_display는 wayland에서 가장 핵심이 되는 객체이다. 
	// (참조: https://www.kernelpanic.kr/7)
	// Weston은 wl_display 객체를 이용하여, wayland protocol에 따른 동작을 정의하고, 
	// Wayland 메인 이벤트 루프를 실행한다.
	// (wl_display 객체 내에는 이벤트 큐가 있어서, 이벤트 루프 내에서 큐에 등록된 이벤트를 실행한다.)
	display = wl_display_create();
	if (display == NULL) {
		weston_log("fatal: failed to create display\n");
		goto out_display;
	}
	
	// (생략)
	// 시그널 처리

	// 2. weston 컴포지터 생성
	// weston_compositor_create를 통해 weston_compositor 객체를 생성한다.
	// weston_compositor 객체를 통해 Backend 컴포넌트, Shell 컴포넌트는 Weston의
	// 동작을 제어, 인터페이스 등록, 자원 접근 등을 수행할 수 있다.
	// weston_compositor 생성에 대한 자세한 설명은 아래 libweston/compositor.c를 참조하자.
	wet.compositor = weston_compositor_create(display, log_ctx, &wet);
	if (wet.compositor == NULL) {
		weston_log("fatal: failed to create compositor\n");
		goto out;
	}
	
	// (생략)
	// config 파일 파싱
	
	// 4. Backend 로딩 (drm, fb, wayland, x11, rdp)
	// Backend는 Weston의 출력을 관리하는 컴포넌트이다. Backend를 통해 Weston은 
	// 컴포지팅이 완료된 버퍼를 화면에 보여줄 수 있다. 
	// Weston은 다양한 출력 환경을 지원하기 위해 Backend 컴포넌트를 공유객체(.so)로 관리하고 있다.
	// 만약 Weston이 linux kernel의 drm을 사용한다면, drm_backend를 로딩하여,
	// weston_compositor backend에 등록하면 된다. 만약 X11을 사용하는 Gnome에서 
	// Weston을 실행한다면, x11_backend를 로딩하면 된다.
	// Weston은 이를 통해 다양한 백앤드 환경에서도 동일한 UI/UX를 제공한다.
	// Backend, 특히 drm은 그 자체로 하나의 쳅터가 되는 주제기 때문에 
	// 여기서는 Backend에 대해 자세히 다루지는 않는다.
	if (load_backend(wet.compositor, backend, &argc, argv, config) < 0) {
		weston_log("fatal: failed to create compositor backend\n");
		goto out;
	}

	// (생략)
	// 소켓 생성

	// 5. Shell 로딩
	// Shell은 우리가 보는 UI/UX를 그리는 컴포넌트이다. Shell을 통해 어플리케이션의 창 관리, 
	// 인풋 관리, 단축키 등록, 배경화면 설정 등 우리가 "데스크탑"이라고 느끼는 요소들을 제공해 준다.
	// Shell도 Backend와 같이 공유객체(.so)로 관리되고 있다. 자세한 내용은 뒤에서 다루도록 하겠다.
	if (wet_load_shell(wet.compositor, shell, &argc, argv) < 0)
		goto out;

	// (생략)
	// 모듈 로딩
	
	// 6. Wayland 메인 이벤트 루프
	// 무한 루프를 wl_display에 등록되는 이벤트를 처리한다. 여기에서 화면 갱신, idle 처리 등 
	// 앞에서 준비한 작업들이 이뤄진다.
	wl_display_run(display);
	
	// (생략)
	// 정리작업
}

위는 Weston compositor의 메인함수이다. 메인함수는 워크플로우 그림과 거의 흡사하게 동작한다. 자잘한 동작들이 많긴 하지만, 큰 틀에서 보면 크게 복잡하지는 않다.

 

// libweston/compositor.c
WL_EXPORT struct weston_compositor *
weston_compositor_create(struct wl_display *display,
			 struct weston_log_context *log_ctx,
			 void *user_data)
{
	struct weston_compositor *ec;
	struct wl_event_loop *loop;

	// (생략)
	// Weston 컴포지터 생성 / 초기화

	// 3. 주요 Wayland Interface 등록
	// wl_global_create api를 통해 Weston은 Wayland 프로토콜 인터페이스에 대한 동작을 정의할 수 있다.
	// 예를 들어, 데스크톱 앱은 create_surface 프로토콜을 보내서, 새로운 surface를 요구할 수 있으며,
	// weston 서버는 새로운 surface를 생성/할당하거나, (메모리 부족 등의 이유로) 거부할 수 있다.
	if (!wl_global_create(ec->wl_display, &wl_compositor_interface, 4,
			      ec, compositor_bind))
		goto fail;

	if (!wl_global_create(ec->wl_display, &wl_subcompositor_interface, 1,
			      ec, bind_subcompositor))
		goto fail;

	if (!wl_global_create(ec->wl_display, &zxdg_output_manager_v1_interface, 2,
			      ec, bind_xdg_output_manager))
		goto fail;
	
    // (생략)
    // weston 의존적인 Interface 등록
    // weston 잔여 작업 초기화

	return ec;

fail:
	free(ec);
	return NULL;
}

weston_compositor_create 함수를 통해 weston_compositor 객체가 생성된다. 이 때 주요 Weston 인터페이스들의 동작이 등록된다. Weston 컴포지터에서 등록되는 인터페이스들은 데스크톱 앱에 대해 다음과 같은 동작을 수행한다.

(참고자료: www.kernelpanic.kr/7)

프로토콜 내용
wl_compositor 새로운 화면 생성
예를 들어 앱에서 새 창을 띄우면 create_surface 호출된다.
wl_surface 앱 화면 변화를 처리
예를 들어, 화면에 마우스가 올라가면 다음과 같은 프로토콜 호출이 발생한다.
- surface에 새로운 버퍼가 생성되었기 때문에 (surface)attach가 우선 호출된다.
- 다음으로 surface에 변경사항이 생겼기 때문에 (surface)damage가 호출이 된다.
- 마지막으로 해당 변경을 반영하기 위해 (surface)commit이 호출된다.

이 외에도 subcompositor, zxdg_output_manager_v1 등 여러 프로토콜이 등록된다. 이 프로토콜들에 대한 자세한 설명은 아래 링크에서 볼 수 있다.

Note

if (!wl_global_create(ec->wl_display, &wl_compositor_interface, 4,
    ec, compositor_bind))
    goto fail;


위 코드에서 "wl_compositor_interface" 때문에 한동안 코드 독해에 고생하였다. 왜냐하면 libweston/compositor.c에 다음 선언이 있기 때문이다.

static const struct wl_compositor_interface compositor_interface = {
    compositor_create_surface,
    compositor_create_region
};

마치 struct의 이름을 '&' 참조를 통해 함수에 인자로 전달한 것 처럼 보이기 때문이다. C 언어에 익숙하다면, 해당 문법이 성립되지 않음을 알 수 있다. 위 구문이 당황스러웠던 것은 나 뿐만이 아니었던 것 같다. Stackoverflow에 보면 비슷한 질문이 올라와 있다. 

(출처: stackoverflow.com/questions/44101813/can-struct-type-itself-be-passed-to-function-as-parameter-in-c)

어떻게 된 일일까? 
사실 wl_compositor_interface은 wayland_protocol.h 헤더에서 struct wl_interface 형으로 선언되어 있다. 즉 wl_global_create에 전달된 wl_compositor_interface는 어떤 구조체의 이름이 아니라, 단순히 wayland_protocol.h에 정의된 struct wl_interface형 변수였던 것이다
반응형