Linux输入子系统分析

之前我们学的都是简单的字符驱动,涉及的内容有字符驱动的框架、自动创建设备节点、linux中断、poll机制、异步通知、同步互斥/非阻塞、定时器去抖动。

其中驱动框架如下:

1)写file_operations结构体的成员函数: .open()、.read()、.write()
2)在入口函数里通过register_chrdev_region()创建驱动名,生成主设备号,赋入file_operations结构体,创建类和设备
3)在出口函数里通过unregister_chrdev_region() 卸载驱动
若有多个不同的驱动程序时,应用程序就要打开多个不同的驱动设备,由于是自己写肯定会很清楚,如果给别人来使用时是不是很麻烦?
所以需要使用输入子系统, 使应用程序无需打开多个不同的驱动设备便能实现

1.输入子系统简介

同样的输入子系统也需要输入驱动的框架,好来辨认应用程序要打开的是哪个输入驱动
比如: 鼠标、键盘、游戏手柄等等这些都属于输入设备;这些输入设备的驱动都是通过输入子系统来实现的(当然,这些设备也依赖于usb子系统)
这些输入设备都各有不同,那么输入子系统也就只能实现他们的共性,差异性则由设备驱动来实现。差异性又体现在哪里?
最直观的就表现在这些设备功能上的不同了。对于我们写驱动的人来说在设备驱动中就只要使用输入子系统提供的工具(也就是函数)来完成这些“差异”就行了,其他的则是输入子系统的工作。这个思想不仅存在于输入子系统,其他子系统也是一样(比如:usb子系统、video子系统等)

所以我们先来分析下输入子系统input.c的代码,然后怎么来使用输入子系统(在内核中以input来形容输入子系统)
2.打开input.c,位于内核deivers/input
有以下这么两段:
subsys_initcall(input_init); //修饰入口函数
module_exit(input_exit); //修饰出口函数
显然输入子系统是作为一个模块存在,我们先来分析下input_int()入口函数

static int __init input_init(void)
{
       int err;
       err = class_register(&input_class);   //(1)注册类,放在/sys/class
       if (err) {
              printk(KERN_ERR "input: unable to register input_dev class\n");
              return err;
       } 
       err = input_proc_init();    //在/proc下面建立相关的文件
       if (err)
              goto fail1;
       err = register_chrdev(INPUT_MAJOR, "input", &input_fops); //(2)注册驱动
       if (err) {
              printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
              goto fail2;
       }
       return 0;
 fail2:     input_proc_exit();
 fail1:     class_unregister(&input_class);
       return err;
}

上面第4行”err = class_register(&input_class);”是在/sys/class 里创建一个 input类, input_class变量如下图:
在这里插入图片描述
为什么这里代码只创建类,没有使用class_device_create()函数在类下面创建驱动设备?
这里简单描述:当注册input子系统的驱动后,才会有驱动设备,此时这里的代码是没有驱动的

这里注册了一个主设备号“INPUT_MAJOR”为 13 的字符设备,名字为“input”,它的
file_operations 结构是“input_fops”。这个结构中只有一个“.open”函数。
输入子系统,如这里想读按键,而这里只有一个“open”函数,那么这个 Open 函数中应该
做了某些工作

3 然后进入input_open_file函数(drivers/input/input.c)

static int input_open_file(struct inode *inode, struct file *file)
 {
     struct input_handler *handler = input_table[iminor(inode) >> 5]; // (1)
     const struct file_operations *old_fops, *new_fops = NULL;
     int err;

     if (!handler || !(new_fops = fops_get(handler->fops)))  //(2)
          return -ENODEV; 

    if (!new_fops->open) {
           fops_put(new_fops);
           return -ENODEV;
    }

    old_fops = file->f_op;
    file->f_op = new_fops;     //(3)

    err = new_fops->open(inode, file);   //(4)
    if (err) {
          fops_put(file->f_op);
           file->f_op = fops_get(old_fops);
   }

   fops_put(old_fops);

    return err;
}

