在事件处理层(evdev.c)中结构体evdev_client定义了一个环形缓冲区(circular buffer),其原理是用数组的方式实现了一个先进先出的循环队列(circular queue),用以缓存内核驱动上报给用户层的input_event事件。

struct evdev_client {
unsigned int head; // 头指针
unsigned int tail; // 尾指针
unsigned int packet_head; // 包指针
spinlock_t buffer_lock;
struct fasync_struct *fasync;
struct evdev *evdev;
struct list_head node;
unsigned int clk_type;
bool revoked;
unsigned long *evmasks[EV_CNT];
unsigned int bufsize; // 循环队列大小
struct input_event buffer[]; // 循环队列数组
};

evdev_client对象维护了三个偏移量:head、tail以及packet_head。head、tail作为循环队列的头尾指针记录入口与出口偏移,那么包指针packet_head有什么作用呢?

packet_head

内核驱动处理一次输入,可能上报一到多个input_event事件,为表示处理完成,会在上报这些input_event事件后再上报一次同步事件。头指针head以input_event事件为单位,记录缓冲区的入口偏移量,而包指针packet_head则以“数据包”(一到多个input_event事件)为单位,记录缓冲区的入口偏移量。

环形缓冲区的工作机制

  • 循环队列入队算法:
head++;
head &= bufsize - 1;
  • 循环队列出队算法:
tail++;
tail &= bufsize - 1;
  • 循环队列已满条件:
head == tail
  • 循环队列为空条件:
packet_head == tail

“求余”和“求与”

为解决头尾指针的上溢和下溢现象,使队列的元素空间可重复使用,一般循环队列的出入队算法都采用“求余”操作:

    head = (head + 1) % bufsize; // 入队

    tail = (tail + 1) % bufsize; // 出队

为避免计算代价高昂的“求余”操作,使内核运作更高效,input子系统的环形缓冲区采用了“求与”算法,这要求bufsize必须为2的幂,在后文中可以看到bufsize的值实际上是为64或者8的n倍,符合“求与”运算的要求。

环形缓冲区的构造以及初始化

用户层通过open()函数打开input设备节点时,调用过程如下:

open() -> sys_open() -> evdev_open()

在evdev_open()函数中完成了对evdev_client对象的构造以及初始化,每一个打开input设备节点的用户都在内核中维护了一个evdev_client对象,这些evdev_client对象通过evdev_attach_client()函数注册在evdev1对象的内核链表上。

接下来我们具体分析evdev_open()函数:

static int evdev_open(struct inode *inode, struct file *file)
{
struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);
// 1.计算环形缓冲区大小bufsize以及evdev_client对象大小size
unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);
unsigned int size = sizeof(struct evdev_client) +
bufsize * sizeof(struct input_event);
struct evdev_client *client;
int error; // 2. 分配内核空间
client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);
if (!client)
client = vzalloc(size);
if (!client)
return -ENOMEM; client->bufsize = bufsize;
spin_lock_init(&client->buffer_lock);
client->evdev = evdev; // 3. 注册到内核链表
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);
kvfree(client);
return error;
}

在evdev_open()函数中,我们看到了evdev_client对象从构造到注册到内核链表的过程,然而它是在哪里初始化的呢?其实kzalloc()函数在分配空间的同时就通过__GFP_ZERO标志做了初始化:

static inline void *kzalloc(size_t size, gfp_t flags)
{
return kmalloc(size, flags | __GFP_ZERO);
}

生产者/消费者模型

内核驱动与用户程序就是典型的生产者/消费者模型,内核驱动产生input_event事件,然后通过input_event()函数写入环形缓冲区,用户程序通过read()函数从环形缓冲区中获取input_event事件。

环形缓冲区的生产者

内核驱动作为生产者,通过input_event()上报input_event事件时,最终调用___pass_event()函数将事件写入环形缓冲区:

static void __pass_event(struct evdev_client *client,
const struct input_event *event)
{
// 将input_event事件存入缓冲区,队头head自增指向下一个元素空间
client->buffer[client->head++] = *event;
client->head &= client->bufsize - 1; // 当队头head与队尾tail相等时,说明缓冲区空间已满
if (unlikely(client->head == client->tail)) {
/*
* This effectively "drops" all unconsumed events, leaving
* EV_SYN/SYN_DROPPED plus the newest event in the queue.
*/
client->tail = (client->head - 2) & (client->bufsize - 1); client->buffer[client->tail].time = event->time;
client->buffer[client->tail].type = EV_SYN;
client->buffer[client->tail].code = SYN_DROPPED;
client->buffer[client->tail].value = 0; client->packet_head = client->tail;
} // 当遇到EV_SYN/SYN_REPORT同步事件时,packet_head移动到队头head位置
if (event->type == EV_SYN && event->code == SYN_REPORT) {
client->packet_head = client->head;
kill_fasync(&client->fasync, SIGIO, POLL_IN);
}
}

环形缓冲区的消费者

用户程序作为消费者,通过read()函数读取input设备节点时,最终在内核调用evdev_fetch_next_event()函数从环形缓冲区中读取input_event事件:

static int evdev_fetch_next_event(struct evdev_client *client,
struct input_event *event)
{
int have_event; spin_lock_irq(&client->buffer_lock); // 判缓冲区中是否有input_event事件
have_event = client->packet_head != client->tail;
if (have_event) {
// 从缓冲区中读取一次input_event事件,队尾tail自增指向下一个元素空间
*event = client->buffer[client->tail++];
client->tail &= client->bufsize - 1;
if (client->use_wake_lock &&
client->packet_head == client->tail)
wake_unlock(&client->wake_lock);
} spin_unlock_irq(&client->buffer_lock); return have_event;
}

input子系统事件处理层(evdev)的环形缓冲区【转】的更多相关文章

  1. 【Linux高级驱动】input子系统框架

    [1.input子系统框架(drivers\input)] 如何得出某个驱动所遵循的框架?    1) 通过网络搜索    2) 自己想办法跟内核代码!         2.1 定位此驱动是属于哪种类 ...

  2. 【Linux高级驱动】input子系统框架【转】

    转自:http://www.cnblogs.com/lcw/p/3802617.html [1.input子系统框架(drivers\input)] 如何得出某个驱动所遵循的框架?    1) 通过网 ...

  3. 【驱动】input子系统全面分析

    初识linux输入子系统 linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler).输入子系统核心层(InputC ...

  4. linux输入子系统(6)-input子系统介绍及结构图

    注:本系列转自: http://www.ourunix.org/post/290.html input子系统介绍         输入设备(如按键,键盘,触摸屏,鼠标,蜂鸣器等)是典型的字符设备,其一 ...

  5. input子系统详解

    一.初识linux输入子系统 linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler).输入子系统核心层(Inpu ...

  6. driver: Linux设备模型之input子系统详解

    本节从整体上讲解了输入子系统的框架结构.有助于读者从整体上认识linux的输入子系统.在陷入代码分析的过程中,通过本节的知识能够找准方向,明白原理. 本节重点: 输入子系统的框架结构 各层对应内核中的 ...

  7. driver: Linux设备模型之input子系统具体解释

    本节从总体上解说了输入子系统的框架结构.有助于读者从总体上认识linux的输入子系统.在陷入代码分析的过程中,通过本节的知识可以找准方向,明确原理. 本节重点: 输入子系统的框架结构 各层相应内核中的 ...

  8. linux kernel input 子系统分析

    Linux 内核为了处理各种不同类型的的输入设备 , 比如说鼠标 , 键盘 , 操纵杆 , 触摸屏 , 设计并实现了一个对上层应用统一的试图的抽象层 , 即是Linux 输入子系统 . 输入子系统的层 ...

  9. Linux Input子系统

    先贴代码: //input.c int input_register_handler(struct input_handler *handler) { //此处省略很多代码 list_for_each ...

随机推荐

  1. Jpush极光推送的一些心得

    在集成极光推送的时候,test完全正常.部署到服务器后只发送一条推送之后推送不继续发送.经排查: 2018-06-28 10:24:26.394 [ThreadPoolTaskExecutor-4] ...

  2. DevExpress GridView自动滚动

    引言 最新有一个winform项目使用的是DevExpress的控件,所以最近都在摸索使用这套控件,实在是佩服整套控件的强大,同时代码写起来也简洁.客户有一个需求,希望报表结果能在外接的大屏幕上定时滚 ...

  3. python面试

    第一部分 python基础篇 1.简述解释型和编译型编程语言? 解释型语言编写的程序不需要编译,在执行的时候,专门有一个解释器能够将VB语言翻译成机器语言,每个语句都是执行的时候才翻译.这样解释型语言 ...

  4. underscore.js源码解析【集合】

    // Collection Functions // -------------------- // The cornerstone, an `each` implementation, aka `f ...

  5. 高德地图添加marker及反地理编码获取POI

    项目中集成百度.高德.腾讯地图已是司空见惯的事情,今天我总结了一下项目中用到的高德地图常用的功能: 1.展示高德地图并定位显示定位图标: 2.添加实时大头针: 3.反地理编码获取周围兴趣点 效果如下: ...

  6. 使用InstallUtil发布windows服务

    发布Windows Service的方法 1. 在默认窗口Service1中右键Add Install,自动生成两个类. 2. 修改serviceInstaller1的属性:  a)ServiceNa ...

  7. virtualBox安装centos,并搭建tomcat

    前言 本文没什么难点,只是发现自己记忆不好,特别是搭建tomcat服务的时候,总是需要去重新查阅资料,特此就写这篇博客来加强自己的记忆,同时也给大家以参考: 路漫漫其修远兮,吾将上下而求索! gith ...

  8. stark组件之多级过滤

    一.引子 在我们浏览很多页面时,会发现一般情况下都有一个分类的功能,而且还是多个类别同时控制,这就是多级过滤.如下图: 一行代表一个类别,第一行就是展示了所有的出版社,选中后就会以出版社分类,第二行就 ...

  9. swiper下滑分页,减少swiper-slide项的时候会出现空白

    修改子项后,先重置当前的页,调用 swiper.slideTo(0); 滚动到初始位置 再调用 swiper.update(); 更新一系列设置就可以了.

  10. c# txt代码转换成HTML格式

    /// <summary> /// 字符串字符处理 /// </summary> /// <param name="chr">等待处理的字符串& ...