在Linux下的輸入設備鍵盤、觸摸屏、鼠標等都可以用輸入子系統來實現驅動。輸入子系統分爲三層,核心層和設備驅動層。事件層。核心層和事件層由Linux輸入子系統自己實現,設備驅動層由咱們實現。咱們在設備驅動層將輸入事件上報給核心層input.c,核心層找到匹配的事件層,將事件交給事件層處理,事件層處理完後傳遞到用戶空間。node
咱們終於要搞清楚的是在用戶空間調用open和read終於在內核中是如何處理的,向內核上報的事件又是誰處理的,處理完後是如何傳遞到用戶空間的?數組
上面兩個圖是輸入子系統的框架。框架
如下以按鍵驅動爲例分析輸入子系統的工做流程。函數
設備驅動層:spa
在設備驅動層的init入口函數中調用input_allocate_device(),分配返回一個input_dev結構體,填充該結構體,調用input_register_device(button_dev),註冊設備。跟蹤input_register_device例如如下:code
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鏈表中的每一個handlerblog
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方法。get
在evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)中調用了 input_register_handle,調用handle以前填充handle中成員dev和handler結構體。input
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是誰調用的?