xserver框架浅析

2024-01-06 16:48
文章标签 框架 浅析 xserver

本文主要是介绍xserver框架浅析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

这个我们应该分成几大块来说吧,
kdrive和xorg的处理是很类似的。
kdrive的驱动都在这个目录下面
hw/kdrive/linux
大家看到有键盘,鼠标,触摸屏,evdev等等的驱动。

其实说白了,在linux系统上面,驱动是分好几部分的,如果我们从最上层来看,我们看到的是图形界面,也就是xserver,实际上xserver这里有这里的驱动层。
以input为例吧,我们看看这个地方。
http://xorg.freedesktop.org/releases/individual/driver/
这个地方凡是input的,都是input的设备的驱动,自然这个只是xserver的驱动,而xserver的驱动是建立在底层驱动基础之上的,
我们可以简单的理解为,xserver的驱动实际上只是提供给xserver一个配置,过滤,或者改写,或者fake这些事件的一种机制。
至于底层的驱动,就是我们通常所说的LDD(linux device driver)了,或者可以这么理解,如果要xserver接收到事件,我们需要在底层驱动和xserver之间做一个中继。

至于这个大体的流程,可以简单的描述如下,
内核里面的驱动一般来说会提供一个设备文件,无论是最早的 2.4的内核的驱动,或者是 2.6的驱动,本质并没有变化,
以前我们使用register_char_device类似这样的函数,而现在我们可能使用platform_register_device.
以前我们需要手动在/dev/目录下面创建设备节点,现在udev会自动帮助我们创建这个节点。但是本质来说并没有变化。
还是设备文件。至于设备文件为什么与普通文件不同,这个是由文件系统来实现的。每个文件系统有不同,如果有兴趣自己看看内核代码吧。
linux下面的东西都是建立在文件系统的基础之上的。或者说至少这是一个目标吧。


说多了,
/dev/input/event0
现在的设备驱动慢慢都使用evdev的框架了,所以我们以evdev为例来说明,
比如这个设备,是一个输入设备,我们先假定这个是一个键盘吧,实际上这里看不出来的,可以到sysfs里面去看,到底是什么东西。
这个是底层驱动提供的一个设备文件。通过kobject发送uevnt到用户空间,然后udev会根据这个uevent来创建设备文件。所以这个major,minor device
就不用我们担心了。

然后xserver的驱动呢,实际上会打开这些设备文件来读,然后向xserver的event queue里面汇报这些事件。然后xserver会把这些事件发送到对应的window去,
这样对应的window就会收到事件了,

xserver的驱动汇报事件的函数很简单,对于kdrive来说。
鼠标
KdEnqueuePointerEvent
示例如下
                        KdEnqueuePointerEvent(pi, flags, ke->rel[a], 0, 0);

键盘
KdEnqueueKeyboardEvent
示例如下
        KdEnqueueKeyboardEvent (ki, events[i].code, !events[i].value);

对于键盘来说比较简单,
我们要带一个设备信息,这样xserver知道这个事件是从谁来的,一个按键的键值,知道是哪个按键,一个value,是按下,还是弹起,或者是repeat

鼠标就比较简单了,
就是坐标,是相对的还是绝对的。
但是新的xserver 1.6.0里面和1.5.3已经有了变化,参数的个数变了,所以驱动要更新一下。

因为kdrive和xorg本身这里处理基本是一样的,我们就简单点,以xorg的来说明,假定现在我们只有/dev/input/event0
基本的流程就是
a)读取/dev/input/event0,注意这里是非阻塞的,稍后说明
b)读取到事件之后,我们就report

其他的还有一些都是外围的处理,重要性相对较低,比如打开设备文件了,或者关闭了等等。

为什么这里的读是非阻塞的,原因是这样的,因为xserver本身要处理很多设备文件,比如我们可能同时有鼠标键盘,触摸屏,手写板等等,所以这里是不能被阻塞的,
我们只能是非阻塞的读,那么这里xserver的这个input的机制到底是怎么样的呢,实际上这里用的是信号处理,只要有SigIO来的时候,就会调用所有的设备的read的函数
这个信息实际上是存在一个数组里面的,每个fd有自己的read函数,当Sigio到来的时候,每个read函数就会读自己的fd,具体的代码在这个地方。

