简介
与 MiniGUI-Threads 不同,MiniGUI-Processes 上的每个程序都是单独的进程,
每个进程可以建立多个窗口。MiniGUI-Processes 实现了一个多进程窗口系统,
采用客户/服务器架构,通过提供一个服务器程序 mginit ,管理所有的客户程
序。
MiniGUI-Process 引入了层和客户的概念。层,类似于 X Window 的工作区,可以
将来自不同客户进程的窗口放到不同的层中。客户,即用户的程序,每个程序是
单独的进程,每个客户都归属于一个层,层把客户组织到不同的工作区中,多个
客户可在一个层中工作。服务器负责统一管理层和客户。
体系结构
 |
| MiniGUI-Process体系结构图 |
整体上,MiniGUI-Process 采用了客户/服务器架构,服务器负责管窗口 Z 序管理、鼠标和键盘的输入事件截获和分发,客户通过发请求,要求服务器完成某个操作,服务器完成任务后将结果发送给客户。
利用 UNIX 域套接字实现了服务器和客户程序之间的通信。系统级的通信任务包括:
- 鼠标光标的管理,当客户需要创建、销毁光标,改变光标形状、位置,显示
和隐藏光标时,需要发送请求到服务器,服务器完成相应任务后将结果发送给
客户;
- 层管理:当客户需要新建层、删除层、查询层信息、加入某个已有层等操作
时,需要发送请求到服务器;
- 窗口管理:当客户创建、销毁、移动主窗口时,要发送请求到服务器;
- 转发鼠标、光标的输入事件,鼠标和光标的输入事件先由服务器截获,然后再转发给具体的客户。
全局使用的资源装载到共享内存中,供所有应用共享,这些资源包括:光标、位
图、图标、字体。共享内存的实现有两种方式选择,一是用 mmap ,二是使用 System V IPC 接口。下
面的小节会详细介绍。
服务器端数据结构
服务器采用了层/客户的方式来组织所有的应用。
层和客户的数据结构及其关系如下图所示:
 |
| 层和客户的组织 |
所有的层组织成一个双向链表,同一个层内的所有客户组成一个双向链表,客户
链表的头结点的指针保存在层结点中。所有的客户结点保存在一块连续的内存区
域中。如上图所示,有三个层:layer1、layer2、layer3,layer1 的客户有
client7、clientN;layer2 的客户有 client0、client2、client5、client6;
layer3 的客户有 client1、client3、client4。
层结点的结构如下所示:
 |
| 层结点结构 |
每个层结点记录如下信息:
- 层名字;
- 本层客户链表的头结点指针;
- 持有焦点的客户的指针;
- 指向前序层结点的指针;
- 指向后序层结点的指针;
- 本层客户的 Z 序信息。
客户结点的结构如下所示:
 |
| 客户结点结构 |
每个客户结点记录如下信息:
- 客户名字;
- 客户进程的 PID;
- 客户进程的 UID;
- 连接到客户进程的 socket 的文件描述符;
- 指向该客户所属层的指针;
- 前序客户结点;
- 后序客户结点。
基于 Unix 域套结字的客户/服务器通信
一个请求的数据格式如下所示:
 |
| 请求的数据格式 |
请求数据由三部分组成:
- req id,请求编号,请求的唯一标识;
- data len,请求中的数据长度;
- data,请求中的数据。
服务器获取请求时,首先读取请求 id,然后读取数据长度,根据数据长度分配
一个内存,之后将数据读出来放到新申请的内存中,最后根据 id 调用不同的
请求处理函数对请求进行处理。
MiniGUI 中的请求有如下几种:
| req id |
req name |
description |
| 0x0001 |
REQID_LOADCURSOR |
装载光标 |
| 0x0002 |
REQID_CREATECURSOR |
创建光标 |
| 0x0003 |
REQID_DESTROYCURSOR |
删除光标 |
| 0x0004 |
REQID_SETCURSOR |
设置光标 |
| 0x0005 |
REQID_GETCURRENTCURSOR |
获取当前光标 |
| 0x0006 |
REQID_SHOWCURSOR |
显示光标 |
| 0x0007 |
REQID_SETCURSORPOS |
设置光标位置 |
| 0x0008 |
REQID_LAYERINFO |
获取层信息 |
| 0x0009 |
REQID_JOINLAYER |
请求加入层 |
| 0x000A |
REQID_JOINOP |
操作层 |
| 0x000B |
REQID_ZORDEROP |
修改 Z 序信息 |
| ... |
... |
... |
使用 UNIX 域套接字, 服务器和客户的通信如下所示:
 |
