1. 程式人生 > >Wayland協議解析 一 什麼是Wayland

Wayland協議解析 一 什麼是Wayland

學習Wayland協議已經好幾個月了,期間也查閱了不少資料,奈何目前對於它的資料並不是很多,對他原理以及解析更是寥寥無幾,其中有一篇非常不錯,給個連結: https://blog.csdn.net/jinzhuojun/article/details/40264449, 需要的可以去檢視檢視.本文的目的是更加通俗詳細的解釋wayland協議.好了,廢話不多說,下面開始講述wayland協議.

什麼是wayland

關於什麼是wayland網上有很多解釋,我這裡就不在贅述,而是以我自己的理解來淺顯易懂的給大家解釋一下.

Wayland協議,是一個定義怎麼來進行視窗管理的協議. 什麼叫視窗管理呢? 我們知道在windows作業系統上有很多很多的應用程式,在啟動該應用程式之後,一般會彈出一個該應用程式的視窗.這個視窗是怎麼產生的? 太深入理解沒必要,我們只需要知道,我們在建立視窗的時候都是必須呼叫指定的接口才可以建立,是吧? 這個介面就相當於視窗管理的協議提供的介面. 各種形形色色的介面在不同的作業系統上是不一樣的,因此如果我們要建立視窗,在不同的作業系統上就需要不同的程式碼,因此,跨平臺就是件很麻煩的事情. 然而,在這種情況下,湧現出了大量跨平臺的框架,他們把和作業系統相關的建立視窗的介面封裝了起來,然後通過提供統一的介面給使用者,這樣,使用者就可以使用相同的程式碼在不同的作業系統上創建出視窗, 比如Qt, 因為作者本身也是在使用Qt的,因此,此處只列出Qt.

上面說了,視窗在不同作業系統上呼叫不同的介面去建立視窗,其實是不準確的,因為,其實視窗管理理論上是和作業系統不強繫結的關係.只是有些作業系統把視窗管理給繫結到核心了,比如windows(這裡我也是看資料瞭解的,至於正確性,我不做保證,但是現象還是很明確的).但是也有明確的例子證明,視窗管理和作業系統是不繫結的,比如高版本的ubuntu(18.04),該系統提供了兩種視窗管理的方式,一種就是本文的wayland,另一種是原有的x11,但由於支援wayland協議的軟體比較少,現在還比較難以流行起來。

為什麼需要視窗管理呢? 我們都知道,那個視窗什麼的都是通過顯示器顯示給我們看的,然後通過滑鼠鍵盤等外設來操作的,但這些東西都只有一份(當然,你有多個顯示器多個滑鼠鍵盤什麼的,也是可以的,但是總不能同時操作吧,顯示屏多個的問題自己想想怎麼反駁吧),如果現在有兩個程序都需要顯示出來,他們都去爭奪這個顯示屏去繪製,最終出來的不就花屏了?因為這兩個程序是不知道對方的存在,不知道對方現在佔用了哪裡,因此,需要一個東西來協調才能讓多個程序完美的同時顯示出來,這個就是視窗管理的作用。

說實話,就上面講的這些東西,都是在我理解wayland是什麼之後才徹底明白的,因此,也希望讀者能夠通過本文對wayland有所瞭解,並且開始理解視窗管理的本質。

好了,開始說說wayland。

上面我說了wayland是一種視窗管理的協議,它和x11協議是同等的。那麼,是什麼樣的協議呢?看下面

<interface name="wl_buffer" version="1">

    <description summary="content for a wl_surface">

      A buffer provides the content for a wl_surface. Buffers are

      created through factory interfaces such as wl_drm, wl_shm or

      similar. It has a width and a height and can be attached to a

      wl_surface, but the mechanism by which a client provides and

      updates the contents is defined by the buffer factory interface.

    </description>

    <request name="destroy" type="destructor">

      <description summary="destroy a buffer">

         Destroy a buffer. If and how you need to release the backing

         storage is defined by the buffer factory interface.

         For possible side-effects to a surface, see wl_surface.attach.

      </description>

    </request>

    <event name="release">

      <description summary="compositor releases buffer">

         Sent when this wl_buffer is no longer used by the compositor.

         The client is now free to reuse or destroy this buffer and its

         backing storage.

         If a client receives a release event before the frame callback

         requested in the same wl_surface.commit that attaches this

         wl_buffer to a surface, then the client is immediately free to

         reuse the buffer and its backing storage, and does not need a

         second buffer for the next surface content update. Typically

         this is possible, when the compositor maintains a copy of the

         wl_surface contents, e.g. as a GL texture. This is an important

         optimization for GL(ES) compositors with wl_shm clients.

      </description>

    </event>

