wl_event_loop表明主消息循環,wl_event_loop_dispatch()的大多數時間會經過epoll_wait()等待在wl_event_loop的epoll_fd上。epoll是相似於select, poll的機制,可以讓調用者等待在一坨fd上,直到當中有fd可讀、可寫或錯誤。這個event loop和當中的epoll_fd是Compositor在wl_display_create() -> wl_event_loop_create()時建立的。
wl_event_source表明wl_event_loop中等待的事件源。它有多種類型,比方wl_event_source_fd, wl_event_source_timer和wl_event_source_signal。它們分別表明由socket fd, timerfd和signalfd觸發的事件源。wl_event_source_idle比較特殊,當消息循環處理完那些epoll_fd上等到的事件後,在下一次堵塞在epoll_wait()上前,會先處理這個idle list裏的事件。比方有Client更新graphic buffer後會調用weston_output_schedule_repaint() -> wl_event_loop_add_idle(),就是往這個idle list里加一個消息。wl_event_source_fd的建立爲例,在Client鏈接到Server時,Server會調用wl_client_create() -> wl_event_loop_add_fd()->add_source()將之增長到display的loop上,其處理回調函數爲wl_client_connection_data(),意味着當主消息循環在這個Client相應的socket上讀到數據時,就會調用wl_client_connection_data()進行處理。
1. wl_connection_flush()將當前out buffer中的數據經過socket發往Server端。這些數據是以前在wl_connection_write()中寫入的。
2. 經過poll()在socket上等待數據,並經過read_events()將這些數據處理生成函數閉包結構wl_closure,而後放到display的wl_event_queue.event_list事件列表中。wl_closure可以看做是一個函數調用實例,裏面包括了一個函數調用需要的所有信息。
3. dispatch_queue()->dispatch_event()用於處理前面加入到隊列的事件。這裏就是把隊列中的wl_closure拿出來生成trampoline後進行調用。
術語上,Wayland中把Client發給Server的跨進程函數調用稱爲request,反方向的跨進程函數調用稱爲event。本質上,它們處理的方式是相似的。要讓兩個進程經過socket進行函數調用,首先需要將調用抽象成數據流的形式。函數的接口部分是同一時候連接到Client和Server端的庫中的,當中包括了對象所支持的request和event的函數簽名。所以這部分不用傳輸,僅僅要傳輸目標對象id,方法id和參數列表這些信息就可以了。這些信息會經過wl_closure_marshal()寫入wl_closure結構,再由serialize_closure()變成數據流。等到了目標進程後,會從數據流經過wl_connection_demarshal()轉回wl_closure。這個過程相似於Android中的Parcel機制。那麼問題來了,參數中的整形,字符串什麼的都好搞,拷貝便可。但假設參數中包括對象,咱們不能把整個對象拷貝過去,也不能傳引用過去。那麼需要一種機制來做同一對象在Server和Client端的映射,這是經過wl_map實現的。wl_map在Client和Server端各有一個,它們分別存了wl_proxy和wl_resource的數組,且是一一相應的。這些對象在這個數組中的索引做爲它們的id。這樣,參數中的對象僅僅要傳id,這個id被傳到目的地後會經過查找這個wl_map表來獲得本地相應的對象。在功能上相似於Android中的BpXXX和BnXXX。wl_proxy和wl_resource都包括wl_object對象。這個wl_object和麪向對象語言裏的對象概念相似,它有interface成員描寫敘述了這個對象所實現的接口,implementation是這些接口的實現函數的函數指針數組,id就是在wl_map結構裏數組中的索引。前面所說的Client綁定Server端資源的過程就是在Client端建立wl_proxy,在Server端建立wl_resource。而後Client就可以經過wl_proxy調用Server端相應wl_resource的request,Server端就可以經過wl_resource調用Client端相應wl_proxy的event。這個映射步驟例如如下圖所看到的(以wl_registry爲例):
與Android不一樣的是,Android中請求到達Server端,調用時需要在目標對象中有一段Stub來生成調用的上下文。而Wayland中,這是由libffi完畢的。
Wayland核心協議是經過protocol/wayland.xml這個文件定義的。它經過wayland_scanner這個程序掃描後會生成wayland-protocol.c, wayland-client-protocol.h和wayland-server-protocol.h三個文件。wayland-client-protocol.h是給Client用的;wayland-server-protocol.h是給Server用的; wayland-protocol.c描寫敘述了接口,Client和Server都會用。這裏以wl_display的get_registry()這個request爲例,分析下跨進程的過程調用是怎樣實現的。
首先在wayland.xml中申明wl_display有get_registry這個request:
54 <request name="get_registry">
55 <description summary="get global registry object">
56 This request creates a registry object that allows the client
57 to list and bind the global objects available from the
58 compositor.
59 </description>
60 <arg name="registry" type="new_id" interface="wl_registry"/>
61 </request>
這裏的參數類型是new_id,說明需要新建一個代理對象。其餘的如object表明一個對象,fd表明表明文件描寫敘述符等。
wayland-protocol.c中描寫敘述了wl_display這個對象的request和event信息,當中包括了它們的函數簽名。get_registry是request中的一項。
147 static const struct wl_message wl_display_requests[] = {
148 { "sync", "n", types + 8 },
149 { "get_registry", "n", types + 9 },
150 };
151
152 static const struct wl_message wl_display_events[] = {
153 { "error", "ous", types + 0 },
154 { "delete_id", "u", types + 0 },
155 };
156
157 WL_EXPORT const struct wl_interface wl_display_interface = {
158 "wl_display", 1,
159 2, wl_display_requests,
160 2, wl_display_events,
161 };
wayland-server-protocol.h中:
115 struct wl_display_interface {
...
143 void (*get_registry)(struct wl_client *client,
144 struct wl_resource *resource,
145 uint32_t registry);
這個聲明是讓Server端定義implementation中的實現函數列表用的,如:
761 static const struct wl_display_interface display_interface = {
762 display_sync,
763 display_get_registry
764 };
wayland-client-protocol.h中:
184 static inline struct wl_registry *
185 wl_display_get_registry(struct wl_display *wl_display)
186 {
187 struct wl_proxy *registry;
188
189 registry = wl_proxy_marshal_constructor((struct wl_proxy *) wl_display,
190 WL_DISPLAY_GET_REGISTRY, &wl_registry_interface, NULL);
191
192 return (struct wl_registry *) registry;
193 }
這是給Client端用來發起request的。當client調用wl_display_get_registry(),由於要返回代理對象,因此調用wl_proxy_mashal_constructor()。返回的wl_registry是一個代理對象。
wl_display_get_registry()
wl_proxy_marshal_constructor()
wl_argument_from_va_list() // 將上面傳來的參數按wl_display_interface->methods[WL_DISPLAY_GET_REGISTRY]中籤名描寫敘述的類型放到wl_argument數組中。
wl_proxy_marshal_array_constructor()
new_proxy = create_outgoing_proxy() // 因爲get_registry()的request參數中有new_id類型,因此要建立一個代理對象。
proxy_create() //建立wl_proxy。設置interface等信息,而後將該wl_proxy插入到display->objects的wl_map中,返回值爲id,事實上就是在wl_map中數組中的索引值。這個值是會被髮到Server端的,這樣Server端就可以在Server端的wl_map數組的一樣索引值的位置插入對應的wl_resource。這樣邏輯上,就建立了wl_proxy和wl_resource的映射關係。之後,Client和Server間要相互引用對象僅僅要傳這個id就可以了。
closure = wl_closure_marshal() //建立wl_closure並依據前面的參數列表初始化。先將前面生成的wl_argument數組複製到wl_closure的args成員中。而後依據類型作調整,如將wl_proxy的對象指針改成id,因爲傳個本地指針到還有一個進程是沒意義的。
wl_closure_send() // 發送前面生成的wl_closure。
copy_fds_to_connection() // 將參數中的fd放到專門的fd out buffer中。因爲它們在發送時是要特殊處理的。
serialize_closure() //將wl_closure轉化爲byte stream。像類型爲object的參數會轉化爲id。
wl_connection_write() // 放到connection的out buffer,準備經過socket發送。
到這裏本地的wl_registry代理對象建立完畢,並且準備向Server發出request。當下次運行到wl_display_dispatch_queue()時,會調用wl_connection_flush()把connection中out buffer的request經過socket發出去。固然,在往out buffer寫時發現滿了也會調用wl_connection_flush()往socket發數據。
到了Server端,前面提到會調用處理函數wl_client_connection_data()進行處理:
wl_client_connection_data()
wl_connection_flush() //向Client發送數據。
wl_connection_read() //從Client接收處理。
while (...) // 循環處理從socket中讀到的數據。
wl_connection_copy() // 每次從緩衝中讀入64字節。它至關於一個request的header,後面會跟參數數據。當中前4個字節表明是向哪一個對象發出request的。後面4個字節包括opcode(表明是這個object的哪一個request),及後面參數的長度。
wl_map_lookup() // 在wl_map中查找目標資源對象wl_resource。其成員object中有該對象的接口和實現列表。結合前面的opcode就可以獲得對應的request的描寫敘述,用wl_message表示。如 { "get_registry", "n", types + 9 }。
wl_connection_demarshal() // 依據interface中的函數簽名信息生成函數閉包wl_closure。主要是經過wl_message中對參數的描寫敘述從緩衝區中把參數讀到wl_closure的args成員中。wl_closure的args成員是wl_argument的數組。因爲這裏沒法預先知道參數類型,因此wl_argument是個union。
wl_closure_lookup_objects() // wl_closure中的參數中假設有object的話,現在還僅僅有id號。這步會經過wl_map把id號轉爲wl_object。
wl_closure_invoke() //使用libffi庫生成trampoline code,跳過去運行。
在這個場景下,由於以前在bind_display()中把client->display_resource的implementation設爲:
761 static const struct wl_display_interface display_interface = {
762 display_sync,
763 display_get_registry
764 };
因此接下來會調用到display_get_registry()。這個函數裏會建立wl_registry相應的wl_resource對象。建立好後會放到display->registry_resource_list中。前面提到過,這個registry資源邏輯上的做用是Client放在Server端的Observer,它用於監聽Server端有哪些Global對象(Service服務)。display_get_registry()函數接下去會對於每一個Global對象向該Client新建的registry發送事件。另外在有Global對象建立和銷燬時(wl_global_create()和wl_global_destroy()),Server會向所有的registry發送事件進行通知。所以,Global對象可以理解爲可動態載入的Service。
那麼,這些Global對象詳細都是些什麼呢?爲了故事的完整性,這裏就插播段題外話。Server端的Compositor在啓動時一般會註冊一些Global對象,邏輯上事實上就是一些服務。經過Wayland提供的wl_global_create()加入:
wl_global_create()
global->name = display->id++; // Global對象的id號。
global->interface = interface;
wl_list_insert(display->global_list.prev, &global->link); // display->global_list保存了Global對象的列表。
wl_list_for_each(resource, &display->registry_resource_list, link) // 向以前註冊過的registry對象發送這個新建立Global對象的event。
wl_resource_post_event(resource, WL_REGISTRY_GLOBAL, global->name, global->interface->name, global->version);
以wl_compositor這個Global對象爲例, Server端調用wl_global_create(display, &wl_compositor_interface, 3, ec, compositor_bind)。而後當Client端調用wl_display_get_registry()時,Server端的display_get_registry()會對每個Global對象向Client發送global事件,所以Server端有幾個Global對象就會發幾個event。Client收到event後調用先前註冊的回調registry_handle_global()。依據interface name推斷當前發來的是哪個,而後調用wl_reigistry_bind(..., &wl_compositor_interface,..)綁定資源,同一時候建立本地代理對象。接着Server端對應地調用registry_bind(),當中會調用先前在wl_global_create()中註冊的回調函數,即compositor_bind()。接着通過wl_resource_create(), wl_resource_set_implementation()等建立wl_resource對象。也就是說,對於同一個Global對象,每有Client綁定一次,就會建立一個wl_resource對象。換句話說,對於Server來講,每一個Client有一個命名空間,同一個Global對象在每一個Client命名空間的wl_resource是不同的。這樣,對於一個Global對象(Service服務),在Client端建立了wl_proxy,在Server端建立了wl_resource,它們就這樣綁定起來了。wl_proxy.object包括了event的處理函數,這是對Server端暴露的接口,而wl_resource.object包括了request的處理函數,這是對Client暴露的接口。
回到故事主線上,前面是從Client端調用Server端對象的request的流程,從Server端向Client端對象發送event並調用其回調函數的過程也是相似的。如下以display_get_registry()中向Client端發送global事件爲例分析下流程。Server端經過wl_resource_post_event()來向Client發送event。
wl_resource_post_event()
wl_resource_post_event_array()
wl_closure_marshal() // 封裝成wl_closure,當中會轉化object等對象。
wl_closure_send()
copy_fds_to_connection()
serialize_closure() // 將closure序列化成數據流,因爲將要放到socket上傳輸。
wl_connection_write()
這樣event就被放到connection的out buffer中,等待從socket上發送。那麼,Client是怎麼讀取和處理這些event呢?首先Client端需要監聽這個wl_proxy,這是經過調用wl_registry_add_listener()->wl_proxy_add_listener()設置的。該函數的參數中包括了這個event的處理函數列表registry_listener,它相應的接口在前面調用wl_display_get_registry()時已設置成wl_registry_interface。wl_registry_interface是在前面依據wayland.xml本身主動生成的一部分。這裏體現了event與request的一點細微區別,request是Server端都要處理的,而event在Client可以選擇不監聽。
而後在Client的主循環中會調用wl_display_dispatch_queue()來處理收到的event和發出out buffer中的request:
wl_display_dispatch_queue()
dispatch_queue()
wl_connection_flush()
read_events() // 從connection的in buffer中讀出數據,轉爲wl_closure,插入到queue->event_list,等待興許處理。
wl_connection_read()
queue_event() //這塊處理有點像Server端的wl_client_connection_data(),差異在於這裏用的是wl_reigstry_interface的events列表而不是methods列表。
wl_connection_copy()
wl_map_lookup() // 查找目標代理對象wl_proxy。
wl_connection_demarshal() // 從connection的緩衝區中讀入數據,結合函數簽名生成wl_closure。
create_proxies()
wl_closure_lookup_objects()
dispatch_queue() // 將前面插入到queue其中的event(wl_closure)依次拿出來處理。
dispatch_event(queue) // display->display_queue->event_list的每一個元素是一個wl_closure,表明一個函數調用實例,最後經過wl_closure_invoke()進行調用。
wl_closure_invoke()
這樣該event的對應處理回調函數就被調用了,在這個場景中,即registry_handle_global()。下圖簡單地描繪了整個流程。