Extension简介
X Window的设计有一个优势,就是X Window在核心层之外提供一个扩展层,开发者可以开发相应扩展,来实现自己的扩展协议,比方说:标准的Window都是矩形的,如何用它来画一个圆形的窗口?X Window协议并未提供,但是通过“shape”这个扩展,X Window可以实现不规矩的窗所以,很多年来X Window除了继续完善核心协议、驱动以外,很大程度上,都是扩展使它保持“与时俱进”,比如说:
要多头显示支持,这个是由“Xinerama”扩展实现的;
要有多媒体视频回放的支持,这个是由“X Video”扩展实现的;
OpenGL的3D支持,则是通过“GL”扩展来实现的;
Keyboard的支持,都是通过“X Keyboard Extension”(也就是“XKB”)的!
X Window的核心,基本上就是在处理Server/Client、驱动之类的,而外部的那些支持,基本上全是通过“扩展”进行的。我们熟悉的Xrandr命令也是X Windows的一个扩展。
众所周知,X Windows采用了Server/Client,Extension是运行在Server端,Client端的程序通过X11协议与其通信。
X Window的核心服务是放在一个函数表中的,当客户端程序有请求到来时,X Window通过请求号从函数表中找到对应的服务函数,然后调用它,服务函数执行完成后返回结果。函数表其实也是一种抽象,它让框架不必知道实现的功能,扩展功能时,只要修改这个函数表,而不用修改框架
这个函数表在programs/Xserver/dix/table.c中,其声明如下:
int (* ProcVector[256]) (
ClientPtr /* client */
) =
{
ProcBadRequest,
ProcCreateWindow,
ProcChangeWindowAttributes,
ProcGetWindowAttributes,
ProcDestroyWindow,
ProcDestroySubwindows, /* 5 */
ProcChangeSaveSet,
……………………
ProcGetModifierMapping,
0, /* 120 */
0,
0,
0,
0,
0, /* 125 */
0,
ProcNoOperation
};
这个函数表的前0-127位是X Windows预留的,以后都是从128开始的。
还有一个函数表,叫作SwappedProcVector,它与ProcVector中的函数功能上基本是对应的,特殊之处在于,在调用真正的服务前,先预处理一下请求的数据,客户端程序可能运行在不同字节顺序的机器上,这时候要交换一下数据的字节顺序。我们可以看SProcCreateWindow的实现:
Int SProcCreateWindow(ClientPtr client)
{
char n;
REQUEST(xCreateWindowReq);
swaps(&stuff->length, n);
REQUEST_AT_LEAST_SIZE(xCreateWindowReq);
swapl(&stuff->wid, n);
swapl(&stuff->parent, n);
swaps(&stuff->x, n);
swaps(&stuff->y, n);
swaps(&stuff->width, n);
swaps(&stuff->height, n);
swaps(&stuff->borderWidth, n);
swaps(&stuff->class, n);
swapl(&stuff->visual, n);
swapl(&stuff->mask, n);
SwapRestL(stuff);
return((* ProcVector[X_CreateWindow])(client));
}
有了这种机制,增加新的功能,只需要增一个处理函数就行了,处理函数并需要知道请求从何而来,何时被调用。相反,要裁剪某些功能,亦只需要从这里拿开这个函数就可以了。
如何编写Extension
XWindow中有大量扩展模块,每个扩展模块完成一组相关的功能,把扩展功能从核心功能中剥离出来,可以大大提高X Server的可配置性和扩展性。扩展模块的实现机制如下:每一个扩展模块都有一个初始化函数,这个函数在X Window起动时被调用,在这里面会初始化一些该模块的数据结构,然后会调用AddExtension把相关回调函数注册进去。
AddExtension的函数原型如下:
ExtensionEntry *
AddExtension(char *name, int NumEvents, int NumErrors,
int (*MainProc)(ClientPtr c1),
int (*SwappedMainProc)(ClientPtr c2),
void (*CloseDownProc)(ExtensionEntry *e),
unsigned short (*MinorOpcodeProc)(ClientPtr c3))
l Name: 插件的名称。每个插件都有唯一的名称,Utility的名称为VIAGFX_API。
l NumEvents: 为扩展保留的事件数。
l NumErrors:为扩展保留的错误码数。
l MainProc: 扩展的处理函数。
l SwappedMainProc: 扩展的处理函数,在处理前先交换字节顺序。
l CloseDownProc: 扩展的析构函数。
l MinorOpcodeProc: 用来得到子处理号,一般没有什么用处,在出错时,设置到错误信息里。
从AddExtension的实现中,我们很容易看出,扩展其实也是前面所说的ProcVector来实现的。
i = NumExtensions;
newexts = (ExtensionEntry **) xrealloc(extensions,
(i + 1) * sizeof(ExtensionEntry *));
if (!newexts)
{
xfree(ext->name);
xfree(ext);
return((ExtensionEntry *) NULL);
}
NumExtensions++;
extensions = newexts;
extensions[i] = ext;
ext->index = i;
ext->base = i + EXTENSION_BASE;
ext->CloseDown = CloseDownProc;
ext->MinorOpcode = MinorOpcodeProc;
ProcVector[i + EXTENSION_BASE] = MainProc;
SwappedProcVector[i + EXTENSION_BASE] = SwappedMainProc;
从理论上说,框架完全是独立于扩展的,增加增/删扩展不需要修改框架的代码。但实际情况往往不是这样的,有的扩展依赖框架提供一些特殊功能,有的扩展依赖另外一些扩展,所以在X Server的代码中,常常出现很多ifdef之类的宏,这些宏用来控制是否启用某些扩展。
Client端的编写
Extension是在Server层运行的,而对这些Extension的使用一般是在client端,client的请求处理过程如下。
1. client调用Xlib或者扩展模块的客户端库函数,这些库函数把服务请求数据按X Protocol打包,然后通过XTransport层发送到 X Server端。
2. X Server收到数据包后,根据请求号调用对应的服务函数。ProcVector是服务函数表,扩展也是通过这个表来扩展功能的。
3. 服务函数处理该请求,并调用WriteToClient把结果发送给客户端。
其过程表示如下图
Client端和server端是通过X 协议来通信的,现在使用的是这个协议的第十一个版本,称为X11。X11的协议格式分为两种,一种是请求格式(request format),一种是响应格式(reply format)
X11协议对其通信的格式有其严格的要求,要求client和server端得request和reply的结构体必须一样,否则数据在传输的过程中会出错。
1. 请求格式(Request Format)
每一个请求由请求头和请求体组成。每个请求最小是4个字节。
在每一个请求的最前面,有一个4字节的请求头,其中第一个字节是8位的主操作码(major opcode),接下来的两字节是16位的请求长度(这个长度值也包括头在内),最后的一个字节在本协议里暂时不用。
跟在头后面的是请求体,格式是不固定的,依请求种类而不同。如果实际的请求体内容长度小于头里说明的长度,不必用0来补位。
++++++++ | ++++++++ ++++++++ | ++++++++ | ++++++++++++++++++++++++
m-opcode | length of request | blank | request content ...
++++++++ | ++++++++ ++++++++ | ++++++++ | ++++++++++++++++++++++++
(每个"+"代表一位)
在本核心协议中,主操作码在0~127之间。128~255保留做扩展用。很多情况下,扩展的请求里可能包含多个操作码,这时请求的结构会有一些改变。比如,一种典型的用法是,在头的第二个字节里存放一个副操作码(minor opcode),把长度值移到后面两个字节去。
下面是utility定义的请求格式
typedef struct {
CARD8 reqType;//主操作码
CARD8 VIAGFXReqType;// 副操作码
CARD16 length B16;
CARD32 pad1 B32;
CARD32 primary_id;//以下为utility扩展需求
CARD32 secondary_id;
CARD32 command;
CARD32 parm[VIAGFX_PARM_NUM];
CARD32 result_header;
CARD32 result[VIAGFX_PARM_NUM];
} xVIAGFXCommandReq;
2. 响应格式(Reply Format)
每一个响应由三部分组成:响应头,响应体以及扩展部分,每个响应最小是32个字节
响应头由4字节的组成,并没有特殊的标识,这4个字节中的内容是整个响应的长度。
接下来是32个字节的响应体。
如果响应的内容比较长,超过了32字节,可继续在响应体后接续,这是响应体的扩展部分,长度向字节对齐。
在响应体和扩展部分中如果有空位的话,server并不保证把空位置0。
在响应的内容中还包含所对应请求的序列号。(但协议没有指明哪几个字节是这一内容。)
下面是utility定义的响应格式
typedef struct {
BYTE type; /* X_Reply */ /* Required */
BOOL pad1;
CARD16 sequenceNumber B16; /* Required */
CARD32 length B32; /* Required */
CARD32 primary_id;
CARD32 secondary_id;
CARD32 command;
CARD32 parm[VIAGFX_PARM_NUM];
CARD32 result_header;
CARD32 result[VIAGFX_PARM_NUM];
} xVIAGFXCommandReply;
在client端使用的时候,需要先填写请求结构体,然后使用_XReply函数发送给Server端。在Server端,Extension会根据request填写reply结构体,client端接收到reply结构体以后,就可以取出数据来使用了,下面为utlity中代码
BOOL VIAGFXCommand(Display *dpy, viagfxcommand *viacmd)
{
XExtDisplayInfo *info = find_display (dpy);
xVIAGFXCommandReply rep;
xVIAGFXCommandReq *req;
int i;
VIAGFXCheckExtension(dpy, info, False); //检查是否有所需的Extension,如果没有,直接返回
LockDisplay(dpy);
GetReq(VIAGFXCommand, req);//填写request结构体的一些变量
req->reqType = info->codes->major_opcode;//赋值主操作码
req->VIAGFXReqType = X_VIAGFXCommand;//副操作码
req->primary_id = viacmd->primary_id;
req->secondary_id = viacmd->secondary_id;
req->command = viacmd->command;
// Send Request
for (i=0; i<VIAGFX_PARM_NUM; i++)
{
req->parm[i] = viacmd->parm[i];
}
if(!_XReply(dpy, (xReply *)&rep, (SIZEOF(xVIAGFXCommandReply) - SIZEOF(xReply)) >> 2, xFalse))//通过_XReply函数来传递request
{
UnlockDisplay(dpy);
SyncHandle();
return False;
} //如果返回true,则说明client获得其请求的数据。
// Get Reply
viacmd->result_header = rep.result_header;
if (rep.result_header)
{
for (i=0; i<VIAGFX_PARM_NUM; i++)
{
viacmd->result[i] = rep.result[i];
}
}
UnlockDisplay(dpy);
SyncHandle();
/* Don't forget to flush! */
XFlush(dpy);
return rep.result_header;
}
这种方法的益处是模块化,修改起来比较方便。不好的地方是如果ExtensionInit需要使用ScrnInfoPtr指针,而xxxProbe函数并没有提供得到ScrnInfoPtr指针的方法,就需要另外想办法给其传递ScrnInfoPtr指针。