标签:
二.内核代码
2.1输入子系统设备驱动层
我们先从设备驱动层进行讲解
首先设备驱动层调用input_allocate_device进行申请input_dev结构体,接着对该结构体进行赋值,然后调用input_register_device进行注册设备。同时我们在open函数里定义中断函数,中断函数里实现input_report_key的操作,向核心层报告按键消息。
通过上面分析我们主要关注input_allocate_device、input_register_device和input_report_key的实现吧。
struct input_dev *input_allocate_device(void) { struct input_dev *dev; dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL); if (dev) { dev->dev.type = &input_dev_type; //设备属性设置 dev->dev.class = &input_class; //设备类设置 device_initialize(&dev->dev); mutex_init(&dev->mutex); //初始化锁 spin_lock_init(&dev->event_lock); //初始化锁 INIT_LIST_HEAD(&dev->h_list); //初始化队列 INIT_LIST_HEAD(&dev->node); //初始化队列 __module_get(THIS_MODULE); } return dev; }在这个input_allocate_device里,我想顺便讲讲设备属性相关的知识。由这个函数中的input_dev_type,我们对此进行跟踪。
static struct device_type input_dev_type = { .groups = input_dev_attr_groups, //待跟踪 .release = input_dev_release, .uevent = input_dev_uevent, #ifdef CONFIG_PM .pm = &input_dev_pm_ops, #endif }; static const struct attribute_group *input_dev_attr_groups[] = { &input_dev_attr_group, //待跟踪 &input_dev_id_attr_group, &input_dev_caps_attr_group, NULL }; static struct attribute_group input_dev_attr_group = { .attrs = input_dev_attrs, //待跟踪 }; static struct attribute *input_dev_attrs[] = { &dev_attr_name.attr, &dev_attr_phys.attr, &dev_attr_uniq.attr, &dev_attr_modalias.attr, //待跟踪 NULL }; static DEVICE_ATTR(modalias, S_IRUGO, input_dev_show_modalias, NULL); static ssize_t input_dev_show_modalias(struct device *dev, struct device_attribute *attr, char *buf) { struct input_dev *id = to_input_dev(dev); ssize_t len; len = input_print_modalias(buf, PAGE_SIZE, id, 1); return min_t(int, len, PAGE_SIZE); }好了,我们把重点之一放在input_register_device和input_report_key的实现上吧。
int input_register_device(struct input_dev *dev) { static atomic_t input_no = ATOMIC_INIT(0); struct input_handler *handler; const char *path; int error; __set_bit(EV_SYN, dev->evbit); //设置input_dev支持所有的事件 init_timer(&dev->timer); //初始化定时器 if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {//处理重复按键 dev->timer.data = (long) dev; dev->timer.function = input_repeat_key; dev->rep[REP_DELAY] = 250; dev->rep[REP_PERIOD] = 33; } if (!dev->getkeycode) dev->getkeycode = input_default_getkeycode; //指定位置的键值 if (!dev->setkeycode) dev->setkeycode = input_default_setkeycode; //设置键值 dev_set_name(&dev->dev, "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1); //设置input_dev中设备名 error = device_add(&dev->dev); //将input_dev中的device注册到设备模型中 if (error) return error; path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL); //获取设备路径 printk(KERN_INFO "input: %s as %s\n", dev->name ? dev->name : "Unspecified device", path ? path : "N/A"); kfree(path); error = mutex_lock_interruptible(&input_mutex); if (error) { device_del(&dev->dev); return error; } 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_wakeup_procfs_readers(); mutex_unlock(&input_mutex); return 0; }对于input_register_device中的input_attach_handler函数主要完成将input_dev与handler尝试匹配,只有匹配成功才能进行下一步操作,我们看看input_attach_handler。
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler) { const struct input_device_id *id; int error; //如果定义了handler的黑名单,并且该input_dev在此黑名单中,则退出 if (handler->blacklist && input_match_device(handler->blacklist, dev)) return -ENODEV; //将该input_dev与所有的handler的白名单相匹配 id = input_match_device(handler->id_table, dev); if (!id) //如果匹配不成功的情况 return -ENODEV; //如果匹配成功,调用handler的connet函数将input_dev与该handler相连 error = handler->connect(handler, dev, id); if (error && error != -ENODEV) printk(KERN_ERR "input: failed to attach handler %s to device %s, " "error: %d\n", handler->name, kobject_name(&dev->dev.kobj), error); return error; }
在input_attach_handler中,我们主要工作是将该input_dev与所有的handler的白名单相匹配函数input_match_device,以及如果匹配成功,调用handler的connet函数将input_dev与该handler相连的函数handler->connect。
首先我们看看input_match_device
static const struct input_device_id *input_match_device(const struct input_device_id *id, struct input_dev *dev) { int i; for (; id->flags || id->driver_info; id++) { if (id->flags & INPUT_DEVICE_ID_MATCH_BUS) if (id->bustype != dev->id.bustype) //总线是否匹配 continue; if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR) if (id->vendor != dev->id.vendor) //设备厂商是否匹配 continue; if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT) if (id->product != dev->id.product) //设备号是否匹配 continue; if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION) if (id->version != dev->id.version) //版本号是否匹配 continue; //只有id->flags没定义或者定义的类型匹配成功才会进入MATCH_BIT的匹配项 MATCH_BIT(evbit, EV_MAX); MATCH_BIT(keybit, KEY_MAX); MATCH_BIT(relbit, REL_MAX); MATCH_BIT(absbit, ABS_MAX); MATCH_BIT(mscbit, MSC_MAX); MATCH_BIT(ledbit, LED_MAX); MATCH_BIT(sndbit, SND_MAX); MATCH_BIT(ffbit, FF_MAX); MATCH_BIT(swbit, SW_MAX); return id; } return NULL; } #define MATCH_BIT(bit, max) for (i = 0; i < BITS_TO_LONGS(max); i++) if ((id->bit[i] & dev->bit[i]) != id->bit[i]) break; if (i != BITS_TO_LONGS(max)) continue;
从MATCH_BIT宏的定义可以看到,只有当input_dev和input_handler的id成员在evbit、keybit……swbit项相同才会匹配成功。而且匹配的顺序是从evbit、keybit到swbit。只要有一项不同,就会循环到id中的下一项进行比较。
总结下,注册input device的过程就是为input device设置默认值,并将其挂以input_dev_list。与挂载在input_handler_list中的handler相匹配。如果匹配成功,就会调用handler的connect函数。
好了,我们来看下Evdev.c下的evdev_connect函数吧,该函数主要用来连接input_dev和input_handler,这样事件的流通链才能建立。流通链建立后,事件才知道被谁处理,或者处理后向谁返回结果。
static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id) { struct evdev *evdev; int minor; int error; //寻找evdev_table中为空的一项 for (minor = 0; minor < EVDEV_MINORS; minor++) if (!evdev_table[minor]) break; // EVDEV_MINORS为32,表示evdev_handler支持32个设备文件 if (minor == EVDEV_MINORS) { printk(KERN_ERR "evdev: no more free evdev devices\n"); return -ENFILE; } //分配struct evdev,里面包含一个handle,它是handler和input device的集合体 evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); if (!evdev) return -ENOMEM; INIT_LIST_HEAD(&evdev->client_list); //初始化链表 spin_lock_init(&evdev->client_lock); //初始化锁 mutex_init(&evdev->mutex); //初始化锁 init_waitqueue_head(&evdev->wait); //初始化等待队列 dev_set_name(&evdev->dev, "event%d", minor); //对evdev命一个名 evdev->exist = 1; evdev->minor = minor; evdev->handle.dev = input_get_device(dev); //给handle的input_dev赋值 evdev->handle.name = dev_name(&evdev->dev); evdev->handle.handler = handler; //给handle的input_handler赋值 evdev->handle.private = evdev; evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor); //初始化evdev->dev这个设备驱动模型 evdev->dev.class = &input_class; evdev->dev.parent = &dev->dev; evdev->dev.release = evdev_free; device_initialize(&evdev->dev); error = input_register_handle(&evdev->handle); //注册input_handle到内核 if (error) goto err_free_evdev; error = evdev_install_chrdev(evdev); //将evdev_table的minor项指向evdev if (error) goto err_unregister_handle; error = device_add(&evdev->dev); //将evdev->dev注册到sysfs文件系统中 if (error) goto err_cleanup_evdev; return 0; err_cleanup_evdev: evdev_cleanup(evdev); err_unregister_handle: input_unregister_handle(&evdev->handle); err_free_evdev: put_device(&evdev->dev); return error; }接下来我们看看evdev_connect函数中注册input_handle的函数input_register_handle的实现
int input_register_handle(struct input_handle *handle) { struct input_handler *handler = handle->handler; struct input_dev *dev = handle->dev; int error; error = mutex_lock_interruptible(&dev->mutex); if (error) return error; list_add_tail_rcu(&handle->d_node, &dev->h_list); //将handle加入dev->h_list中 mutex_unlock(&dev->mutex); list_add_tail(&handle->h_node, &handler->h_list); //将handle加入handler->h_list中 if (handler->start) handler->start(handle); return 0; }好了,我们跟着input_register_device一直分析到了input_dev,input_dev与input_handler的匹配,input_handle的注册。
------------------------------------------------------------------------------分割线--------------------------------------------------------------------------------
我们知道在设备驱动层的open函数中会注册一个中断,然后中断处理函数中会调用input_report_key来报告一次事件给输入子系统,那么我们就看看这个函数吧。
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value) { input_event(dev, EV_KEY, code, !!value); }
该函数的第一个参数是产生事件的输入设备,第二个参数是产生的事件,第三个参数是事件的值。另外,第二个参数可以取BTN_0、 BTN_1、BTN_LEFT、 BTN_RIGTH等值。当第二个参数为按键时,第三个参数表示按键的状态,value值为0表示按键释放,非0表示按键按下。
我们可以很清晰的发现,input_report_key函数中真正起作用的是input_event,该函数是用来向输入子系统报告输入设备产生的事件。
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { unsigned long flags; if (is_event_supported(type, dev->evbit, EV_MAX)) { //检查输入设备是否支持该事件 spin_lock_irqsave(&dev->event_lock, flags); //对事件锁锁定 add_input_randomness(type, code, value); //没有多大的作用 input_handle_event(dev, type, code, value); //继续输入子系统的相关模块发送数据 spin_unlock_irqrestore(&dev->event_lock, flags); } }
上面input_event函数调用的input_handle_event函数是向输入子系统传送事件信息。第一个参数是输入设备input_dev,第二个参数是事件的类型,第三个参数是键码,第四个参数是键值。好,让我们看看这个input_handle_event函数吧
static void input_handle_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { int disposition = INPUT_IGNORE_EVENT; //表示使用什么样的方式处理事件 switch (type) { case EV_SYN: switch (code) { case SYN_CONFIG: disposition = INPUT_PASS_TO_ALL; break; case SYN_REPORT: if (!dev->sync) { dev->sync = 1; disposition = INPUT_PASS_TO_HANDLERS; } break; case SYN_MT_REPORT: dev->sync = 0; disposition = INPUT_PASS_TO_HANDLERS; break; } break; case EV_KEY: //判断是否支持按键以及测试按键状态是否改变 if (is_event_supported(code, dev->keybit, KEY_MAX) && !!test_bit(code, dev->key) != value) { if (value != 2) { __change_bit(code, dev->key); //改变键的状态 if (value) input_start_autorepeat(dev, code); //处理重复按键 else input_stop_autorepeat(dev); } disposition = INPUT_PASS_TO_HANDLERS; //把事件交给handler处理 } break; case EV_SW: if (is_event_supported(code, dev->swbit, SW_MAX) && !!test_bit(code, dev->sw) != value) { __change_bit(code, dev->sw); disposition = INPUT_PASS_TO_HANDLERS; } break; case EV_ABS: if (is_event_supported(code, dev->absbit, ABS_MAX)) { if (test_bit(code, input_abs_bypass)) { disposition = INPUT_PASS_TO_HANDLERS; break; } value = input_defuzz_abs_event(value, dev->abs[code], dev->absfuzz[code]); if (dev->abs[code] != value) { dev->abs[code] = value; disposition = INPUT_PASS_TO_HANDLERS; } } break; case EV_REL: if (is_event_supported(code, dev->relbit, REL_MAX) && value) disposition = INPUT_PASS_TO_HANDLERS; break; case EV_MSC: if (is_event_supported(code, dev->mscbit, MSC_MAX)) disposition = INPUT_PASS_TO_ALL; break; case EV_LED: if (is_event_supported(code, dev->ledbit, LED_MAX) && !!test_bit(code, dev->led) != value) { __change_bit(code, dev->led); disposition = INPUT_PASS_TO_ALL; } break; case EV_SND: if (is_event_supported(code, dev->sndbit, SND_MAX)) { if (!!test_bit(code, dev->snd) != !!value) __change_bit(code, dev->snd); disposition = INPUT_PASS_TO_ALL; } break; case EV_REP: if (code <= REP_MAX && value >= 0 && dev->rep[code] != value) { dev->rep[code] = value; disposition = INPUT_PASS_TO_ALL; } break; case EV_FF: if (value >= 0) disposition = INPUT_PASS_TO_ALL; break; case EV_PWR: disposition = INPUT_PASS_TO_ALL; break; } if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN) dev->sync = 0; if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event) dev->event(dev, type, code, value); //事件交给input_dev处理 if (disposition & INPUT_PASS_TO_HANDLERS) input_pass_event(dev, type, code, value); //事件交给input_handler处理 }
在上面这个input_handle_event函数中,因为我们主要讨论的是键盘事件,所以在此只关注EV_KEY事件,其他事件与之类似。在这个函数的最后几行代码,我想加以补充说明。有些事件是发给设备的,而不是发给handler处理的。event函数用来向输入子系统报告一个将要发送给设备的事件,例如让LED灯点亮事件、蜂鸣器鸣叫事件等。当事件报告给输入子系统后,就要求这个设备处理这个事件。
在我们这个按键事件中,最终是把这个事件交给handler处理的,我们跟踪input_pass_event看看,它主要是将事件传递到合适的函数。
static void input_pass_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { struct input_handle *handle; rcu_read_lock(); handle = rcu_dereference(dev->grab); //grab是强制为input_dev的handle if (handle) handle->handler->event(handle, type, code, value); //有grab时候 else //遍历input_dev上的handle链表 list_for_each_entry_rcu(handle, &dev->h_list, d_node) if (handle->open) //handle的open打开表示有handler与这个input_dev匹配 handle->handler->event(handle, type, code, value); //调用事件驱动层的evdev_event函数 rcu_read_unlock(); }既然如此,我们就看看事件驱动层的evdev_event函数吧
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) { struct evdev *evdev = handle->private; struct evdev_client *client; struct input_event event; do_gettimeofday(&event.time); //给input_event的type、code和value赋值 event.type = type; event.code = code; event.value = value; rcu_read_lock(); client = rcu_dereference(evdev->grab); if (client) evdev_pass_event(client, &event); else list_for_each_entry_rcu(client, &evdev->client_list, node) evdev_pass_event(client, &event); rcu_read_unlock(); wake_up_interruptible(&evdev->wait); //唤醒事件驱动层read中的一个等待队列 }继续跟踪 evdev_pass_event(client, &event);
static void evdev_pass_event(struct evdev_client *client, struct input_event *event) { /* * Interrupts are disabled, just acquire the lock */ spin_lock(&client->buffer_lock); wake_lock_timeout(&client->wake_lock, 5 * HZ); client->buffer[client->head++] = *event;//保存到client buffer中 client->head &= EVDEV_BUFFER_SIZE - 1; spin_unlock(&client->buffer_lock); kill_fasync(&client->fasync, SIGIO, POLL_IN); }好了,到此为止,我们已经把中断处理函数中向输入子系统报告事件的整个流程分析完毕了。
2.2输入子系统核心层
我们现在来看看Input子系统的核心层input.c
输入子系统的核心层向上为用户层提供接口函数,向下为驱动程序提供统一的接口函数。这样就能够使输入设备的事件通过输入子系统发送给用户层应用程序,用户层应用程序也可以通过输入子系统通知驱动程序完成某项功能。输入子系统的核心层作为一个模块存在,必然有一个初始化函数,该函数为input_init。
static int __init input_init(void) { int err input_init_abs_bypass(); err = class_register(&input_class); if (err) { printk(KERN_ERR "input: unable to register input_dev class\n"); return err; } err = input_proc_init(); //在proc下建立相关交互文件 if (err) goto fail1; err = register_chrdev(INPUT_MAJOR, "input", &input_fops); //注册cdev设备号为13 if (err) { printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR); goto fail2; } return 0; fail2: input_proc_exit(); fail1: class_unregister(&input_class); return err; }我们跟踪下input_fops
static const struct file_operations input_fops = { .owner = THIS_MODULE, .open = input_open_file, };文件操作指针中定义了input_open_file函数,该函数将控制转到input_handler中定义的fopss文件指针的open函数,该函数在input_handler中实现,这样就使不同的handler处理器对应了不同的文件打开方式,为完成不同功能提供了方便。我们来看看input_open_file
static int input_open_file(struct inode *inode, struct file *file) { struct input_handler *handler; const struct file_operations *old_fops, *new_fops = NULL; int err; lock_kernel(); handler = input_table[iminor(inode) >> 5]; //由次设备号获取handler if (!handler || !(new_fops = fops_get(handler->fops))) { //获取handler的fops err = -ENODEV; goto out; } if (!new_fops->open) { //handler的fops没有实现open,表示该设备不存在 fops_put(new_fops); err = -ENODEV; goto out; } old_fops = file->f_op; //保存老的fops file->f_op = new_fops; //设置新的fops err = new_fops->open(inode, file); //打开handler中fops下的open if (err) { fops_put(file->f_op); file->f_op = fops_get(old_fops); } fops_put(old_fops); out: unlock_kernel(); return err; }既然这里打开的是handler下fops的open,那我们就跟踪看看那里的open函数evdev_open
static int evdev_open(struct inode *inode, struct file *file) { struct evdev *evdev; struct evdev_client *client; int i = iminor(inode) - EVDEV_MINOR_BASE; //得到evdev_table[]中的序号给i int error; if (i >= EVDEV_MINORS) return -ENODEV; error = mutex_lock_interruptible(&evdev_table_mutex); if (error) return error; evdev = evdev_table[i]; //取出evdev_table[]中对应的evdev if (evdev) get_device(&evdev->dev); //增加引用计数 mutex_unlock(&evdev_table_mutex); if (!evdev) return -ENODEV; client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL); //分配evdev_client if (!client) { error = -ENOMEM; goto err_put_evdev; } spin_lock_init(&client->buffer_lock); client->evdev = evdev; //将client->evdev指向它所表示的evdev evdev_attach_client(evdev, client); //将client挂在evdev->client_list上 error = evdev_open_device(evdev); //打开输入设备 if (error) goto err_free_client; file->private_data = client; return 0; err_free_client: evdev_detach_client(evdev, client); kfree(client); err_put_evdev: put_device(&evdev->dev); return error; }在evdev_open里,我们重点跟踪evdev_open_device,该函数用来打开相应的输入设备,使设备准备好接收或者发送数据。
static int evdev_open_device(struct evdev *evdev) { int retval; retval = mutex_lock_interruptible(&evdev->mutex); //获得锁 if (retval) return retval; if (!evdev->exist) //检查设备是否存在 retval = -ENODEV; else if (!evdev->open++) { //检查设备是否已经打开 retval = input_open_device(&evdev->handle); //没打开则调用该函数打开设备 if (retval) evdev->open--; } mutex_unlock(&evdev->mutex); return retval; }我们跟踪上面evdev_open_device中的input_open_device吧
int input_open_device(struct input_handle *handle) { struct input_dev *dev = handle->dev; int retval; retval = mutex_lock_interruptible(&dev->mutex); if (retval) return retval; if (dev->going_away) { retval = -ENODEV; goto out; } handle->open++; //递增handle的打开计数 if (!dev->users++ && dev->open) //如果第一次打开 retval = dev->open(dev); //调用input_dev下的open函数 if (retval) { dev->users--; if (!--handle->open) { synchronize_rcu(); } } out: mutex_unlock(&dev->mutex); return retval; }在input_open_device中,我们最终调用了input_dev下的open函数,而该函数就是设备驱动层的open,在按键驱动中,该open函数实现了一个申请中断的操作。
2.3输入子系统事件处理层
最后,我们来看看事件驱动层的Evdev.c,先看下该模块下的初始化函数evdev_init
static int __init evdev_init(void) { return input_register_handler(&evdev_handler); }跟踪input_register_handler
int input_register_handler(struct input_handler *handler) { struct input_dev *dev; int retval; retval = mutex_lock_interruptible(&input_mutex); if (retval) return retval; INIT_LIST_HEAD(&handler->h_list); //初始化链表 if (handler->fops != NULL) { if (input_table[handler->minor >> 5]) { //是否该次设备号对应的input_table已满 retval = -EBUSY; goto out; } input_table[handler->minor >> 5] = handler; //将handler插入input_table } list_add_tail(&handler->node, &input_handler_list); //把handler挂到input_handler_list上 list_for_each_entry(dev, &input_dev_list, node) //遍历input_dev_list上的input_dev input_attach_handler(dev, handler); //查看input_dev与handler是否匹配 input_wakeup_procfs_readers(); out: mutex_unlock(&input_mutex); return retval; }我们知道通过input_register_handler注册了evdev_handler,我们看看evdev_handler是什么
static struct input_handler evdev_handler = { .event = evdev_event, //向输入子系统报告事件 .connect = evdev_connect, //input_dev与input_handler相连 .disconnect = evdev_disconnect, .fops = &evdev_fops, .minor = EVDEV_MINOR_BASE, .name = "evdev", .id_table = evdev_ids, };向输入子系统报告事件的函数evdev_even和将input_dev与input_handler相连的函数evdev_connect已经在前面分析过了,下面我们继续看看evdev_handler下的evdev_fops成员
static const struct file_operations evdev_fops = { .owner = THIS_MODULE, .read = evdev_read, //读 .write = evdev_write, //写 .poll = evdev_poll, .open = evdev_open, //打开 .release = evdev_release, .unlocked_ioctl = evdev_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = evdev_ioctl_compat, #endif .fasync = evdev_fasync, .flush = evdev_flush };上面这个file_operations结构体中的open我们已经分析过了,现在我们看看上面结构体中的read吧
static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) { struct evdev_client *client = file->private_data; struct evdev *evdev = client->evdev; struct input_event event; int retval; if (count < input_event_size()) return -EINVAL; if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK)) return -EAGAIN; retval = wait_event_interruptible(evdev->wait, client->head != client->tail || !evdev->exist); //进入等待队列,等待有数据报告给系统 if (retval) return retval; if (!evdev->exist) return -ENODEV; while (retval + input_event_size() <= count && evdev_fetch_next_event(client, &event)) { if (input_event_to_user(buffer + retval, &event)) //拷贝内核数据给用户 return -EFAULT; retval += input_event_size(); } return retval; }在这个read函数中有个等待队列,什么时候这个等待队列的条件才满足呢?我们知道在有按键,然后驱动层的中断处理函数会向输入子系统报告,最终调用了handler下的evdev_event函数,在那个函数中唤醒了这个等待队列。
好了,读分析好了,我们再讨论下写操作。其实写操作可以有两种方法,一种就是在这个handler的fops中的write里实现,但是这个不是2.6的内核常用的手段。写操作举个例子比如向LED中写0或者1,点灯。这个操作往往可以在上面的input_dev的event函数中实现。
标签:
原文地址:http://blog.csdn.net/xmzzy2012/article/details/51140730