Wayland窗口系统(二)

gewenbin
gewenbin
gewenbin
188
文章
15
评论
2020年12月26日21:30:03 1 2,015

4. Wayland协议实现浅析

4.1 几个重要的数据结构

4.1.1 wl_object

wl_object是一个很重要的数据结构,在客户端和服务端都有此数据结构的封装,是wl_proxy、wl_resource数据结构的第一个成员,wayland中所有对象的概念都是基于wl_object而言的。wl_object包括interface、implementation、id三个重要成员,每个成员的作用如下:

  • interface:是客户端和服务端交互的接口。Interface主要包含以下几项内容:
  1. name:接口的名字。
  2. version:此接口的版本号。
  3. request信息表:用于表明有哪些request以及每项request具体参数的类型。
  4. event信息表:类似request信息表,用于表明有哪些event事件以及每项event具体参数的类型。
  • implementation:在服务端此成员表示request方法的具体实现,在客户端此成员表示event事件的具体实现。
  • id:该object的ID,用于client/server端索引此object,服务端和客户端就是通过此ID建立映射。

如下所示:

Wayland窗口系统(二)

4.1.2 wl_proxy

这是wl_object在客户端的封装,wayland客户端的对象都是对wl_proxy进一步的封装。wl_proxy主要包含以下成员:

  • object:wl_object,通过此成员可以获得interface等信息。
  • display:wl_display,服务端和客户端启动后最先创建的就是wl_display。
  • version:对象的版本。
4.1.3 wl_resource

这是wl_object在服务端的封装,wayland服务端的对象都是对wl_resource进一步的封装。wl_resource主要包含以下成员:

  • object:wl_object,通过此成员可以获得interface等信息。
  • destroy:用于对象销毁时回调的销毁函数。
  • link:用于将对象连接成链表。
  • client:wl_client,每有一个客户端连接到服务端时,在服务端就会创建一个wl_client结构,用于表示客户端的信息。
4.1.4 wl_global

wl_global用于表示服务端的全局对象,一个全局对象就表示服务端提供的一个服务,比如wl_compositor、wl_shm、wl_shell等。wl_global主要包括以下内容:

  • display:wl_display,服务端和客户端启动后最先创建的就是wl_display。
  • interface:wl_interface,客户端和服务端交互的接口。
  • name:全局对象的名字。
  • version:全局对象的版本。
  • bind:在wl_registry接口进行bind这个request这个操作时会回调wl_global中的bind回调函数。
  • link:用于将全局对象连接成链表。
4.1.5 wl_array

动态数组,数组中的每一项内容都是服务端和客户端对象数据结构的地址,也就是说,wl_array其实就是一个指针数组。在服务端和客户端的对象通过数组下标建立映射关系,也就是说,同一个对象在服务端和客户端数组中的位置是相同的,这样在client/server两端进行对象传递时,只需要传递数组下标就可以了。当数组内容满了的时候,如果继续插入数据会自动扩充数组的大小。wl_array的内容如下:

  • size:数组已经使用的空间大小。
  • alloc:整个数组的空间大小。
  • data:实际数据缓存区地址。

client/server端的对象映射关系如下所示:

Wayland窗口系统(二)

需要注意的是,不管服务端还是客户端,映射表中的第0项都是空的,新的对象从第一项开始。

4.1.6 wl_map

wl_map是对wl_array的封装,服务端和客户端都有此数据结构,主要成员如下:

  • client_entries:wl_array类型,客户端使用。
  • server_entries:wl_array类型,服务端使用。
  • side:用于表示当前的wl_map是服务端还是客户端的。
4.1.7 wl_buffer

wl_buffer用于缓存客户端和服务端通信数据,通信数据格式见3.3章节。此buffer不同是环形缓冲区,不同于wl_array,如果填满了会回到起始处继续填充。wl_buffer包含的内容如下:

  • data[4096]:这是4KB的缓存区。
  • head:用于表示当前缓存中待发送数据的位置。
  • tail:用于表示当前缓存中已经发送完后数据的位置。
4.1.8 wl_connection

