linux内核代码分析

代码跟读方式

  1. 带着问题去读
  2. 做好笔记和画图
  3. 驱动联系应用,应用是调用的,驱动是实现的

分层分析

  • Input handler层:/driver/input/evdev.c
module_init(evdev_init);
module_exit(evdev_exit);

   static struct input_handler evdev_handler = {
	.event		= evdev_event,
	.connect	= evdev_connect,
	.disconnect	= evdev_disconnect,
	.fops		= &evdev_fops,
	.minor		= EVDEV_MINOR_BASE,
	.name		= "evdev",
	.id_table	= evdev_ids,
};

static int __init evdev_init(void)
{
	return input_register_handler(&evdev_handler);
}
//将当前的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);

	//将当前的handler和input dev 进行匹配,event handler能够匹配所有的input dev
	id = input_match_device(handler, dev);

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

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

总结:
(1)注册了evdev_handler
(2)遍历input dev list,并行匹配,恒匹配成功,自动会调用handler中connect方法------evdev_connect

- Input 核心层:/driver/input/input.c

	subsys_initcall(input_init);    //类似于module_init();不过等级较高
	module_exit(input_exit);

	//注册类,类似于class_create();
	err = class_register(&input_class);
	//在/proc目录下创建bus/input/devices handlers
	err = input_proc_init();
	//申请设备号
	err = register_chrdev(INPUT_MAJOR, "input", &input_fops);

总结:
(1)注册了主设备号
(2)注册了input class

  • Simple_input.c
  	input_register_device(inputdev);

	//将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中connect方法
	//实际就是event handler
	error = handler->connect(handler, dev, id);

分层图
在这里插入图片描述

分析:evdev.c中evdev_connect() —属于input handler层

evdev_connect();
|
//找到一个没有被使用的次设备号,从64开始,65,66
minior = input_get_new_minior(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, EVDEV_MINOR_BASE + minor);//12 64
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&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
cdev_init(&evdev->cdev,&evdev_fops);
evdev->cdev.kobj.parent = &evdev->dev.kobj;
error = cdev_add(&evdev->cdev,evdev->dev.devt,1);



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);
		|
		dev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
		device_initialize(dev);
		
		dev->devt = devt;
		dev->class = class;
		dev->parent = parent;
		dev->release = device_create_release;
		dev_set_drvdata(dev,drvdata);
		kobject_set_name_vargs(&dev->kobj,fmt,args);  //设置名字
		device_add(dev);  //注册到系统

**总结:

  1. 分配edev,并初始化,记录input device和handler的关系
  2. 创建设备节点 /dev/event0
  3. 注册cdev,并实现fops**
  4. 关系:
    多个input device可以对应一个event handler
    一个input device对应一个 evdev,对应于一个设备节点/dev/event1,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,
};

input内核层input_dev 和 input_handler 关系图:
在这里插入图片描述

应用程序中调用输入子系统的代码,数据是如何传递给用户层的

应用层与驱动层调用图
在这里插入图片描述

如下描述:
在这里插入图片描述

总结:
一个设备节点对应一个 cdev,一个 cdev 对应一个 evdev ,evdev中记录 input_dev 和 input_handler 的关系,同时还有一个缓冲队列。evdev作用就是把所用东西记录下来。

evdev_open方法

evdev_open 方法做的事情


	//通过儿子,找到老母 input device
	bufsize = evdev_compute_buffer_size(evdev->handle.dev);

	//分配一个client对象,描述一个缓冲队列,存放发就是 input_event
	/*sizeof(struct evdev_client) 
		+bufsize * sizeof(struct input_event)
		包含了很多input_event大小
	*/
	client = kzalloc(sizeof(struct evdev_client) +
				bufsize * sizeof(struct input_event),
			 GFP_KERNEL);
	if (!client) {
		error = -ENOMEM;
		goto err_put_evdev;
	}

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

	error = evdev_open_device(evdev);
	if (error)
		goto err_free_client;

	//将client记录到file,方便其他的接口使用
	file->private_data = client;
	nonseekable_open(inode, file);
  1. 为输入设备分配一个缓冲区 evdev_client ,用户存放input device层上报的数据
  2. evdev_client 记录到 evdev 中
  3. evdev_client 记录到 file 中

应用程序中read,如何获取到数据

read
在这里插入图片描述

evdev_read方法
	//获取到open中分配的缓冲区对象
	struct evdev_client *client = file->private_data;
	//获取到 evdev
	struct evdev *evdev = client->evdev;
	//表示一个数据包,要给用户
	struct input_event event;

	for(;;)
	{
		
		//实现非阻塞
		if (client->head == client->tail  &&
		    (file->f_flags & O_NONBLOCK))
			return -EAGAIN;
			
		while (retval + 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 + retval, &event))
				|
				copy_to_user(buffer,event,sizeof)
			return -EFAULT;
		// 3.统计上报多少数据
		retval += 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); //上报数据结束

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);
							|
							//如果handler儿子找到handler父亲
							struct input_handler *handler = handler->handler;
							//如果有events函数指针,那就调用
							if(hadnler->events)
								handler->events(handle,vals,count);
								else if(handler->event)  //否则调用event();
								else if(handler->event)
									for(v = vals; v != end; v++)
										handler->event(handle,v->type,v->code,v->value);
							
			}	
			
			

总结:

  • 如果将数据上报,最终是调用handler中的events()或者event()
    实际是 evdev.c
.event = evdev_event,
.events = evdev_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_event(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->valuel;
			 	//将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_sync(inputdev); 唤醒的

总结:

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

整体逻辑图如下
在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