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