其中有一个“input_handler *” (“输入处理器”或“输入处理句柄” ):这里这个“输入处理句柄”结构指向一个“input_table[]”数组。从这个数组里面根据这个“次设 备号 iminor(inode) >> 5”把打开的文件,根据它的次设备号找到一项。
接着新的“file_operations”结构"new_fops"等于上面的“input_handler *”指针变量,handler 的成员“ops”(这是一个 file_operations 结构。 Input_handler 结构中有这个file_operations 成员)。接着把这个新的 file_operations 结构赋给此函数“input_open_file”的形参“file”的 f_op。
然后再调用这个新的 file_operations 结构“new_fops”的 open(inode,file)函数,这样以后要来读按键时,用到的是“struct input_handler *handler”中的“new_fops” .Input.c 只是一个“中转”作用。最终还会用到“input_table[]”

问题:怎么读按键?
这里只有一个 Open 函数而没有读函数。则可能是这个 “input_open_file”函数中做了什么工作?

int input_open_file(struct inode *inode, struct file *file):
->input_handler *handler = input_table[iminor(inode) >> 5];
根据传进来的“inode” 这个打开的文件的次设备号(“iminor(inode) >> 5”)得到
一个
“input_handler *handler”。
->new_fops = fops_get(handler->fops):
然后的新的 file_operations 结构体(new_fops)等于这个“input_handler *handler”结构
里面的“file_operations”结构“handler->fops”。
->file->f_op = new_fops:
然后所打开的这个 file 中的 f_op(file_operations)等于这个“new_fops”。
->err = new_fops->open(inode, file):
接着调用这个 new_fops 的“open”函数。以后 APP 来读的时候,是最终调用“file->f_op”中的 read 函数。最终是要明白“input_handler”
是如何定义的, input_table 数组由谁构造。

4 "input_table[]"的构造
“static struct input_handler *input_table[8]”是个静态变量,所以只能用在这个文件里面,
所以只 在这个 Input.c 中找到使用了这个数组的地方,如下:
int input_register_handler(struct input_handler *handler)这个函数中构造了这个 input_table[]数 组项:

int input_register_handler(struct input_handler *handler)
{
... ...
input_table[handler->minor >> 5] = handler;   //input_table[]被赋值
... ...
list_add_tail(&handler->node, &input_handler_list); //然后将这个input_handler放到input_handler_list链表中  
... ...
}

那么这个“input_register_handler()”函数被谁调用过,继续来搜索input_register_handler,看看这个函数被谁来调用
在这里插入图片描述
有游戏手柄,键盘和鼠标等的源代码调用过它。这些使用就离开了“input.c”这个核心层了。
它们就向上 面的核心层“input.c”注册了“input_register_handler()”
在这里插入图片描述
打开这个“evdev.c”源码。查看调用“input_register_handler()”的调用:
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler); //注册
}
只是在这个入口函数“evdev_init()”中调用了"input_register_handler()"。
以上是具体的设备与核心层 input.c 的层次调用关系
在“evdev.c”的入口函数“evdev_init()”中注册了“evdev_handler”这个结构。它的原型如
下:

static struct input_handler evdev_handler = {
       .event =  evdev_event,    
       .connect =      evdev_connect,  //(4)
       .disconnect = evdev_disconnect,
       .fops =           &evdev_fops,    //(1)
       .minor =  EVDEV_MINOR_BASE, //(2)
       .name =         "evdev",
       .id_table =      evdev_ids, //(3)
};

(1)它是一个“input_handler”结构体。就是我们之前看的input_handler驱动处理结构体
从上面的"input_handler"结构变量“evdev_handler”的定义可以看到一个“file_operations”
结构“.fops = &evdev_fops”。在这个“evdev_fops”中便有相关的 read,write 等
以前自已写驱动时,这个 file_operations 结构体是自已构造的,这里是由系统构造了。

(2)次设备号是“64”。
在这里插入图片描述

