Linux驅(qū)動(dòng)之輸入子系統(tǒng)(張棲銀詳談)

一、input子系統(tǒng)介紹

1.1 系統(tǒng)介紹

本文是基于linux-2.6.32內(nèi)核進(jìn)行分析的,如果使用的是其他版本的內(nèi)核,其內(nèi)核調(diào)用的函數(shù)可能有所不同,但是其實(shí)現(xiàn)原理是相通的。

1.2 input子系統(tǒng)的引入

以前我們寫一些輸入設(shè)備(鍵盤、鼠標(biāo)等)的驅(qū)動(dòng)都是采用字符設(shè)備、混雜設(shè)備處理的。問題由此而來,Linux開源社區(qū)的大神們看到了這大量輸入設(shè)備如此分散不堪,有木有可以實(shí)現(xiàn)一種機(jī)制,可以對(duì)分散的、不同類別的輸入設(shè)備進(jìn)行統(tǒng)一的驅(qū)動(dòng),所以才出現(xiàn)了輸入子系統(tǒng)。

輸入設(shè)備(如按鍵、鍵盤,觸摸屏,鼠標(biāo)等)是典型的字符設(shè)備,其主設(shè)備號(hào)固定為13,其一般的工作機(jī)制是在底層在按鍵、觸摸、鼠標(biāo)點(diǎn)擊等動(dòng)作發(fā)生時(shí)產(chǎn)生一個(gè)中斷(或驅(qū)動(dòng)通過timer定時(shí)查詢),然后CPU通過SPI、IIC或者外部存儲(chǔ)器總線讀取鍵值、坐標(biāo)等數(shù)據(jù),放在一個(gè)緩沖區(qū),字符設(shè)備驅(qū)動(dòng)管理該緩沖區(qū),而驅(qū)動(dòng)的read()接口讓用戶可以讀取鍵值、坐標(biāo)等數(shù)據(jù),其過程如圖1.1所示。

image
image

在Linux中,輸入子系統(tǒng)是由輸入子系統(tǒng)設(shè)備驅(qū)動(dòng)層、輸入子系統(tǒng)核心層(input core)和輸入子系統(tǒng)事件處理層(Event handler)組成,如圖1.2所示。其中設(shè)備驅(qū)動(dòng)層提供對(duì)硬件各寄存器的讀寫訪問和將底層硬件對(duì)用戶輸入訪問的響應(yīng)轉(zhuǎn)換為標(biāo)準(zhǔn)的輸入事件,再通過核心層提交給事件處理層;而核心層對(duì)下提供了設(shè)備驅(qū)動(dòng)層的編程接口,對(duì)上又提供了事件處理層的編程接口;而事件處理層就為我們用戶空間的應(yīng)用程序提供了統(tǒng)一訪問設(shè)備的接口和驅(qū)動(dòng)層提交來的事件處理。所以這使得我們輸入設(shè)備的驅(qū)動(dòng)部分不在用戶關(guān)心對(duì)設(shè)備文件的操作,而是要關(guān)心對(duì)各硬件寄存器的操作和提交的輸入事件。

image
image

1.3 input子系統(tǒng)的優(yōu)點(diǎn)

輸入子系統(tǒng)的引入,也為我們帶來了許多的好處:

  • 統(tǒng)一了物理形態(tài)各異的相似的輸入設(shè)備的處理功能。例如,各種鼠標(biāo),不論P(yáng)S/2、USB、還是藍(lán)牙,都被同樣處理。
  • 提供了用于分發(fā)輸入報(bào)告給用戶應(yīng)用程序的簡(jiǎn)單的事件(event)接口。你的驅(qū)動(dòng)不必創(chuàng)建、管理/dev節(jié)點(diǎn)以及相關(guān)的訪問方法。因此它能夠很方便的調(diào)用輸入API以發(fā)送鼠標(biāo)移動(dòng)、鍵盤按鍵,或觸摸事件給用戶空間。X windows這樣的應(yīng)用程序能夠無縫地運(yùn)行于輸入子系統(tǒng)提供的event接口之上。
  • 抽取出了輸入驅(qū)動(dòng)的通用部分,簡(jiǎn)化了驅(qū)動(dòng),并提供了一致性。例如,輸入子系統(tǒng)提供了一個(gè)底層驅(qū)動(dòng)(稱為serio)的集合,支持對(duì)串口和鍵盤控制器等硬件輸入的訪問。

