自行跟读输入子系统的三层代码实验
判断通关:
上传跟读代码笔记
//第一小节内容合并到上一节LEVEL12DAY3中去了
输入子系统驱动流程梗概:
目的: a, 学会如何分析内核中子系统的代码,从而举一反三 b,整体把握框架思想,理解分层中各层的配合方式 c,掌握子系统,增强排错能力 代码跟读方法: 1,带着问题去读 2,做好笔记和画图 3,驱动联系应用,应用是调用的,驱动是实现的
input handler层:evdev.c 内核自带 编译内核前可自行设置 详见Level12day2
module_init(evdev_init); module_exit(evdev_exit); //查阅一个驱动源代码的时候第一件事先找出口函数 即搜索module_exit 然后顺藤摸瓜找到入口函数 static struct input_handler evdev_handler = { .event = evdev_event, .events = evdev_events, .connect = evdev_connect, .disconnect = evdev_disconnect, .legacy_minors = true, .minor = EVDEV_MINOR_BASE, .name = "evdev", .id_table = evdev_ids, }; evdev_init(void) | input_register_handler(&evdev_handler); | //添加互斥锁 error = mutex_lock_interruptible(&input_mutex); if (error) return error; //初始化h_list INIT_LIST_HEAD(&handler->h_list); //将当前的 handler 加入到一个input_handler_list list_add_tail(&handler->node, &input_handler_list); //遍历链表input_dev_list,将handler与对应的simple_dev相匹配 list_for_each_entry(dev, &input_dev_list, node) input_attach_handler(dev, handler); | //将当前的handler 和 input dev进行匹配,event handler能够匹配所有的input dev input_match_device(handler, dev); //匹配成功,之后要调用handler种connect方法 //这里的handler实际就是event handler,实际调用了endev_connect handler->connect(handler, dev, id); //将当前的handler加入到/proc/bus/input/handlers文件中 input_wakeup_procfs_readers(); //释放互斥锁 mutex_unlock(&input_mutex);
input核心层:input.c 内核自带 编译内核前可自行设置 详见Level12day2
subsys_initcall(input_init); //subsys_initcall 与 module_init 相比,功能相同但优先级更高。 module_exit(input_exit); static int __init input_init(void) { //注册类,类似于class_create(); //class_create(); -> __class_create(); -> __class_register(); //class_register(); -------------> __class_register(); err = class_register(&input_class); //创建好了之后在 /sys/class 目录下可以看到创建的input类 //技巧:不认识 先放一边 继续向下看 //在/proc目录下创建一些文件: bus/input/device s 和 bus/input/handlers err = input_proc_init(); //申请设备号 类似于register_chrdev_dev(); 主设备号是 INPUT_MAJOR=13。 err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),INPUT_MAX_CHAR_DEVICES, "input"); } 总结: 1.注册了主设备号 2.注册了input class
simple_input.c驱动代码层: 我们自己写的代码
//注册驱动信息到链表input_dev_list input_register_device(pInputdev); //上锁 error = mutex_lock_interruptible(&input_mutex); if (error) goto err_device_del; //将input_dev 加入到链表input_dev_list list_add_tail(&dev->node, &input_dev_list); //遍历input_handler_list, 进行匹配 list_for_each_entry(handler, &input_handler_list, node) input_attach_handler(dev, handler); | //将当前的handler 和 input dev进行匹配,event handler能够匹配所有的input dev input_match_device(handler, dev); //匹配成功,之后要调用handler种connect方法 //这里的handler实际就是event handler,实际调用了endev_connect handler->connect(handler, dev, id); //将input_dev添加进/proc/bus/input/dev文件中 input_wakeup_procfs_readers(); //解锁 mutex_unlock(&input_mutex);
分析: evdev.c中, evdev_connect()函数 --属于input_handler层
evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id) | //找到一个没有被使用的次设备号, 从64开始, 65,66 minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true); // 实例化 一个evdev对象 evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); //初始化evdev对象 INIT_LIST_HEAD(&evdev->client_list); spin_lock_init(&evdev->client_lock); mutex_init(&evdev->mutex); //等待队列是完成阻塞 init_waitqueue_head(&evdev->wait); evdev->exist = true; dev_no = minor; dev_no -= EVDEV_MINOR_BASE; //减去了64 // 创建设备文件/dev/event0,1,2 dev_set_name(&evdev->dev, "event%d", dev_no); evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);// 12, 64 evdev->dev.class = &input_class; evdev->dev.parent = &dev->dev; evdev->dev.release = evdev_free; device_initialize(&evdev->dev) device_add(&evdev->dev); //以上代码和device_create是一样 //利用handle记录input device和input handler evdev->handle.dev = input_get_device(dev); evdev->handle.name = dev_name(&evdev->dev); evdev->handle.handler = handler; //你中有有我,我中有你 evdev->handle.private = evdev; //将儿子关联到父亲(input handler)和母亲(input dev) error = input_register_handle(&evdev->handle); | list_add_tail_rcu(&handle->d_node, &dev->h_list); list_add_tail_rcu(&handle->h_node, &handler->h_list); //初始化了cdev,完成了fops, 为用户提供文件io cdev_init(&evdev->cdev, &evdev_fops); evdev->cdev.kobj.parent = &evdev->dev.kobj; error = cdev_add(&evdev->cdev, evdev->dev.devt, 1); 总结: 1,分配evdev,并初始化,记录input device和handler的关系 2,创建设备节点/dev/event0 3, 注册cdev,并实现fops 4,关系: 多个input device可以对应一个event handler 一个input device对应一个 evdev,对应于一个设备节点/dev/event0,1,2 5, 所有的设备节点调用open,read,write文件io的时候 实际是调用cdev中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, .llseek = no_llseek, }; ``` ```cpp device_create(struct class * class, struct device * parent, dev_t devt, void * drvdata, const char * fmt, ...) | dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs); | device_create_groups_vargs(class, parent, devt, drvdata, NULL, fmt, args); | struct device *dev = NULL; dev = kzalloc(sizeof(*dev), GFP_KERNEL); device_initialize(dev); dev->devt = devt; dev->class = class; dev->parent = parent; dev->groups = groups; dev->release = device_create_release; dev_set_drvdata(dev, drvdata); kobject_set_name_vargs(&dev->kobj, fmt, args);//设置名字 device_add(dev);//注册到系统
应用程序中调用了输入子系统的代码,数据是如何传递给用户层的?
open("/dev/event1", O_RDWR); --------------------------------------------- vfs sys_open(); struct file file->f_ops = cdev->ops; file->f_ops->open(); ------------------------------------------- 设备驱动层:输入子系统 input handler 层:evdev.c cdev; xxx_ops = { .open = xxx_open, .write = xxx_write, } static const struct file_operations evdev_fops = { .owner = THIS_MODULE, .read = evdev_read, .write = evdev_write, .poll = evdev_poll, .open = evdev_open, } 实际最终调用了evdev_open(); | // 实际cdev是谁,就是evdev_connect注册的那个 struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev); // 通过儿子,找到老母input device unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev); // size就包含了很多input_event unsigned int size = sizeof(struct evdev_client) + bufsize * sizeof(struct input_event); struct evdev_client *client; // 分配一个client对像,描述一个缓冲队列,存放的就是input_event client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN); // client中有一个缓冲区 client->bufsize = bufsize; spin_lock_init(&client->buffer_lock); //在client中记录evdev client->evdev = evdev; // 将client加入到evdev中一个小链表 evdev_attach_client(evdev, client); | list_add_tail_rcu(&client->node, &evdev->client_list); // 将client记录到file,方面其他的接口使用 file->private_data = client; 总结: 1,为输入设备分配一个缓冲区evdev_client,用户存放input device层上报的数据 2,evdev_client记录到evdev中 3,evdev_client记录到file中,方面其他的接口使用