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有关。

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值