以1.6.0的代码为例
xorg的实现是在这个文件里面
hw/xfree86/os-support/shared/sigio.c

static void
xf86SIGIO (int sig)
{
    int        i;
    fd_set  ready;
    struct timeval  to;
    int save_errno = errno;    /* do not clobber the global errno */
    int        r;

    ready = xf86SigIOMask;
    to.tv_sec = 0;
    to.tv_usec = 0;
    SYSCALL (r = select (xf86SigIOMaxFd, &ready, 0, 0, &to));
    for (i = 0; r > 0 && i < xf86SigIOMax; i++)
    if (xf86SigIOFuncs[i].f && FD_ISSET (xf86SigIOFuncs[i].fd, &ready))
    {
        (*xf86SigIOFuncs[i].f)(xf86SigIOFuncs[i].fd,
                   xf86SigIOFuncs[i].closure);
        r--;
    }
    if (r > 0) {
      xf86Msg(X_ERROR, "SIGIO %d descriptors not handled/n", r);
    }
    /* restore global errno */
    errno = save_errno;
}


kdrive的实现相当精简,也很容易看懂在kinput.c里面
static void
KdSigio (int sig)
{
    int    i;

    for (i = 0; i < kdNumInputFds; i++)
    (*kdInputFds[i].read) (kdInputFds[i].fd, kdInputFds[i].closure);
}

大家要问这些read是什么时候注册的或者说这些fd是什么时候打开的。
这个和xserver的输入设备自动探测有关。
以kdrive为例。
linux平台上面,设备自动探测无非是和下面的几个因素有关。
kernel driver(这个提供kobject uevent)
udev
hald
dbus
大概就是上面几个要素。
所以xserver也不例外,是通过dbus和hald来实现的。
代码在xserver的根路径的config目录下面,主要和两个文件有关
hal.c
dbus.c

很明显了,xserver起来的时候首先就要驱动所有的输入设备,这个没有hald是不行的,这个和所有的volume manager是一样的。
起来的时候就要自动探测所有的块设备,然后mount

还有另外一个过程,就是xserver起来之后,再热插拔设备,这个是需要dbus消息的。

hal.c里面的核心代码在
    libhal_ctx_set_device_added(info->hal_ctx, device_added);
    libhal_ctx_set_device_removed(info->hal_ctx, device_removed);

    devices = libhal_find_device_by_capability(info->hal_ctx, "input",
                                               &num_devices, &error);
    /* FIXME: Get default devices if error is set. */
    for (i = 0; i < num_devices; i++)
        device_added(info->hal_ctx, devices[i]);

也就是说只要有input属性的设备到来,就会调用device_added
然后就会调用NewInputDeviceRequest
注意这里还有一个过程,就是NewInputDeviceRequest之前,我们是需要设置一些属性的,就是,比如这个设备来了,我们要使用什么驱动来驱动这个设备。
这个东西是从hal里面获取到的,比如鼠标键盘我们现在都基本使用了evdev的driver,所以这个配置是在hald的配置里面,就是hald的那堆fdi的配置里面
比如发现是键盘就merge_key evdev这样的,所以xorg要支持热插拔是需要hald的支持的,包括驱动的种类是在hald里面设定的。
NewInputDeviceRequest这个函数的话,不同的xserver实现就不一样了,xorg有xorg的实现在
hw/xfree86/common/xf86Xinput.c

xf86NewInputDevice
具体的,驱动里面enable的时候会注册,比如xf86-input-evdev里面在evdevon这个函数里面
xf86AddEnabledDevice

这个是xorg提供的函数
_X_EXPORT void
xf86AddEnabledDevice(InputInfoPtr pInfo)
{
    if (!xf86InstallSIGIOHandler (pInfo->fd, xf86SigioReadInput, pInfo)) {
    AddEnabledDevice(pInfo->fd);
    }
}