wl_connection是对wl_buffer进一步的封装,当服务端和客户端需要发送和接受数据时,都会对wl_connection数据结构进行操作。wl_connection的内容如下:

  • in:wl_buffer类型,用于缓存接受到的数据。
  • out:wl_buffer类型,用于缓存发送的数据。
  • fds_in:wl_buffer类型,如果要接收的数据有文件描述符类参数,缓存在fds_in中。
  • fds_out:wl_buffer类型,如果要发送的数据有文件描述符类参数,缓存在fds_out中。
  • fd:表示已经建立连接的socket fd。
  • want_flush:表示缓存区中的数据是否需要强制发送。

客户端和服务端通信的示意图如下所示:

Wayland窗口系统(二)

4.1.9 wl_event_source

wl_event_source由服务端使用,服务端启动后会通过epool机制等待一系列的事件信息,wl_event_source就表示某个等待的事件的信息,其内容如下:

  • interface:wl_event_source_interface类型,当事件激活时会调用interface中的dispatch函数。
  • loop:wl_event_loop类型,用于描述服务端循环等待事件的epool fd相关信息。
  • link:用于形成链表。
  • fd:等待的事件fd。

服务端真正使用的表示事件资源的数据结构会对wl_event_source做进一步封装,比如wl_event_source_fd,表示等待fd可读可写事件。wl_event_source_fd有一个func成员,此成员会被wl_event_source中的interface中的dispatch函数调用,比如当有客户端链接socket fd时,此回调函数是socket_data,当客户端有数据通信时,此回调函数是wl_client_connection_data。

4.1.10 wl_closure

wl_closure用于表示函数调用信息和要调用的函数的参数信息。此数据结构是一个非常重要的数据结构,客户端和服务端在发送前都会先将数据组织到wl_closure数据结构中,然后由此数据结构解析到wl_connection中进行发送;接收时会将wl_connection中的原始数据解析成wl_closure数据结构,然后进行函数调用。

wl_closure内容如下:

  • count:表示要调用的函数中的参数个数。
  • message:表示request/event信息表。
  • opcode:表示request/event信息表中的哪一个。
  • sender_id:发送对象的ID。
  • args[20]:函数调用的每个参数值都会存放在args数组中,每次最多20个参数。
  • link:用于形成链表。
  • proxy:表明是哪个对象。

通过wl_closure进行数据通信过程示意如下所示:

Wayland窗口系统(二)

4.2 几个重要的链表

4.2.1 global_list

global_list用于将服务端的所有全局对象连接成链表,在客户端进行获取注册表操作时会将全局对象链表中的每一个全局对象信息发送给客户端。global_list链表头是在服务端的wl_display数据结构中,如下所示:

Wayland窗口系统(二)

4.2.2 socket_list

服务端使用,socket_list用于将所有监听的socket信息连接成链表,一般情况下链表中只有一个监听的socket,名字默认为“wayland-0”,如下所示:

Wayland窗口系统(二)

4.2.3 client_list

服务端使用,每当有一个客户端和服务端连接时,服务端就会创建一个wl_client数据结构,用于表示连接信息。wl_client主要包含以下内容:

  • wl_connection:发送/接收数据都是缓存在wl_connection中。
  • wl_event_source_fd:用于表示已经连接的client fd信息。
  • wl_resource:服务端的wl_display对象。
  • link:用于形成链表。
  • wl_map:用于client/server进行映射对象。

如下所示:

Wayland窗口系统(二)

4.2.4 registry_resource_list

服务端使用,每当客户端发送get_registry这个request时,服务端都会创建一个新的wl_ resource对象,这些wl_ resource对象通过registry_resource_list形成链表,如下所示:

Wayland窗口系统(二)

4.2.5 display_queue

客户端使用,当客户端收到服务端发送的数据后,将其解析成wl_closure数据结构,然后将wl_closure插入到display_queue中。随后在客户端dispatch_queue的时候会将display_queue中的wl_closure取出解析进行函数调用。如下所示:

Wayland窗口系统(二)

4.2.6 default_queue

