一文帶你搞懂Linux之輸入子系統(tǒng)分析詳解(含源碼)
一.輸入子系統(tǒng)簡介
同樣的輸入子系統(tǒng)也需要輸入驅(qū)動的框架,好來辨認(rèn)應(yīng)用程序要打開的是哪個輸入驅(qū)動
比如: 鼠標(biāo)、鍵盤、游戲手柄等等這些都屬于輸入設(shè)備;這些輸入設(shè)備的驅(qū)動都是通過輸入子系統(tǒng)來實現(xiàn)的(當(dāng)然,這些設(shè)備也依賴于usb子系統(tǒng))
這些輸入設(shè)備都各有不同,那么輸入子系統(tǒng)也就只能實現(xiàn)他們的共性,差異性則由設(shè)備驅(qū)動來實現(xiàn)。差異性又體現(xiàn)在哪里?
最直觀的就表現(xiàn)在這些設(shè)備功能上的不同了。對于我們寫驅(qū)動的人來說在設(shè)備驅(qū)動中就只要使用輸入子系統(tǒng)提供的工具(也就是函數(shù))來完成這些“差異”就行了,其他的則是輸入子系統(tǒng)的工作。這個思想不僅存在于輸入子系統(tǒng),其他子系統(tǒng)也是一樣(比如:usb子系統(tǒng)、video子系統(tǒng)等)
所以我們先來分析下輸入子系統(tǒng)input.c的代碼,然后怎么來使用輸入子系統(tǒng)(在內(nèi)核中以input來形容輸入子系統(tǒng))
二.打開input.c,位于內(nèi)核deivers/input
有以下這么兩段:
顯然輸入子系統(tǒng)是作為一個模塊存在,我們先來分析下input_int()入口函數(shù)
上面第4行”err = class_register(&input_class);”是在/sys/class 里創(chuàng)建一個 input類, input_class變量如下圖:

如下圖,我們啟動內(nèi)核,再啟動一個input子系統(tǒng)的驅(qū)動后,也可以看到創(chuàng)建了個"input"類 :

為什么這里代碼只創(chuàng)建類,沒有使用class_device_create()函數(shù)在類下面創(chuàng)建驅(qū)動設(shè)備?
在下面第8小結(jié)會詳細(xì)講到,這里簡單描述:當(dāng)注冊input子系統(tǒng)的驅(qū)動后,才會有驅(qū)動設(shè)備,此時這里的代碼是沒有驅(qū)動的
2. 上面第14行通過register_chrdev創(chuàng)建驅(qū)動設(shè)備,其中變量INPUT_MAJOR =13,所以創(chuàng)建了一個主設(shè)備為13的"input"設(shè)備。
然后我們來看看它的操作結(jié)構(gòu)體input_fops,如下圖:

只有一個.open函數(shù),比如當(dāng)我們掛載一個新的input驅(qū)動,則內(nèi)核便會調(diào)用該.open函數(shù),接下來分析該.open函數(shù)
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。?!前100名進(jìn)群領(lǐng)取,額外贈送一份價值699的內(nèi)核資料包(含視頻教程、電子書、實戰(zhàn)項目及代碼)?

?
三. 然后進(jìn)入input_open_file函數(shù)(drivers/input/input.c)
第3行中,其中iminor (inode)函數(shù)調(diào)用了MINOR(inode->i_rdev);讀取子設(shè)備號,然后將子設(shè)備除以32,找到新掛載的input驅(qū)動的數(shù)組號,然后放在input_handler 驅(qū)動處理函數(shù)handler中
第7行中,若handler有值,說明掛載有這個驅(qū)動,就將handler結(jié)構(gòu)體里的成員file_operations * fops賦到新的file_operations *new_fops里面
第16行中, 再將新的file_operations *new_fops賦到file-> file_operations ?*f_op里, 此時input子系統(tǒng)的file_operations就等于新掛載的input驅(qū)動的file_operations結(jié)構(gòu)體,實現(xiàn)一個偷天換日的效果.
第18行中,然后調(diào)用新掛載的input驅(qū)動的*new_fops里面的成員.open函數(shù)
四.上面代碼的input_table[]數(shù)組在初始時是沒有值的,
所以我們來看看input_table數(shù)組里面的數(shù)據(jù)又是在哪個函數(shù)里被賦值
在input.c函數(shù)(drivers/input/input.c)中搜索input_table,找到它在input_register_handler()函數(shù)中被賦值,代碼如下:
就是將驅(qū)動處理程序input_handler注冊到input_table[]中,然后放在input_handler_list鏈表中,后面會講這個鏈表
五.繼續(xù)來搜索input_register_handler,看看這個函數(shù)被誰來調(diào)用
如下圖所示,有evdev.c(事件設(shè)備),tsdev.c(觸摸屏設(shè)備),joydev.c(joystick操作桿設(shè)備),keyboard.c(鍵盤設(shè)備),mousedev.c(鼠標(biāo)設(shè)備) 這5個內(nèi)核自帶的設(shè)備處理函數(shù)注冊到input子系統(tǒng)中

