Wayland协议解析 三 Wayland的工作原理

首先,需要了解wayland的工作原理,需要了解几个其他的内容。

  1. int mkstemp(char *template); // 创建临时文件 (标C接口)
  2. long int strtol(const char *nptr,char **endptr,int base); // 字符串转数字
  3. ssize_t sendmsg (int s, const struct msghdr *msg, int flags); // 可以往其他进程发送fd的函数

众所周知,子进程会继承父进程已经打开的文件描述符fd,但是fork之后的是不会被继承的,这个时候是否无能无力了?答应是NO。Linux提供了一个系统调用sendmsg,借助它,可以实现进程间传递文件描述符fd,而且不仅限于父进程到子进程。sendmsg函数的原型如下:

 

#include

ssize_t sendmsg(int socket, const struct msghdr *message, int flags);

ssize_t recvmsg(int socket, struct msghdr *message, int flags);

 

recvmsg函数用来接收fd,这里的socket必须为UnixSocket(AF_UNIX),在Linux上执行man 7 unix,并搜索SCM_RIGHTS,即看到有关说明:Send or receive a set of open file descriptors from another process。

 

通过sendmsg发送的fd,并不是将fd值传递给目标进程,而是活生生地在目标进程空间里复制指向同一个file结构体的fd,所以不要期望在两个进程中,fd值相同。

 

具体的使用示例,请baidu或google关键词:sendmsg fd,即可找到,这里就不多说了。

 

  1. int ftruncate(int fd,off_t length);   // 修改文件大小
  2. epoll函数  // 实现一个fd监听多个fd的效果

int epoll_create(int size)   // 创建epoll fd描述符

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) // 监听其他的文件描述符

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)   // 启动监听

 

https://baike.baidu.com/item/epoll%E5%87%BD%E6%95%B0/10792875

 

  1. int socket(int domain, int type, int protocol); // 创建一个网络描述符.如果第一个参数是AF_UNIX, 获取到的描述符可以传递文件描述符(通过sendmsg函数), 实现本地的进程间通信

int bind(SOCKET socket, const struct sockaddr* address, socklen_t address_len);

// 在上一个函数使用AF_UNIX参数创建时, bind函数指定一个文件路径来实现进程间通信, 使用结构体(sockaddr_un)保存文件路径

int listen( int sockfd, int backlog); // 启动监听网络套接字

int accept( int fd, struct socketaddr* addr, socklen_t* len); // 接受连接进来的客户端

 

  1. /* Request notification for delivery of signals in MASK to be

performed using descriptor FD.*/

extern int signalfd (int __fd, const sigset_t *__mask, int __flags) // 产生信号监听fd

 

/* Clear all signals from SET.  */

extern int sigemptyset (sigset_t *__set) __THROW __nonnull ((1));

 

/* Set all signals in SET.  */

extern int sigfillset (sigset_t *__set) __THROW __nonnull ((1));

 

/* Add SIGNO to SET.  */

extern int sigaddset (sigset_t *__set, int __signo) __THROW __nonnull ((1));

 

// 让信号在被处理之前程序都处于阻塞状态

extern int sigprocmask (int __how, const sigset_t *__restrict __set,

                    sigset_t *__restrict __oset)

// 以上API  创建一个信号的fd

 

  1. Libffi库,FFI(Foreign Function Interface)允许以一种语言编写的代码调用另一种语言的代码,而libffi库提供了最底层的、与架构相关的、完整的FFI。libffi的作用就相当于编译器,它为多种调用规则提供了一系列高级语言编程接口,然后通过相应接口完成函数调用,底层会根据对应的规则,完成数据准备,生成相应的汇编指令代码。

 

 

我来描述一下,wayland是怎么把上面的知识点给联系起来的。

首先,我们必须要清楚wayland是一个CS架构的协议,所以肯定涉及到通信,但是wayland的CS局限于同一个操作系统,因为底层会利用操作系统的特性来传递进程中的文件描述符(文件句柄),接下来我们分为服务器端和客户端来分析:

  1. 首先服务器端创建一个epoll_fd,为什么需要创建呢?因为服务器端会监听非常多的fd,而epoll_fd是linux最高效的用来监听大量fd的方法(sylixos操作系统的epoll_fd是模拟出来的,不具有linux那样的高效特性,但是确实还是支持,只是不能去select epoll_fd)。
  2. 服务器端创建一个绑定到指定文件(默认文件名为wayland-%d,路径由XDG_RUNTIME_DIR环境变量指定)的socket,并把这个socket添加到epoll_fd,在此之前,服务器端还需要注册很多客户端能用的服务(通过这个接口注册:wl_global_create),并开始等待客户端的连接,随后连接进来的socket也会添加到epoll_fd里面一起监听。
  3. 这时客户端可以开始连接服务器端了,通过wl_display_connect返回客户端wl_display句柄,这个结构体内部就包含了socket连接的fd,并可以和服务器进行通信了。
  4. 客户端调用的第一个请求只能是获取服务器端有哪些服务可以使用(因为其他的接口都是各种服务提供的请求):wl_display_get_registry,该请求返回一个结构体wl_registry,就是我前面说的客户端所有的interface对象实际上都是wl_proxy。然后给这个结构体设置事件处理函数,这样客户端在接收到服务器端的消息的时候会转换成函数调用,调用到这个事件处理函数。(这里我要说明一点。客户端和服务器通信的数据全是函数调用的序列化数据(包括函数调用的参数及函数名(函数名用一个数字代表)),接收端再反序列化为函数调用,最后去调用该函数),这个函数调用用到了libffi的接口,函数序列化数据传递用的sendmsg接口,可以额外传递文件描述符,但是传递方式和一般的不太一样,所以才会大费周章的把fd做为特殊的参数类型,就是在这里用的。

 

以上就是简单的描述wayland的工作原理,下面开始更详细的描述wayland的工作原理。不过,在进行更详细的原理介绍之前,我必须要给大家把wayland通过的接口介绍一遍,其实wayland接口明白之后,读者应该就能明白大概的原理了.

你可能感兴趣的:(Wayland)