注:更多詳細(xì)描述可參見《精通Linux設(shè)備驅(qū)動(dòng)程序開發(fā)》這本書。

更重要的是,input子系統(tǒng)的引入,使我們在具體的開發(fā)中可以在輸入硬件設(shè)備更換的情況下保持應(yīng)用不做任何修改。

1.4 input子系統(tǒng)與字符設(shè)備實(shí)現(xiàn)比較

在進(jìn)行字符設(shè)備驅(qū)動(dòng)程序開發(fā)的過程中,我們的實(shí)現(xiàn)步驟如下:

  • 申請(qǐng)一個(gè)字符設(shè)備號(hào):可以自己指定,也可系統(tǒng)自動(dòng)分配;
  • 構(gòu)造一個(gè)file_operations結(jié)構(gòu)體,其包含對(duì)設(shè)備的所有操作;
  • 實(shí)現(xiàn)file_operations結(jié)構(gòu)體中的成員函數(shù);
  • 將字符設(shè)備注冊(cè)進(jìn)系統(tǒng)中:register_chrdev();
  • 創(chuàng)建設(shè)備類和設(shè)備節(jié)點(diǎn):class_create()、device_create();
  • 告訴內(nèi)核入口與出口函數(shù):module_init()、module_exit();

輸入子系統(tǒng)與混雜設(shè)備驅(qū)動(dòng)一樣,也是一個(gè)典型的字符設(shè)備,那么其注冊(cè)的過程與字符設(shè)備驅(qū)動(dòng)一樣,也必須經(jīng)過上面的這些步驟。只是輸入子系統(tǒng)中的輸入設(shè)備一般只接收輸入設(shè)備的中斷和獲取輸入設(shè)備的數(shù)據(jù),而不輸出數(shù)據(jù)到輸入設(shè)備而已。

在輸入子系統(tǒng)中,將字符設(shè)備驅(qū)動(dòng)分為了三個(gè)部分:與硬件操作相關(guān)的設(shè)備驅(qū)動(dòng)層(由驅(qū)動(dòng)工程師實(shí)現(xiàn))、輸入子系統(tǒng)核心層(input core)和輸入子系統(tǒng)事件處理層(Event handler),其中后面兩個(gè)部分都已經(jīng)由系統(tǒng)幫我們實(shí)現(xiàn)了。其實(shí)后面兩個(gè)部分可以把它看作是一個(gè)整體,就是與硬件操作無關(guān)的軟件部分。那么我們實(shí)現(xiàn)input設(shè)備驅(qū)動(dòng)的過程為:

  • 申請(qǐng)一個(gè)輸入設(shè)備結(jié)構(gòu)體:input_allocate_device();
  • 設(shè)置輸入設(shè)備支持的事務(wù)類型:set_bit(xxx,devp->evbit);
  • 設(shè)置輸入設(shè)備支持的具體哪些事務(wù):set_bit(xxx,devp->xxxbit);
  • 注冊(cè)輸入設(shè)備到輸入子系統(tǒng):input_register_device(devp);
  • 實(shí)現(xiàn)具體的硬件相關(guān)操作,如注冊(cè)中斷等,并在中斷處理函數(shù)中通知事務(wù)已發(fā)生:input_event()、input_sync();

也就是說:無論輸入子系統(tǒng)多么強(qiáng)大、封裝的多么的好,與硬件相關(guān)的操作還是得我們親自實(shí)現(xiàn)。

1.5 input子系統(tǒng)與字符設(shè)備與應(yīng)用層數(shù)據(jù)交互比較

