天天看點

Wayland協定解析 二 Wayland中的資料結構解析

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

  1. 首先介紹wl_array

struct wl_array {

size_t size;

size_t alloc;

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 {

size_t size;

size_t alloc;

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 {

struct wl_list *prev;

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結構體對象。

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

Wayland協定解析 二 Wayland中的資料結構解析

從上圖中可知,所有的對象都有個wl_object成員來記錄它是屬于哪個interface的,并用implementation成員來儲存真正需要調用的函數指針,這個id就是用來進行用戶端和伺服器端對象映射的關鍵。

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

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

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

Wayland協定解析 二 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++面向對象,看起來更容易。