1.3 X11 Server
- 1 架构
- 2 源码目录树
- 3 xfree86处理流程
- 3.1 Dispatch
- 3.2 WaitForSomething
- 3.3 client调度机制
- 4 DDX函数
- 4.1 初始化屏幕输出
- 4.2 初始化屏幕输入
- 4.3 输入事件处理函数
- 4.4 退出server处理函数
- 4.5 屏幕初始化函数
- 5 xfree86驱动接口
- 6 Extensions代码框架
- 7 事件处理机制
介绍xorg Server实现框架,引导代码阅读
架构
X11图形系统为C/S架构,Server管理输入输出设备等资源,Client发起请求操作图形等。
DDX:设备相关的X实现,包括键盘,鼠标等输入设备,显示等输出设备
DIX:设备无关的X实现,包括任务的分发,事件队列,扩展相关实现等
Extensions:扩展协议,相对X11核心协议,其增加了client与server之间的交互功能,包括请求和事件
Inputs:输入设备包括键盘、鼠标、触控板、触摸屏等
https://x.org/wiki/guide/concepts/
源码目录树
介绍关键目录文件功能,除了hw和os相关代码,其它代码可以共享。扩展协议中与硬件相关的实现通过回调函数来抽象,硬件相关代码需实现函数功能。
Name | Explanation |
---|---|
dix/ | 设备无关部分的代码实现,如:请求的分发和X资源处理 |
exa/ | 硬件设备驱动开发框架,xfree86实现依赖该框架 |
fb/ | 针对帧缓冲的图形操作实现代码,如:图像的位传输和合成等操作 |
glamor/ | 2D和3D图形操作实现,支持OpenEGL硬件渲染 |
hw/kdrive/ephyr/ | 嵌套的X11 Server实现,其屏幕输出在X11 Server的一个窗口中 |
hw/vfb/ | 虚拟帧缓冲的X11 server实现 |
hw/xfree86/ | X11 server原生实现,运行在类unix操作系统上,如:Linux,BSD等 |
hw/xnest/ | 嵌套X11 Server的实现,类似于一个X11代理server |
hw/xquartz/ | 运行在Mac OS X平台上的X11 server实现, |
hw/xwayland/ | 运行在Wayland平台上的X11 server实现 |
hw/xwin/ | 运行在window平台上的Cygwin/X 代码 |
mi/ | 与机器无关核心协议代码,通过调用screens、windows和graphic contexts等的回调函数来操作调用fb/下的代码 |
miext/ | 与机器无关的扩展协议代码,如:SyncFence,Damage等 |
os/ | 与硬件无关的操作系统相关代码,如:认证,命令行参数处理等 |
composite/ | composite扩展实现,实现窗口合成功能 |
damageext/ | damage扩展实现,跟踪窗口内容变化区域 |
dbe/ | 实现dbe扩展协议,支持窗口的双缓冲功能 |
dri3/ | 实现DRI3扩展协议,支持client直接渲染 |
glx/ | 实现GLX扩展协议,支持GLX硬件渲染 |
present/ | 实现present扩展协议,支持帧缓冲翻页等硬件特性 |
randr/ | 实现randr扩展协议,支持缩放,旋转,投射等功能 |
render/ | 实现render扩展协议,支持更多的渲染操作,增加了Picture等类型 |
Xext/ | 多种扩展协议的实现,如:XvMC,Xv,Xshm,Xfence,Xshape,Xdpms等 |
xfixes/ |
|
Xi/ | 实现XI2扩展协议,支持多种输入设备功能,如多点触控等 |
xkb/ | 实现xkb扩展协议,支持各种键盘设备功能 |
注:
Xephyr和Xnest实现的功能将被xf86-video-nested驱动代码
Xvfb实现的功能将被xf86-video-dummy驱动代码
xfree86处理流程
Dispatch
任务分发主函数。
void Dispatch(void){
// skipped
while (!dispatchException) {
if (InputCheckPending()) {
// 处理输入事件
ProcessInputEvents();
// 处理输入事件时产生关键事件等,在此发送
FlushIfCriticalOutputPending();
}
// 阻塞等待,当client请求,输入事件,视频事件等发生时返回
if (!WaitForSomething(clients_are_ready()))
continue;
// client请求处理
if (!dispatchException && clients_are_ready()) {
// 分发处理无异常且有client请求,选择其中一个client处理
client = SmartScheduleClient();
isItTimeToYield = FALSE;
start_tick = SmartScheduleTime;
// client出错,优先级改变,处理事件过长等,都会结束循环
while (!isItTimeToYield) {
// 处理键盘,鼠标等输入事件
if (InputCheckPending())
ProcessInputEvents();
FlushIfCriticalOutputPending();
// 分时调度,处理单个client时间过长(15ms时间片),降低其优先级,且重新调度
if ((SmartScheduleTime - start_tick) >= SmartScheduleSlice){
if (client->smart_priority > SMART_MIN_PRIORITY)
client->smart_priority--;
break;
}
// 读取client请求信息,返回请求长度,result为0,请求处理完成,进行下一个调度
result = ReadRequestFromClient(client);
if (result <= 0) {
if (result < 0)
CloseDownClient(client);
break;
}
client->sequence++;
client->majorOp = ((xReq *) client->requestBuffer)->reqType;
client->minorOp = 0;
if (client->majorOp >= EXTENSION_BASE) {
// 扩展协议请求,获取扩展协议的操作码minorOp,以区分请求类型
ExtensionEntry *ext = GetExtensionEntry(client->majorOp);
if (ext)
client->minorOp = ext->MinorOpcode(client);
}
// 请求长度过大,不符合规范
if (result > (maxBigRequestSize << 2))
result = BadLength;
else {
result = XaceHookDispatch(client, client->majorOp);
if (result == Success) {
currentClient = client;
// 处理请求
result = (*client->requestVector[client->majorOp]) (client);
currentClient = NULL;
}
}
// 更新client运行时间,可能使用timer更新事件(SIGALRM)
if (!SmartScheduleSignalEnable)
SmartScheduleTime = GetTimeInMillis();
if (client->noClientException != Success) {
// client异常,关闭该client
CloseDownClient(client);
break;
}
else if (result != Success) {
// 处理请求失败,发送错误码给client,如:BadLength
SendErrorToClient(client, client->majorOp, client->minorOp, client->errorValue, result);
break;
}
}
// 发送请求回复或者事件
FlushAllOutput();
if (client == SmartLastClient)
client->smart_stop_tick = SmartScheduleTime;
}
// 若client改变了自己的优先级,则需重新调度client
dispatchException &= ~DE_PRIORITYCHANGE;
}
// server被重置,则进入下一轮运行。server被中断,则退出执行
KillAllClients();
dispatchException &= ~DE_RESET;
SmartScheduleLatencyLimited = 0;
ResetOsBuffers();
}
WaitForSomething
使用同步多路IO阻塞等待和定时器功能。
// are_ready是否有输入事件
// 等待和处理定时任务。出错或有事件需处理时返回
Bool WaitForSomething(Bool are_ready){
// skipped
timer_is_running = were_ready;
if (were_ready && !are_ready) {
// 无输入事件,停止调度定时器
timer_is_running = FALSE;
SmartScheduleStopTimer();
}
were_ready = FALSE;
busfault_check();
while (1) {
// 执行工作队列中的处理函数
ProcessWorkQueue();
// 处理定时任务,返回下次超时时间
timeout = check_timers();
// 检查是否有设备的输入事件
are_ready = clients_are_ready();
if (are_ready)
timeout = 0; // 有输入事件等待处理,即时返回
// 阻塞任务,通过超时处理,可能会修改timeout时间
BlockHandler(&timeout);
if (NewOutputPending)
FlushAllOutput();
/* keep this check close to select() call to minimize race */
if (dispatchException)
i = -1; // 出现异常
else
i = ospoll_wait(server_poll, timeout);
pollerr = GetErrno();
// 执行操作
WakeupHandler(i);
if (i <= 0) { /* An error or timeout occurred */
if (dispatchException)
return FALSE;
// skipped
} else
are_ready = clients_are_ready();
// 有设备输入事件
if (InputCheckPending())
return FALSE;
if (are_ready) {
were_ready = TRUE;
if (!timer_is_running)
SmartScheduleStartTimer(); // 启动调度计时器,5ms为一个时间片
return TRUE;
}
}
}
client调度机制
采用优先级和分时调度系统来选择client,响应该client的请求。client有两种优先级,静态优先级和动态优先级(smart priority)。优先比较静态优先级,在比较动态优先级。
分时调度的基础时间片为5ms,连续处理一个client超过一个时间片时重新调度client。当长时间只有一个活动client时,动态调整时间片,每1秒钟增加5ms,时间片最大不超过15ms;当有多个活动client时,时间片恢复至5ms。
动态优先级范围为[-20,0],当连续处理client请求超过1个时间片时,动态优先级减一;当连续空间时间超过2个时间片时,动态优先级加一。
// 选择一个client,处理该client的请求
static ClientPtr SmartScheduleClient(void);
DDX函数
不同功能,运行不同平台上的X11 server,都需重新实现这些函数,接口保持一致。通过预编译宏来选择静态编译X11 server。
初始化屏幕输出
发现并初始化输出设备,硬件设备或者虚拟设备
初始化ScreenInfo数据结构
初始化屏幕输入
发现并初始化输入设备(键盘,鼠标等),硬件设备或者虚拟设备
输入事件处理函数
处理输入事件,InternalEvent类型。这些事件由硬件输入处理模块将硬件事件转换成InternalEvent类型事件,插入到队列中,通过管道方式通知处理。mieqProcessInputEvents封装了通用实现。
一次调用处理队列中所有输入事件
退出server处理函数
server退出时调用函数,进行资源回收
屏幕初始化函数
初始化屏幕尺寸,分配一个前端帧缓冲(front framebuffer),为每个CRTC分配硬件光标帧缓冲(cursor framebuffer),计算硬件光标尺寸等,设置屏幕的视觉类型(像素深度,色彩格式等)
根据像素深度初始化帧缓冲(framebuffer)的视觉类型(格式和像素深度),初始化屏幕的窗口,pixmap,colormap,图形等处理函数,以及默认值参数。(fbScreenInit)
初始化Screen的图像(picture)处理函数 (fbPictureInit)
初始化屏幕与光标相关的处理函数(machine independent cursor display routines)。初始化光标(xf86PointerScreenFuncs)与屏幕和精灵(miSpritePointerFuncs)相关的处理函数(miDCInitialize)
调用extension协议的ScreenInit回调函数初始化Screen的私有属性,窗口操作、图形操作等回调函数,如:DPMS,Xv,RandR等
如:xf86ScreenInit,xwl_screen_init。
xfree86驱动接口
xfree86驱动接口定义了DDX的输入输出设备驱动接口,驱动以模块(动态库)的形式提供。
实现模块加载和卸载函数,调用xf86AddDriver和xf86DeleteDriver注册和卸载驱动,及定义模块描述信息
实现硬件特性查询函数(driverFunc)
实现获取硬件可用选项的函数(AvailableOptions)(硬件配置)
实现设备驱动探测函数。共有3种设备探测方式
probe:老版探测设备方法,通过配置文件配置
PciProbe:通过扫描pci设备进行探测设备,使用libpciaccess库实现
platformProbe:通过dbus机制探测设备
xfree86使用udev处理设备热插拔时,udev根据驱动名称加载无需探测函数
xfree86显卡驱动需实现的接口,X11 server提供默认驱动modesetting,其它厂商的显卡在第3方库中,如:xf86-video-amdgpu,xf86-video-intel等。
xfree86通用层实现接口
Probe:探测是否能驱动该设备,目前不再使用该函数进行探测,而是由xfree86驱动实现该功能
PreInit:根据视频硬件设备和配置信息初始化屏幕内容
EnableDisableFBAccess:使能帧缓冲的访问
驱动独自实现接口
ScreenInit:屏幕初始化接口,DIX层为每个屏幕调用初始化函数
FreeScreen:是否屏幕资源
SwitchMode:切换视频模式
EnterVT:进入控制台,获得控制台的控制权
LeaveVT:离开控制台,释放控制台的控制权
ValidMode:检测特定硬件约束的视频模式
AdjustFrame:更新视口(viewport),如:视口缩放
扩展协议实现依赖接口:若xfree86 video mode,Xv,randr等扩展协议
https://www.x.org/releases/current/doc/xorg-server/ddxDesign.html
Extensions代码框架
扩展协议组件一般可以组件方式存在,通过配置来加载和初始化扩展协议,一般使静态加载。
NumEvents:定义事件数量,可以没有
NumErrors:定义错误编号数量,可以没有
MainProc:请求处理函数
SwappedMainProc:请求处理函数,与client端的字节序相反时,调用该处理函数
CloseDownProc:清理函数,一般为空
MinorOpcodeProc:次操作码处理,X11协议中有个次操作码,扩展协议可根据需要进行转换。
InitExtension:实现初始化函数,注册资源类型,定义扩展私有属性,封装屏幕回调函数,调用AddExtension注册扩展协议等
注:扩展协议全局的事件编号和请求编号在server启动时确定。
X11 server对每个扩展协议分配一个请求操作码,扩展协议的起始请求操作码从128其,128以下的请求码为核心协议的请求。
X11 server对扩展协议的事件编号,起始编号为64,扩展协议初始化顺序累计编号,总数不能超过64个。
事件处理机制
所有事件在主流程中处理,使用操作系统poll机制的同步多路IO响应IO事件。
各类IO事件注册和处理函数列表,均在主线程上下文处理。
注册函数 | 处理函数 | 文件或句柄 | 说明 |
---|---|---|---|
SetNotifyFd | EstablishNewConnections | /tmp/.X11-unix/X0 | server监听client连接 |
ospoll_add | ClientReady | client connection | 监听client请求 |
SetNotifyFd | ms_drm_socket_handler | dev/dri/card0 | drm事件处理 |
SetNotifyFd | xf86ReadInput | input device fd | 响应输入设备IO |
SetNotifyFd | socket_handler | udev monitor netlink | 设备热插拔 |
SetNotifyFd | socket_handler | DBUS_BUS_SYSTEM | 设备暂停或恢复 |
xf86AddGeneralHandler | drmmode_handle_uevents | udev monitor netlink | 显示器插拔 |
xf86AddGeneralHandler | xf86HandlePMEvents | /var/run/acpid.socket | ACPI电源管理 |
输入设备事件
为了快速响应输入设备事件,xorg server使用一个专门线程响应输入事件,并将输入事件转换为InternalEvent类型事件,插入输入事件队列中,再交由主线程处理输入事件。
定义了两个pipe队列:hotplug和read/write
主线程监听到设备的热插拔,对设备处理后,通过写入hotplug一个字节数据通知IO线程响应设备IO事件,IO线程监听新增设备IO事件(InputReady)
主线程监听read/write队列的读事件,当IO线程收到输入设备IO事件后,将IO事件转换成InternalEvent事件,并插入miEventQueue循环队列中,向read/write队列写一个字节数据通知主线程,主线程调用ProcessInputEvents函数处理事件。
两个线程共享队列数据接口,采用了一把锁进行互斥,在队列操作,输入设备操作等都加锁了。IO事件处理的互斥操作会影响交互性能。
注:xwayland未使用单独线程处理IO事件
https://x.org/wiki/Development/Documentation/InputEventProcessing/
设备管理
xserver的xfree86实现使用udev机制发现设备,注册udev的socket_handler处理函数。
NewGPUDeviceRequest / DeleteGPUDeviceRequest:处理插入或拔出的drm GPU设备
NewInputDeviceRequest / remove_devices:处理插入或拔出的输入设备
使用udev显示器的热插拔,modesetting驱动注册drmmode_handle_uevents处理函数
采用dbus的system-logind处理设备的暂停与恢复,注册dbus的socket_handler处理函数,使用system-logind的message_filter响应dbus的设备暂停或恢复消息。
注:均在主线程中使用同步多路IO响应事件。
工作队列
工作队列中的请求,在每次处理轮回中执行,共有注册了四个处理函数:
compScreenUpdate:将子窗口内容绘制父窗口中
compRepaintBorder:重绘窗口边框
xf86libinput_hotplug_device_cb:设备插拔,处理子设备的初始化(libinput)
update_mode_prop_cb:设备状态更新(libinput)
回调链表
某些状态、动作、发生时,扩展协议或平台的实现需要对这些事件进行处理。如:属性,状态等发生变化
名称 | 处理函数 | 执行时机 |
---|---|---|
ClientStateCallback | RRClientCallback | client状态变化时调用 |
RootWindowFinalizeCallback | AddVTAtoms AddSeatId addEDIDProp xwl_root_window_finalized_callback | 创建根窗口后调用 |
PropertyStateCallback | xwl_property_callback | 窗口属性发生变化时调用 |
SelectionCallback | wm_selection_callback XFixesSelectionCallback | client发起select操作时调用 |
ResourceStateCallback | SELinuxResourceState | 资源状态发生改变时调用 |
miCallbacksWhenDrained | N/A | 此轮输入事件处理完成后调用 |
阻塞唤醒处理函数
阻塞函数(BlockHandler)是在epoll等待事件之前执行的函数,唤醒函数(WakeupHandler)是在epoll等待之后调用。有的扩展协议使用该方式实现定时器功能,在阻塞函数中计算超时事件,在唤醒函数中检查条件,满足时执行任务,如SyncCounter的实现。这些任务是临时性的,执行一次之后可不再执行(回调函数清空)。
系统处理函数
调用RegisterBlockAndWakeupHandlers动态注册阻塞唤醒函数,插入到handlers队列中。
BlockHandler | WakeupHandler | description |
---|---|---|
IdleTimeBlockHandler | IdleTimeWakeupHandler | SyncCounter的实现,定时执行任务 |
DRIBlockHandler | DRIWakeupHandler | DRI1相关实现,已淘汰 |
fs_block_handler | FontWakeup | 字体相关操作 |
block_handler | wakeup_handler | xwayland发送所有请求给Wayland server |
N/A | xf86Wakeup | 进入图形界面相关操作 |
屏幕处理函数
与Screen相关的处理函数,以下函数封装成函数链,调用Screen的BlockHandler和WakeupHandler回调函数即可。
BlockHandler | WakeupHandler | description |
---|---|---|
DRIDoBlockHandler | DRIDoWakeupHandler | DRI1相关实现,已淘汰 |
ExaBlockHandler | ExaWakeupHandler | 与exa驱动框架相关,已淘汰 |
_glamor_block_handler | N/A | 执行挂起(未处理)的渲染请求 |
miSpriteBlockHandler | N/A | 显示不再帧缓冲的光标 |
msBlockHandler | N/A | 处理屏幕显示相关操作 |
xf86RotateBlockHandler | N/A | 处理屏幕旋转 |