編寫過字符設(shè)備驅(qū)動(dòng)的人都知道,應(yīng)用程序與驅(qū)動(dòng)之間實(shí)現(xiàn)數(shù)據(jù)交互就是通過應(yīng)用API的read()、write()調(diào)用,從而產(chǎn)生一個(gè)SWI軟件中斷,然后通過主設(shè)備號(hào)找到對(duì)應(yīng)的struct cdev結(jié)構(gòu)體實(shí)體,從而找到具體硬件設(shè)備的struct file_operations結(jié)構(gòu)體,然后具體調(diào)用底層的drv_read()、drv_write(),我們就是在具體的drv_read()和drv_write()中實(shí)現(xiàn)對(duì)硬件的操作的,其過程如下:

read()—>swi_read()—>drv_read()—>硬件操作

那么對(duì)應(yīng)到輸入子系統(tǒng)呢?前面已經(jīng)說了,輸入子系統(tǒng)也是字符設(shè)備,那么它也必須經(jīng)歷上面的這些步驟,只是中間穿插了幾個(gè)查找具體輸入設(shè)備的過程(畢竟將所有的輸入設(shè)備都加入到輸入子系統(tǒng),就不止一個(gè)設(shè)備了)而已。那么是如何穿插的呢:

首先在input.c(輸入子系統(tǒng)的核心)文件的打開函數(shù)中找到具體的input_handler,然后取出具體input_handler中的fops(也即struct file_operations結(jié)構(gòu)體)填充struct file中的f_op成員,那么之后應(yīng)用調(diào)用read()、write()函數(shù)就是調(diào)用具體input_handler指向的struct file_operations結(jié)構(gòu)體中的成員了;最后再調(diào)用fops中的open()函數(shù)打開具體的函數(shù):

struct input_handler *handler;
handler = input_table[iminor(inode) >> 5];
old_fops = file->f_op;
file->f_op = new_fops;
err = new_fops->open(inode, file);

這里說明一下:input.c是input子系統(tǒng)的核心,內(nèi)核已經(jīng)實(shí)現(xiàn),各種input_handler(包含open、release、read、write、ioctl、fasync、poll等,即硬件處理函數(shù))也由系統(tǒng)抽象出來幫我們實(shí)現(xiàn)了,后面會(huì)講解其具體實(shí)現(xiàn)過程。

通過前面的介紹,不太理解也沒有關(guān)系。你只需要記住,其實(shí)輸入子系統(tǒng)就是一個(gè)典型的字符設(shè)備,它也逃不過字符設(shè)備的框架,其應(yīng)用與驅(qū)動(dòng)交互的流程也和字符設(shè)備驅(qū)動(dòng)一樣,沒有什么不同就是了(要從心里小瞧它)。

要想搞明白輸入子系統(tǒng)的框架,只需要弄明白應(yīng)用程序是如何與具體的硬件設(shè)備驅(qū)動(dòng)進(jìn)行交互的就行了。而這個(gè)過程如下其實(shí)就是主設(shè)備號(hào)13的字符設(shè)備、input_handler、input_handle和input_dev幾個(gè)的關(guān)系

  • Linux系統(tǒng)啟動(dòng)時(shí)注冊(cè)輸入子系統(tǒng)(注冊(cè)主設(shè)備號(hào)為13的字符設(shè)備);
  • 應(yīng)用程序調(diào)用open(),對(duì)應(yīng)調(diào)用輸入子系統(tǒng)的input_open_file();
  • input_open_file()找到對(duì)應(yīng)的input_handler,并調(diào)用其中的open();
  • 應(yīng)用程序調(diào)用read()函數(shù),對(duì)應(yīng)調(diào)用open()中找到的input_handler中的read()函數(shù),阻塞;
  • 驅(qū)動(dòng)收到硬件訪問需求,進(jìn)入中斷處理函數(shù),對(duì)應(yīng)到input_dev;
  • 驅(qū)動(dòng)調(diào)用input_event()上報(bào)事件,上報(bào)過程為:通過input_dev找到input_handle,再通過input_handle找到匹配的input_handler,然后調(diào)用該input_handler的event()函數(shù),該函數(shù)即是喚醒對(duì)應(yīng)read()、write()函數(shù)的實(shí)現(xiàn);

