Wayland中的跨进程过程调用浅析

原文地址:http://blog.csdn.net/jinzhuojun/article/details/40264449

Wayland协议主要提供了Client端应用与Server端Compositor的通讯机制,Weston是Server端Compositor的一个參考实现。Wayland协议中最基础的是提供了一种面向对象的跨进程过程调用的功能,在做用上相似于Android中的Binder。与Binder不一样的是,在Wayland中Client和Server底层经过domain socket进行链接。和Binder同样,domain socket支持在进程间传递fd,这为传递graphic buffer和shared memory等提供了基础。Client和Server端一方面各自在消息循环中等待socket上的数据,拿到数据后通过反序列化处理生成本地的函数调用,而后进行调用;还有一方面将本地的远端调用请求封装序列化后经过socket发出。另外,由于是基于消息循环的处理模型,意味着这样的调用不是同步,但经过等待Server端处理完毕后的事件再返回可以达到同步调用的效果。

如下从几个基本方面介绍Wayland协议的跨进程过程调用是怎样实现的。

1、基本工做流程


以Weston自带的例程simple-shm为例,先感觉一下Client怎样经过Wayland协议和Compositor通讯。
1. 链接Server,绑定服务
display->display = wl_display_connect() // 经过socket创建与Server端的链接,获得wl_display。它即表明了Server端的display资源,同一时候也是代理对象wl_proxy。Client可以经过它来向Server端提交调用请求和接收事件。
display->registry = wl_display_get_registry(display->display) // 申请建立registry,获得代理对象wl_registry。这个对象至关于Client在Server端放的一个用于嗅探资源的Observer。Client经过它获得Server端有哪些Global对象的信息。Server端有一系列的Global对象,如wl_compositor, wl_shm等,串在display->global_list链表里。Global对象在概念上相似于Service服务,所以Server端至关于充当了ServiceManager的角色。
wl_registry_add_listener(display->registry, &registry_listener,...) // 让Client监听刚才建立的wl_registry代理对象。这样, 当Client调用wl_display_get_registry()函数或者有新的Global对象增长到Server端时,Client就会收到event通知。
wl_display_roundtrip() // 等待前面的请求全被Server端处理完,它同步了Client和Server端。这意味着到这个函数返回时,Server端有几个Global对象,回调处理函数registry_handle_global()应该就已经被调用过几回了。 registry_handle_global()中会推断是当前此次event表明何种Global对象,而后调用wl_registry_bind()进行绑定,获得远程服务对象的本地代理对象。这些代理对象类型可以是wl_shm, wl_compositor等,但本质上都是wl_proxy类型。这步的做用相似于Android中的bindService(),它会获得一个远端Service的本地代理。

2. 建立窗体
window->surface = wl_compositor_create_surface() // 经过刚才绑定的wl_compositor服务建立Server端的weston_surface,返回代理对象 wl_surface
xdg_shell_get_xdg_surface(..., window->surface, ...) // 经过刚才绑定的xdg_shell服务建立Server端的shell_surface,返回代理对象 xdg_surface。有些样例中用的是wl_shell_surface,它和xdg_surface的做用是同样的。xdg_surface是做为wl_shell_surface未来的替代品,但还没进Wayland核心协议。
为何一个窗体要建立两个surface呢?因为Wayland协议若是Server端对Surface的管理分两个层次。以Weston为例,Compositor仅仅负责合成(代码主要在compositor.c),它至关于Android中的SurfaceFligner,它所示主要是weston_surface。而Weston在启动时会载入shell模块(如desktop-shell.so,代码主要在desktop-shell/shell.c),它至关于Android中的WindowManagerService,它所示主要是shell_surface。shell_surface在结构上是weston_surface的进一步封装,为了作窗体管理。这样,合成渲染和窗体管理的模块既可以方便地相互訪问又保证了较低的耦合度。

3. 分配buffer与绘制
wl_surface_damage() // 告诉Compositor该surface哪块区域是脏的,需要重绘。一開始是整个窗体区域。
redraw() //  接下来调用redraw()開始绘制的循环,这里是双buffer的软件渲染。
    window_next_buffer() // 取一个buffer,用做绘制。
        create_shm_buffer() // 假设该buffer还没有分配则用以前绑定的wl_shm服务分配一块共享内存。
            fd = os_create_anonymous_file() // 为了建立共享内存,先建立一个暂时文件做为内存映射的backing file。
            mmap(..., fd,...) // 将该文件先映射到Client端的内存空间。
            pool = wl_shm_create_pool(..., fd,...) // 经过wl_shm服务建立共享内存池。将刚才的fd做为參数传过去,这样Server端就可以和Client经过这个fd映射到同一块物理内存。
            buffer->buffer = wl_shm_pool_create_buffer(pool, ...) // 经过这个共享内存池在Server端分配buffer,返回wl_buffer为其本地代理对象。
            wl_buffer_add_listener(buffer->buffer, &buffer_listener,...) // 监听这个buffer代理对象,当Server端再也不用这个buffer时,会发送release事件。这样,Client就可以重用这个buffer做下一帧的绘制。
    paint_pixels() // Client在buffer上绘制本身的内容。
    wl_surface_attach()// 将绘制好的buffer attach到surface上。做用上相似于Android中的updateTexImage(),即把某一个buffer与surface绑定。
    wl_surface_damage()// 告诉Server端的Compositor这个surface哪块区域是脏区域,需要又一次绘制。 
    window->callback = wl_surface_frame() // 在Server端建立Frame callback,它会被放在该surface下的frame_callback_list列表中。返回它的代理对象wl_callback。
    wl_callback_add_listener(window->callback, &frame_listener, ...) // 监听前面获得的callback代理对象。在Server端Compositor在完毕一帧的合成渲染后,会往这些callback对象发done的事件(參考weston_output_repaint())。Client收到后会调用參数中wl_callback_listener中的done事件相应的方法,这里是redraw()函数。这样,就造成了一个循环。
    wl_surface_commit() // 在0.99版本号后,为了保证原子性及使surface属性的修改顺序无关,Server端对于surface的属性(damage region, input region, opaque region, etc.)都是双buffer的(weston_surface_state)。因此commit前的修改都存在backing buffer中。仅仅有当Client调用wl_surface_commit()时,这些修改才生效。

