wayland的client端和server端的跨进程通信是通过socket实现的。本文首先对server端的socket的生成,绑定,监听进行分析,以wayland的源码中自带的weston代码为例,在server端的main函数中,会调用weston_create_listening_socket,该函数的实现如下:
static int
weston_create_listening_socket(struct wl_display *display, const char *socket_name)
{
if (socket_name) {
if (wl_display_add_socket(display, socket_name)) {
weston_log("fatal: failed to add socket: %m\n");
return -1;
}
} else {
socket_name = wl_display_add_socket_auto(display);
if (!socket_name) {
weston_log("fatal: failed to add socket: %m\n");
return -1;
}
}
setenv("WAYLAND_DISPLAY", socket_name, 1);
return 0;
}
若没有指定socket的名称,会自动生成socket名称,为"wayland-0",随后调用_wl_display_add_socket函数完成socket的生成,绑定,该函数的实现如下:
static int
_wl_display_add_socket(struct wl_display *display, struct wl_socket *s)
{
socklen_t size;
s->fd = wl_os_socket_cloexec(PF_LOCAL, SOCK_STREAM, 0);
if (s->fd < 0) {
return -1;
}
size = offsetof (struct sockaddr_un, sun_path) + strlen(s->addr.sun_path);
if (bind(s->fd, (struct sockaddr *) &s->addr, size) < 0) {
wl_log("bind() failed with error: %m\n");
return -1;
}
if (listen(s->fd, 128) < 0) {
wl_log("listen() failed with error: %m\n");
return -1;
}
s->source = wl_event_loop_add_fd(display->loop, s->fd,
WL_EVENT_READABLE,
socket_data, display);
if (s->source == NULL) {
return -1;
}
wl_list_insert(display->socket_list.prev, &s->link);
return 0;
}
在该函数的调用中,wl_os_socket_cloexec的作用是生成socket,实现如下:
wl_os_socket_cloexec(int domain, int type, int protocol)
{
int fd;
fd = socket(domain, type | SOCK_CLOEXEC, protocol);
if (fd >= 0)
return fd;
if (errno != EINVAL)
return -1;
fd = socket(domain, type, protocol);
return set_cloexec_or_close(fd);
}
生成的socket的函数为socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC,0),其中PF_LOCAL表示unix系统间的通信,SOCK_STREAM标示我们用的是TCP协议,这样会提供按顺序的、可靠、双向、面向连接的比特流,关于SOCK_CLOEXEC的作用可以参考博客。创建socket完成后,将该socket保存在wl_socket结构体s的成员fd中,接下来需要做的是socket的bind操作,它的参数设置在wl_socket_init_for_display_name中完成,该函数的实现如下所示:
static int
wl_socket_init_for_display_name(struct wl_socket *s, const char *name)
{
int name_size;
const char *runtime_dir;
runtime_dir = getenv("XDG_RUNTIME_DIR");
if (!runtime_dir) {
wl_log("error: XDG_RUNTIME_DIR not set in the environment\n");
/* to prevent programs reporting
* "failed to add socket: Success" */
errno = ENOENT;
return -1;
}
s->addr.sun_family = AF_LOCAL;
name_size = snprintf(s->addr.sun_path, sizeof s->addr.sun_path,
"%s/%s", runtime_dir, name) + 1;
s->display_name = (s->addr.sun_path + name_size - 1) - strlen(name);
assert(name_size > 0);
if (name_size > (int)sizeof s->addr.sun_path) {
wl_log("error: socket path \"%s/%s\" plus null terminator"
" exceeds 108 bytes\n", runtime_dir, name);
*s->addr.sun_path = 0;
/* to prevent programs reporting
* "failed to add socket: Success" */
errno = ENAMETOOLONG;
return -1;
}
return 0;
}
可以看到该socket的addr.sun_path由环境变量XDG_RUNTIME_DIR与display_name指定,在weston中该环境变量为
XDG_RUNTIME_DIR=/run/user/1000
若没有指定display名称,会在该目录下生成一个wayland-0的文件。下一步需要对该socket文件进行监听,设置请求的最大排队长度为128。接下来调用wl_event_loop_add_fd创建了wl_event_source_fd,它代表一个基于socket fd的事件源,该函数的实现如下所示:
WL_EXPORT struct wl_event_source *
wl_event_loop_add_fd(struct wl_event_loop *loop,
int fd, uint32_t mask,
wl_event_loop_fd_func_t func,
void *data)
{
struct wl_event_source_fd *source;
source = malloc(sizeof *source);
if (source == NULL)
return NULL;
source->base.interface = &fd_source_interface;
source->base.fd = wl_os_dupfd_cloexec(fd, 0);
source->func = func;
source->fd = fd;
return add_source(loop, &source->base, mask, data);
}
将刚刚监听的fd通过add_source添加到display的loop->epoll_fd上,消息循环在上面等待client的连接。
static struct wl_event_source *
add_source(struct wl_event_loop *loop,
struct wl_event_source *source, uint32_t mask, void *data)
{
struct epoll_event ep;
if (source->fd < 0) {
free(source);
return NULL;
}
source->loop = loop;
source->data = data;
wl_list_init(&source->link);
memset(&ep, 0, sizeof ep);
if (mask & WL_EVENT_READABLE)
ep.events |= EPOLLIN;
if (mask & WL_EVENT_WRITABLE)
ep.events |= EPOLLOUT;
ep.data.ptr = source;
if (epoll_ctl(loop->epoll_fd, EPOLL_CTL_ADD, source->fd, &ep) < 0) {
close(source->fd);
free(source);
return NULL;
}
return source;
}
其中epoll_ctl的用法可参考博客,当client端连接到server端的fd时,会调用处理函数wl_event_source_fd_dispatch(),该函数会调用之前注册的回调函数socket_data,其中wl_event_source_fd_dispatch实现如下:
static int
wl_event_source_fd_dispatch(struct wl_event_source *source,
struct epoll_event *ep)
{
struct wl_event_source_fd *fd_source = (struct wl_event_source_fd *) source;
uint32_t mask;
mask = 0;
if (ep->events & EPOLLIN)
mask |= WL_EVENT_READABLE;
if (ep->events & EPOLLOUT)
mask |= WL_EVENT_WRITABLE;
if (ep->events & EPOLLHUP)
mask |= WL_EVENT_HANGUP;
if (ep->events & EPOLLERR)
mask |= WL_EVENT_ERROR;
return fd_source->func(fd_source->fd, mask, source->data);
}
其中socket_data的实现如下所示:
static int
socket_data(int fd, uint32_t mask, void *data)
{
struct wl_display *display = data;
struct sockaddr_un name;
socklen_t length;
int client_fd;
length = sizeof name;
client_fd = wl_os_accept_cloexec(fd, (struct sockaddr *) &name,
&length);
if (client_fd < 0)
wl_log("failed to accept: %m\n");
else
if (!wl_client_create(display, client_fd))
close(client_fd);
return 1;
}
在函数wl_os_accept_cloexec中对client端的连接请求进行处理
int
wl_os_accept_cloexec(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
{
int fd;
#ifdef HAVE_ACCEPT4
fd = accept4(sockfd, addr, addrlen, SOCK_CLOEXEC);
if (fd >= 0)
return fd;
if (errno != ENOSYS)
return -1;
#endif
fd = accept(sockfd, addr, addrlen);
return set_cloexec_or_close(fd);
}
紧接着是通过wl_client_create创建client对象,同时将该对象添加到compositor的client list中,那个时候,客户端已经初始化完成。
WL_EXPORT struct wl_client *
wl_client_create(struct wl_display *display, int fd)
{
struct wl_client *client;
socklen_t len;
client = zalloc(sizeof *client);
if (client == NULL)
return NULL;
wl_priv_signal_init(&client->resource_created_signal);
client->display = display;
client->source = wl_event_loop_add_fd(display->loop, fd,
WL_EVENT_READABLE,
wl_client_connection_data, client);
if (!client->source)
goto err_client;
len = sizeof client->ucred;
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED,
&client->ucred, &len) < 0)
goto err_source;
client->connection = wl_connection_create(fd);
if (client->connection == NULL)
goto err_source;
wl_map_init(&client->objects, WL_MAP_SERVER_SIDE);
if (wl_map_insert_at(&client->objects, 0, 0, NULL) < 0)
goto err_map;
wl_priv_signal_init(&client->destroy_signal);
if (bind_display(client, display) < 0)
goto err_map;
wl_list_insert(display->client_list.prev, &client->link);
wl_priv_signal_emit(&display->create_client_signal, client);
return client;
err_map:
wl_map_release(&client->objects);
wl_connection_destroy(client->connection);
err_source:
wl_event_source_remove(client->source);
err_client:
free(client);
return NULL;
}
着重看下bind_display函数,它将display与client对象进行绑定,当client端有display相关请求时,会调用client中的相关回调。实现如下:
static int
bind_display(struct wl_client *client, struct wl_display *display)
{
client->display_resource =
wl_resource_create(client, &wl_display_interface, 1, 1);
if (client->display_resource == NULL) {
/* DON'T send no-memory error to client - it has no
* resource to which it could post the event */
return -1;
}
wl_resource_set_implementation(client->display_resource,
&display_interface, display,
destroy_client_display_resource);
return 0;
}
该函数首先创建了一个wl_resource类型对象,将值赋给client->display_resource。实际注册回调是在wl_resource_set_implementation。
WL_EXPORT void
wl_resource_set_implementation(struct wl_resource *resource,
const void *implementation,
void *data, wl_resource_destroy_func_t destroy)
{
resource->object.implementation = implementation;
resource->data = data;
resource->destroy = destroy;
resource->dispatcher = NULL;
}
其中display_interface封装的为对于request相关回调函数指针。
static const struct wl_display_interface display_interface = {
display_sync,
display_get_registry
};
以上就是在启动wayland的server端时做的相关动作,而server端向client端发送event是通过wl_resource_post_event完成。
WL_EXPORT void
wl_resource_post_event(struct wl_resource *resource, uint32_t opcode, ...)
{
union wl_argument args[WL_CLOSURE_MAX_ARGS];
struct wl_object *object = &resource->object;
va_list ap;
va_start(ap, opcode);
wl_argument_from_va_list(object->interface->events[opcode].signature,
args, WL_CLOSURE_MAX_ARGS, ap);
va_end(ap);
wl_resource_post_event_array(resource, opcode, args);
}
第4行为可能出现的参数定义一个数组,wayland规定opcode后续的参数个数最多为20个,数组元素类型为wl_argument,该类型是一个联合体,定义如下:
union wl_argument {
int32_t i; /**< `int` */
uint32_t u; /**< `uint` */
wl_fixed_t f; /**< `fixed` */
const char *s; /**< `string` */
struct wl_object *o; /**< `object` */
uint32_t n; /**< `new_id` */
struct wl_array *a; /**< `array` */
int32_t h; /**< `fd` */
};
可以看到可能出现的参数的类型包括int,uint...等八种。第8行所调用的函数va_start是c语言中对不定参数函数所做的处理,主要目的时将参数堆栈地址赋值给ap,通常与va_end配套使用,具体用法和参考va_start说明文档。而第9行函数wl_argument_from_va_list的主要功能是函数传入参数存入args中。具体发送事件在wl_resource_post_event_array中实现,代码如下:
WL_EXPORT void
wl_resource_post_event_array(struct wl_resource *resource, uint32_t opcode,
union wl_argument *args)
{
handle_array(resource, opcode, args, wl_closure_send);
}
该函数为对handle_array做的封装,handle_array多了一个参数wl_closure_send,该参数为一个函数指针,具体向client端socket发送消息在该函数中实现,该函数的实现如下所示:
int
wl_closure_send(struct wl_closure *closure, struct wl_connection *connection)
{
int size;
uint32_t buffer_size;
uint32_t *buffer;
int result;
if (copy_fds_to_connection(closure, connection))
return -1;
buffer_size = buffer_size_for_closure(closure);
buffer = zalloc(buffer_size * sizeof buffer[0]);
if (buffer == NULL)
return -1;
size = serialize_closure(closure, buffer, buffer_size);
if (size < 0) {
free(buffer);
return -1;
}
result = wl_connection_write(connection, buffer, size);
free(buffer);
return result;
}
可以看到在该函数中
handle_array的具体实现如下:
static void
handle_array(struct wl_resource *resource, uint32_t opcode,
union wl_argument *args,
int (*send_func)(struct wl_closure *, struct wl_connection *))
{
struct wl_closure *closure;
struct wl_object *object = &resource->object;
if (resource->client->error)
return;
if (!verify_objects(resource, opcode, args)) {
resource->client->error = 1;
return;
}
closure = wl_closure_marshal(object, opcode, args,
&object->interface->events[opcode]);
if (closure == NULL) {
resource->client->error = 1;
return;
}
if (send_func(closure, resource->client->connection))
resource->client->error = 1;
log_closure(resource, closure, true);
wl_closure_destroy(closure);
}