詳細(xì)過程如下三條線路所示:

  1. open()—>input_open_file()—>input_handler->fops->open()
  2. read()/write()/ioctl()—>input_handler->fops->read()/write()/ioctl()
  3. 硬件—>input_dev的中斷處理程序—>input_event()—>input_handle->input_handler->event()

注意:->是指針;—>是下一步調(diào)用。

從應(yīng)用到底層的匹配過程是通過input_handler,具體驅(qū)動(dòng)中是通過靜態(tài)全局指針數(shù)組變量input_table[]實(shí)現(xiàn)的;而硬件到應(yīng)用程序的匹配過程是通過input_handle結(jié)構(gòu)體找到對(duì)應(yīng)的input_handler,從而實(shí)現(xiàn)數(shù)據(jù)傳輸?shù)模唧w到代碼就是通過input_handler->connect()函數(shù)將input_dev、input_handler和input_handle三者進(jìn)行綁定的,三者綁定的關(guān)系如圖1.3所示。


image
image

二、input子系統(tǒng)實(shí)現(xiàn)

本章節(jié)將詳細(xì)講解input子系統(tǒng)的框架,也就是input子系統(tǒng)中如何實(shí)現(xiàn)應(yīng)用層數(shù)據(jù)與底層硬件之間的數(shù)據(jù)交互、底層硬件(input_dev)如何與系統(tǒng)實(shí)現(xiàn)的驅(qū)動(dòng)(input_handler)關(guān)聯(lián)等。

2.1 input子系統(tǒng)框架

正如前面的介紹,input子系統(tǒng)將所有的輸入設(shè)備統(tǒng)稱為像鼠標(biāo)輸入、鍵盤輸入、joydev輸入等,將輸入的數(shù)據(jù)封裝成統(tǒng)一的事務(wù)格式(struct input_event)上傳到應(yīng)用,而將具體的硬件設(shè)備分離出來(這才是我們要做的事),如圖2.1所示。


image
image

在如上的系統(tǒng)分層結(jié)構(gòu)下,我們的應(yīng)用程序就可以不用關(guān)心獲取到的數(shù)據(jù)是來源于SPI的鍵盤、還是IIC的鍵盤,它只需要關(guān)心獲取到數(shù)據(jù)的具體含義就行了,這樣就保證了更換不同的硬件設(shè)備,而不用修改一行應(yīng)用程序代碼,如圖2.2所示。

還有 80% 的精彩內(nèi)容
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
支付 ¥3.99 繼續(xù)閱讀

相關(guān)閱讀更多精彩內(nèi)容

  • 輸入子系統(tǒng)概述 Linux內(nèi)核為了能夠處理各種不同類型的輸入設(shè)備,比如 觸摸屏 ,鼠標(biāo) , 鍵盤 , 操縱桿...
    JalynFang閱讀 10,604評(píng)論 4 10
  • 大學(xué)的時(shí)候,幫朋友寫的操作系統(tǒng)調(diào)研的作業(yè),最近整理過去的文檔時(shí)候偶然發(fā)現(xiàn),遂作為博客發(fā)出來。 從串口驅(qū)動(dòng)到Linu...
    free_will閱讀 7,680評(píng)論 7 59
  • 本文開啟 linux 內(nèi)核 V4L2 框架部分的學(xué)習(xí)之旅,本文僅先對(duì) V4L2 的框架做一個(gè)綜述性的概括介紹,然后...
    yellowmax閱讀 7,854評(píng)論 0 13
  • 1:InputChannel提供函數(shù)創(chuàng)建底層的Pipe對(duì)象 2: 1)客戶端需要新建窗口 2)new ViewRo...
    自由人是工程師閱讀 5,701評(píng)論 0 18
  • ??JavaScript 與 HTML 之間的交互是通過事件實(shí)現(xiàn)的。 ??事件,就是文檔或?yàn)g覽器窗口中發(fā)生的一些特...
    霜天曉閱讀 3,678評(píng)論 1 11

友情鏈接更多精彩內(nèi)容