与Android做个类比,这里的wl_surface相应SF中的Layer,wl_buffer相应GraphicBuffer。Weston相应SF+WMS。一个surface相应用户视角看到的一个窗体。为了使Client和Server并行做业,一般会用多个buffer。和Android比較不一样的是,Android是Server端来维护buffer的生命周期,而 Wayland中是Client端来作的。

2、链接的创建


首先Server端的Compositor在启动时会在$XDG_RUNTIME_DIR文件夹下建立用于监听的socket,默以为wayland-0。而后把该socket fd增长event loop等待的fd列表。參考实现位于 weston_create_listening_socket() -> wl_display_add_socket_auto() -> _wl_display_add_socket()。当有Client链接时,回调处理函数为socket_data(),当中会调用wl_os_accept_cloexec()获得与该Client相连的socket fd。而后调用wl_client_create(),建立wl_client。Server端会为每一个链接的Client建立wl_client,这些对象被串在display->client_list这个列表里。wl_client中的wl_connection表明这个与Client的链接,当中包括了in buffer和out buffer,分别做为输入和输出的缓冲区。注意这个in buffer和out buffer都是双份的,一份for普通数据,一份for fd,因为fd需要以out-of-band形式传输,要特殊处理。wl_event_loop_add_fd()会把这个与Client链接的socket fd增长到event loop等待的列表中,回调处理函数为wl_client_connection_data()。
weston_create_listening_socket()
    wl_display_add_socket_auto()
        wl_socket_init_for_display_name() // $XDG_RUNTIME_DIR/wayland-0,
        _wl_display_add_socket() 
            wl_os_socket_cloexec() // create socket
            bind() 
            listen() 
            wl_event_loop_add_fd(.., socket_data,...) // 建立wl_event_source_fd,它表明一个基于socket fd的事件源。处理函数是wl_event_source_fd_dispatch(),当中会调用这里參数里的回调函数 socket_data()。
                add_source() // 把刚建立的监听socket fd,经过epoll_ctl()附加到loop->epoll_fd上。这样消息循环就可以在上面等待Client的链接了。

当有Client链接到Server的监听socket上,会调用刚才注冊的回调函数socket_data(),而后会调用wl_os_accept_cloexec()->accept()建立与Client链接的fd。接着调用wl_client_create()建立Client对象。
socket_data()
    wl_os_accept_cloexec()
    wl_client_create()
        client->connection = wl_connection_create() 
         wl_map_init(&client->objects) // 初始化wl_map对象,用于映射Server和Client端的对象。
        bind_display() // 绑定Client端的wl_display代理对象与Server端的display资源对象。
            client->display_resource = wl_resource_create(.., &wl_display_interface,...) // display资源对象的接口就是wl_display_interface,request的实现在display_interface中。这里建立wl_resource结构,当中resource->object是一个可供Client调用的远端对象的抽象,当中的成员interface和implementation分别表明其接口和实现。而后用wl_map_insert_at()插入到client->objects的wl_map结构中供之后查询。
            wl_resource_set_implementation(..., &display_interface, ...)  // 对Client而言,Server端提供的接口实现是request部分。

Client端要链接Server端,是经过调用wl_display_connect()。当中会建立socket并且调用connect()链接Server端建立的监听port。获得与Server端链接的socket fd后调用wl_display_connect_to_fd()建立wl_display。wl_display是Server端的display资源在Client端的代理对象,它的第一个元素wl_proxy,所以它可以与wl_proxy互转。和Server端同样,也会建立一个wl_connection包括读写缓冲区。
wl_display_connect()
    fd =connect_to_socket() // 尝试链接$XDG_RUNTIME_DIR/wayland-0,返回与Server端相连的socket fd。
        wl_os_socket_cloexec() 
        connect()
    wl_display_connect_to_fd()// 建立和返回wl_display。
        display->proxy.object_interface = &wl_display_interface; // 设置wl_display的接口。
        display->proxy.object.implementation = (void(**)(void)) &display_listener // 对Server而言,Client端提供的接口实现是event部分。
        display->connection = wl_connection_create() 

可以看到display在Client端的wl_proxy和Server端wl_resource都包括了它完整的接口描写叙述wl_display_interface。但wl_proxy仅仅包括了event的实现display_listener,wl_resource仅仅包括了request的实现display_interface。

3、消息处理模型


在Server端,Compositor初始化完后会调用wl_display_run()进入大循环。这个函数主体是:
while (...) {
    wl_display_flush_clients() // 将相应的out buffer经过socket发出去。
    wl_event_loop_dispatch() // 处理消息循环。
}
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()进行处理。

在Client端,当需要与Server端交换数据时,终于会调用wl_display_dispatch_queue()。当中最基本的是三件事:
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后进行调用。

4、跨进程过程调用


术语上,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,..)绑定资源,同一时候建立本地代理对象。接着S erver端对应地调用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事件为例分析下流程。S erver端经过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()。下图简单地描绘了整个流程。 
相关文章
相关标签/搜索