1. 程式人生 > >Wayland協議解析 一 Wayland中的資料結構解析

Wayland協議解析 一 Wayland中的資料結構解析

為了更好的學習wayland,我們可以先學習wayland中定義的一些資料結構.因為貫穿wayland的所有東西都是基於這些資料結構.

  1. 首先介紹wl_array

struct wl_array {

/** Array size */

size_t size;

/** Allocated space */

size_t alloc;

/** Array data */

void *data;

};

Wayland定義的陣列資料結構. 其中data儲存實際的資料,size儲存實際資料的大小,alloc儲存當前data總共分配的大小(malloc/realloc分配的大小)。

注意,alloc總是大於size,因為空間總要比儲存的資料大才行,另外當往數組裡面插入資料的時候,alloc不夠大了,

那麼就會以當前alloc大小翻倍的大小重新分配記憶體。因此在進行網路傳輸的時候,只需要把data裡面的資料傳送出去即可.

  1. 然後介紹wl_map

struct wl_map {

struct wl_array client_entries;

struct wl_array server_entries;

uint32_t side;

uint32_t free_list;

};

struct wl_array {

/** Array size */

size_t size;

/** Allocated space */

size_t alloc;

/** Array data */

void *data;

};

union map_entry {

uintptr_t next;

 void *data;

};

這個資料結構是wayland的核心,用來儲存程序間通訊的實際物件指標,並得到對於指標的ID,用於程序間傳遞。

wl_map結構體存放wayland客戶端和伺服器端對應的物件。其中:

client_entries: 用wl_array陣列儲存客戶端的物件。(這種情況server_entries不使用)

server_entries:用wl_array陣列儲存伺服器端的物件。(這種情況client_entries不使用)

side:表明當前map儲存的是客戶端還是伺服器端物件(通過這個變數,確定client_entries/server_entries裡面儲存有物件,並且這個變數只在客戶端初始化為WL_MAP_CLIENT_SIDE,在伺服器端初始化為WL_MAP_SERVER_SIDE)

free_list:這個變數用來記錄當前已經被刪除了的物件的存放位置,但是對這個位置做了個處理。((i << 1) | 1  : i代表下標, 也就是指標的最後一位置為1),

           然後,這個下標所指的位置的map_entry.next變數記錄著下一個被刪除的位置(直到為0,free_list初始值為0),形成連結串列

提示: map的節點是map_entry結構。data儲存實際的物件的地址。但是這個地址做了處理(原理上data是指標,編譯器為了4位元組對齊,最後兩位都是0,map利用了這兩位。把倒數第二位儲存flags的值(0/1),最後一位表示當前物件是否已經被刪除了(1表示刪除,0表示沒刪除)),map_entry是個聯合體,data成員儲存實際的物件指標,而next是在物件被刪除的時候,用來儲存下一個被刪除的物件的下標。

資料儲存關係: wl_map儲存兩個wl_array陣列, wl_array儲存map_entry聯合體的節點元素。

   flags: entry->next |= (flags & 0x1) << 1;

   deleted: map->free_list = (i << 1) | 1;

wayland協議實現基本決定了,建立物件都是客戶端發起的,流程是客戶端請求建立物件,通過wl_map_insert_new函式插入物件,並返回物件的ID(其實是下標),然後把ID傳遞到伺服器端,在伺服器端通過wl_map_insert_at函式把建立的物件插入到指定的下標(ID),這樣就建立了兩個物件的對應關係。只有一個物件是特殊的,就是wl_display.這個物件是寫死的下標(為1),因為這個物件肯定是第一個建立的。

  1. wl_list 連結串列

struct wl_list {

/** Previous list element */

struct wl_list *prev;

/** Next list element */

struct wl_list *next;

};

wayland實現的這種連結串列在很多優秀的程式碼裡面都有過,這種連結串列非常優秀,它可以儲存任何型別的元素。為什麼可以儲存任何型別的元素呢? 我舉個例子,你一定很好理解。

