爲了更好的學習wayland,我們可以先學習wayland中定義的一些數據結構.因爲貫穿wayland的所有東西都是基於這些數據結構.
- 首先介紹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裏面的數據發送出去即可.
- 然後介紹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),因爲這個對象肯定是第一個創建的。
- 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提供了接口來更好的操作這種鏈表。
- 客戶端真正的對象結構體
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的一些接口返回回來的。全部都是,無一例外。讀者如果不相信可以自己去找找,並且可以自己嘗試創建那些對象,肯定是會報錯的,因爲那些結構體都是不存在的,創建會編譯出錯。
- 服務器端真正的對象結構體
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++面向對象,看起來更容易。