作者:Bright-Ho 聯繫方式:[email protected] input輸入子系統框架分析(純軟體方面): 上一節,我們簡單的描述的什麼是輸入子系統;什麼是字元設備;以及其作用;重點是我們講到分析輸入子系統必須結合硬體設備來分析;那麼這一節,我們主要講解輸入子系統的軟體框架;接下來,我們就進 ...
作者:Bright-Ho
聯繫方式:[email protected]
input輸入子系統框架分析(純軟體方面):
上一節,我們簡單的描述的什麼是輸入子系統;什麼是字元設備;以及其作用;重點是我們講到分析輸入子系統必須結合硬體設備來分析;那麼這一節,我們主要講解輸入子系統的軟體框架;接下來,我們就進入主題;
那麼在進入主題之前,我們先來瞭解一個簡單的字元設備驅動程式的框架;
(1)構造一個file_operations結構體;裡面實現了read,write,open等函數,用於應用層調用;也就是給應用層提供介面;
(2)通過 register_chrdev()函數來註冊驅動程式;所謂註冊,就是以主設備號為下標,把file_operations結構體放入一個內核數組;
(3)通過udev機制,自動創建設備節點,通過下麵兩個函數來實現;
class_create(THIS_MODULE,"firstdrv");先創建一個類firstdrv,會在sys/class下生成;
class_device_create(firstdrv_class,NULL,MKDEV(major,0),NULL,"xyz");在類下麵創建一個xyz設備;
(4)通過void first_drv_exit(void)函數,對所有資源進行卸載;
(5)修飾驅動程式的入口函數;
-
module_init(first_drv_init); /*載入驅動(insmod)的時候,會執行該巨集*/
-
module_exit(first_drv_exit); /*卸載驅動(rmmod)的時候,會執行該巨集*/
註意,內核版本不一樣, class_create() class_device_create()這兩函數也不一樣,但實際效果是一樣的;
這上面5個步驟就是一個簡單的字元設備框架,不涉及字元設備的高級技巧;也不對應任何具體設備;就單單是一個簡單字元設備的框架;
上面這5部需要理解兩個問題:
-
從應用層的角度來看,應用層調用read,write,open等函數,是如何調用到驅動程式裡面的open,read,write等函數的?
-
就是所謂udev機制,需要知道怎麼使用內核提供的函數來自動創建設備節點;
上面這種字元設備驅動框架有一個很大缺點:它提供給應用程式的介面,也就是設備節點,只有自己清楚,或者只有自己公司的人知道它所提供的介面,別人不知到!所以它不是一個通用的設備驅動程式;這是引入udev機制的原因;
接下來,我們就要真正的講解輸入子系統了;
我一直認為內核就是最好的老師,所以學習輸入子系統,就得去分析內核源代碼;我採用的內核版本為2.6.22.6的版本,該版本有點老,有些東西和新版本內核不一樣,但是對於學習來說沒什麼太大影響;如果對新老版本都瞭解的話,那就更好了;那麼,在學習輸入子系統之前,需要牢記一個問題,加入輸入子系統的字元設備驅動的步驟和上面沒有加入輸入子系統的字元設備框架的5個步驟有什麼區別?這個問題,只有分析了內核自帶的輸入子系統源碼,你才能知道其中的區別;弄清楚了這個問題之後,你應該清楚,哪些事情是由內核幫我們實現的,哪些事情是需要我們自己完成的;
input輸入子系統的內核源碼位於:linux-2.6.22.6/drivers/input下;
首先明確一點,輸入子系統分為三層,核心層,事件處理層,設備硬體層;
核心層(input.c):分析主要代碼!!!
(1)入口函數input_init()
static int __init input_init(void)/*入口*/
{
int err;
err = class_register(&input_class); /*sys/class/input 創建設備類*/
err = input_proc_init();/* proc文件系統相關的初始化*/
err = register_chrdev(INPUT_MAJOR, "input", &input_fops); /*註冊字元設備,以majoy 13為索引,存放於把內核數組*/
...
}
input_fops結構體如下:
1279 static const struct file_operations input_fops = {
1280 .owner = THIS_MODULE,
1281 .open = input_open_file,
1282 };
<解析> 在入口函數中:
1. 在sys/class目錄下,創建設備類,用於生成設備節點;
class_register(&input_class);
2. 註冊字元設備驅動,以主設備號為下標,把input_fops 放入內核數組中,方便應用層通過主設備號索引;
register_chrdev(INPUT_MAJOR, "input", &input_fops);
註意:該函數執行後會在proc/devices裡面生成設備信息;
問題: 我們知道應用程式調用open,read,write函數來操作設備,最終會調用到驅動程式裡面所提供的open_drv,read_drv,write_drv函數,但是input_fops結構裡面只提供了open函數,那麼這是怎麼回事呢?所以接下來,繼續分析input_open_file函數;
(2)input_open_file函數分析:
static int input_open_file(struct inode *inode, struct file *file)
1245 {
1246 /*(1)從input_table數組中以次設備號為下標,取出一項給handler*/
1247 struct input_handler *handler = input_table[iminor(inode) >> 5];
1248 const struct file_operations *old_fops, *new_fops = NULL;
1252 /*(2)把handler裡面的fops賦給new_fops*/
1253 if (!handler || !(new_fops = fops_get(handler->fops)))
1264 /*(3)把new_fops賦給file_f_op,實際上也就是handler裡面的fops;*/
1265 old_fops = file->f_op;
1266 file->f_op = new_fops;
1267
1268 /*(4)調用new_fops裡面的open函數*/
1269 err = new_fops->open(inode, file);
...
}
<解析>在分析input_open_file函數之前,有一個非常重要的結構體需要瞭解!!!
struct input_handler *handler
1065 struct input_handler {
1066
1067 void *private;
1069 void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
1070 int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
1071 void (*disconnect)(struct input_handle *handle);
1072 void (*start)(struct input_handle *handle);
1074 const struct file_operations *fops;
1075 int minor;
1076 const char *name;
1078 const struct input_device_id *id_table;
1079 const struct input_device_id *blacklist;
1081 struct list_head h_list;
1082 struct list_head node;
1083 };
該input_handler結構體在核心層引用,那麼誰來初始化該結構體呢?後面會分析到;
-
把input_table數組中,通過次設備號找到一項,賦給input_handler 指針;
struct input_handler *handler = input_table[iminor(inode) >> 5];
-
把handler->fops賦給file_operations 指針;
new_fops = fops_get(handler->fops)
-
調用new_fops裡面的open函數
new_fops->open(inode, file)
-
如果read的話,最終會調用到 file->f_op中的read函數;
實際上,最終調用的open函數,是input_handler裡面的open函數;input_handler是從input_table數組中得到的;那麼歸根結底的問題就是input_table數組是由誰構造的?
(3)input_table數組是由誰構造的?
搜索“input_table”欄位,可知:
static struct input_handler *input_table[8];
它是一個靜態結構體指針數組,說明只能在本文件使用;
最終在input_register_handler(struct input_handler *handler) 函數中找到該“input_table”數組,在這裡被賦值的;
所以接下來得分析input_register_handler()函數:
源碼如下:
1182 int input_register_handler(struct input_handler *handler)
1183 {
1184 struct input_dev *dev;
1185
1186 INIT_LIST_HEAD(&handler->h_list);
1187
1188 if (handler->fops != NULL) {
1189 if (input_table[handler->minor >> 5])
1190 return -EBUSY;
1191 /*(1)把傳進來的handler保存在input_table數組中*/
1192 input_table[handler->minor >> 5] = handler;
1193 }
1194
1195 /*(2)把handler放入handler鏈表*/
1196 list_add_tail(&handler->node, &input_handler_list);
1197 /*(3)把設備鏈表的每項,與handler比較看是否匹配*/
1198 list_for_each_entry(dev, &input_dev_list, node)
1199 input_attach_handler(dev, handler);
1200
1201 input_wakeup_procfs_readers();
1202 return 0;
1203 }
input_table[]數組是通過input_register_handler()函數的參數,來賦值的;也就是說誰調用該函數,就是誰傳的這個input_handler結構體;
-
input_register_handler()函數把這個結構體,存放於input_table數組;
-
並且把這個input_handler結構體放入handler鏈表;
-
把設備鏈表的每項取出來,與handler比較看是否匹配;
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
input_attach_handler()函數源碼如下:
static int input_attach_handler(struct inpuft_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
id = input_match_device(handler->id_table, dev);
error = handler->connect(handler, dev, id);
...
return error;
}
匹配設備和handler的id,匹配成功則調用handler->connect(handler, dev, id);
分析到這裡,關鍵是看誰調用input_register_handler()函數;
遍歷所有內核源代碼:
input_register_handler()函數;被evdev.c(事件設備),tsdev.c(觸摸屏設備),keyborad.c(鍵盤設備),以及mousedev.c(滑鼠設備)等;都調用了該註冊函數;
實際上我們這裡就進入了所謂的“事件處理層”了!!!
所以說目前對於事件處理層來說,核心層做瞭如下幾件事情:
-
提供了用於事件處理層的註冊介面,也就是input_register_handler()函數;
-
提供了 input_match_device()匹配函數,通過id號來匹配設備,一旦匹配成功,則調用input_handler->connect函數;
註意:這個connect函數是由“事件處理層”實現的;
-
把事件處理層所提供的input_handler->fops通過register_chrdev()函數,放入內核數組,為應用層提供設備介面;
上面我們一直講到input_handler這個結構體,它是在核心層裡面聲明,定義的,至於在哪裡初始化的?就是“事件處理層”的事情了,“事件處理層”中會初始化這個結構體,並提交給核心層;
所以接下來,下一節我們具體的分析一個事件設備;