Linux設(shè)備模型之input子系統(tǒng)詳解

本節(jié)從整體上講解了輸入子系統(tǒng)的框架結(jié)構(gòu)。有助于讀者從整體上認(rèn)識(shí)linux的輸入子系統(tǒng)。在陷入代碼分析的過(guò)程中,通過(guò)本節(jié)的知識(shí)能夠找準(zhǔn)方向,明白原理。
本節(jié)重點(diǎn):
- 輸入子系統(tǒng)的框架結(jié)構(gòu)
- 各層對(duì)應(yīng)內(nèi)核中的文件位置
- 輸入子系統(tǒng)的事件處理機(jī)制
- 輸入子系統(tǒng)的驅(qū)動(dòng)層基本操作流程
- 輸入子系統(tǒng)的驅(qū)動(dòng)層常用函數(shù)
本節(jié)難點(diǎn):
輸入子系統(tǒng)的事件處理機(jī)制
輸入子系統(tǒng)的驅(qū)動(dòng)工作流程
1 初識(shí)linux輸入子系統(tǒng)
linux輸入子系統(tǒng)(linux input subsystem)從上到下由三層實(shí)現(xiàn),分別為:輸入子系統(tǒng)事件處理層(EventHandler)、輸入子系統(tǒng)核心層(InputCore)和輸入子系統(tǒng)設(shè)備驅(qū)動(dòng)層。
對(duì)于輸入子系統(tǒng)設(shè)備驅(qū)動(dòng)層而言,主要實(shí)現(xiàn)對(duì)硬件設(shè)備的讀寫(xiě)訪問(wèn),中斷設(shè)置,并把硬件產(chǎn)生的事件轉(zhuǎn)換為核心層定義的規(guī)范提交給事件處理層。
對(duì)于核心層而言,為設(shè)備驅(qū)動(dòng)層提供了規(guī)范和接口。設(shè)備驅(qū)動(dòng)層只要關(guān)心如何驅(qū)動(dòng)硬件并獲得硬件數(shù)據(jù)(例如按下的按鍵數(shù)據(jù)),然后調(diào)用核心層提供的接口,核心層會(huì)自動(dòng)把數(shù)據(jù)提交給事件處理層。
對(duì)于事件處理層而言,則是用戶編程的接口(設(shè)備節(jié)點(diǎn)),并處理驅(qū)動(dòng)層提交的數(shù)據(jù)處理。
對(duì)于linux輸入子系統(tǒng)的框架結(jié)構(gòu)如下圖1所示:
圖1 linux輸入子系統(tǒng)框架結(jié)構(gòu)
由上圖所展現(xiàn)的內(nèi)容就是linux輸入子系統(tǒng)的分層結(jié)構(gòu)。
/dev/input目錄下顯示的是已經(jīng)注冊(cè)在內(nèi)核中的設(shè)備編程接口,用戶通過(guò)open這些設(shè)備文件來(lái)打開(kāi)不同的輸入設(shè)備進(jìn)行硬件操作。
事件處理層為不同硬件類型提供了用戶訪問(wèn)及處理接口。例如當(dāng)我們打開(kāi)設(shè)備/dev/input/mice時(shí),會(huì)調(diào)用到事件處理層的Mouse Handler來(lái)處理輸入事件,這也使得設(shè)備驅(qū)動(dòng)層無(wú)需關(guān)心設(shè)備文件的操作,因?yàn)镸ouse Handler已經(jīng)有了對(duì)應(yīng)事件處理的方法。
輸入子系統(tǒng)由內(nèi)核代碼drivers/input/input.c構(gòu)成,它的存在屏蔽了用戶到設(shè)備驅(qū)動(dòng)的交互細(xì)節(jié),為設(shè)備驅(qū)動(dòng)層和事件處理層提供了相互通信的統(tǒng)一界面。
下圖2簡(jiǎn)單描述了linux輸入子系統(tǒng)的事件處理機(jī)制:
圖2 linux輸入子系統(tǒng)事件處理機(jī)制
由上圖可知輸入子系統(tǒng)核心層提供的支持以及如何上報(bào)事件到input event drivers。
作為輸入設(shè)備的驅(qū)動(dòng)開(kāi)發(fā)者,需要做以下幾步:
1、在驅(qū)動(dòng)加載模塊中,設(shè)置你的input設(shè)備支持的事件類型,類型參見(jiàn)表1設(shè)置
2、 注冊(cè)中斷處理函數(shù),例如鍵盤設(shè)備需要編寫(xiě)按鍵的抬起、放下,觸摸屏設(shè)備需要編寫(xiě)按下、抬起、絕對(duì)移動(dòng),鼠標(biāo)設(shè)備需要編寫(xiě)單擊、抬起、相對(duì)移動(dòng),并且需要在必要的時(shí)候提交硬件數(shù)據(jù)(鍵值/坐標(biāo)/狀態(tài)等等)
3、 將輸入設(shè)備注冊(cè)到輸入子系統(tǒng)中
表1 Linux輸入子系統(tǒng)支持的數(shù)據(jù)類型
EV_SYN 0x00 同步事件 EV_KEY 0x01 按鍵事件 EV_REL 0x02 相對(duì)坐標(biāo)(如:鼠標(biāo)移動(dòng),報(bào)告相對(duì)最后一次位置的偏移) EV_ABS 0x03 絕對(duì)坐標(biāo)(如:觸摸屏或操作桿,報(bào)告絕對(duì)的坐標(biāo)位置) EV_MSC 0x04 其它 EV_SW 0x05 開(kāi)關(guān) EV_LED 0x11 按鍵/設(shè)備燈 EV_SND 0x12 聲音/警報(bào) EV_REP 0x14 重復(fù) EV_FF 0x15 力反饋 EV_PWR 0x16 電源 EV_FF_STATUS 0x17 力反饋狀態(tài) EV_MAX 0x1f 事件類型最大個(gè)數(shù)和提供位掩碼支持
由表1可知,設(shè)備所能表示的事件種類,一個(gè)設(shè)備可以選擇一個(gè)或多個(gè)事件類型上報(bào)給輸入子系統(tǒng)。
Linux輸入子系統(tǒng)提供了設(shè)備驅(qū)動(dòng)層上報(bào)輸入事件的函數(shù),在include/linux/input.h中:
voidinput_report_key(struct input_dev *dev, unsigned int code, int value); //上報(bào)按鍵事件 voidinput_report_rel(struct input_dev *dev, unsigned int code, int value); //上報(bào)相對(duì)坐標(biāo)事件 voidinput_report_abs(struct input_dev *dev, unsigned int code, int value); //上報(bào)絕對(duì)坐標(biāo)事件
當(dāng)提交輸入設(shè)備產(chǎn)生的輸入事件之后,需要調(diào)用下面的函數(shù)來(lái)通知輸入子系統(tǒng),以處理設(shè)備產(chǎn)生的完整事件:
void input_sync(struct input_dev *dev);
2 輸入設(shè)備驅(qū)動(dòng)的簡(jiǎn)單案例
在Linux內(nèi)核文檔的documentation/input下,有一個(gè)input-programming.txt文件,講解了編寫(xiě)輸入設(shè)備驅(qū)動(dòng)程序的核心步驟。
提供的案例代碼描述了一個(gè)button設(shè)備,產(chǎn)生的事件通過(guò)BUTTON_PORT引腳獲取,當(dāng)有按下/釋放發(fā)生時(shí),BUTTON_IRQ被觸發(fā),以下是驅(qū)動(dòng)的源代碼:
#include #include #include #include #include static struct input_dev *button_dev; static void button_interrupt(int irq, void*dummy, struct pt_regs *fp) { input_report_key(button_dev, BTN_1, inb(BUTTON_PORT) & 1); input_sync(button_dev); } static int __init button_init(void) { int error; if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button",NULL)) { printk(KERN_ERR"button.c: Can't allocate irq %d\n", button_irq); return -EBUSY; } button_dev = input_allocate_device(); if (!button_dev) { printk(KERN_ERR"button.c: Not enough memory\n"); error = -ENOMEM; goto err_free_irq; } button_dev->evbit[0] = BIT(EV_KEY); button_dev->keybit[LONG(BTN_0)] = BIT(BTN_0); error = input_register_device(button_dev); if (error) { printk(KERN_ERR"button.c: Failed to register device\n"); goto err_free_dev; } return 0; err_free_dev: input_free_device(button_dev); err_free_irq: free_irq(BUTTON_IRQ, button_interrupt); return error; } static void __exit button_exit(void) { input_unregister_device(button_dev); free_irq(BUTTON_IRQ, button_interrupt); } module_init(button_init); module_exit(button_exit);
編寫(xiě)基于輸入子系統(tǒng)的設(shè)備驅(qū)動(dòng)程序需要包含,因?yàn)樗溯斎胱酉到y(tǒng)的接口和所有的宏定義,這些內(nèi)容在編寫(xiě)輸入設(shè)備驅(qū)動(dòng)程序時(shí)需要用到。
button_init函數(shù)說(shuō)明:
當(dāng)模塊加載(insmod)或內(nèi)核引導(dǎo)過(guò)程中,button_init函數(shù)會(huì)被調(diào)用。首先做的工作是獲取能夠正確控制硬件設(shè)備的硬件資源(例如內(nèi)存、IO內(nèi)存、中斷和DMA),在代碼中BUTTON_IRQ作為BUTTON設(shè)備的中斷資源,通過(guò)request_irq()函數(shù)被申請(qǐng)注冊(cè)。當(dāng)有按鍵按下/釋放時(shí),調(diào)用button_interrupt()中斷處理函數(shù)獲取按鍵值BUTTON_PORT(BUTTON設(shè)備的I/O資源)。
那么輸入子系統(tǒng)怎么能夠知道這個(gè)設(shè)備為輸入設(shè)備呢?通過(guò)第8行為設(shè)備定義一個(gè)用于描述一個(gè)輸入設(shè)備對(duì)象。
static struct input_dev *button_dev;
定義了button_dev之后,如何通知輸入子系統(tǒng)有新的輸入設(shè)備了呢?或者說(shuō)如何把一個(gè)新的輸入設(shè)備加入到輸入子系統(tǒng)中呢?可以通過(guò)輸入子系統(tǒng)核心層input.c中提供的函數(shù)分配一個(gè)輸入設(shè)備,在代碼的第25行。
button_dev= input_allocate_device();
有了輸入設(shè)備的描述,當(dāng)事件產(chǎn)生時(shí),輸入子系統(tǒng)怎么能夠知道設(shè)備產(chǎn)生的事件類型呢?通過(guò)32和33行的代碼。
button_dev->evbit[0]= BIT(EV_KEY); button_dev->keybit[LONG(BTN_0)]= BIT(BTN_0);
其中evbit和keybit成員分別代表設(shè)備產(chǎn)生的事件類型和上報(bào)的按鍵值。其中輸入子系統(tǒng)的一些位操作NBITS、BIT、LONG經(jīng)常被用到:
#defineNBITS(x) (((x)/BITS_PER_LONG)+1) //通過(guò)位x獲取數(shù)組的長(zhǎng)度 #defineBIT(x) (1UL<<((x)%BITS_PER_LONG)) //返回位x在數(shù)組中的位域 #defineLONG(x) ((x)/BITS_PER_LONG) //返回位x的索引
以上的工作做完之后,即可注冊(cè)為輸入設(shè)備了,代碼的35行。
input_register_device(button_dev);
這個(gè)函數(shù)把button_dev輸入設(shè)備掛入輸入設(shè)備鏈表中,并且通知事件處理層調(diào)用connect函數(shù)完成設(shè)備和事件處理的綁定,當(dāng)用戶打開(kāi)設(shè)備時(shí),便能夠調(diào)用到相應(yīng)的事件處理接口獲得硬件上報(bào)的數(shù)據(jù)了。input_register_device()函數(shù)是會(huì)睡眠的函數(shù),因此不能夠在中斷上下文和持有自旋鎖的代碼中調(diào)用。
當(dāng)我們把上面的工作做完之后,設(shè)備驅(qū)動(dòng)中唯一值得關(guān)注的就是button_interrupt()中斷處理函數(shù)了。當(dāng)按鍵動(dòng)作發(fā)生,button_interrupt()函數(shù)被調(diào)用,完成事件的上報(bào)由其中的兩條語(yǔ)句完成。
input_report_key(button_dev, BTN_1, inb(BUTTON_PORT) & 1); input_sync(button_dev);
其中input_report_key上報(bào)了這是一個(gè)按鍵事件,且它的值為inb(BUTTON_PORT) & 1,由于案例代碼只產(chǎn)生一個(gè)按鍵的值,因此input_sync()在這里不起關(guān)鍵作用。但如果是一個(gè)觸摸屏,即有x坐標(biāo)和y坐標(biāo),則需要通過(guò)input_sync()函數(shù)把x和y坐標(biāo)完整地傳遞給輸入子系統(tǒng)。
用于測(cè)試的應(yīng)用層代碼:
相關(guān)文章
linux系統(tǒng)中InputStream輸入流的方法之reset()和mark()命令的注意事項(xiàng)
BufferInputStream重寫(xiě)了父類FilterInputStream的mark和resetf方法,其有支持mark和reset方法的能力。而FileInputStream則沒(méi)有重寫(xiě)父類InputStream的這兩個(gè)方法,其不具有m2014-10-07- 用Linux系統(tǒng)防火墻功能抵御網(wǎng)絡(luò)攻擊 虛擬主機(jī)服務(wù)商在運(yùn)營(yíng)過(guò)程中可能會(huì)受到黑客攻擊,常見(jiàn)的攻擊方式有SYN,DDOS等。 通過(guò)更換IP,查找被攻擊的站點(diǎn)可能避開(kāi)攻擊,2008-09-08
如何運(yùn)行openSUSE?Win10中安裝SUSE Linux子系統(tǒng)的詳細(xì)圖文教程
此前微軟與Canonical達(dá)成合作協(xié)議并在Windows 10內(nèi)置了Ubuntu子系統(tǒng),支持使用Linux命令行工具等。如今開(kāi)發(fā)者也可以選擇安裝使用SUSE Linux,下面就詳情來(lái)看看具體的安裝過(guò)2017-01-17Win10 Version 1607中的Linux子系統(tǒng)安裝方法詳解
微軟正式宣布了與Ubuntu母公司達(dá)成合作,在Windows 10 Version 1607中,增加了一個(gè)Linux子系統(tǒng)。那么如何啟用win10 Version 1607中的Linux子系統(tǒng)呢?下面就詳細(xì)看看具體安2017-01-17win10內(nèi)置linux子系統(tǒng)或帶來(lái)新的安全隱患
在win10一周年跟新版本中內(nèi)置了linux子系統(tǒng),這雖然大大的方便開(kāi)發(fā)人員,但是也給win10系統(tǒng)帶來(lái)了新的危險(xiǎn)因素,下面我們就來(lái)看看詳細(xì)的內(nèi)容,需要的朋友可以參考下2016-08-08Win10 RS1預(yù)覽版14251或包含神秘Linux子系統(tǒng) 安卓移植項(xiàng)目回歸?
Wi10 Build 14251包含Linux子系統(tǒng)是怎么回事?有網(wǎng)友發(fā)現(xiàn)在最新的Win10 Redstone桌面系統(tǒng)14251上出現(xiàn)了類似文件,Astoria項(xiàng)目失寵的其中一個(gè)原因是,安卓應(yīng)用只能運(yùn)行在Win2016-02-01Linux下使用blkid命令查詢?cè)O(shè)備及文件系統(tǒng)信息的方法
lsblk 是一個(gè) Linux 工具,它會(huì)顯示有關(guān)你系統(tǒng)里所有可用塊設(shè)備的信息,它從 sysfs 文件系統(tǒng)中獲取信息,默認(rèn)情況下,這個(gè)工具將會(huì)以樹(shù)狀格式顯示(除了內(nèi)存虛擬磁盤外的)所有2016-05-19在Linux筆記本上執(zhí)行這句命令就能導(dǎo)致設(shè)備永久變磚
上個(gè)月,有用戶在Arch Linux論壇發(fā)帖提問(wèn),為什么他的筆記本在運(yùn)行了一個(gè)簡(jiǎn)單的rm -rf -no-preserve-root命令之后就完全沒(méi)法啟動(dòng)了2016-02-03如何獲得Linux系統(tǒng)的內(nèi)置模塊和設(shè)備驅(qū)動(dòng)列表
最新的Linux發(fā)行版的內(nèi)核只帶了相對(duì)較小的“內(nèi)置模塊(built-in modules)”,其余的特定硬件驅(qū)動(dòng)或者自定義功能作為“可加載模塊”來(lái)讓你有選擇地加載或卸載。我想要知道L2015-12-07