下面就很容易理解了。
_X_EXPORT void
AddEnabledDevice(int fd)
{
    FD_SET(fd, &EnabledDevices);
    AddGeneralSocket(fd);
}
xorg里面默认的读的函数是这个。

xf86SigioReadInput
static void
xf86SigioReadInput(int fd,
           void *closure)
{
    int errno_save = errno;
    int sigstate = xf86BlockSIGIO();
    InputInfoPtr pInfo = (InputInfoPtr) closure;

    pInfo->read_input(pInfo);

    xf86UnblockSIGIO(sigstate);
    errno = errno_save;
}
而这个函数就是input驱动来注册的。
比如evdev里面是这个函数EvdevReadInput
static InputInfoPtr
EvdevPreInit(InputDriverPtr drv, IDevPtr dev, int flags)
{
    InputInfoPtr pInfo;
    const char *device;
    EvdevPtr pEvdev;

    if (!(pInfo = xf86AllocateInput(drv, 0)))
    return NULL;

    /* Initialise the InputInfoRec. */
    pInfo->name = dev->identifier;
    pInfo->flags = 0;
    pInfo->type_name = "UNKNOWN";
    pInfo->device_control = EvdevProc;
    pInfo->read_input = EvdevReadInput;
    pInfo->history_size = 0;
    pInfo->control_proc = NULL;
    pInfo->close_proc = NULL;
    pInfo->switch_mode = NULL;
    pInfo->conversion_proc = NULL;
    pInfo->reverse_conversion_proc = NULL;
    pInfo->dev = NULL;
    pInfo->private_flags = 0;
    pInfo->always_core_feedback = NULL;
    pInfo->conf_idev = dev;



而kdrive的实现是在
hw/kdrive/src/kinput.c
kdrive的实现很简单看属性,然后调用下面的某个
KdNewPointer
KdNewKeyboard
然后添加设备
KdAddPointer
或者
KdAddKeyboard
以keyboard为例
KdAddKeyboard (KdKeyboardInfo *ki)
{
    KdKeyboardInfo **prev;

    if (!ki)
        return !Success;
    
    ki->dixdev = AddInputDevice(serverClient, KdKeyboardProc, TRUE);
    if (!ki->dixdev) {
        ErrorF("Couldn't register keyboard device %s/n",
               ki->name ? ki->name : "(unnamed)");
        return !Success;
    }

    RegisterOtherDevice(ki->dixdev);

#ifdef DEBUG
    ErrorF("added keyboard %s with dix id %d/n", ki->name, ki->dixdev->id);
#endif

    for (prev = &kdKeyboards; *prev; prev = &(*prev)->next);
    *prev = ki;

    return Success;
}

至于注册fd是这个函数,当然这个是在kdrive的驱动evdev里面的例子
    if (!KdRegisterFd (fd, EvdevPtrRead, pi)) {

Bool
KdRegisterFd (int fd, void (*read) (int fd, void *closure), void *closure)
{
    if (kdNumInputFds == KD_MAX_INPUT_FDS)
    return FALSE;
    kdInputFds[kdNumInputFds].fd = fd;
    kdInputFds[kdNumInputFds].read = read;
    kdInputFds[kdNumInputFds].enable = 0;
    kdInputFds[kdNumInputFds].disable = 0;
    kdInputFds[kdNumInputFds].closure = closure;
    kdNumInputFds++;
    if (kdInputEnabled)
    KdAddFd (fd);
    return TRUE;
}
这样就把info放到了数组里面,这样sigio来的时候我们就会调用这里的函数了。


总之就是告诉xserver探测到了这个设备,并且使用了这个驱动来驱动这个设备。
如果遇到重复的设备,就不会被添加。注意kdrive指定设备文件只有一个地方,就是在命令行里面
有些驱动写的很烂,不支持hal,就需要命令行里面指定了,比如 -mouse,xorg则比较灵活
可以用命令行-pointer或者使用hal来探测,或者使用配置文件,反正就是方法比较多了。

添加了设备之后就可以使用了,不同的xserver添加方式不一样
xorg是这样的
xf86NewInputDevice
然后就会调用设备驱动里面的函数了,比如PreInit
    pInfo = drv->PreInit(drv, idev, 0);
比如
            drv->UnInit(drv, pInfo, 0);
比如
xf86ActivateDevice

ActivateDevice
激活设备的时候就会调用驱动里面的init函数了。
在dix/device.c里面,都是通用的了。
    ret = (*dev->deviceProc) (dev, DEVICE_INIT);


当要关闭设备的时候xserver就会调用
CloseDevice然后就跑到驱动里面去关闭设备了,也是一个回调。
    (void)(*dev->deviceProc)(dev, DEVICE_CLOSE);


这个deviceProc实际是设备相关的,如果是鼠标就是鼠标,如果是键盘就是键盘。
对于xorg来说。
都是在添加设备的时候指定的,比如pointer,就是鼠标了,我们可以这么理解。
    pointer = AddInputDevice(client, CorePointerProc, TRUE);


这个proc函数其实比较简单了。
static int
CorePointerProc(DeviceIntPtr pDev, int what)
{
    BYTE map[33];
    int i = 0;
    ClassesPtr classes;

    switch (what) {
    case DEVICE_INIT:
        if (!(classes = xcalloc(1, sizeof(ClassesRec))))
            return BadAlloc;

        for (i = 1; i <= 32; i++)
            map[i] = i;
        InitPointerDeviceStruct((DevicePtr)pDev, map, 32,
                                (PtrCtrlProcPtr)NoopDDA,
                                GetMotionHistorySize(), 2);
        pDev->valuator->axisVal[0] = screenInfo.screens[0]->width / 2;
        pDev->last.valuators[0] = pDev->valuator->axisVal[0];
        pDev->valuator->axisVal[1] = screenInfo.screens[0]->height / 2;
        pDev->last.valuators[1] = pDev->valuator->axisVal[1];
        break;

    case DEVICE_CLOSE:
        break;

    default:
        break;
    }

    return Success;
}


注意最新的xserver必须要有
InitPointerDeviceStruct
没有valuator的鼠标是不会被使用的,在汇报事件之后计算valuator会发生错误,然后事件就不会进到队列里面。
自己写鼠标驱动如果出问题一般都是这里出问题,当然不同鼠标有不同特性,可能也是鼠标驱动开发的一个问题。
关于valuator,比较常见的错误就是鼠标不能使用,或者bad valuator导致xserver崩溃,或者out-of-order的错误,或者是event queue溢出。
不过自己debug一下,或者使用ErrorF很容易看到问题出在什么地方。


好了,xorg的汇报事件的方法比较简单,如果像看驱动这里,evdev的驱动将来应该会一统天下,但是目前触摸拼还是有问题的。
evdev是将来linux平台标准的input驱动框架,至少我个人是这么理解的。好处比较多了,
evdev就是event device了,可以是鼠标键盘或者其他的设备。都遵循标准了,如果debug,最好的工具摸过evbug这个内核模块。
如果evbug出来的数据就不对,说明问题肯定不在上层,在下面的驱动就出错了。


另外要说说的就是touchscreen的driver,本来有xf86-input-tslib,kdrive里面也是移植的这个,
这个东西的框架实际上是这样的
evdev->tslib->xf86-input-tslib
tslib这边可以做一些过滤,平滑等等的处理,包括校正,不过校正不能在xserver运行过程中起作用。所以校正完毕之后需要重新启动xserver,加载tslib的驱动,
然后就可以正常使用了,本来有Xcaliberate的扩展,不过目前只能在kdrive上面使用,不知道xorg以后会不会有。触摸屏的pc太少了,没人维护。
evdev的caliberate实际上很不好用。


另外就是键盘实际上比鼠标的处理要复杂的多了。
因为键盘涉及到国际化的问题,比如键盘的layout,比如键盘的按键个数不同,语言不同,还有各种不同的metakey。或者组合键。
总之是很麻烦的事情,之前解决不少这样的问题。xserver这边有xkb来处理,不过这个东西并不是那么好用的。配置起来也不太顺手。
注意xkb的处理是必须配合使用的,比如xserver这边打开了xkb,并且设置了xkb的话,上层的应用程序必须知道这个,不然就会出问题,比如gdk这边的键值不对。
或者发现windowmanager的快捷键不起作用,或者发现按“a"出"b"这样的情况。一般来说都和xkb有关。

这篇关于xserver框架浅析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/576979

相关文章

Python Dash框架在数据可视化仪表板中的应用与实践记录

《PythonDash框架在数据可视化仪表板中的应用与实践记录》Python的PlotlyDash库提供了一种简便且强大的方式来构建和展示互动式数据仪表板,本篇文章将深入探讨如何使用Dash设计一... 目录python Dash框架在数据可视化仪表板中的应用与实践1. 什么是Plotly Dash?1.1

基于Flask框架添加多个AI模型的API并进行交互

《基于Flask框架添加多个AI模型的API并进行交互》:本文主要介绍如何基于Flask框架开发AI模型API管理系统,允许用户添加、删除不同AI模型的API密钥,感兴趣的可以了解下... 目录1. 概述2. 后端代码说明2.1 依赖库导入2.2 应用初始化2.3 API 存储字典2.4 路由函数2.5 应

Python GUI框架中的PyQt详解

《PythonGUI框架中的PyQt详解》PyQt是Python语言中最强大且广泛应用的GUI框架之一,基于Qt库的Python绑定实现,本文将深入解析PyQt的核心模块,并通过代码示例展示其应用场... 目录一、PyQt核心模块概览二、核心模块详解与示例1. QtCore - 核心基础模块2. QtWid

浅析CSS 中z - index属性的作用及在什么情况下会失效

《浅析CSS中z-index属性的作用及在什么情况下会失效》z-index属性用于控制元素的堆叠顺序,值越大,元素越显示在上层,它需要元素具有定位属性(如relative、absolute、fi... 目录1. z-index 属性的作用2. z-index 失效的情况2.1 元素没有定位属性2.2 元素处

最新Spring Security实战教程之Spring Security安全框架指南

《最新SpringSecurity实战教程之SpringSecurity安全框架指南》SpringSecurity是Spring生态系统中的核心组件,提供认证、授权和防护机制,以保护应用免受各种安... 目录前言什么是Spring Security?同类框架对比Spring Security典型应用场景传统

Python结合Flask框架构建一个简易的远程控制系统

《Python结合Flask框架构建一个简易的远程控制系统》这篇文章主要为大家详细介绍了如何使用Python与Flask框架构建一个简易的远程控制系统,能够远程执行操作命令(如关机、重启、锁屏等),还... 目录1.概述2.功能使用系统命令执行实时屏幕监控3. BUG修复过程1. Authorization

SpringBoot集成图片验证码框架easy-captcha的详细过程

《SpringBoot集成图片验证码框架easy-captcha的详细过程》本文介绍了如何将Easy-Captcha框架集成到SpringBoot项目中,实现图片验证码功能,Easy-Captcha是... 目录SpringBoot集成图片验证码框架easy-captcha一、引言二、依赖三、代码1. Ea

浅析Python中的绝对导入与相对导入

《浅析Python中的绝对导入与相对导入》这篇文章主要为大家详细介绍了Python中的绝对导入与相对导入的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1 Imports快速介绍2 import语句的语法2.1 基本使用2.2 导入声明的样式3 绝对import和相对i

Gin框架中的GET和POST表单处理的实现

《Gin框架中的GET和POST表单处理的实现》Gin框架提供了简单而强大的机制来处理GET和POST表单提交的数据,通过c.Query、c.PostForm、c.Bind和c.Request.For... 目录一、GET表单处理二、POST表单处理1. 使用c.PostForm获取表单字段:2. 绑定到结

浅析如何使用Swagger生成带权限控制的API文档

《浅析如何使用Swagger生成带权限控制的API文档》当涉及到权限控制时,如何生成既安全又详细的API文档就成了一个关键问题,所以这篇文章小编就来和大家好好聊聊如何用Swagger来生成带有... 目录准备工作配置 Swagger权限控制给 API 加上权限注解查看文档注意事项在咱们的开发工作里,API