2020-11-25 wayland 進(jìn)程間函數(shù)調(diào)用

桌面環(huán)境做了好幾年,對 X 和 wayland 比較感興趣,但是一直沒有機(jī)會深入去看。正好我要離職了,在現(xiàn)在這個單位,北京上海加起來有將近十年了,最年輕的十年,也收獲了不少。最近工作交接完沒啥事,就抓著 wayland 代碼看了看,整體概念大概了解一點(diǎn),先總結(jié)一下吧,免得又忘了。這篇文章主要目的是梳理 server 端和 client 端的函數(shù)調(diào)用是怎么實現(xiàn)的?并且拿 display 和 registry 為例做簡單地說明。

wayland 核心協(xié)議定義了許多全局對象,包括:display、registry、compositor、output、seat、surface、buffer 等等。客戶端連接到服務(wù)端后,會創(chuàng)建一個客戶端的可用對象,一般叫 proxy,服務(wù)端對應(yīng)會創(chuàng)建一個 resource,這倆是對應(yīng)的,實現(xiàn) client 端對 server 端的資源綁定。

client 和 server 一方面各自在消息循環(huán)上等待數(shù)據(jù)(socket 上的數(shù)據(jù)),拿到數(shù)據(jù)經(jīng)過反序列化后生成本地的函數(shù)掉用;另一方面,將本地的調(diào)用請求封裝序列化后,通過 socket 發(fā)出去。

wayland 協(xié)議

核心協(xié)議通過 xml 文件來定義,wayland-scanner 程序掃描并生成一個 wayland-protocol.c 和兩個頭文件,分別給服務(wù)端程序和客戶端程序使用。
以 display 和 registry 為例,在生成的 wayland-protocol.c 中,display 的簽名:

static const struct wl_message wl_display_requests[] = {
    { "sync", "n", wayland_types + 8 }, // wl_callback_interface
    { "get_registry", "n", wayland_types + 9 }, //wl_registry_interface
};

static const struct wl_message wl_display_events[] = {
    { "error", "ous", wayland_types + 0 },
    { "delete_id", "u", wayland_types + 0 },
};

WL_EXPORT const struct wl_interface wl_display_interface = {
    "wl_display", 1,
    2, wl_display_requests,
    2, wl_display_events,
};

wl_message 的結(jié)構(gòu):

 struct wl_message {
    /** Message name */
    const char *name;
    /** Message signature */
    const char *signature;
    /** Object argument interfaces */
    const struct wl_interface **types;
};

{ "get_registry", "n", wayland_types + 9 } 的這個 n 表示 new_id,也就是說客戶端在發(fā)出 get_registry 請求的時候會傳遞一個 wl_interface * 的參數(shù),對應(yīng)為這個參數(shù)創(chuàng)建一個新的對象。比如 wl_display_get_registry 返回 wl_registry 對象,這個對象就是 registry 在客戶端的代理對象,這樣創(chuàng)建出來的。registry 對象的相關(guān)簽名如下:

static const struct wl_message wl_registry_requests[] = {
    { "bind", "usun", wayland_types + 0 },
};

static const struct wl_message wl_registry_events[] = {
    { "global", "usu", wayland_types + 0 },
    { "global_remove", "u", wayland_types + 0 },
};

WL_EXPORT const struct wl_interface wl_registry_interface = {
    "wl_registry", 1,
    1, wl_registry_requests,
    2, wl_registry_events,
};

簽名類型簡單羅列如下:

 * * `i`: int
 * * `u`: uint
 * * `f`: fixed
 * * `s`: string
 * * `o`: object
 * * `n`: new_id
 * * `a`: array
 * * `h`: fd
 * * `?`: 問號表示后面接的參數(shù)可空

wl_message 有個成員 types,在 demarshal 消息的過程中,如果傳過來的參數(shù)中有 object 或者 new_id,那么我們設(shè)置的 implementation 必須要知道對象的類型是什么,這個 types 就是用來做這個的。

wayland_types 是個指針數(shù)組,數(shù)組里面的成員是 wl_interface *,比如 global 對應(yīng) wayland_types + 0 指向 NULL,get_registry 對應(yīng)的 wayland_types + 9 指向 wl_registry_interface。
對于 display,客戶端先創(chuàng)建一個 proxy,將 request 發(fā)給服務(wù)端,服務(wù)端收到消息后創(chuàng)建對應(yīng)的 display_resource,這個 display_resource 與客戶端的 display_proxy 是對應(yīng)的,客戶端給服務(wù)端發(fā)送 request 消息,服務(wù)端給客戶端發(fā)送 event 消息。display 的 request 有兩個: sync 和 get_registry,在服務(wù)端實現(xiàn),event 有兩個: error 和 delete_id,在客戶端實現(xiàn)。

