天天看点

Linux输入子系统过程分析笔记

    这篇笔记的目的不是把input子系统应用起来,而是解答我自己在学习使用input子系统的时候心里的一些疑惑。供有相同疑惑的朋友参考。其中可能有错误或理解不正确之处,欢迎批评指正。    

    输入子系统核心层 driver/input/input.c 文件里做了如下工作:

    入口函数 input_init()里面调用register_chardev(INPUT_MAJOR,"input",&input_fops) 注册了字符设备input,主设备号为INPUT_MAJOR=13,对应的file_operation 为input_fops,查看input_fops结构体的初始化,发现其仅仅填充了open()函数,这个open()函数是input_open_file(),即input_fops->open = input_open_file()。

    至此还没有创建设备节点(设备节点的创建过程,下面再分析),应用程序还不能通过open()函数 调用到 驱动的 input_open_file().但我们现在可以知道最终应用程序通过open 打开主设备号为 13 的字符设备时,就会调用到 input_open_file(),我们先看一看static int input_open_file(struct inode *inode, struct file *file)里面做了什么事情:

static int input_open_file(struct inode *inode, struct file *file)

{

        struct input_handler *handler = input_table[iminor(inode) >> 5];

        const struct file_operations *old_fops, *new_fops = NULL;

        int err;

        if (!handler || !(new_fops = fops_get(handler->fops)))

        return -ENODEV;

        if (!new_fops->open) {

                fops_put(new_fops);

                return -ENODEV;

        }

        old_fops = file->f_op;

        file->f_op = new_fops;

        err = new_fops->open(inode, file);

        if (err) {

                fops_put(file->f_op);

                file->f_op = fops_get(old_fops);

        }

        fops_put(old_fops);

        return err;

}

    抽出三行关键代码阅读:

struct input_handler *handler = input_table[iminor(inode) >> 5];

if (!handler || !(new_fops = fops_get(handler->fops)))

    file->f_op = new_fops;

    第一行:用iminor(inode)取出次设备号(主设备号是INPUT_MAJOR=13),再iminor(inode) >> 5,即次设备号右移 5 位,也即次设备号除以32,用这个结果值作为数组下标,取出对应的数组元素input_table[iminor(inode) >> 5],然后赋给handler: *handler = input_table[iminor(inode) >> 5];

    这里有两点要说一说:

1:右移5位有什么含义?

    最多可以有32个不同的次设备号取出的是同一个数组元素input_table[n]。或者说同一个数组元素input_table[n],或者同一个handler 最多可以有32个不同的输入设备与之对应。

2:数组input_table 的数据类型 与handler 一样,是struct input_handler 类型,他在哪里被谁构造好?

    它是输入子系统事件处理层构造好的,用户无需关心。具体是在 driver/input 目录下的evdev.c   keyboard.c mousedev.c 等文件里 通过调用 input_register_handler(evdev_handler),  input_register_handler(keyboard_handler)等来构造input_table[]数组,就是把已经初始好了的  struct input_handler 类型结构体 evdev_handler,keyboard_handler等填充到input_table 数组。简单说就是 初始化input_handler 类型结构体,然后向输入子系统核心注册,这一过程输入子系统自动完成。

      具体的初始化过程和注册过程,本人正在搞得头晕晕的(后面我也准备写篇笔记),但意义也不是很大,所以不建议大家花太多时间钻研它。(实际上 内核里写了 static struct input_handler *input_table[8];即最多8 个元素,我看了源码,好像这8个元素并没有填充完,并且元素 input_table[0] 被多个文件初始化,搞不懂这里。)

     第二行和第三行代码:if (!handler || !(new_fops = fops_get(handler->fops)));file->f_op = new_fops;结果是file->f_op = fops_get(handler->fops),即我们打开的文件file,其f_op字段现在才被填充,被输入子系统事件处理层构造好的handler->fops 填充。到现在应该明白应用层对设备文件file的操作集就是这个系统构造好的handler->fops.不同的设备文件可能对应不同的handler,其fops也不同。但handler总数不超过8个(input_table[8]),因为输入设备发生的事件类型就那么几种。

    画一下过程:

input_init()

        register_chrdev(INPUT_MAJOR,"input",input_fops)

        input_fops->open = input_open_file(struct inode *inode, struct file *file)

                struct input_handler *handler = input_table[iminor(inode)>>5]

                file->fops = fops_get(handler->fops)

    现在总结一下,应用程序通过open 打开主设备号为 13 的字符设备时,就会调用到input_open_file(),然后对设备的读写等操作是通过 input_table[]数组某个元素的fops操作的。具体是哪一个元素的fops 与次设备号有关。相同类型的设备用的是相同的input_table[]数组里的元素,即相同的handler,也即相同的fops.但是,现在这个fops 还没有与具体的设备建立联系,还不能操作设备,并且到现在设备节点还没有创建。

    具体设备与系统构建好的handler 建立联系的过程发生在注册输入设备的时候,其实就是发生在调用int input_register_device(struct input_dev *dev)函数的时候(这说法虽然片面,但实际应用中应该大多数是这样的)。传入的参数是一个输入设备结构体,这个结构体要我们分配和设置好一些必要的内容后再调用int input_register_device(struct input_dev *dev)向输入子系统注册。下面参考了网友对input_register_device的分析。http://blog.csdn.net/happyguys12345/article/details/53487921

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;  
  
    /* 默认所有的输入设备都支持EV_SYN同步事件 */  
    set_bit(EV_SYN, dev->evbit);  
  
    /* 
     * 如果设备驱动没有指定重复按键(连击),系统默认提供以下的支持 
     * 其中init_timer为连击产生的定时器,时间到调用input_repeat_key函数 
     * 上报,REP_DELAY用于设置重复按键的键值,REP_PERIOD设置延时时间 
     */  
    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;  
  
    /* 重要,把设备挂到全局的input子系统设备链表input_dev_list上 */  
    list_add_tail(&dev->node, &input_dev_list);  
  
    /* 动态获取input设备的ID号,名称为input*,其中后面的“*”动态获得,唯一的 */  
    snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),  
         "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);  
  
    /* 如果这个值没有设置,系统把输入设备挂入设备链表 */  
    if (!dev->cdev.dev)  
        dev->cdev.dev = dev->dev.parent;  
  
    /* 在/sys目录下创建设备目录和文件 */  
    error = class_device_add(&dev->cdev);  
    if (error)  
        return error;  
  
    /* 获取并打印设备的绝对路径名称 */  
    path = kobject_get_path(&dev->cdev.kobj, GFP_KERNEL);  
    printk(KERN_INFO "input: %s as %s\n",  
        dev->name ? dev->name : "Unspecified device", path ? path : "N/A");  
    kfree(path);  
  
    /* 核心重点,input设备在增加到input_dev_list链表上之后,会查找 
     * input_handler_list事件处理链表上的handler进行匹配,这里的匹配 
     * 方式与设备模型的device和driver匹配过程很相似,所有的input 
     * 都挂在input_dev_list上,所有类型的事件都挂在input_handler_list 
     * 上,进行“匹配相亲”*/  
    list_for_each_entry(handler, &input_handler_list, node)  
        input_attach_handler(dev, handler);  
  
    input_wakeup_procfs_readers();  
  
    return 0;  
}
           

    经过 input_register_device(struct input_dev *dev)后,系统已经根据 struct input_dev *dev 提供的一些信息创建了设备节点,并且在经过最后三行代码

list_for_each_entry(handler, &input_handler_list, node)  

        input_attach_handler(dev, handler);  

        input_wakeup_procfs_readers();  

后,成功与handler 匹配。那么应用层就可以通过handler 操作设备了。

下一篇分析匹配过程,有兴趣的朋友可以参考看一看。

继续阅读