81.linux-输入子系统实现原理

我们为什么要用输入子系统?我们为什么要分析输入子系统源码??

之前的学习中我们也知道了字符设备驱动的编写方法,嵌入式系统中有很多不同类型的输入系统,触摸屏,键盘等,难道我们都要按照流程编写一遍吗?当然不是我们肯定是想把可复用的部分抽象出来,只改变差异化的东西,输入子系统就帮我们做了这个工作,并提供了API,这样的好处肯定是简化我们的工作量啊,好多的代码都可以省略掉了。但是不好之处就是在不了解输入子系统框架的情况下,代码理解上有些困难,不了解它是怎么实现的。

目的:

a,学会如何分析内核中子系统的代码,从而可以举一反三

b,整体把握框架思想,理解分层中各层的配合方式

c,掌握子系统,增强排错能力

 

一.输入子系统学习方法

输入子系统的源码是很多的,我们要有方法的去学习,不然很容易就迷失在源码中了。

代码跟读方法:

1,带着问题去读

2,做好笔记和画图

3,驱动联系应用,应用是调用的,驱动是实现的

 

二.输入子系统框架初识

输入子系统由Input driver(驱动层)、Input core(输入子系统核心)、input handler(事件处理层)三部分组成。

  • Input driver :主要实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。(我们主要需要写的代码)

  • Input core :承上启下。为设备驱动层提供了规范和接口;通知事件处理层对事件进行处理;

  • input handler :提供用户编程的接口(设备节点),并处理驱动层提交的数据处理。

可以大概的看出原理,有两条关键的链表,驱动层和处理层分别创建了input_dev结构体,和handler结构体,分别注册到相应的链表中,然后通过一种方法进行匹配,匹配成功后自动调用connect方法。


三.框架逐层分析


1.input core核心层

(1)核心文件及分析入口:

input核心层:input.c

subsys_initcall(input_init);  //入口

module_exit(input_exit);

核心层主要文件为input.c,其实它也相当于一个独立的驱动,入口函数input_init();

(2)该层主要的内容:

  • 注册了主设备号
  • 注册input class(注册的是类)
  • /proc/bus/input/目录下创建 devices  handlers

(3)关键代码

input_init()
		|
		//注册类,类似于class_create();
		err = class_register(&input_class);
		//在/proc创建 bus/input/devices  handlers
		err = input_proc_init();
		//申请设备号
		err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
				     INPUT_MAX_CHAR_DEVICES, "input");

2.input handler层

(1)核心文件及分析入口:

input handler层:evdev.c(通用)

module_init(evdev_init);

module_exit(evdev_exit);

(2)该层主要的内容:

  • 注册了evdev_handler
  • 遍历input dev list,并行匹配,匹配成功后,自动会调用handler中connect方法--- evdev_connect

(3)关键代码

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);//注册evdev_handler
					|
					//初始化h_list
					INIT_LIST_HEAD(&handler->h_list);

					//将当前的handler加入到一个input_handler_list
					list_add_tail(&handler->node, &input_handler_list);

					//遍历链表input_dev_list
					list_for_each_entry(dev, &input_dev_list, node)
					input_attach_handler(dev, handler);
							|
							// 将当前的hanler和input dev进行匹配, event handler   
                            能够匹配所有的input dev
							id = input_match_device(handler, dev);

							//匹配成功,之后要调用handler中connect方法
							// 实际就是event handler,实际调用了evdev_connect
							error = handler->connect(handler, dev, id);

					//将当前的handler加入到/proc/bus/input/handlers文件中
					input_wakeup_procfs_readers();

分析到这里,可以看出来evdev_connect是关键的一个函数,需要继续跟入分析。connect中又实现了什么功能呢?

(4)evdev_connect函数(非常关键的一个函数)

 

当我们进行匹配成功后,会自动调用这个函数,这个函数是很关键的。

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,
				};

2》关键代码:

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);

 

(5)两条链表和一个结构体

在最开始的框图中,我们也说道了框架的核心主要是两个链表,他们会进行匹配,匹配成功后调用connect方法。

  • 对于handler和device,分别用链表input_handler_list和input_device_list进行维护,
    当handler或者device增加或减少的时候,分别往这两链表增加或删除节点,这两条都是全局链表。
  • input_handle 结构体代表一个成功配对的input_dev和input_handler。input_hande 没有一个全局的链表,它注册的时候将自己分别挂在了input_device_list和 input_handler_list的h_list上了;同时,input_handle的成员.*dev,关联到input_dev结构,.*handler关联到input_handler结构 。从此,建立好了三者的铁三角关系,通过任何一方,都可以找到彼此。