服務(wù)端

1、wayland server 啟動時會創(chuàng)建一個 socket(_wl_display_add_socket),并將這個 socket fd 加入 epoll 中,這樣如果有客戶端程序連接,epoll 就會通知服務(wù)端,從而執(zhí)行回調(diào)函數(shù) socket_data。

2、當(dāng)有客戶端程序連接過來,在 socket_data 函數(shù)中調(diào)用 accept 創(chuàng)建新的 socket fd,緊接著創(chuàng)建 wl_client,并把它加入合成器的 client list;在創(chuàng)建 wl_client 的時候,wl_client_create (display, fd) 將這個 socket fd 加入 epoll,這樣從客戶端發(fā)過來的請求,也可以通過 epoll 通知服務(wù)端,回調(diào)函數(shù)是 wl_client_connect_data。

3、等創(chuàng)建了 wl_client,接著執(zhí)行函數(shù) bind_display,創(chuàng)建 wl_display 在 server 端的 resource 對象 client->display_resource,設(shè)置它的 implementation,也就是 wl_display_requests 的倆函數(shù):

static const struct wl_display_interface display_interface = {
    display_sync,
    display_get_registry
};

客戶端

1、客戶端程序在運(yùn)行時先調(diào)用 wl_display_connect 連接到服務(wù)端,這個函數(shù)返回的 wl_display 就是 display 對象在 client 端的代理[上面第 3 步中說了 server 端是 display_resource,這倆配合工作,實現(xiàn)互相調(diào)用函數(shù)]。
在 wl_display_connect 中為代理對象 set_implementation,也就是 wl_display_events

static const struct wl_display_listener display_listener = {
    display_handle_error,
    display_handle_delete_id
};

這時第一個資源對象 display 就在 server 和client 端都設(shè)置好了。

2、然后調(diào)用 wl_display_get_registry 來獲取 server 端所有可用的 global 對象,并執(zhí)行 wl_registry_add_listener 為新的 registry 對象注冊事件:

static const struct wl_registry_listener registry_listener = {
    registry_handle_global,
    registry_handle_global_remove
};

函數(shù) wl_display_get_registry,它會調(diào)用下面這個函數(shù)
wl_proxy_marshal_constructor((struct wl_proxy *) wl_display, WL_DISPLAY_GET_REGISTRY, &wl_registry_interface, NULL);
上面的 &wl_registry_interface 就是 get_registry 的簽名 "n"。一方面它會返回 registry 在 客戶端的 proxy,也就是 display->registry,另一方面把請求發(fā)出去。

簡單分析這兩個函數(shù),首先介紹
wl_registry_add_listener(display->registry, &registry_listener, display);

第一個參數(shù)是 registry 的客戶端 proxy,第二個參數(shù)是 implementation,這個函數(shù)的作用就是設(shè)置 proxy event 的 implementation

    proxy->object.implementation = implementation;
    proxy->user_data = data;

接著再分析 wl_proxy_marshal_constructor,在 wayland-client.c 中層層調(diào)用到了 wl_proxy_marshal_array_constructor_versioned:

    message = &proxy->object.interface->methods[opcode];
    if (interface) {
        new_proxy = create_outgoing_proxy(proxy, message,
                          args, interface,
                          version);
        if (new_proxy == NULL)
            goto err_unlock;
    }

解析 message 這里需要先看 wl_interface 的結(jié)構(gòu)定義,如下:

struct wl_interface {
    /** Interface name */
    const char *name;
    /** Interface version */
    int version;
    /** Number of methods (requests) */
    int method_count;
    /** Method (request) signatures */
    const struct wl_message *methods;
    /** Number of events */
    int event_count;
    /** Event signatures */
    const struct wl_message *events;
};

