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");
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;
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) {
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;
uint32_t u;
wl_fixed_t f;
const char *s;
struct wl_object *o;
uint32_t n;
struct wl_array *a;
int32_t h;
};
可以看到可能出現的參數的類型包括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);
}
————————————————
版權聲明:本文為CSDN部落客「馬劍聖」的原創文章,遵循 CC 4.0 BY-SA 版權協定,轉載請附上原文出處連結及本聲明。
原文連結:https://blog.csdn.net/LK1993/article/details/79957866