我們以evdev.c為例,它在evdev_ini()函數(shù)中注冊:
六.我們來看看這個evdev_handler變量是什么結(jié)構(gòu)體,:
就是我們之前看的input_handler驅(qū)動處理結(jié)構(gòu)體
第5行中.fops:文件操作結(jié)構(gòu)體,其中evdev_fops函數(shù)就是自己的寫的操作函數(shù),然后賦到.fops中
第6行中 .minor:用來存放次設(shè)備號
其中EVDEV_MINOR_BASE=64, 然后調(diào)用input_register_handler(&evdev_handler)后,由于EVDEV_MINOR_BASE/32=2,所以存到input_table[2]中
所以當(dāng)open打開這個input設(shè)備,就會進(jìn)入 input_open_file()函數(shù),執(zhí)行evdev_handler-> evdev_fops -> .open函數(shù),如下圖所示:

3. 第8行中.id_table : 表示能支持哪些輸入設(shè)備,比如某個驅(qū)動設(shè)備的input_dev->的id和某個input_handler的id_table相匹配,就會調(diào)用.connect連接函數(shù),如下圖
4. 第3行中.connect:連接函數(shù),將設(shè)備input_dev和某個input_handler建立連接,如下圖

七.我們先來看看上圖的input_register_device()函數(shù),如何創(chuàng)建驅(qū)動設(shè)備的
搜索input_register_device,發(fā)現(xiàn)內(nèi)核自己就已經(jīng)注冊了很多驅(qū)動設(shè)備
7.1然后進(jìn)入input_register_device()函數(shù),代碼如下:
第4行中,將要注冊的input_dev驅(qū)動設(shè)備放在input_dev_list鏈表中
第6行中,其中input_handler_list在前面講過,就是存放每個input_handle驅(qū)動處理結(jié)構(gòu)體,然后list_for_each_entry()函數(shù)會將每個input_handle從鏈表中取出,放到handler中最后會調(diào)用input_attach_handler()函數(shù),將每個input_handle的id_table進(jìn)行判斷,若兩者支持便進(jìn)行連接。
7.2然后我們在回過頭來看注冊input_handler的input_register_handler()函數(shù),如下圖所示

所以,不管新添加input_dev還是input_handler,都會進(jìn)入input_attach_handler()判斷兩者id是否有支持, 若兩者支持便進(jìn)行連接。
7.3我們來看看input_attach_handler()如何實現(xiàn)匹配兩者id的:
若兩者匹配成功,就會自動進(jìn)入input_handler 的connect函數(shù)建立連接
八.我們還是以evdev.c(事件驅(qū)動) 的evdev_handler->connect函數(shù)
來分析是怎樣建立連接的,如下圖:

8.1 evdev_handler的.connect函數(shù)是evdev_connect(),代碼如下:
第16行中,是在保存驅(qū)動設(shè)備名字,名為event%d, 比如下圖(鍵盤驅(qū)動)event1: 因為沒有設(shè)置子設(shè)備號,默認(rèn)從小到大排列,其中event0是表示這個input子系統(tǒng),所以這個鍵盤驅(qū)動名字就是event1
第18行中,是在保存驅(qū)動設(shè)備的主次設(shè)備號,其中主設(shè)備號INPUT_MAJOR=13,因為EVDEV_MINOR_BASE=64,所以此設(shè)備號=64+驅(qū)動程序本事子設(shè)備號, 比如下圖(鍵盤驅(qū)動)event1: ?主次設(shè)備號就是13,65
在之前在2小結(jié)里就分析了input_class類結(jié)構(gòu),所以第19行中,會在/sys/class/input類下創(chuàng)建驅(qū)動設(shè)備event%d,比如下圖(鍵盤驅(qū)動)event1:

4. 最終會進(jìn)入input_register_handle()函數(shù)來注冊,代碼在下面
8.2 input_register_handle()函數(shù)如下:
在第5行中, 因為handle->dev指向input_dev驅(qū)動設(shè)備,所以就是將handle->d_node放入到input_dev驅(qū)動設(shè)備的h_list鏈表中,
即input_dev驅(qū)動設(shè)備的h_list鏈表就指向handle->d_node
2. 在第6行中, 同樣, input_handler驅(qū)動處理結(jié)構(gòu)體的h_list也指向了handle->h_node
最終如下圖所示:

兩者的.h_list都指向了同一個handle結(jié)構(gòu)體,然后通過.h_list 來找到handle的成員.dev和handler,便能找到對方,便建立了連接
九.建立了連接后,又如何讀取evdev.c(事件驅(qū)動) 的evdev_handler->.fops->.read函數(shù)?
事件驅(qū)動的.read函數(shù)是evdev_read()函數(shù),我們來分析下:
十.若read函數(shù)進(jìn)入了休眠狀態(tài),又是誰來喚醒?
十.若read函數(shù)進(jìn)入了休眠狀態(tài),又是誰來喚醒?
我們搜索這個evdev->wait這個等待隊列變量,找到evdev_event函數(shù)里喚醒:
其中evdev_event()是evdev.c(事件驅(qū)動) 的evdev_handler->.event成員,如下圖所示:

當(dāng)有事件發(fā)生了,比如對于按鍵驅(qū)動,當(dāng)有按鍵按下時,就會進(jìn)入.event函數(shù)中處理事件
十一.分析下,是誰調(diào)用evdev_event()這個.event事件驅(qū)動函數(shù)
應(yīng)該就是之前分析的input_dev那層調(diào)用的
我們來看看內(nèi)核 gpio_keys_isr()函數(shù)代碼例子就知道了 (driver/input/keyboard/gpio_key.c)
顯然就是通過input_event()來調(diào)用.event事件函數(shù),我們來看看:
顯然就是通過input_event()來調(diào)用.event事件函數(shù),我們來看看:
若之前驅(qū)動input_dev和處理input_handler已經(jīng)通過input_handler 的.connect函數(shù)建立起了連接,那么就調(diào)用evdev_event()的.event事件函數(shù),如下圖所示:
若之前驅(qū)動input_dev和處理input_handler已經(jīng)通過input_handler 的.connect函數(shù)建立起了連接,那么就調(diào)用evdev_event()的.event事件函數(shù),如下圖所示:

十二.本節(jié)總結(jié)分析:
1. 注冊輸入子系統(tǒng),進(jìn)入put_init():
創(chuàng)建主設(shè)備號為13的"input"字符設(shè)備
2. open打開驅(qū)動,進(jìn)入input_open_file():
2. open打開驅(qū)動,進(jìn)入input_open_file():
更新設(shè)備的file_oprations
執(zhí)行file_oprations->open函數(shù)
3. 注冊input_handler,進(jìn)入input_register_handler():
添加到input_table[]處理數(shù)組中
添加到input_handler_list鏈表中
添加到input_handler_list鏈表中
判斷input_dev的id,是否有支持這個驅(qū)動的設(shè)備
判斷input_dev的id,是否有支持這個驅(qū)動的設(shè)備
4. 注冊input_dev,進(jìn)入input_register_device():
4. 注冊input_dev,進(jìn)入input_register_device():
放在input_dev_list鏈表中
判斷input_handler的id,是否有支持這個設(shè)備的驅(qū)動
判斷input_handler的id,是否有支持這個設(shè)備的驅(qū)動
5. 判斷input_handler和input_dev的id,進(jìn)入input_attach_handler():
5. 判斷input_handler和input_dev的id,進(jìn)入input_attach_handler():
匹配兩者id,
匹配成功調(diào)用input_handler ->connecthandler->connect(handler, dev, id); ? ? ? ? ? ? ?//建立連接
6. 建立input_handler和input_dev的連接,進(jìn)入input_handler->connect():
創(chuàng)建全局結(jié)構(gòu)體,通過input_handle結(jié)構(gòu)體連接雙方
7. 有事件發(fā)生時,比如按鍵中斷,在中斷函數(shù)中需要進(jìn)入input_event()上報事件:
找到驅(qū)動處理結(jié)構(gòu)體,然后執(zhí)行input_handler->event()