通过“input_register_handler(&evdev_handler)”注册后,就放到
在这里插入图片描述
通过传值后,形参“handler”就是“&evdev_handler”,则“handler->minor”就是
“evdev_handler->minor”即“EVDEV_MINOR_BASE”(即次设备号 64)。
就相当于 64 除以 2 的 5 次方,即 64 除以 32 为 2.
则这个“handler”是放在“input_table[2]”这个第二项处。 handler 是纯软件的概念。
软件方面(evdev.c/keyboard.c/mousedev.c)是向核心层“input.c”注册“handler”
(input_register_handler),这一边代表“软件”;还有另一边另一个层“设备”,是向“核 心
层 input.c”注册“input_register_device”,这一边代表硬件.
在这里插入图片描述
(3)“id_table”表示这个“evdev_handler”能够支持哪些“输入设备”。当我们注册上图中的
“handler”和 “device”时,这两者就会比较(handler 和设备比较),看 handler 是否支持
这个设备。若能支持则,则从 上面的“evdev_handler”结构中知道,应该会调用其中的
“.connect = evdev_connect”

*5下面就是注册输入设备:int input_register_device(struct input_dev dev)

int input_register_device(struct input_dev *dev)   //*dev:要注册的驱动设备
{
 ... ...
       list_add_tail(&dev->node, &input_dev_list);   //(1)放入链表中
 ... ...
       list_for_each_entry(handler, &input_handler_list, node)  //(2)
       input_attach_handler(dev, handler); 
 ... ...
}

(1)list_add_tail(&dev->node, &input_dev_list);
input_dev:子系统中用此结构体来描述一个输入设备 .这里是:
将设备加入全局链表中→
然后遍历 input_handler_list 中的每一个 handler,调用 input_attach_handler 进行 attach
(2), “input_handler_list(注册 handler 时加入的链表) ”都会调用“input_attach_handler()”函数来
对“硬件设备 device”与“处理方式 handler”进行关联。
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
List_for_each_entry 函数遍历 Input_handler_list(全局链表,连接所有的 input_handler)上的
handler,并调用 Input_attach_handler 来进行输入设备和处理方法的关联。
在这里插入图片描述
从上图可知,不管是先注册加载右边的“handler”还是左边的“device”。最终都会成对的
调用“input_attach_handler()”。看看源码“input_attach_handler()”:

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
... ...
id = input_match_device(handler->id_table, dev);  //匹配两者

if (!id)                                     //若不匹配,return退出
return -ENODEV; 

error = handler->connect(handler, dev, id);  //调用input_handler ->connect函数建立连接
... ...

}

注册 input_dev 或 input_handler 时,会两两比较左边的 input_dev 和右边的 input_handler,
根据 input_handler 的 id_table 判断这个 input_handler 能否支持这个 input_dev,如果能支
持,则调用 input_handler 的 connect 函数建立"连接。
如何建立连接,可能不同的 handler 都有自已不同的方式。

(6)下面分析“evdev.c”中的“connect”:

static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)     
{
... ... 
for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++); //查找驱动设备的子设备号
    if (minor == EVDEV_MINORS) {  // EVDEV_MINORS=32,所以该事件下的驱动设备最多存32个,
        printk(KERN_ERR "evdev: no more free evdev devices\n");
        return -ENFILE;                //没找到驱动设备
    }
 ... ...
 evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);   //分配一个input_handle全局结构体(没有r)
 ... ...
 evdev->handle.dev = dev;              //指向参数input_dev驱动设备
evdev->handle.name = evdev->name;
evdev->handle.handler = handler;    //指向参数 input_handler驱动处理结构体
evdev->handle.private = evdev;
sprintf(evdev->name, "event%d", minor);    //(1)保存驱动设备名字, event%d
... ...
devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),  //(2) 将主设备号和次设备号转换成dev_t类型
cdev = class_device_create(&input_class, &dev->cdev, devt,dev->cdev.dev, evdev->name); 
                                                           // (3)在input类下创建驱动设备

... ...
error = input_register_handle(&evdev->handle); //(4)注册这个input_handle结构体

... ...
}
  1. ,分配了一个“input_handle evdev”结构变量
    (不是 input_handler 结构)。
    查看这个 evdev 结构中的成员:
    在这里插入图片描述
    成员中有一个“input_handle handle”结构。
  2. 再对这个“input_handle evdev”进行设置:
    在这里插入图片描述
  3. 最后注册这个 handle:
    在这里插入图片描述
    将形参“handle”放到一个输入设备的链表里面:
    在这里插入图片描述
    再把“handler”放到右边一个“h_list”链表里面
    在这里插入图片描述
    在这里插入图片描述