在中國有很多很多的家庭,如果想要把這些家庭給串聯起來,可以怎麼做呢?有個非常好的辦法就是,我給每一個家庭一個電話簿,電話簿裡面儲存兩個電話號碼,一個是這個家庭前面一家的號碼,另一個是後面一個家庭的號碼。所有家庭都是這樣,是不是就把他們的關係給串聯起來了?任何一個家庭都可以通過他家裡的電話簿聯絡到所有家庭。

怎麼樣? Wayland裡面的連結串列就是這個電話簿一樣的角色。

但是這裡有個問題,就是連結串列裡面儲存的都是電話簿,要怎麼把這個電話簿轉換成家庭呢? 這個就是C語言的語言特性。如果知道一個結構體成員的地址,就可以反推到這個結構體的地址。

#define wl_container_of(ptr, sample, member)                \

(__typeof__(sample))((char *)(ptr) -                                    \

                         offsetof(__typeof__(*sample), member))

offsetof 這個是C語言標準裡面提供的獲取成員偏移量的巨集,整個巨集就是提供成員變數的地址獲取到整個結構體的地址。有興趣的讀者可以多翻閱資料,也可以留言諮詢。

Wayland原始碼裡面大量使用了這個結構體,來儲存各種不同的結構體物件的連結串列。並且wayland提供了介面來更好的操作這種連結串列。

  1. 客戶端真正的物件結構體

struct wl_proxy {

struct wl_object object;

struct wl_display *display;

struct wl_event_queue *queue;

uint32_t flags;

int refcount;

void *user_data;

wl_dispatcher_func_t dispatcher;

uint32_t version;

};

在這裡我要告訴大家一個事實:

Wayland協議裡面的那些interface的物件在客戶端其實真正的結構體是wl_proxy,而那些結構體都是不存在的,只是一個宣告而已,根本不存在。如果讀者有看過wayland的原始碼,會發現一個問題,就是wayland協議裡面的那些interface物件,從來都不是程式設計師自己創建出來的,而是通過wayland的一些介面返回回來的。全部都是,無一例外。讀者如果不相信可以自己去找找,並且可以自己嘗試建立那些物件,肯定是會報錯的,因為那些結構體都是不存在的,建立會編譯出錯。

  1. 伺服器端真正的物件結構體

struct wl_resource {

struct wl_object object;

wl_resource_destroy_func_t destroy;

struct wl_list link;

struct wl_signal destroy_signal;

struct wl_client *client;

void *data;

};

和客戶端一樣,伺服器端所有的interface物件全部都是wl_resource結構體物件。

接下來我要總結一下這個結構體裡面都儲存了些什麼東西。看下圖:

從上圖中可知,所有的物件都有個wl_object成員來記錄它是屬於哪個interface的,並用implementation成員來儲存真正需要呼叫的函式指標,這個id就是用來進行客戶端和伺服器端物件對映的關鍵。

然後wl_interface就是wayland協議裡面的interface,一個inerface有名字屬性,還有版本號,版本號是非常有用的東西。而interface裡面的request和event就是由wl_message結構體儲存,表示一個函式。wl_message結構體包含函式名,以及函式的引數(所有的引數都儲存在signature變數裡面,通過字元的方式表示),方法如下:(wayland原始碼裡面的解釋)

