天天看點

Linux輸入子系統架構分析(1)

在Linux下的輸入裝置鍵盤、觸摸屏、滑鼠等都可以用輸入子系統來實作驅動。輸入子系統分為三層,核心層和裝置驅動層,事件層。核心層和事件層由Linux輸入子系統本身實作,裝置驅動層由我們實作。我們在裝置驅動層将輸入事件上報給核心層input.c,核心層找到比對的事件層,将事件交給事件層處理,事件層處理完後傳遞到使用者空間。

我們最終要搞清楚的是在使用者空間調用open和read最終在核心中是怎樣處理的,向核心上報的事件又是誰處理的,處理完後是怎樣傳遞到使用者空間的?

Linux輸入子系統架構分析(1)
Linux輸入子系統架構分析(1)

上面兩個圖是輸入子系統的架構。

下面以按鍵驅動為例分析輸入子系統的工作流程。

裝置驅動層:

在裝置驅動層的init入口函數中調用input_allocate_device(),配置設定傳回一個input_dev結構體,填充該結構體,調用input_register_device(button_dev),注冊裝置。跟蹤input_register_device如下:

list_add_tail(&dev->node, &input_dev_list);将input_dev結構體放進input_dev_list連結清單中

list_for_each_entry(handler, &input_handler_list, node)周遊input_handler_list連結清單中的每一個handler

input_attach_handler(dev, handler);将input_dev和handler比較

input_match_device(handler->id_table, dev);<*input.c*>

handler->connect(handler, dev, id);<*input.c*>比較的方式是通過對比handler的id_table和input_dev,若找  到比對的handler,則調用handler的connect方法。

在evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)中調用了 input_register_handle,調用handle之前填充handle中成員dev和handler結構體。

evdev->handle.dev = input_get_device(dev);

evdev->handle.handler = handler;

在input_register_handle中list_add_tail_rcu(&handle->d_node, &dev->h_list)list_add_tail(&handle->h_node, &h andler->h_list)兩個函數将handle加入進handler和dev的h_list連結清單中。通過dev->h_list可以找到handle,通過 handle找到handle.handler,同理,通過handler->h_list可以找到handle,再找到handle.dev。

關鍵的問題來了?handler到底是什麼?由誰注冊的?handler是事件層調用核心層的函數注冊的。

事件層:(evdev.c,keyboard.c,ts.c)

 static struct input_handler evdev_handler = {//handler結構體

.event = evdev_event,

.connect = evdev_connect,

.disconnect = evdev_disconnect,

.fops = &evdev_fops,

.minor = EVDEV_MINOR_BASE,

.name = "evdev",

.id_table = evdev_ids,

};

在evdev_init(void)入口函數中調用input_register_handler(&evdev_handler)來注冊handler,注冊的handler會放入input_table[ ]數組中。

核心層:(input.c)

核心層的入口函數input_init(void)注冊裝置input。

當在應用層調用open時會在核心中調用input_open_file(struct inode *inode, struct file *file),根據handler = input_table[iminor(inode) >> 5];根據打開的檔案的次裝置号從數組input_table中得到已注冊的對應的handler。

new_fops = fops_get(handler->fops)從handler中得到新的fop。

file->f_op = new_fops;

err = new_fops->open(inode, file);open最終調用的是handler->fops->open。原來的裝置驅動的open等方法是自 己寫的,現在輸入子系統中的事件層幫我們寫好了open等裝置方法。同理在應用層調用read會調用事件層中的 read,即handler->fops->read,即evdev_fops.read,在read中會阻塞。直到在裝置驅動層中過input_report_key 上報事件:

input_event(dev, EV_KEY, code, !!value);

input_handle_event(dev, type, code, value); 

input_pass_event(dev, type, code, value);

handle->handler->event(handle,type, code, value);調用handler中的event事件方法,處理完上報的事 件後,喚醒休眠的read,再read出事件的處理結果。

總結:

在使用者空間調用open将最終調用事件層中handler的fops的open裝置方法,具體是比對到evdev.c還是keyboard.c的handler要根據裝置驅動init入口函數中填充的input_dev結構體的id_table。使用者空間調用read将調用handler的read。在裝置驅動中通過input_event上報事件到核心層,最終調用對應的handler的event方法來處理事件,處理完後通過read傳遞到使用者空間。這樣就搞清楚了open是誰調用的?read是誰調用的?