default_queue作用和display_queue一样,只是在收到服务端发送的数据后,如果解析出的发送对象proxy就是客户端wl_display中的proxy,那么就会将wl_closure插入到display_queue中,否则插入到default_queue中。

dispatch_queue时会首先处理display_queue中的wl_closure,然后再处理default_queue中的wl_closure。

4.3 跨进程调用

客户端和服务端跨进程进行函数调用的大致流程如下所示:

Wayland窗口系统(二)

4.3.1 wl_closure_marshal()

创建wl_closure数据结构,并根据具体的参数初始化。客户端/服务端在发送数据之前都需要调用此函数将要发送的参数等信息组织到wl_closure数据结构中。

4.3.2 wl_connection_write()

将wl_closure数据结构解析成具体要发送的数据填入到wl_connection中。在这个函数中会将对象指针替换成对象ID,以及对fd类型参数做特殊处理等。

4.3.3 wl_conntction_flush()

将wl_buffer中缓存的数据通过sendmsg接口发送出去。

4.3.4 wl_connection_read()

通过recvmsg接口将socket中的数据接收到wl_buffer中。

4.3.5 wl_closure_demarshal()

将wl_connection缓存中的数据解析成wl_closure数据结构,用来传递给wl_closure_invoke进行函数调用。每一条IPC信息都会被解析成一个wl_closure数据结构。

4.3.6 wl_closure_invoke()

根据wl_closure数据结构进行真正的函数调用,注意,这个函数中使用了libffi接口以作为函数调用的跳板。

4.4 Server端IPC信息循环处理流程

  • Server端在进行一些初始化操作之后会阻塞在wl_event_loop_dispatch中的epool_wait上。
  • epool_wait主要在等待两个事件:
  1. listen socket fd,如果有client连接,调用socket_data处理。
  2. connect socket fd,如果有client传送数据,调用wl_client_connection_data处理。

4.5 Client端IPC信息循环处理流程

  • Client端在进行初始化后调用wl_display_dispatch_queue。
  • Client阻塞在wl_display_poll中的poll上,等待server端发送数据。
  • 如果server端有数据传送,则调用read_events,最终调用到queue_event,主要做以下几件事:
  1. 将收到的数据组织成wl_closure数据结构。
  2. 将wl_closure插入到queue->event_list中。
  3. 这里有两个queue:display_queue和default_queue。具体插到哪个queue请参见2.5和4.2.6章节。
  • 调用dispatch_queue,首先处理display_queue,然后处理default_queue。主要是将queue中的wl_closure逐个进行解析,然后进行真正的函数调用。

4.6 基于SHM机制传递窗口

  • 客户端首先打开一个临时文件。
  • 然后通过ftruncate改变文件的大小,这个大小根据实际的窗口大小决定。
  • 客户端mmap临时文件fd,客户端通过socket机制将此fd发送给服务端。
  • 服务端收到fd后,同样mmap此fd,那么客户端和服务端分别使用mmap后获得的地址来访问同一块共享内存区。
  • 客户端对共享内存区进行渲染绘制,完成后通知服务端。
  • 服务端拿到共享内存区数据进行合成。

5. Demo合成器、客户端实现简析

5.1 demo合成器分析

5.1.1 wl_display_create

此函数主要进行如下工作:

  • 分配wl_display结构体空间。
  • 初始化event_loop,event_loop是服务端用于循环处理各种等待事件的机制。
  • 初始化global_list、socket_list等各种链表。
  • 初始化shm formats array,这个数组中记录了服务端将以何种格式解析共享内存区中的数据。
5.1.2 wl_display_add_socket_auto

此函数主要进行如下工作:

  • 创建wayland lock文件并锁住此文件,一般名为wayland-0.lock。
  • 创建、绑定、监听socket,名字一般为wayland-0。
  • 将上个步骤创建的wayland socket fd加入到epool中监听。
  • 将wl_socket加入到display的socket_list链表中。
5.1.3 wl_global_create

这个函数负责创建全局对象并将其插入到链表中。

  • 对interface的版本做检查。
  • 创建struct wl_global结构体空间并初始化。
  • 将全局对象插入到display中的global_list中。
  • 创建新全局对象后,给每个客户端发送registry global事件,通知客户端服务端有新全局对象创建了。

