• 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协议的跨进程过程调用是怎样实现的。

    一、基本工作流程


    以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端来做的。

    二、连接的建立


    首先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。

    三、消息处理模型


    在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后进行调用。

    四、跨进程过程调用


    术语上,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()。下图简单地描绘了整个流程。 



  • 相关阅读:
    ArcObjects SDK(AE)10.1在vs2012安装的方法(亲测,并未成功,尝试需谨慎)
    PTA 求链式表的表长
    PTA 输出数组元素
    PTA 找出不是两个数组共有的元素
    PTA 将数组中的数逆序存放
    PTA 利用指针找最大值
    PTA 有序数组的插入
    PTA 冒泡排序
    PTA 报数
    PTA 数组循环右移
  • 原文地址:https://www.cnblogs.com/blfshiye/p/4303278.html
Copyright © 2020-2023  润新知