/**

 * Protocol message signature

 *

 * A wl_message describes the signature of an actual protocol message, such as a

 * request or event, that adheres to the Wayland protocol wire format. The

 * protocol implementation uses a wl_message within its demarshal machinery for

 * decoding messages between a compositor and its clients. In a sense, a

 * wl_message is to a protocol message like a class is to an object.

 *

 * The `name` of a wl_message is the name of the corresponding protocol message.

 *

 * The `signature` is an ordered list of symbols representing the data types

 * of message arguments and, optionally, a protocol version and indicators for

 * nullability. A leading integer in the `signature` indicates the _since_

 * version of the protocol message. A `?` preceding a data type symbol indicates

 * that the following argument type is nullable. While it is a protocol violation

 * to send messages with non-nullable arguments set to `NULL`, event handlers in

 * clients might still get called with non-nullable object arguments set to

 * `NULL`. This can happen when the client destroyed the object being used as

 * argument on its side and an event referencing that object was sent before the

 * server knew about its destruction. As this race cannot be prevented, clients

 * should - as a general rule - program their event handlers such that they can

 * handle object arguments declared non-nullable being `NULL` gracefully.

 *

 * When no arguments accompany a message, `signature` is an empty string.

 *

 * Symbols:

 *

 * * `i`: int

 * * `u`: uint

 * * `f`: fixed

 * * `s`: string

 * * `o`: object

 * * `n`: new_id

 * * `a`: array

 * * `h`: fd

 * * `?`: following argument is nullable

 *

 * While demarshaling primitive arguments is straightforward, when demarshaling

 * messages containing `object` or `new_id` arguments, the protocol

 * implementation often must determine the type of the object. The `types` of a

 * wl_message is an array of wl_interface references that correspond to `o` and

 * `n` arguments in `signature`, with `NULL` placeholders for arguments with

 * non-object types.

 *

 * Consider the protocol event wl_display `delete_id` that has a single `uint`

 * argument. The wl_message is:

 *

 * \code

 * { "delete_id", "u", [NULL] }

 * \endcode

 *

 * Here, the message `name` is `"delete_id"`, the `signature` is `"u"`, and the

 * argument `types` is `[NULL]`, indicating that the `uint` argument has no

 * corresponding wl_interface since it is a primitive argument.

 *

 * In contrast, consider a `wl_foo` interface supporting protocol request `bar`

 * that has existed since version 2, and has two arguments: a `uint` and an

 * object of type `wl_baz_interface` that may be `NULL`. Such a `wl_message`

 * might be:

 *

 * \code

 * { "bar", "2u?o", [NULL, &wl_baz_interface] }

 * \endcode

 *

 * Here, the message `name` is `"bar"`, and the `signature` is `"2u?o"`. Notice

 * how the `2` indicates the protocol version, the `u` indicates the first

 * argument type is `uint`, and the `?o` indicates that the second argument

 * is an object that may be `NULL`. Lastly, the argument `types` array indicates

 * that no wl_interface corresponds to the first argument, while the type

 * `wl_baz_interface` corresponds to the second argument.

 **/

我簡單解釋一下:wayland引數不是就那幾種嗎?這個地方就把這幾個引數再縮短標記,用一個字元來表示,但是有個特殊的‘?’,它表示後面這個引數可以為空。在引數的最前面有一個數字,這個數字代表著版本號。但是呢,引數中有可能是interface的物件,那麼就必須指明到底是哪個interface的物件,因此wl_message的最後一個成員types,就用一個數組的方式記錄這個引數是哪個interface的物件,如果是非interface的引數就為空。

最後,我們來得出,wayland解析協議檔案產生了些什麼,其實,看上面的圖片就能知道很多東西。看下面的檔案:

  • 產生一個協議原始檔, 裡面儲存了協議檔案裡面所有的interface轉換而成的wl_interface結構體變數,包括wl_message結構體記錄的request和event函式。
  • 產生一個客戶端使用的標頭檔案,裡面封裝了wl_proxy轉換成指定interface假宣告的結構體操作的一些介面函式。以及request函式的實現,但是這個實現只是把請求傳送到伺服器端而已,實際呼叫在伺服器端進行。最後,檔案裡面還封裝了一個回撥函式的結構體,成員就是所有的event函式指標,需要客戶端去實現,並設定到interface的物件裡面,該檔案生成了這個設定的介面,實際就是填充到wl_object結構體的implementation變數中。
  • 產生一個伺服器端標頭檔案,裡面基本和客戶端一樣的組成。只是結構體是wl_resource,函式結構體的成員是所有request的函式指標。以及所有的event的實現。

也就是說,客戶端需要程式設計師自己實現事件(event),伺服器端需要程式設計師實現請求(request)。

好了,wayland協議的解析差不多都說完了,有什麼不清楚的,可以留言諮詢。接下來就開始講述wayland協議解析之後的工作原理。

對了,感興趣的讀者可以去看看QtWayland裡面解析wayland協議的工具原始碼,它把wayland協議再做了一層封裝成了C++面向物件,看起來更容易。