输入子系统

来源:互联网 发布:查分数的软件 编辑:程序博客网 时间:2024/04/28 23:42

日常生活中,使用的输入设备多种多样,功能也各不相同,因此linux为了统一管理,引出了输入子系统,在子系统中,把输入设备的每个动作归为一个输入事件进行解析,且系统启动的时候先初始化这个输入子系统,供后面驱动程序注册进来,这个初始化的过程在input.c中实现,如下是它的初始化函数:

static int __init input_init(void){    int err;    err = class_register(&input_class);    if (err) {        pr_err("unable to register input_dev class\n");        return err;    }    err = input_proc_init();    if (err)        goto fail1;    err = register_chrdev(INPUT_MAJOR, "input", &input_fops);    if (err) {        pr_err("unable to register char major %d", INPUT_MAJOR);        goto fail2;    }    return 0;}

这个初始化的函数主要做三件事,第一:注册一个input_class类、第二:调用input_proc_init在proc目录下创建一个input目录,另外在这个目录下又创建了两个分别为:devices和handlers的文件,用来描述注册进输入子系统的input_dev和input_handler成员、第三:调用register_chrdev把主设备号INPUT_MAJOR与文件操作函数集进行绑定,并进行注册,之后调用这个主设备号来创建一个设备文就使得件,input_fops成为这个输入设备文件操作函数集的入口(通过虚拟文件系统映射),INPUT_MAJOR是内核为输入子系统分配固定的主设备号为13。学习字符设备的时候我们就知道编写一个字符设备的基本步骤大致可分为三步:第一步是要注册一个字符设备、第二步注册一个类、第三步创建一个设备文件,由上面可知在初始化输入子系统时,已经完成了编写一个字符设备第一、二步,至于第三步是在输入设备注册成功了之后才会去做。应用层需要访问一个输入设备,首先需要打开这个输入设备文件,由上可知在linux中input_fops是所有输入设备文件的访问入口函数集,它的open函数如下

static int input_open_file(struct inode *inode, struct file *file){    struct input_handler *handler;    const struct file_operations *old_fops, *new_fops = NULL;    int err;    err = mutex_lock_interruptible(&input_mutex);    if (err)        return err;    handler = input_table[iminor(inode) >> 5];    if (handler)        new_fops = fops_get(handler->fops);    mutex_unlock(&input_mutex);    if (!new_fops || !new_fops->open) {        fops_put(new_fops);        err = -ENODEV;        goto out;    }    old_fops = file->f_op;    file->f_op = new_fops;    err = new_fops->open(inode, file);    return err;}

iminor(inode)得到的是该输入设备的次设备号,然后次设备号右移五位得到输入设备管理数组input_table的下标,取出该输入设备的struct input_handler,如果该input_handler存在则把它的ops(文件操作函数集)赋值给struct file的f_op成员,之后是打开该input_handler的open函数,在这里暂时还不知道这个input_handler是做什么用、且也还不知它是在哪里注册进input_table数组中?先绕开这些问题,在linux中既然提供了这么一个输入子系统,必然会为驱动人员提供一个接口注册进来,通过了解知道输入子系统提供了这么一个接口int input_register_device(struct input_dev *dev)来供驱动人员调用进行输入设备驱动的注册,看下它的实现过程:

int input_register_device(struct input_dev *dev){    static atomic_t input_no = ATOMIC_INIT(0);    struct input_handler *handler;    const char *path;    int error;    __set_bit(EV_SYN, dev->evbit);    __clear_bit(KEY_RESERVED, dev->keybit);    input_cleanse_bitmasks(dev);    if (!dev->hint_events_per_packet)        dev->hint_events_per_packet =                input_estimate_events_per_packet(dev);    init_timer(&dev->timer);    if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {        dev->timer.data = (long) dev;        dev->timer.function = input_repeat_key;        dev->rep[REP_DELAY] = 250;        dev->rep[REP_PERIOD] = 33;    }    if (!dev->getkeycode)        dev->getkeycode = input_default_getkeycode;    if (!dev->setkeycode)        dev->setkeycode = input_default_setkeycode;    dev_set_name(&dev->dev, "input%ld",(unsigned long) atomic_inc_return(&input_no) - 1);            error = device_add(&dev->dev);    if (error)        return error;    path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);    kfree(path);    error = mutex_lock_interruptible(&input_mutex);    if (error) {        device_del(&dev->dev);        return error;    }    list_add_tail(&dev->node, &input_dev_list);    list_for_each_entry(handler, &input_handler_list, node)        input_attach_handler(dev, handler);    input_wakeup_procfs_readers();    mutex_unlock(&input_mutex);    return 0;}