这面这个图从网上找的,更加形象:

3.input driver驱动层

这层也是我们编写驱动主要打交道的地方

这一层我们主要看一下input_register_device(inputdev);函数

	input_register_device(inputdev);
		|
		//将input dev加入到链表input_dev_list
		list_add_tail(&dev->node, &input_dev_list);

		//遍历input handler链表,进行匹配
		list_for_each_entry(handler, &input_handler_list, node)
		input_attach_handler(dev, handler);
					|
				//匹配成功,之后要调用handler中connect方法
				// 实际就是event handler,实际调用了evdev_connect
				error = handler->connect(handler, dev, id);

 

四.应用是如何调用到驱动层的?

几个层次主要的功能分析完了,那么我们还是有个需要关心的问题,应用程序中调用了输入子系统的代码,数据是如何传递给用户层的?

1.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中,方面其他的接口使用

2.evdev_read()


evdev_read(struct file *file, char __user *buffer,size_t count, loff_t *ppos)
			|
		// 获取到open中分配的缓冲区对象
		struct evdev_client *client = file->private_data;
		//获取到evdev
		struct evdev *evdev = client->evdev;
		//表示一个数据包,要给用户
		struct input_event event;

		for (;;) {
			// 实现非阻塞
			if (client->packet_head == client->tail &&
				(file->f_flags & O_NONBLOCK))
				return -EAGAIN;

				while (read + input_event_size() <= count &&
				// 1从缓冲区获取数据,存放在 input_event数据包
				   evdev_fetch_next_event(client, &event)) {
							|
							*event = client->buffer[client->tail++];
				// 2, 将数据上报给用户
				if (input_event_to_user(buffer + read, &event))
							|
							copy_to_user(buffer, event, sizeof(struct input_event)

				// 3,统计上报多少数据
				read += input_event_size();
			}

			// 如果当前是阻塞模式
			if (!(file->f_flags & O_NONBLOCK)) {
				//等待---休眠,需要被唤醒,有数据时候唤醒
				error = wait_event_interruptible(evdev->wait,
						client->packet_head != client->tail ||
						!evdev->exist || client->revoked);
		}

        总结:
            1,如果没有数据,就会休眠等待
            2,如果有数据,就从缓冲区client->buffer[client->tail++]拿数据
                通过copy_to_user上报给用户
        
            疑问:
                数据到底是如何存放在缓冲区的
                等待队列是谁唤醒的
                input_report_key(inputdev, pdesc->key_code, 0);
                input_sync(inputdev);//上报数据结束

 

3.数据上报(数据如何存放到缓冲区的)

input_report_key(inputdev, pdesc->key_code, 0);
	input_sync(inputdev);//上报数据结束
		|//input_event(dev, EV_KEY, code, !!value);
		input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value);// 上报数据
				|
				input_handle_event(dev, type, code, value);
						|
						// 如果将数据交给input handler去处理
						if (disposition & INPUT_PASS_TO_HANDLERS) {
							struct input_value *v;
							//将input device获取到数据暂存一下input value
							v = &dev->vals[dev->num_vals++];
							v->type = type;
							v->code = code;
							v->value = value;


							input_pass_values(dev, dev->vals, dev->num_vals)
								|
								// 从input device中获取到input handle
							else {
	
							list_for_each_entry_rcu(handle, &dev->h_list, d_node)
								if (handle->open)
									count = input_to_handler(handle, vals, count);
											|
										// 通过handle儿子找到handler父亲
										struct input_handler *handler = handle->handler;
										// 如果有events函数指针,那就调用
										if (handler->events)
											handler->events(handle, vals, count);
										else if (handler->event)//否则就调用event();
											for (v = vals; v != end; v++)
												handler->event(handle, v->type, v->code, v->value);
										
										
						}

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,
};
            总结: 如果将数据上报,最终是调用handler中events()或者event()
                实际是evdev.c
                .event        = evdev_event,
                .events        = evdev_events,

那么evdev_event函数里面实现了什么内容??

	events()
            |
						// 拿到evdev,肯定要拿到缓冲区
						struct evdev *evdev = handle->private;
						struct evdev_client *client;

						// 获取到缓冲evdev_client
						else
						list_for_each_entry_rcu(client, &evdev->client_list, node)
							evdev_pass_values(client, vals, count,
									  time_mono, time_real);
								|
								// 通过client获取到evdev
								struct evdev *evdev = client->evdev;
								
								const struct input_value *v;
								struct input_event event;

								event.time = ktime_to_timeval(client->clkid == CLOCK_MONOTONIC ? mono : real);
								for (v = vals; v != vals + count; v++) {
									// 将input device上报的数据获取到,并且封装成input event对象
									event.type = v->type;
									event.code = v->code;
									event.value = v->value;
									// 将input event数据存放到缓冲区中
									__pass_event(client, &event);
											|
											client->buffer[client->head++] = *event;
											client->head &= client->bufsize - 1;
									for (v = vals; v != vals + count; v++) {
										// 将input device上报的数据获取到,并且封装成input event对象
										event.type = v->type;
										event.code = v->code;
										event.value = v->value;
										// 将input event数据存放到缓冲区中
										__pass_event(client, &event);
												client->buffer[client->head++] = *event;
												client->head &= client->bufsize - 1;
										// 如果调用 input_sync()
										if (v->type == EV_SYN && v->code == SYN_REPORT)
											wakeup = true;
									}

										// 唤醒等待队列
										if (wakeup)
											wake_up_interruptible(&evdev->wait);

   总结:
                input_report_key(inputdev, pdesc->key_code, 0);
                               //将输入设备产生数据交给input handler,调用events();将数据存放在
                               // 缓冲区client->buffer[client->head++] = *event;
                input_sync(inputdev);//上报数据结束
                               // 唤醒等待队列,表示输入设备上报数据完毕

 

 

 

 

 

 

 

 

 

 

 

 

 

1. ARIMA 2. SARIMA 3. VAR 4. Auto-ARIMA 5. Auto-SARIMA 6. LSTM 7. GRU 8. RNN 9. CNN 10. MLP 11. DNN 12. MLP-LSTM 13. MLP-GRU 14. MLP-RNN 15. MLP-CNN 16. LSTM-ARIMA 17. LSTM-MLP 18. LSTM-CNN 19. GRU-ARIMA 20. GRU-MLP 21. GRU-CNN 22. RNN-ARIMA 23. RNN-MLP 24. RNN-CNN 25. CNN-ARIMA 26. CNN-MLP 27. CNN-LSTM 28. CNN-GRU 29. ARIMA-SVM 30. SARIMA-SVM 31. VAR-SVM 32. Auto-ARIMA-SVM 33. Auto-SARIMA-SVM 34. LSTM-SVM 35. GRU-SVM 36. RNN-SVM 37. CNN-SVM 38. MLP-SVM 39. LSTM-ARIMA-SVM 40. LSTM-MLP-SVM 41. LSTM-CNN-SVM 42. GRU-ARIMA-SVM 43. GRU-MLP-SVM 44. GRU-CNN-SVM 45. RNN-ARIMA-SVM 46. RNN-MLP-SVM 47. RNN-CNN-SVM 48. CNN-ARIMA-SVM 49. CNN-MLP-SVM 50. CNN-LSTM-SVM 51. CNN-GRU-SVM 52. ARIMA-RF 53. SARIMA-RF 54. VAR-RF 55. Auto-ARIMA-RF 56. Auto-SARIMA-RF 57. LSTM-RF 58. GRU-RF 59. RNN-RF 60. CNN-RF 61. MLP-RF 62. LSTM-ARIMA-RF 63. LSTM-MLP-RF 64. LSTM-CNN-RF 65. GRU-ARIMA-RF 66. GRU-MLP-RF 67. GRU-CNN-RF 68. RNN-ARIMA-RF 69. RNN-MLP-RF 70. RNN-CNN-RF 71. CNN-ARIMA-RF 72. CNN-MLP-RF 73. CNN-LSTM-RF 74. CNN-GRU-RF 75. ARIMA-XGBoost 76. SARIMA-XGBoost 77. VAR-XGBoost 78. Auto-ARIMA-XGBoost 79. Auto-SARIMA-XGBoost 80. LSTM-XGBoost 81. GRU-XGBoost 82. RNN-XGBoost 83. CNN-XGBoost 84. MLP-XGBoost 85. LSTM-ARIMA-XGBoost 86. LSTM-MLP-XGBoost 87. LSTM-CNN-XGBoost 88. GRU-ARIMA-XGBoost 89. GRU-MLP-XGBoost 90. GRU-CNN-XGBoost 91. RNN-ARIMA-XGBoost 92. RNN-MLP-XGBoost 93. RNN-CNN-XGBoost 94. CNN-ARIMA-XGBoost 95. CNN-MLP-XGBoost 96. CNN-LSTM-XGBoost 97. CNN-GRU-XGBoost 98. ARIMA-ANN 99. SARIMA-ANN 100. VAR-ANN 上面这些缩写模型的全称及相关用途功能详细解释
07-15
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值