Embedded : : Linux/: : Linux UX

The Wayland Protocol 개요

Jay.P Morgan 2024. 5. 12. 05:52

 

  0. Intro

 

  오늘날, 리눅스 UX 관련해서 부각되는 것 중에 하나는 바로 WAYLAND 이다. (WAYLAND 는 기존의 윈도우 매니저를 개발하기 위해 사용해왔던 X11 프로토콜을 대체하는 프로토콜이다.) 대다수의 핵심 UX 관련 프로젝트들(GTK+, Qt, Clutter 등등)이 Wayland를 백엔드로 지원하기 위한 작업들을 진행 중이고, 짧은 시간 동안 상당 부분 진척이 이루어졌다..

 

 

  사실 현재 X11은 Wayland에서 제공하는 기능들을 전혀 제공하지 못하는 것은 아니다. (심지어 X12 프로젝트 또한 진행 중이다.) 하지만 중요한 것은 이와 같은 여러 가지 이유로 모두가 X 를 버리고 새롭게 시작하기를 원한다는 것이다.

다음은 기술적인 그리고 구조적인 몇 가지 이유를 설명하도록 하겠다.

  • Wayland는 오픈 소스 친화적이고, 최소한만을 유지한다. (KMS (Kernel-Mode Setting) 와 EVDEV, PIXMAN 등 Wayland는 외부 오픈 소스를 최대한 활용하여 내부를 최소한으로 유지한다.)
  • Wayland로컬 기반이다. (원격 접속은 클라이언트 계층에서 VNC 같은 프로토콜을 사용하면 된다.)
  • Wayland컴포지팅 API만을 제공한다. (Wayland는 Direct Rendering 과 CSD (Client-Side Decoration) 만을 제공한다. CSD 에 대해서는 반대하는 여론도 많지만, 기본적으로 Wayland는 굉장히 단순해졌다.)

  위의 세 가지는 Wayland의 가장 중요한 특징이며, X가 가진 반대의 이슈에 해당한다. 결론적으로 Wayland가 등장할 수 밖에 없었던 이유를 한 마디로 정리하면, X는 덩치가 너무 커져서 개발하기 힘드니 Wayland라는 새롭고 작고 가벼운걸 만들어서 편하게 개발하자라고 할 수 있다.

 

 

  1. Wayland 기본 구조

 

  X 서버와 Wayland의 가장 기본적인 차이는 X서버는 독립적인 프로세스이고, Wayland는 라이브러리이다. 즉, Wayland는 컴포지터에 라이브러리 형태로 합쳐져서 동작한다.

 

※ 출처 : Wayland 홈페이지

 

  위의 그림은 X 서버의 동작 과정을 간략하게 표현해주는 그림이다. 그림에 나타난 번호는 X 클라이언트에서 사용자 입력을 받아서 화면을 갱신하는 과정을 순서대로 표현한 것이다. 간략한 설명을 덧붙이면 다음과 같다.

  1. X 서버는 EVDEV 를 통해 커널로부터 사용자 입력을 전달받는다.
  2. X 서버는 사용자 입력을 X 클라이언트에게 전달한다.
  3. X 클라이언트는 필요한 화면을 갱신 후, X 서버에게 알린다.
  4. X 서버는 컴포지터에게 X 클라이언트의 화면이 갱신된 사실을 알린다.
  5. 컴포지터는 X 클라이언트의 갱신된 화면을 최종 화면에 반영 후, X 서버에게 알린다.
  6. X 서버는 KMS 를 통해 갱신된 최종 화면을 프레임버퍼에 반영한다.

이에 반해 Wayland의 동작 과정은 다음과 같다.

  그림에서 보는 것처럼 Wayland와 컴포지터가 하나로 합쳐져서 불필요하게 IPC 로 메세지를 주고 받는 일이 줄어들었다.

 

 

  2. Wayland 프로토콜

 

  Wayland는 기본적으로는 윈도우 서버와 클라이언트가 통신하기 위한 프로토콜이다. 아래는 Wayland 소스 코드에 포함되어, 기본 프로토콜을 정의하고 있는 wayland.xml 의 일부분이다. 이 파일에는 wl_display, wl_registry, wl_shm 과 같은 기본적인 프로토콜들이 정의되어 있다. 이 파일은 RPC (Remote Procedure Call) 의 stub 파일처럼 Wayland 컴파일 시에 헤더 파일과 소스 파일로 변환되어, 컴포지터와 클라이언트에서 편리하게 해당 메시지를 주고 받을 수 있도록 도와준다.

<interface name="wl_registry" version="1">
  <request name="bind">
    <arg name="name" type="uint"/>
    <arg name="id" type="new_id"/>
  </request>
  <event name="global">
    <arg name="name" type="uint"/>
    <arg name="interface" type="string"/>
    <arg name="version" type="uint"/>
  </event>