| 服务器与客户通信 |
上图是服务器和一个客户的通信过程。服务器创建套接字进入监听状态,当启动客
户程序时,客户创建套接字与服务器建立连接,然后往服务器依次写入
req_id/data_len/data,等待服务器应答;服务器获得连接后,从套接字读取请
求,根据 req_id 做不同的处理,处理完后,根据处理结果发送一条消息给客户,
通知其完成相应的操作 (如窗口显示隐藏、输入事件等);客户从套接字读取消
息后将其放入消息队列中,供窗口过程处理,一次通信结束,最后服务器和客户
需要关闭各自的套接字。Unix 域套接字的接口请查阅相应的手册。
服务器对多个客户连接的管理如下图所示:
 |
| 管理多个连接 |
服务器记录了与自己建立连接的客户的 socket 描述符、进程 id 等信息,并将
这些信息组成一个客户信息数组。服务器依次从各个客户的 socket 描述符中读
取请求,处理后将结果以消息 (MSG) 的方式返回给客户。服务器不断重复这个
过程,实现对客户请求的管理。伪代码如下所示:
while (1) {
for (i = 0; i < maxi; i++) {
/* mgClients have all connected clients info. */
clifd = mgClients [i].fd;
if (clifd < 0)
continue;
/* read request from client. */
nread = socket_read (clifd, &req_id, sizeof (int));
if (nread == SOCKETERROR){
/* remove the disconnected connection. */
remove_client (i, clifd);
}
else {
/* handle requests and return result to client*/
handle_request (req_id);
socket_write (clifd, &ret_value);
}
}
}
从全局看,服务器端要处理两类连接:
- 随时监听新建客户端发来的连接请求;
- 随时监测已经建立的连接是否有数据过来;
为管理这两类请求,服务器端采用下图所示的流程进行处理:
 |
| 连接管理总体流程 |
图中的 listenfd 用于监听是否有新的连接请求,当有新的客户请求连接时,通过 accept (listenfd, ...) 就可以接受
客户的连接请求并建立连接; clifd 用于监测在已建立的连接中是否有数据,
服务器接受客户的连接后会记下每个客户的 clifd,通过依次监测每个客户的
clifd 来决定是否需要读取数据。
使用 select 函数监听上述两类连接。将 listfd 和所有的 clifd 放入 fdset
中,将 fdset 作为参数传给 select,select 返回处于 ready 状态的文件描述
符个数。处于 ready 状态就是有新的连接请求或者在已建立的连接中有数据过
来。 当 select 返回值大于 0 时,说明有描述符处于 ready 状态,通过
FD_ISSET (listenfd) 判断是否有新的连接请求;通过 FD_ISSET (clifd) 判断
是否 clifd 对应的客户发来数据。
对两类连接的监听和处理在服务器端始终存在,将上述流程放在一个循环中,
只要服务器进程在运行,该循环就会一直执行。为了让这个循环不影响服务器进
程的其它工作,可将其放在一个单独的线程中。
进程之间共享资源
有好多资源在各个进程之间是共用的,为了节省内存空间,MiniGUI-Processes
采用了共享内存的方式,即在内存中开辟一块共享内存区,将共用资源加载到这
个共享内存区中,所有进程自己不再单独保存共用资源,而是从共享内存区中取。
资源共享的核心是共享内存的设计,下图给出了 MiniGUI-Processes 定义的共
享内存区的结构示意图。
 |
| 共享内存区 |
MiniGUI-Processes 共享的资源有很多,如位图、字体、光标、图标等等。上图只给出了位图数据和字体数据
的布局,其它共享资源与此类似。
共享内存由两部分组成,一是数据头 (head),它记录了各个数据块的数据属
性,如 bmpnum 记录了位图的个数,bmpoffset 记录了位图数据区在整个共享内
存中的 offset;再比如,fontnum 记录了字体的个数,fontoffset 记录了字体
数据区的 offset。第二部分是数据区,它保存了具体的资源数据,各类资源按
种类依次存放在数据区中,通过数据头的 offset 信息,可以定位某类资源的数
据区地址。
服务器在初始化时创建共享内存,并将共享资源逐个加载到共享内存中,并正确
填写数据头的各个属性数据。当客户应用启动时,就到共享内存中取需要的资源。
在技术上有很多方式实现共享内存,如使用 mmap 或者 System V 进程通信机制
等等。
参考资源
|