在这个函数中前面的一大段代码是用来初始化输入设备处理重复事件工作的,且把注册进来的struct input_dev结构 添加到input_dev_list链表中去,主要是看下后面,list_for_each_entry是个宏,在这它的作用是从input_handler_list链表中逐个取出struct input_handler的结构赋值给handler指针,然后调用input_attach_handler并传入dev和取出的这个handler结构进行逐一比较,这个函数,input_attach_handle里面主要是做两件事,调用input_match_device取出两个待匹配的成员的id_table结构并对这个结构的每个成员进行逐一比较都匹配,则判断handler的match成员函数是否提供,提供则调用,如果匹配成功调用handler的connect成员函数,进行handler和dev的关联工作,以及创建一个设备文件,表示注册一个输入设备文件成功,这些后面提供一个具体的实例进行说明。到这里知道input_dev跟handler的关系了,且由上知道有两个宿主结构存放了struct input_handler结构,但是还不知它从哪里注册进去的?下面就来了解它的注册过程,通过搜索input_handler_list或者input_table都可以找到,在输入子系统还提供了另一个接口input_register_handler,看下它的源码:

int input_register_handler(struct input_handler *handler){    struct input_dev *dev;    int retval;    retval = mutex_lock_interruptible(&input_mutex);    if (retval)        return retval;    INIT_LIST_HEAD(&handler->h_list);    if (handler->fops != NULL) {        if (input_table[handler->minor >> 5]) {            retval = -EBUSY;            goto out;        }        input_table[handler->minor >> 5] = handler;    }    list_add_tail(&handler->node, &input_handler_list);    list_for_each_entry(dev, &input_dev_list, node)        input_attach_handler(dev, handler);    input_wakeup_procfs_readers();    return retval;}

从这里可以知道是通过调用input_register_handler把struct input_handler注册进input_handler_list和input_table的,且在这个函数的底部通过list_for_each_entry遍历input_dev_list逐个取出struct input_dev结构并赋值给dev,然后调用input_attach_handler对dev和handler进行匹配,这个匹配的过程跟上面说的是一致的,input_dev_list这个链表在前面提到过,就是调用input_register_device注册一个input_dev会把这个结构添加到input_dev_list中去。由此可以知道在输入子系统中input_dev和input_handler是双匹配的机制,即是无论谁先注册进来,最终匹配成功了,结果还是一样的。从上面可以知道匹配成功了最终是调用input_handler的connect成员函数,也就是说匹配成功了之后是由input_handle决定去怎么做。在linux中,内核针对不同的输入设备注册了很多个input_handle,其中有键盘input_handler、鼠标input_handler、事件input_handler等等,且事件input_handler会被所有的输入设备驱动匹配,下面就以事件input_handler进行说明(它在evdev中实现):

static int __init evdev_init(void){    return input_register_handler(&evdev_handler);}![enter description here][1]static struct input_handler evdev_handler = {    .event      = evdev_event,    .connect    = evdev_connect,    .disconnect = evdev_disconnect,    .fops       = &evdev_fops,    .minor      = EVDEV_MINOR_BASE,    .name       = "evdev",    .id_table   = evdev_ids,};

它的初始化函数调用input_register_handler把evdev_handler 结构注册进去这里主要看下它的成员函数实现,先看下它的id_table
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, /* Matches all devices */
{ }, /* Terminating zero entry */
};由它的注释可知,这个handler会被所有的输入设备匹配
从上面可知,input_dev和input_handler匹配成功后会调用input_handler的connect成员函数,看下这里的evdev_connect函数做什么事情:

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)          {    struct evdev *evdev;    int minor;    int error;    for (minor = 0; minor < EVDEV_MINORS; minor++)        if (!evdev_table[minor])            break;    if (minor == EVDEV_MINORS) {        pr_err("no more free evdev devices\n");        return -ENFILE;    }    evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);    if (!evdev)        return -ENOMEM;    INIT_LIST_HEAD(&evdev->client_list);    spin_lock_init(&evdev->client_lock);    mutex_init(&evdev->mutex);    init_waitqueue_head(&evdev->wait);    dev_set_name(&evdev->dev, "event%d", minor);    evdev->exist = true;    evdev->minor = minor;    evdev->handle.dev = input_get_device(dev);    evdev->handle.name = dev_name(&evdev->dev);    evdev->handle.handler = handler;    evdev->handle.private = evdev;    evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);    evdev->dev.class = &input_class;    evdev->dev.parent = &dev->dev;    evdev->dev.release = evdev_free;    device_initialize(&evdev->dev);    error = input_register_handle(&evdev->handle);    if (error)        goto err_free_evdev;    error = evdev_install_chrdev(evdev);    if (error)        goto err_unregister_handle;    error = device_add(&evdev->dev);    if (error)        goto err_cleanup_evdev;    return 0; err_cleanup_evdev:    evdev_cleanup(evdev); err_unregister_handle:    input_unregister_handle(&evdev->handle); err_free_evdev:    put_device(&evdev->dev);    return error;}

