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;

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章