1.3 X11 Server

介绍xorg Server实现框架,引导代码阅读

架构

X11图形系统为C/S架构,Server管理输入输出设备等资源,Client发起请求操作图形等。

  • DDX:设备相关的X实现,包括键盘,鼠标等输入设备,显示等输出设备

  • DIX:设备无关的X实现,包括任务的分发,事件队列,扩展相关实现等

  • Extensions:扩展协议,相对X11核心协议,其增加了client与server之间的交互功能,包括请求和事件

  • Inputs:输入设备包括键盘、鼠标、触控板、触摸屏等

image-20240301-070059.png

https://x.org/wiki/guide/concepts/

源码目录树

介绍关键目录文件功能,除了hw和os相关代码,其它代码可以共享。扩展协议中与硬件相关的实现通过回调函数来抽象,硬件相关代码需实现函数功能。

Name

Explanation

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扩展协议,支持各种键盘设备功能

注:

  1. Xephyr和Xnest实现的功能将被xf86-video-nested驱动代码

  2. 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的输入输出设备驱动接口,驱动以模块(动态库)的形式提供。

  1. 实现模块加载和卸载函数,调用xf86AddDriver和xf86DeleteDriver注册和卸载驱动,及定义模块描述信息

  2. 实现硬件特性查询函数(driverFunc)

  3. 实现获取硬件可用选项的函数(AvailableOptions)(硬件配置)

  4. 实现设备驱动探测函数。共有3种设备探测方式

    1. probe:老版探测设备方法,通过配置文件配置

    2. PciProbe:通过扫描pci设备进行探测设备,使用libpciaccess库实现

    3. platformProbe:通过dbus机制探测设备

    4. xfree86使用udev处理设备热插拔时,udev根据驱动名称加载无需探测函数

xfree86显卡驱动需实现的接口,X11 server提供默认驱动modesetting,其它厂商的显卡在第3方库中,如:xf86-video-amdgpu,xf86-video-intel等。

  1. xfree86通用层实现接口

    1. Probe:探测是否能驱动该设备,目前不再使用该函数进行探测,而是由xfree86驱动实现该功能

    2. PreInit:根据视频硬件设备和配置信息初始化屏幕内容

    3. EnableDisableFBAccess:使能帧缓冲的访问

  2. 驱动独自实现接口

    1. ScreenInit:屏幕初始化接口,DIX层为每个屏幕调用初始化函数

    2. FreeScreen:是否屏幕资源

    3. SwitchMode:切换视频模式

    4. EnterVT:进入控制台,获得控制台的控制权

    5. LeaveVT:离开控制台,释放控制台的控制权

    6. ValidMode:检测特定硬件约束的视频模式

    7. AdjustFrame:更新视口(viewport),如:视口缩放

  3. 扩展协议实现依赖接口:若xfree86 video mode,Xv,randr等扩展协议

https://www.x.org/releases/current/doc/xorg-server/ddxDesign.html

Extensions代码框架

扩展协议组件一般可以组件方式存在,通过配置来加载和初始化扩展协议,一般使静态加载。

  1. NumEvents:定义事件数量,可以没有

  2. NumErrors:定义错误编号数量,可以没有

  3. MainProc:请求处理函数

  4. SwappedMainProc:请求处理函数,与client端的字节序相反时,调用该处理函数

  5. CloseDownProc:清理函数,一般为空

  6. MinorOpcodeProc:次操作码处理,X11协议中有个次操作码,扩展协议可根据需要进行转换。

  7. 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

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

BlockHandler

WakeupHandler

description

DRIDoBlockHandler

DRIDoWakeupHandler

DRI1相关实现,已淘汰

ExaBlockHandler

ExaWakeupHandler

与exa驱动框架相关,已淘汰

_glamor_block_handler

N/A

执行挂起(未处理)的渲染请求

miSpriteBlockHandler

N/A

显示不再帧缓冲的光标

msBlockHandler

N/A

处理屏幕显示相关操作

xf86RotateBlockHandler

N/A

处理屏幕旋转