应用程序如何读数据按键值

应用程序来读,最终会导致“handler”里面的新的“.fops”里面的“读函数”被调用。如:
“evdev.c”中的“evdev_handler”结构里面的成员“.fops=&evdev_fops”,在“evdev_fops”
结构中有一个“读”函数“evdev_read
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在“evdev_read()”中:

static ssize_t evdev_read(struct file *file, char __user *      buffer, size_t count, loff_t *ppos)
{
 ... ...
/*判断应用层要读取的数据是否正确*/
if (count < evdev_event_size())
return -EINVAL;

/*在非阻塞操作情况下,若client->head == client->tail|| evdev->exist时(没有数据),则return返回*/
 if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
return -EAGAIN;
 
/*若client->head == client->tail|| evdev->exist时(没有数据),等待中断进入睡眠状态  */
  retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist);

  ... ...           //上传数据

}

在这里插入图片描述
可以从上面的“wait_event_interruptible()”知道是在“evdev”上休眠,则唤醒也会是在它上面。

(1) 谁来“唤醒”
如按下一个按键后,中断处理函数就会被调用。在中断处理函数里面先确定按键值。然后才
来唤醒
在这里插入图片描述
在代码中搜索“evdev->wait”后找到在“evdev_event()”中有唤醒操作。这个“evdev->wait”
是结构“evdev_handler”的“.event”事件成员。
分析事件处理函数“evdev_event()” :
主图中右边是“纯软件”的部分,按键按下时应该是由左边输入设备这一层触发的。

**(2) 谁调用“evdev_event()” **

在这里插入图片描述

总结输入子系统

(1) 分为上下两层:
核心层“input.c” .它里面有“register_chdev”(input_init()中),但它简单:
在这里插入图片描述
因为这个 register_chrdev()中的“input_fops” file_operations 结构很简单:
在这里插入图片描述
只有一个“.open”函数,所以让它去读去写不行,里面还有个“中转”的过程:
在这里插入图片描述
根据打开的设备节点的次设备号找到一个“handler”:
在这里插入图片描述
并且把这个文件的 f_op 指向新的“input_handler *handler”里面的“fops” :
在这里插入图片描述
然后再调用这个“handler”中的 open 函数:
在这里插入图片描述
其中的“iput_table[]”数组由下面的各个“纯软件”代码构建(如: evdev.c,keyboard.c) .
“纯软件(input_handler)”部分和“硬件部分(input_dev)”联系起来,纯软件部分是由
“input_register_handler ” 向 上 “input.c ” 核 心 层 注 册 处 理 方 式 ; 硬 件 部 分 是 由
“input_register_device”向上“input.c”核心层注册硬层。这样注册后,会使它们两两比较,
看看其中的某个“handler”是否支持其中的某个“dev”。若是“handler”能支持某个“dev”,
则会接着调用“input_handler”结构下的“.connect”函数,这个函数一般会创建一个
“input_handle”结构(是 handle 而非 handler),并且这个结构“input_handle”会分别放
在两边的“h_list”链表中去。这个结构体中有“.dev”和“.handler”,让它两分别具体指向
右边的“纯软件处理部分”和左右的硬件部分,这样具体的某硬件就和纯软件联系起来了。
可以从任何一边通过“h_list”找到这个“input_handle”结构,再通过成员找到另一端的“.dev”
或“.handler”。
在这里插入图片描述
举例是如何读按键:
最终是应用程序读,最终会导致 handler 中的“read”函数。读的过程中,没有数据可读时
就休眠,有休眠就会唤醒,搜索的结果是“event”函数来唤醒。分析到这里就没有接着去分
析而是猜测是由“硬件”调用的“event”函数,硬件则是指“input_dev”层的设备中断服
务程序调用了“event”函数。通过这个“event”函数可以最终追踪到“纯软件”部分的
“input_handler”结构体中的“.event”成员。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值