在事件處理層()中結構體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中三個偏移量head、tail、packet_head的意義如下:
-
head循環隊列的頭指針。
-
tail是循環隊列的尾指針。
-
在說pack_head意義之前先介紹一下數據包的概念:
輸入設備上報的多個(>=0)input_event事件和同步事件組成一個數據包。
packet_head是下一個數據包的第一個元素的位置索引,在每次上報同步事件時更新。需要注意的head和pack_head的不同:
head是以input_event爲單位,每上報一個input_event,head計數+1,而packet_head是以數據包爲單位,在上報同步事件時纔會更新,且一個數據包可能包含多個input_event。大多數情況下同步事件發生時pack_head更新爲head,在同步事件發生之前,head在packe_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或者8的n倍,符合“求與”運算的要求。
環形緩衝區的構造以及初始化
用戶層通過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_head爲tail(?不清楚爲什麼更新爲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;
}