</interface>

上面的片段是從wayland.xml裡面截取出來的,以此來介紹. Wayland的核心協議全部在wayland.xml這個xml檔案裡面.也有很多wayland的擴充套件協議,是為了實現一些其他目的而新增,當然,讀者如果學了該協議也可以自己新增一些協議內容. 為了實現一些功能,這個是非常有必要的

解析上面的xml可以分為以下標籤:

  1. Interface (介面類)
  2. Description (描述, 可能是介面的描述,請求的描述,事件的描述等)
  3. Request (請求,客戶端主動呼叫的介面, 實際就是個函式)
  4. Event (事件,伺服器端主動呼叫的介面, 實際就是個函式)

如果,總結整個wayland.xml協議檔案還有以下標籤:

  1. Arg (引數, 請求或者事件的引數)
  2. Enum (列舉, 用來更形象的代表一些值)
  3. Entry (列舉的成員)

這些是什麼東西? 如果第一次接觸肯定不知道是什麼了,是吧! 我這裡就給大家解釋一下.

Wayland協議就是通過這樣一個檔案描述一個客戶端和伺服器端相互知道的呼叫介面. 轉換成C++的方式來理解這些你們肯定會很容易的.

首先,這些標籤是有包含關係的, 一個wayland協議xml檔案裡包含一個或多個interface, 一個interface裡面可能包含一個或多個request和event, 有些可能包含enum,也可能沒有,根據實際情況.

其中最簡單的enum最終就是直接轉換成C語言的列舉型別,Entry即是它的成員。

現在結合上面的定義來解釋: wayland.xml協議檔案裡面定義一個類wl_buffer,該類是幹什麼的,可以通過description標籤的描述知道.然後,該類有兩個成員函式,一個是request(destroy),還有一個event(release) 這兩個函式又有個description標籤的描述該函式是用來幹什麼的. 但是擷取的這個片段兩個成員函式都沒有引數,所以沒有arg標籤. 但是我們知道,一個函式定義的組成一般都有引數, 這個arg標籤就是用來描述引數的,所以有些request和event包含arg標籤, 用來描述他的引數. 然後我們知道,一個引數我們肯定得知道引數的名字,型別, 因此arg標籤有一些屬性是用來表示這些的,看下面:

<request name="attach">

<arg name="buffer" type="object" interface="wl_buffer" allow-null="true" summary="buffer of surface contents"/>

<arg name="x" type="int" summary="surface-local x coordinate"/>

<arg name="y" type="int" summary="surface-local y coordinate"/>

</request>

一個引數可能有5個屬性:

  1. Name (引數的名字)
  2. Type (引數的型別)
  3. Interface (引數的型別)
  4. Allow-null (是否可以為空)
  5. Summary (描述引數是做什麼的)

一個引數有幾個要素, 名字和型別, 然後也可能是一個類的指標(interface就是描述是指標型別)

通過上面的解釋來分析buffer這個引數:

buffer 是一個wl_buffer型別的指標引數,名字是buffer,並且這個指標可以為空, 對這個指標引數的描述是” buffer of surface contents”.

X是一個int型別的引數,名字是x, 對這個引數的描述是” surface-local x coordinate

Y是一個int型別的引數,名字是y, 對這個引數的描述是” surface-local y coordinate”

最後把上面的函式翻譯成: attach(wl_buffer *buffer, int x, int y), 這樣看好像有點問題,有沒有?有沒有?有沒有?哈哈哈,確實是。一個函式必須有返回值,但是wayland協議裡面沒有描述返回值的,因此,大部分函式的返回值都是void,這裡說了,只是大部分,還有小部分怎麼描述呢?這個就是有一種arg型別是new_id的會作為函式的返回值型別。因此上面的函式最終會轉換成:

void wl_surface_attach(struct wl_surface *wl_surface, struct wl_buffer *buffer, int32_t x, int32_t y)

然後,是不是感覺又不對? 咋多了個引數?

是這樣的,wayland協議在翻譯協議檔案的時候,會給所有的函式新增一個預設的引數,就是它所在的interface,作為第一個引數傳入進去,就相當於給一個類成員函式傳遞this指標一樣。告訴是哪個具體物件呼叫的這個介面。

好了,到這裡該總結性的介紹下wayland的引數型別到底有哪些,不然你肯定不能自己翻譯出來的。首先,我必須說明一點,wayland是由C語言實現的,因此,最終協議檔案肯定是翻譯成c語言的語法。只是,這個協議其實理解成C++面向物件更通俗易懂。如果你會面向物件語言。反正我是,嘿嘿。好了,看下面:

union wl_argument {

       int32_t i;           /**< `int`    */

       uint32_t u;         /**< `uint`   */

       wl_fixed_t f;        /**< `fixed`  */

       const char *s;       /**< `string` */

       struct wl_object *o;  /**< `object` */

       uint32_t n;         /**< `new_id` */

       struct wl_array *a;   /**< `array`  */

       int32_t h;          /**< `fd`     */

};

上面這個結構體是從wayland原始碼裡面截取出來的,由此可以看出來,wayland協議裡面的引數型別有以上幾種。

  1. 其中int、uint比較簡單,就直接轉換成32位的有符號整數和無符號整數。
  2. Fixed型別就相當於是一個double和int的相容型別,wayland有一段對它的解釋:

/**

 * Fixed-point number

 *

 * A `wl_fixed_t` is a 24.8 signed fixed-point number with a sign bit, 23 bits

 * of integer precision and 8 bits of decimal precision. Consider `wl_fixed_t`

 * as an opaque struct with methods that facilitate conversion to and from

 * `double` and `int` types.

 */

然後實際定義為: typedef int32_t wl_fixed_t; ,並且wayland提供了幾個對這種型別轉換成int或者double的介面wl_fixed_to_double、wl_fixed_from_double、wl_fixed_to_int、wl_fixed_from_int。

  1. String型別很簡單就是char *型別
  2. Object型別比較特殊,他表示是一種基類指標,可以接受儲存所有interface的指標,因此,object型別的引數會多一個描述interface的屬性,指明是哪一個interface,但是C語言哪裡來的基類指標這回事?在C語言裡,只有結構體,結構體的第一個成員可以看做是它的基類,不信你試試?
  3. new_id型別就更特殊了。前面我說了,這個型別是會作為函式的返回值的,但是說的不準確,這個型別的引數在客戶端被解釋的時候是返回值,被返回回來給呼叫者使用,但是在伺服器端,這個引數被轉換成uint32_t型別傳遞過來一個id,具體為什麼這麼做,我們後面再說。所以,這個也算是個引數。
  4. array型別,這個是wayland定義的一種資料結構,具體如下:

struct wl_array {

/** Array size */

size_t size;

/** Allocated space */

size_t alloc;

/** Array data */

void *data;

};

並且wayland提供了一系列對該資料結構操作的介面。

  1. fd型別,這個型別比較簡單,就是表面這個引數是個檔案描述符,為什麼要特別指出呢?因為底層在傳遞這種引數的時候,和其他的方式不一樣,具體可以參考linux 程序間檔案描述符傳遞方法。另外需要強調,檔案描述符在程序間傳遞之後,大多數情況下,是不一樣的值。

好了,所有的wayland可傳遞的引數都說完了,你們能不能總結出來些什麼?

是的,程序間傳遞基本型別還好,傳遞檔案描述符有系統的支援也還好,但是傳遞一個獨屬於一個程序的指標,這不合理了吧???哈哈哈,確實是的,因此wayland協議底層根本不是傳遞的指標,而是傳遞的一個物件的id,就是說,wayland的所有的interface物件在客戶端和伺服器端都有個對應關係,通過一個id來查詢對應的物件。因此,程序間傳遞的時候只需要傳遞這個id,對應程序就知道是哪個具體物件了。具體的原理後面再說。這個id就和new_id型別的引數類似。

Wayland有一個專門解析wayland協議檔案的工具,原始碼是scanner.c, 就是把wayland協議檔案的內容轉換成C語言的原始檔,分為以下:

注意: 其中有幾個interface是wayland特殊處理的,規則和一般的有點不一樣,被wayland特殊處理(wl_display、wl_registry)

因此,如果想更多的瞭解wayland協議,可以嘗試閱讀此原始碼. 另外,Qt也實現了一個解析wayland協議的工具, 但是Qtwayland把wayland協議解釋為C++語法, 就像我之前列舉那樣interface就解釋成了一個類. 更多的可以去檢視Qtwayland的原始碼.

我大篇幅的介紹其實是在說明怎麼去解析wayland協議,但是我前面說了,wayland協議是一個視窗管理的協議, 因此,協議內容才是最關鍵的. 協議內容裡面給出了怎麼建立視窗的標準介面,對視窗的操作的標準介面,以及一些輸入(鍵盤滑鼠)獲取的標準介面.只有通過這些標準介面建立的視窗管理器,就滿足wayland協議,即使客戶端不知道伺服器端的存在,也可以根據這些標準來實現.

到此,wayland的協議基本就描述完了。有什麼不懂的,可以留言諮詢。