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++面向對象,看起來更容易。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章