对于输入子系统,内核为它分配了固定的主设备号13,而一个主设备号的次设备号数是有限的(最大为256),且在liunx中不止一个input_handler,因此内核限定了每个input_handler的次设备号,这里它的起始次设备号为EVDEV_MINOR_BASE(64),占用的次设备号个数为EVDEV_MINORS(32),所以evdev_connect一进来先再这个次设备号区间分配一个次设备号,在这个函数会构造一个struct evdev 结构,这个结构有个成员handle用来存放input_handler和input_dev,这就把input_handler和input_dev关系关联起来了,然后调用input_register_handle把handle注册进去,然后调用evdev_install_chrdev安装一个evdev字符设备,其实就是把evdev以次设备号为下标放进evdev_table数组进行管理,再就是调用device_add生成一个设备文件。在上面讲到打开一个输入设备驱动最终会去调用该输入设备input_handle的open函数,在这里是

static int evdev_open(struct inode *inode, struct file *file){    struct evdev *evdev;    struct evdev_client *client;    int i = iminor(inode) - EVDEV_MINOR_BASE;    unsigned int bufsize;    int error;    if (i >= EVDEV_MINORS)        return -ENODEV;    error = mutex_lock_interruptible(&evdev_table_mutex);    if (error)        return error;    evdev = evdev_table[i];    if (evdev)        get_device(&evdev->dev);    mutex_unlock(&evdev_table_mutex);    if (!evdev)        return -ENODEV;    bufsize = evdev_compute_buffer_size(evdev->handle.dev);    client = kzalloc(sizeof(struct evdev_client) +                bufsize * sizeof(struct input_event),             GFP_KERNEL);    if (!client) {        error = -ENOMEM;        goto err_put_evdev;    }    client->bufsize = bufsize;    spin_lock_init(&client->buffer_lock);    client->evdev = evdev;    evdev_attach_client(evdev, client);    error = evdev_open_device(evdev);    if (error)        goto err_free_client;    file->private_data = client;    nonseekable_open(inode, file);    return 0; err_free_client:    evdev_detach_client(evdev, client);    kfree(client); err_put_evdev:    put_device(&evdev->dev);    return error;}

首先是从evdev_table数组中取出该设备驱动的struct evdev evdev结构,这个结构在上面可以看到是connect函数被调用的时候构造的,然后是又构造另一个结构struct evdev_client client,client的evdev成员变量又存放了上面取出来的evdev,调用evdev_open_device打开一个设备,如果设备驱动提供open的话。最后把新构造的client结构存放在file的private_data数据里面,这样在读写函数中就可以从file的private_data数据里面取出用来描述该输入设备的struct evdev_client,struct evdev_client里面存放有struct evdev,struct evdev里面存有该设备驱动的input_handle,input_handle又存放有input_handler和input_dev,通过结构之间的层层关联,最终找到底层提供的input_dev和事件的处理者input_handle。对于设备驱动收到一个输入事件,怎么传进来呢?在内核中提供了一个接口

void input_event(struct input_dev *dev,         unsigned int type, unsigned int code, int value){    unsigned long flags;    if (is_event_supported(type, dev->evbit, EV_MAX)) {        spin_lock_irqsave(&dev->event_lock, flags);        add_input_randomness(type, code, value);        input_handle_event(dev, type, code, value);        spin_unlock_irqrestore(&dev->event_lock, flags);    }}

在上传的过程中,需要做一些互斥操作工作,然后调用input_handle_event转接一下,input_handle_event里面对不同的事件作相应的处理后调用该输入设备的input_handler的event函数进行事件的处理。调用input_event上传事件之后,一般在还需要跟一个函数iinput_sync(idev);,不然在应用层会收不到输入事件的