其中 methods 是 request 的簽名,這里的 object.interface 應(yīng)該是 &wl_display_interface,在函數(shù) wl_display_connect 設(shè)置它的值:
display->proxy.object.interface = &wl_display_interface;
這個接口的 methods 有兩個,method_count 就是2; opcode 是 WL_DISPLAY_GET_REGISTRY,拿到的 message 也就是 “{ "get_registry", "n", wayland_types + 9 }”。
傳遞過來的 interface 為 wl_registry_interface,在 create_outgoing_proxy 得到 “n”,就會創(chuàng)建新的 proxy 對象,設(shè)置這個對象的 interface 為 wl_registry_interface,并把這個新的 proxy 對象加入映射表中,得到 object.id 填充到 args 中 。然后,再把 message,opcode,args 等組好 wl_closure ,序列化完成后,通過 socket 發(fā)給服務(wù)端。

再回到服務(wù)端,收到客戶端消息后,進(jìn)入回調(diào)函數(shù) wl_client_connection_data,反序列化消息,得到 wl_closure,找到目標(biāo)對象對應(yīng)的接口函數(shù),利用 libffi 執(zhí)行 server 端的函數(shù): display_get_registry,這個函數(shù)會在 server 端創(chuàng)建對應(yīng)的 wl_resource 也就是 registry_resource,將它 set_implementation,也就是 registry_bind。然后再遍歷可用的 global 對象發(fā)信號 global。

    wl_list_for_each(global, &display->global_list, link)
        if (wl_global_is_visible(client, global) && !global->removed)
            wl_resource_post_event(registry_resource,
                           WL_REGISTRY_GLOBAL,
                           global->name,
                           global->interface->name,
                           global->version);

這樣,第二個對象 registry 在服務(wù)端和客戶端的 request 和 event 也都設(shè)置好了。

進(jìn)程間的函數(shù)調(diào)用

當(dāng)服務(wù)端發(fā)出 global 信號時,客戶端程序就可以 wl_registry_bind 這些對象,生成本地的可用對象 wl_proxy。[參考 weston 的客戶程序代碼 window.c]
但是信號機(jī)制一般是對同一個進(jìn)程來說的,我們可以監(jiān)聽某個對象的某個信號,當(dāng)收到信號時執(zhí)行對應(yīng)的回調(diào)函數(shù);而這里其實是兩個程序,服務(wù)端和客戶端,這種跨進(jìn)程的信號不是簡單地 connect 就可以的,而是需要通過 socket 來傳遞。
要讓兩個進(jìn)程通過 socket 進(jìn)行函數(shù)調(diào)用,首先需要將調(diào)用抽象成數(shù)據(jù)流的形式,這些信息通過 wl_closure_marshal 寫入 wl_closure 結(jié)構(gòu),再由 serialize_closure 變成數(shù)據(jù)流;等到了目標(biāo)進(jìn)程,從數(shù)據(jù)流中通過 wl_connection_demarshal 轉(zhuǎn)回 wl_closure 結(jié)構(gòu)。
如果參數(shù)中不止整型字符串等簡單類型,還存在對象的話,就需要 wl_map 來做這個對象在 server 和 client 端的映射了。
上面所謂的發(fā)出 global 信號,其實也就是把函數(shù)調(diào)用的請求序列化,然后再發(fā)給客戶端程序,客戶端收到消息,反序列化,解析出object id, 從 wl_map 中找到本地可用對象,拿到 opcode,再解析本地對象的 implementation 對應(yīng) opcode 的函數(shù),也就是執(zhí)行 registry_handle_global。

    if (strcmp(interface, "wl_compositor") == 0) {
        d->compositor = wl_registry_bind(registry, id,
                         &wl_compositor_interface, 3);
    } else if (strcmp(interface, "wl_output") == 0) {
        display_add_output(d, id);
    } else if (strcmp(interface, "wl_seat") == 0) {
        display_add_input(d, id, version);
    }

同樣的,這個 wl_registry_bind 也是把客戶端的請求消息序列化后,發(fā)給服務(wù)端;服務(wù)端收到后反序列化,執(zhí)行對應(yīng)的 bind,也就是 wayland-server.c 中定義的 registry_bind

整個流程就是 client 端通過 wl_proxy 調(diào)用 server 端的 wl_resource 的 request,server 端通過 wl_resource 調(diào)用 client 端的 wl_proxy 的 event。

再簡單說明一下客戶端收到消息的處理流程,wl_display_dispatch -> wl_display_read_events -> read_events -> queue_event,讀取數(shù)據(jù),將解析出來的 event 加入 event_list 中。
在 dispatch_queue 中 dispatch_event,也就是執(zhí)行 wl_closure_invoke。具體的下次再說吧……

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容