demo合成器创建了3个全局对象:wl_compositor_interface、wl_shell_interface、wl_shm_interface。每个全局对象创建的时候都会有一个bind回调函数,此函数在wl_registry对象进行bind request操作时被调用,主要作用就是根据全局对象来创建其服务端对应的resource,并设置resource对应的request实现函数。

5.1.4 wl_display_init_shm

这个函数内部调用wl_global_create来创建wl_shm_interface全局对象。

5.1.5 wl_display_add_shm_format

此函数用来设置服务端支持的共享内存像素数据格式,默认支持ARGB8888和XRGB8888。

这个函数内部调用wl_array_add,用来在wl_array中预留指定大小的空间并返回此空间的地址,这样用户就可以通过这个地址向这个空间中填写数据。

5.1.6 wl_display_run
  • 首先调用wl_display_flush_clients将client_list链表中所有客户端缓存区中的数据通过socket接口发送出去。
  • 接着调用wl_event_loop_dispatch等待接收服务端的数据。等待的事件主要是:
  1. listen socket fd,用于监听客户端连接,如果有连接,回调socket_data函数。
  2. connect socket fd,用于处理客户端数据通信,如果有数据,回调wl_client_connection_data函数。
  • 回到步骤1循环处理。
5.1.7 socket_data
  • 通过accept获得与客户端通讯的fd。
  • 创建wl_client结构,并初始化。
  • 将wl_client结构插入client_list链表中。
5.1.8 wl_client_connection_data
  • wl_connection_read:通过recvmsg将数据读取到wl_connection的缓存区中。
  • wl_connection_demarshal:将数据组织成wl_closure结构。
  • wl_closure_invoke:根据wl_closure结构进行函数调用。

5.2 demo客户端分析

5.2.1 wl_display_connect
  • 连接到服务端创建的“wayland-0”socket接口。
  • 创建并初始化wl_display数据结构。
5.2.2 wl_display_get_registry
  • 向服务端发送wl_display对象的get_registry请求。
  • 创建wl_registry对象。
5.2.3 wl_registry_add_listener
  • 设置wl_registry对象的event实现函数。
5.2.4 wl_display_roundtrip
  • 向服务端发送wl_display对象的sync请求。
  • 创建wl_callback对象。
  • 设置wl_callback对象的event实现函数。
  • 将客户端缓冲区的数据通过socket发送出去。
  • 等待接收服务端发送的数据。
  • 处理服务端发送的事件。
5.2.5 create_window
  • wl_compositor_create_surface:向服务端发送wl_compositor对象的create_surface请求,同时创建wl_surface对象。
  • wl_shell_get_shell_surface:向服务端发送wl_shell对象的get_shell_surface请求,同时创建wl_shell_surface对象。
  • wl_shell_surface_add_listener:设置wl_shell_surface对象的event实现函数。
  • 创建临时文件。
  • 通过ftruncate设置临时文件大小。
  • 通过mmap映射临时文件。
  • wl_shm_create_pool:向服务端发送wl_shm对象的create_pool请求。
  • 创建wl_shm_pool对象。
  • wl_shm_pool_create_buffer:向服务端发送wl_shm_pool对象的create_buffer请求。
5.2.6 draw_window
  • 根据create_window中mmap出来的共享内存区渲染像素数据。
  • wl_surface_attach:向服务端发送wl_surface对象的attach请求。
  • wl_surface_damage:向服务端发送wl_surface对象的damage请求。
  • wl_surface_commit:向服务端发送wl_surface对象的commit请求。
5.2.7 wl_display_dispatch
  • 将客户端缓冲区的数据通过socket发送出去。
  • 等待接收服务端发送的数据。
  • 处理服务端发送的事件。
  • 回到步骤1循环处理。
gewenbin
  • 本文由 发表于 2020年12月26日21:30:03
  • 转载请务必保留本文链接:http://www.databusworld.cn/9909.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

评论:1   其中:访客  1   博主  0
    • mini mini 4