总体软件架构
先来个整体的软件层次架构图:
从上图输入子系统的框架图,可以看出,输入子系统由Input driver(驱动层)、Input core(输入子系统核心)、Event handler(事件处理层)三部分组成。一个输入事件,如鼠标移动、键盘按下等通过Input driver -> Input core -> Event handler -> userspace的顺序到达用户空间的应用程序。
1.系统核心层(Input core)
抽象出来的与具体硬件无关,提供一些通用功能,主要包括如下:
struct input_dev *input_allocate_device(void)
具体的输入设备都被抽象出来了统一用struct input_dev来表示
int input_register_device(struct input_dev *dev)
输入设备注册
功能 | 接口 |
---|---|
分配输入设备结构体 | struct input_dev *input_allocate_device(void) |
输入设备注册 | int input_register_device(struct input_dev *dev) |
报告输入事件 | void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) |
报告结束 | input_sync()同步用于告诉input core子系统报告结束 |
注册一个事件处理器 | int input_register_handler(struct input_handler *handler) |
向内核注册一个handle结构 | int input_register_handle(struct input_handle *) |
input_register_device()用于把输入设备挂到输入设备链表input_dev_list中;
input_register_handler()用于事件处理器挂到input_handler_list中;
下面从代码层面来具体分析:
int input_register_device(struct input_dev *dev)
{
...
list_add_tail(&dev->node, &input_dev_list);
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
...
}
int input_register_handler(struct input_handler *handler)
{
...
INIT_LIST_HEAD(&handler->h_list);
list_add_tail(&handler->node, &input_handler_list);
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(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;
id = input_match_device(handler, dev);
if (!id)
return -ENODEV;
error = handler->connect(handler, dev, id);
if (error && error != -ENODEV)
pr_err("failed to attach handler %s to device %s, error: %d\n",
handler->name, kobject_name(&dev->dev.kobj), error);
return error;
}
static const struct input_device_id *input_match_device(struct input_handler *handler,
struct input_dev *dev)
{
const struct input_device_id *id;
for (id = handler->id_table; 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;
...
if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX))
continue;
if (!bitmap_subset(id->keybit, dev->keybit, KEY_MAX))
continue;
...
if (!handler->match || handler->match(handler, dev))
return id;
}
return NULL;
}
从上述代码可以看出,对应新注册到input core中的输入设备或者事件处理器,都会遍历input_dev_list或者input_handler_list进行匹配,具体的匹配规则是在input_match_device()函数中实现的,主要的依据是vendor,product,version,另外还有能够产生的事件是否事件处理器都能够处理。
如果输入设备和事件处理器匹配,则调用事件处理器的connect()进行进一步的处理,下面以evdev事件处理器为例进行说明:
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,
};
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev;
int minor;
int dev_no;
int error;
minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
if (minor < 0) {
error = minor;
pr_err("failed to reserve new minor: %d\n", error);
return error;
}
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
if (!evdev) {
error = -ENOMEM;
goto err_free_minor;
}
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;
/* Normalize device number if it falls into legacy range */
if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
dev_no -= EVDEV_MINOR_BASE;
dev_set_name(&evdev->dev, "event%d", dev_no);
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
evdev->handle.private = evdev;
evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
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);
if (error)
goto err_free_evdev;
cdev_init(&evdev->cdev, &evdev_fops);
evdev->cdev.kobj.parent = &evdev->dev.kobj;
error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
if (error)
goto err_unregister_handle;
error = device_add(&evdev->dev);
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);
err_free_minor:
input_free_minor(minor);
return error;
}
这个函数包含的信息量比较大,首先通过input_register_handle()将输入设备和事件处理器管理起来(通过struct input_handle这个结构联系起来的),其次向用户空间注册了一个字符设备(/dev/input/eventx)
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler;
struct input_dev *dev = handle->dev;
int error;
/*
* Filters go to the head of the list, normal handlers
* to the tail.
*/
if (handler->filter)
list_add_rcu(&handle->d_node, &dev->h_list);
else
list_add_tail_rcu(&handle->d_node, &dev->h_list);//将该handle加到该输入设备的handle list 列表中
/*
* Since we are supposed to be called from ->connect()
* which is mutually exclusive with ->disconnect()
* we can't be racing with input_unregister_handle()
* and so separate lock is not needed here.
*/
list_add_tail_rcu(&handle->h_node, &handler->h_list);
if (handler->start)
handler->start(handle);
}
对应evdev来说,start()方法没有实现。
下面来看看用户按下一个按键后,整个经历的过程是什么样子的。
输入设备驱动层不是这里的重点,略去不讲,从设备驱动报告输入事件给input core开始讲起:
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);
input_handle_event(dev, type, code, value);
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
static void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
int disposition = input_get_disposition(dev, type, code, &value); //决定输入事件应该分发去哪里
if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
add_input_randomness(type, code, value);
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);
if (!dev->vals)
return;
if (disposition & INPUT_PASS_TO_HANDLERS) {
struct input_value *v;
if (disposition & INPUT_SLOT) {
v = &dev->vals[dev->num_vals++];
v->type = EV_ABS;
v->code = ABS_MT_SLOT;
v->value = dev->mt->slot;
}
v = &dev->vals[dev->num_vals++];
v->type = type;
v->code = code;
v->value = value;
}
if (disposition & INPUT_FLUSH) {
if (dev->num_vals >= 2)
input_pass_values(dev, dev->vals, dev->num_vals);
dev->num_vals = 0;
} else if (dev->num_vals >= dev->max_vals - 2) {
dev->vals[dev->num_vals++] = input_value_sync;
input_pass_values(dev, dev->vals, dev->num_vals);
dev->num_vals = 0;
}
}
input_get_disposition()决定输入事件应该分发去哪里,这里我们只关心INPUT_PASS_TO_HANDLERS,继续往下追:
static void input_pass_values(struct input_dev *dev,
struct input_value *vals, unsigned int count)
{
struct input_handle *handle;
struct input_value *v;
handle = rcu_dereference(dev->grab);
if (handle) {
count = input_to_handler(handle, vals, count);
} else {
list_for_each_entry_rcu(handle, &dev->h_list, d_node)
if (handle->open) {
count = input_to_handler(handle, vals, count);
if (!count)
break;
}
}
}
上面代码中的grab是什么,这里暂且跳过这条线,后面再分析。另外一条线,一个输入事件可能有多个事件处理器,注意到handle->open没有,只有当事件处理器打开了,才会将事件分发给该事件处理器,那么handle->open是在哪里设置的呢,通过搜索很容易找到:
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;
}
也就是说只有/dev/input/eventx 被用户空间程序打开了,该事件处理器才会被打开,触发的事件才会分发给该事件处理器。
ok,继续往下分析:
static unsigned int input_to_handler(struct input_handle *handle,
struct input_value *vals, unsigned int count)
{
struct input_handler *handler = handle->handler; //得到事件处理器
...
if (handler->events)
handler->events(handle, vals, count);
else if (handler->event)
for (v = vals; v != vals + count; v++)
handler->event(handle, v->type, v->code, v->value);
}
events()一次可以处理多个事件,这里简单化假设只包含一个事件:
/*
* Pass incoming event to all connected clients.
*/
static void evdev_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct input_value vals[] = { { type, code, value } };
evdev_events(handle, vals, 1);
}
/*
* Pass incoming events to all connected clients.
*/
static void evdev_events(struct input_handle *handle,
const struct input_value *vals, unsigned int count)
{
struct evdev *evdev = handle->private;
struct evdev_client *client;
ktime_t ev_time[EV_CLK_MAX];
ev_time[EV_CLK_MONO] = ktime_get();
ev_time[EV_CLK_REAL] = ktime_mono_to_real(ev_time[EV_CLK_MONO]);
ev_time[EV_CLK_BOOT] = ktime_mono_to_any(ev_time[EV_CLK_MONO],
TK_OFFS_BOOT);
rcu_read_lock();
client = rcu_dereference(evdev->grab);
if (client)
evdev_pass_values(client, vals, count, ev_time);
else
list_for_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_values(client, vals, count, ev_time);
rcu_read_unlock();
}
注意观察这里的client_list,会将该事件分发给所有的client,那么这里的client指的是什么呢?
注意观察evdev_open()函数,这里只提取跟client相关的代码:
static int evdev_open(struct inode *inode, struct file *file)
{
struct evdev_client *client;
client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);
client->evdev = evdev;
evdev_attach_client(evdev, client);
file->private_data = client;
}
static void evdev_attach_client(struct evdev *evdev,
struct evdev_client *client)
{
spin_lock(&evdev->client_lock);
list_add_tail_rcu(&client->node, &evdev->client_list);
spin_unlock(&evdev->client_lock);
}
不用过多解释了吧,代码一目了然,每open一次分配一个client,并将该client链接到client_list中,然后在文件指针的private_data 中跟client对应起来,后面通过该文件指针读取的都是该client下面的事件列表。
也就是说/dev/input/eventx 可以被多次打开,并且相互之间不会干扰,这也就是为什么linux可以通过/dev/input/eventx监控键盘的输入了,并且对应其他读取键盘的应用完全不受影响。
下面来讲讲上面忽略掉的grab是个什么东西,通过搜索不难找到:
static int evdev_grab(struct evdev *evdev, struct evdev_client *client)
{
int error;
if (evdev->grab)
return -EBUSY;
error = input_grab_device(&evdev->handle);
if (error)
return error;
rcu_assign_pointer(evdev->grab, client);
return 0;
}
static long evdev_do_ioctl(struct file *file, unsigned int cmd,
void __user *p, int compat_mode)
{
...
case EVIOCGRAB:
if (p)
return evdev_grab(evdev, client);
else
return evdev_ungrab(evdev, client);
...
}
上述的代码应该不难理解,也就是说用户程序可以打开/dev/input/eventx后通过ioctl(fd, EVIOCGRAB)设置为独占键盘输入,该特性在输入密码前设置为独占键盘输入,密码输入完成后取消独占,这样密码就没办法被监控到了,对于密码安全特别重要。
下面对键盘输入重点分析一下
//./drivers/tty/vt/keyboard.c
int __init kbd_init(void)
{
int i;
int error;
for (i = 0; i < MAX_NR_CONSOLES; i++) {
kbd_table[i].ledflagstate = kbd_defleds();
kbd_table[i].default_ledflagstate = kbd_defleds();
kbd_table[i].ledmode = LED_SHOW_FLAGS;
kbd_table[i].lockstate = KBD_DEFLOCK;
kbd_table[i].slockstate = 0;
kbd_table[i].modeflags = KBD_DEFMODE;
kbd_table[i].kbdmode = default_utf8 ? VC_UNICODE : VC_XLATE;
}
kbd_init_leds();
error = input_register_handler(&kbd_handler);
if (error)
return error;
tasklet_enable(&keyboard_tasklet);
tasklet_schedule(&keyboard_tasklet);
return 0;
}
static struct input_handler kbd_handler = {
.event = kbd_event,
.match = kbd_match,
.connect = kbd_connect,
.disconnect = kbd_disconnect,
.start = kbd_start,
.name = "kbd",
.id_table = kbd_ids,
};
对于键盘输入,在开启了VT的情况下,注册了kbd_handler专门来处理键盘的事件处理器:
下面来简要分析一下,按键来了之后如何处理:
static void kbd_event(struct input_handle *handle, unsigned int event_type,
unsigned int event_code, int value)
{
...
if (event_type == EV_MSC && event_code == MSC_RAW && HW_RAW(handle->dev))
kbd_rawcode(value);
if (event_type == EV_KEY)
kbd_keycode(event_code, value, HW_RAW(handle->dev));
...
}
static void kbd_keycode(unsigned int keycode, int down, int hw_raw)
{
struct vc_data *vc = vc_cons[fg_console].d;
struct tty_struct *tty;
tty = vc->port.tty;
if (tty && (!tty->driver_data)) {
/* No driver data? Strange. Okay we fix it then. */
tty->driver_data = vc;
}
...
if (rep &&
(!vc_kbd_mode(kbd, VC_REPEAT) ||
(tty && !L_ECHO(tty) && tty_chars_in_buffer(tty)))) {
/*
* Don't repeat a key if the input buffers are not empty and the
* characters get aren't echoed locally. This makes key repeat
* usable with slow applications and under heavy loads.
*/
return;
}
...
}
因为虚拟终端有多个,比如/dev/tty1-n, 所有会将按键分发给处于focus的虚拟终端,由fg_console来记录那个虚拟终端处于focus,即前台终端。
将输入通过tty_chars_in_buffer(tty)放入,这样就可以读取focus的/dev/ttyx 来读取键盘输入了。
参考资料:
http://www.embeddedlinux.org.cn/essentiallinuxdevicedrivers/final/ch07lev1sec2.html
https://www.cnblogs.com/cute/archive/2011/08/30/2159305.html
https://www.jianshu.com/p/e9cfae59e3df