evdev事件处理器数据处理过程



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

struct evdev_client {

   unsignedint head;                 //头指针

   unsignedint tail;                 //尾指针

   unsignedint packet_head;          //包头指针

   spinlock_t buffer_lock;

   struct fasync_struct *fasync;

   struct evdev *evdev;

   struct list_head node;

   unsignedint clk_type;

   bool revoked;

   unsignedlong *evmasks[EV_CNT];

   unsignedint bufsize;              //循环队列大小

   struct input_event buffer[];       //循环队列数组

};

evdev_client中三个偏移量headtailpacket_head的意义如下:

  1. head循环队列的头指针。

  2. tail是循环队列的尾指针。

  3. 在说pack_head意义之前先介绍一下数据包的概念:

输入设备上报的多个(>=0)input_event事件和同步事件组成一个数据包。

packet_head是下一个数据包的第一个元素的位置索引,在每次上报同步事件时更新。需要注意的headpack_head的不同:

head是以input_event为单位,每上报一个input_eventhead计数+1,而packet_head是以数据包为单位,在上报同步事件时才会更新,且一个数据包可能包含多个input_event。大多数情况下同步事件发生时pack_head更新为head,在同步事件发生之前,headpacke_head之前;只有在buffer满时,有点特殊,更新为tail 

环形缓冲区的工作机制

循环队列入队算法:

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或者8n倍,符合求与运算的要求。

                     


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

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

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

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


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

staticint evdev_open(struct inode *inode, struct file *file){

   struct evdev *evdev = container_of(inode->i_cdev,struct evdev, cdev);

   // 1.计算环形缓冲区大小bufsize以及evdev_client对象大小size

   unsignedint bufsize =evdev_compute_buffer_size(evdev->handle.dev);

   unsignedint 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);

   return0;

 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()函数将事件写入环形缓冲区:

staticvoid __pass_event(struct evdev_client *client, conststruct 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 unconsumedevents, leaving

    *EV_SYN/SYN_DROPPED plus the newest event in the queue.

*Buffer满了,说明上层读取的速度较慢,需要放弃部分老旧的数据,仅保留最新的input_event,同时上报一个

*SYN_DROPPED同步事件说明去掉了一些数据,最后更新packet_headtail(?不清楚为什么更新为tail*/

       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 intevdev_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;

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值