</interface>

  Wayland는 여러 개의 인터페이스(interface)를 정의하여 사용할 수 있고, 각각의 인터페이스는 리퀘스트(request)와 이벤트(event)로 구성되어 있다. 리퀘스트는 클라이언트가 서버에게 요청하는 것이고, 이벤트는 서버가 클라이언트에게 요청하는 것이다. 위의 xml 을 보면 wl_registry 라는 인터페이스가 정의되어있고, 여기에는 bind 라는 리퀘스트와 global 이라는 이벤트가 정의되어 있다.

static void
registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version)
{
  ...
}

static const struct wl_registry_listener registry_listener = {
  registry_handle_global
};

wl_registry_add_listener(display->registry, &registry_listener, display);

  위의 코드는 클라이언트에서 wl_registry 의 이벤트 핸들러를 등록하는 부분이다. 서버가 클라이언트에게 global 이벤트를 보내면 위의 registry_handle_global() 함수가 호출된다. 아래 코드는 이와 반대로 서버에서 wl_registry 의 리퀘스트 핸들러를 등록하는 부분이다. 클라이언트가 서버에게 bind 리퀘스트를 보내면 registry_bind() 함수가 호출된다.

static void registry_bind(struct wl_client *client, struct wl_resource *resource, uint32_t name, const char *interface, uint32_t version, uint32_t id)
{
  ...
}

static const struct wl_registry_interface registry_interface = {
  registry_bind
};

  이와 같은 방식으로 Wayland서버와 클라이언트는 메시지를 주고 받으면서 동작한다. (실제 Wayland와 Weston의 컴포지터와 클라이언트 소스를 보면 다양한 인터페이스들이 어떤 식으로 사용되는지 알 수 있다.)

 

 

  Wayland 기본 동작

 

  Wayland에서는 서버와 클라이언트가 기본적으로 하나의 소켓을 통해 여러 가지 인터페이스의 리퀘스트와 이벤트를 주고받는다. Wayland는 이를 편리하게 처리할 수 있도록 몇 가지 자료구조를 미리 정의해두고 있다. wl_connection 객체는 소켓을 통해 편리하게 메시지를 주고 받을 수 있도록 도와주는 객체이고, wl_object 객체는 각각의 인터페이스를 구분해주는 역할을 한다. 이들의 관계를 그림으로 간단히 표현하면 아래와 같다.

  위의 그림에서 왼쪽은 클라이언트이고, 오른쪽은 서버이다. 우선, 클라이언트가 서버로 소켓을 통해 접속을 요청하면 양쪽에 wl_connection 객체가 생성되고, 여기에 필요한 인터페이스를 처리하기 위한 여러 개의 wl_object 가 순서대로 등록된다. 기본적인 등록 과정이 마무리된 후, 만약 클라이언트가 새로운 서피스(surface) 를 만들기 위해 wl_compositor_interface 의 create_surface 리퀘스트를 서버에게 요청하고 싶다면, wl_connection 과 wl_compositor 객체를 이용해 간단히 리퀘스트를 전달할 수 있다. 반대로 서버가 클라이언트에게 지원 가능한 인터페이스 목록을 전달하고 싶다면, wl_registry_interface 의 global 이벤트를 해당 wl_resource 객체를 통해 전달할 수 있다.

Wayland의 가장 기본적인 인터페이스는 wl_display 와 wl_registry 이다. wl_display 는 클라이언트가 서버에 접속하는 순간 바로 생성되어 wl_registry 인터페이스 연결을 돕는 인터페이스이다. 그리고 wl_registry 는 컴포지터가 지원하는 인터페이스들(global)을 연결할 수 있도록 도와주는 인터페이스이다.

전체적인 동작 과정을 간단하게 요약하면 아래와 같다.

 

     • Wayland 서버는 자신이 지원하는 인터페이스들을 global 로 등록해둔다.

     • Wayland 서버는 소켓을 열고 대기한다.

     • Wayland 클라이언트는 소켓을 통해 서버로 접속한다. (소켓이 연결되고 나면 wl_display 인터페이스를 위한 wl_object 가 서버와 클라이언트에 생성된다.)

     • Wayland 클라이언트는 wl_display 인터페이스를 이용하여 wl_registry 인터페이스를 연결한다.

     • Wayland 서버는 wl_registry 인터페이스를 통해 global 이벤트를 전달한다. (컴포지터가 지원하는 인터페이스 목록 전달)

     • Wayland 클라이언트는 필요한 인터페이스들(wl_compositor_interface, wl_seat_interface 등등)을 연결하여 필요한 동작들을 수행한다.