Android 按键处理二(按键流程之内核层篇)

前言:上一篇中,我们掌握一些必要的基础知识,这一篇就来分析当我们点击一个按键时,按键上报的具体流程。

一、按键按下 涉及到的层级

按键按下 会通过以下系统层级:
Driver(设备驱动层)->Inputcore(输入子系统核心层)
->Event handler(事件处理层)->userspace(用户空间)


输入事件流程

整个过程涉及到内核层、Framework层以及应用层.

本文主要分析输入事件内核层的流程

二、键值上报具体流程

1.中断注册 kpd_pdrv_probe()

路径:kernel-3.18/drivers/input/keyboard/mediatek/kpd.c

static int kpd_pdrv_probe(struct platform_device *pdev){
//省略代码...
 /* initialize and register input device (/dev/input/eventX) */
    kpd_input_dev = input_allocate_device();//申请input设备
    if (!kpd_input_dev) {
        kpd_print("input allocate device fail.\n");
        return -ENOMEM;
    }
//input设备初始化  填充kpd_input_dev  设备驱动结构体*
    kpd_input_dev->name = KPD_NAME;
    kpd_input_dev->id.bustype = BUS_HOST;
    kpd_input_dev->id.vendor = 0x2454;
    kpd_input_dev->id.product = 0x6500;
    kpd_input_dev->id.version = 0x0010;
    kpd_input_dev->open = kpd_open;
//解析dts中keypad节点的信息,赋值给kpd_dts_data结构体
    kpd_get_dts_info(pdev->dev.of_node);
//input设备支持EV_KEY事件
    __set_bit(EV_KEY, kpd_input_dev->evbit);
//注册input设备
    r = input_register_device(kpd_input_dev);
//设置按键消抖
    kpd_set_debounce(kpd_dts_data.kpd_key_debounce);
//申请中断处理
  r = request_irq(kp_irqnr, kpd_irq_handler, 
                      IRQF_TRIGGER_NONE, KPD_NAME, NULL);
}

分析:可以看到,在probe()函数中,调用
request_irq();注册了中断处理函数kpd_irq_handle()

2.中断处理函数 kpd_irq_handler()

当我们按键按下了,就会触发中断,进入中断服务子程序,系统会调用相应的中断处理函数kpd_irq_handler()去上报事件。

路径:kernel-3.18/drivers/input/keyboard/mediatek/kpd.c
//中断处理函数
static irqreturn_t kpd_irq_handler(int irq, void *dev_id) 
{
    /* use _nosync to avoid deadlock */
    disable_irq_nosync(kp_irqnr);//禁止中断,无需进行同步,防止死锁
    tasklet_schedule(&kpd_keymap_tasklet);//调度tasklet
    return IRQ_HANDLED;
}

分析:先disable中断(防止死锁),在调度tasklet。

补充知识:
######中断处理的 tasklet 机制
中断服务程序一般都是在中断请求关闭的条件下执行的,
以避免嵌套而使中断控制复杂化。(具体机制就不是本文重点啦)
######tasklet 使用方法
/*用静态方式声明并定义一个tasklet,动态方式一样*/
DECLARE_TASKLET(my_tasklet,&tasklet_func,0);
/*直接调度我们的函数*/
tasklet_schedule(&my_tasklet);

因此,跟踪DECLARE_TASKLET()方法

/* for keymap handling */
static void kpd_keymap_handler(unsigned long data);
static DECLARE_TASKLET(kpd_keymap_tasklet, kpd_keymap_handler, 0);

分析:可以看到,中断服务程序里面执行tasklet_schedule(&kpd_keymap_tasklet);实际上会去调用这个函数kpd_keymap_handler()

static void kpd_keymap_handler(unsigned long data)
{
    int i, j;
    bool pressed;
    u16 new_state[KPD_NUM_MEMS], change, mask;
    u16 hw_keycode, linux_keycode;
//mtk通过5组寄存器来保存按键的状态,这里回读寄存器并保存为new_state
    kpd_get_keymap_state(new_state);
//激活锁唤醒系统,500ms后就释放掉
    wake_lock_timeout(&kpd_suspend_lock, HZ / 2);

    for (i = 0; i < KPD_NUM_MEMS; i++) {
//每组中按键状态未改变则对比下一组,按位处理
        change = new_state[i] ^ kpd_keymap_state[i];
        if (!change)
            continue;

        for (j = 0; j < 16; j++) {
//每组(16位)中对比按位查看是否状态发生改变
            mask = 1U << j;
            if (!(change & mask))
                continue;

            hw_keycode = (i << 4) + j;
            /* bit is 1: not pressed, 0: pressed */
//按键是否按下,寄存器中0表示按键处于按下状态            
            pressed = !(new_state[i] & mask);
            if (kpd_show_hw_keycode)
                kpd_print("(%s) HW keycode = %u\n", pressed ? 
                              "pressed" : "released", hw_keycode);
            BUG_ON(hw_keycode >= KPD_NUM_KEYS);
            linux_keycode = kpd_keymap[hw_keycode];
            if (unlikely(linux_keycode == 0)) {
                kpd_print("Linux keycode = 0\n");
                continue;
            }
            kpd_aee_handler(linux_keycode, pressed);
//上报键值
            input_report_key(kpd_input_dev, linux_keycode, pressed);
//同步用于告诉input core子系统报告结束
            input_sync(kpd_input_dev);
            kpd_print("report Linux keycode = %u\n", linux_keycode);
        }
    }
//kpd_keymap_state保存new_state,用于下轮对比
    memcpy(kpd_keymap_state, new_state, sizeof(new_state));
    kpd_print("save new keymap state\n");
//按键处理完毕,打开中断
    enable_irq(kp_irqnr);
}

分析:该函数中,最重要的是
//向INPUT子系统上报键值
input_report_key(kpd_input_dev, linux_keycode, pressed);
//同步用于告诉input core子系统报告结束
input_sync(kpd_input_dev);
也就是上篇文章中抓取到的adb信息,因为按键按下到松开,会产生2次中断,因此会调用2次

按键按下
0001 0072 00000001//input_report_key();
0000 0000 00000000 // input_sync(kpd_input_dev);
按键松开
0001 0072 00000000//input_report_key();
0000 0000 00000000// input_sync(kpd_input_dev);

接下来,就通过input子系统一层层调用,最终调用input_event_to_user()上报到用户空间。
我们来跟踪以下具体流程

路径:kernel-3.18/include/linux/input.h
static inline void input_report_key(struct input_dev *dev, 
                                         unsigned int code, int value)
{
    input_event(dev, EV_KEY, code, !!value);
}

static inline void input_sync(struct input_dev *dev)
{
    input_event(dev, EV_SYN, SYN_REPORT, 0);
}

可以看到,无论是input_report_key还是input_sync,都会调用input_event继续上报

路径:kernel-3.18/drivers/input/input.c
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);//内核锁
    }    
}
EXPORT_SYMBOL(input_event);

分析,可以看到,代码又继续调用了input_handle_event()进一步上报数值,

路径:kernel-3.18/drivers/input/input.c
static void input_handle_event(struct input_dev *dev,
                   unsigned int type, unsigned int code, int value)
{
    int disposition;

//获得事件处理者身份
    disposition = input_get_disposition(dev, type, code, &value);
//如果事件处理者身份为INPUT_PASS_TO_HANDLERS表示交给input hardler处理
    if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
        dev->event(dev, type, code, value);

    if (!dev->vals)
        return;
//如果事件处理者身份为INPUT_PASS_TO_DEVICE表示交给input device处理
    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;
    }
//如果事件处理者身份为INPUT_FLUSH表示需要handler立即处理
    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_handle_event函数向输入子系统传送事件信息,
参数1是输入设备input_dev,参数2是事件类型,
参数3是键码,参数4是键值。
 input_get_disposition()确定事件的处理方式,返回值主要有以下几种: 
INPUT_IGNORE_EVENT:表示忽略事件,不进行处理。 
INPUT_PASS_TO_HANDLERS:表示事件交给handler处理。 
INPUT_PASS_TO_DEVICE:表示将事件交给input_dev处理。 
INPUT_PASS_TO_ALL:表示将事件交给handler和 input_dev共同处理。

那到底返回的disposition是啥的?那就的继续跟一下
input_get_disposition()函数去看

路径:kernel-3.18/drivers/input/input.c
static int input_get_disposition(struct input_dev *dev,
              unsigned int type, unsigned int code, int *pval)
{
    switch (type) {

    case EV_SYN:
        switch (code) {
        case SYN_CONFIG:
            disposition = INPUT_PASS_TO_ALL;
            break;
//传进来的参数是:EV_SYN, SYN_REPORT,因此 代码会走这里
        case SYN_REPORT:
            disposition = INPUT_PASS_TO_HANDLERS | INPUT_FLUSH;
            break;
        case SYN_MT_REPORT:
            disposition = INPUT_PASS_TO_HANDLERS;
            break;
        }
        break;

    case EV_KEY:
        if (is_event_supported(code, dev->keybit, KEY_MAX)) {

            /* auto-repeat bypasses state updates */
            if (value == 2) {
                disposition = INPUT_PASS_TO_HANDLERS;
                break;
            }

            if (!!test_bit(code, dev->key) != !!value) {

                __change_bit(code, dev->key);
                disposition = INPUT_PASS_TO_HANDLERS;
            }
        }
        break;

    case EV_SW :
    //..........省略..................

分析:从前面的分析,每上报一个事件,就会调用input_sync,告诉Input子系统事件上报完毕,
可以看到,传的参数是EV_SYN, SYN_REPORT,0

static inline void input_sync(struct input_dev *dev)
{
    input_event(dev, EV_SYN, SYN_REPORT, 0);
}

所以 最后的 disposition = INPUT_PASS_TO_HANDLERS | INPUT_FLUSH;
从input_handle_event(),可以看出,会接着去调用
input_pass_values()继续上报

路径:kernel-3.18/drivers/input/input.c
static void input_pass_values(struct input_dev *dev,
                  struct input_value *vals, unsigned int count)
{
    struct input_handle *handle;
    struct input_value *v;

    if (!count)
        return;

    rcu_read_lock();
    
    handle = rcu_dereference(dev->grab);
    if (handle) {//如果是绑定的handle,则调用绑定的handler->event函数
// 调用input_to_handler,进行事件的处理 
        count = input_to_handler(handle, vals, count);
    } else {
//如果没有绑定,则遍历dev的h_list链表,寻找handle,
//如果handle已经打开,说明有进程读取设备关联的evdev。   
        list_for_each_entry_rcu(handle, &dev->h_list, d_node)
            if (handle->open)
  // 调用input_to_handler,进行事件的处理  
                count = input_to_handler(handle, vals, count);
    }

    rcu_read_unlock();
//.......................省略...................................
}

分析:input_pass_values()函数用于确定input_dev的handler,并通过input_to_handler()调用handler的event函数。在上述代码中,rcu_dereference(),该接口用来获取RCU protected pointer。reader要访问RCU保护的共享数据,当然要获取RCU protected pointer,然后通过该指针进行dereference的操作。dev->grab是强制为input device的handler,如果rcu_dereference()函数返回值不为空,说明有为input_device强制指定handler,就直接调用handler的event函数。如果为NULL,表示没有为input_device强制指定handler,就会通过遍历input device->h_list上的handle成员。如果该handle被打开,表示该设备已经被一个用户进程使用,就会调用与输入设备对应的handler的event函数。
注:只有在handle被打开的情况下才会接收到事件,这就是说,只有设备被用户程序使用时,才有必要向用户空间导出信息。
接下来会去调用input_to_handler()继续上报

路径:kernel-3.18/drivers/input/input.c
static unsigned int input_to_handler(struct input_handle *handle,
            struct input_value *vals, unsigned int count)
{
    struct input_handler *handler = handle->handler;
    struct input_value *end = vals;
    struct input_value *v;
    //通过过滤器进行事件过滤
    for (v = vals; v != vals + count; v++) {
        if (handler->filter &&
            handler->filter(handle, v->type, v->code, v->value))
            continue;
        if (end != v)
            *end = *v;
        end++;
    }

    count = end - vals;
    if (!count)
        return 0;

    if (handler->events)
  //继续上报数据
        handler->events(handle, vals, count);
    else if (handler->event)
        for (v = vals; v != end; v++)
            handler->event(handle, v->type, v->code, v->value);

    return count;
}

分析:首先会通过handler->filter去过滤事件,接着调用 handler->event()【handle->handler->event()】或者handler->events继续上报,那这个event()或者events()函数在哪定义呢
那就得看input_handler 结构体

路径:kernel-3.18/drivers/input/evdev.c
input_handler 结构体
路径:kernel-3.18/drivers/input/evdev.c
static void evdev_event(struct input_handle *handle,
            unsigned int type, unsigned int code, int value)
{
    struct input_value vals[] = { { type, code, value } };
  //可以看到 evdev_event调用了evdev_events
    evdev_events(handle, vals, 1);
}

static void evdev_events(struct input_handle *handle,
             const struct input_value *vals, unsigned int count)
{
  //......省略代码
    rcu_read_lock();

    client = rcu_dereference(evdev->grab);

    if (client)//如果evdev绑定了client那么,处理这个客户端
        evdev_pass_values(client, vals, count, time_mono, time_real);
    else 
      //遍历client链表,调用evdev_pass_values函数  
        list_for_each_entry_rcu(client, &evdev->client_list, node)
            evdev_pass_values(client, vals, count,
                      time_mono, time_real);

    rcu_read_unlock();
//...........省略代码.................
}

分析:从代码中可以看到evdev_event()去调用了evdev_events(),
所以最终都走evdev_events()方法,该方法里面又去evdev_pass_values()进一步上报数据

路径:kernel-3.18/drivers/input/evdev.c
static void evdev_pass_values(struct evdev_client *client,
            const struct input_value *vals, unsigned int count,
            ktime_t mono, ktime_t real)
{
 //.....................省略........................
    /* Interrupts are disabled, just acquire the lock. */
    spin_lock(&client->buffer_lock);

    for (v = vals; v != vals + count; v++) {
        event.type = v->type;
        event.code = v->code;
        event.value = v->value;
        __pass_event(client, &event);
        if (v->type == EV_SYN && v->code == SYN_REPORT)
            wakeup = true;
    }

    spin_unlock(&client->buffer_lock);

    if (wakeup)
        wake_up_interruptible(&evdev->wait);
}

分析:先调用__pass_event()进一步上报数据,然后调用wake_up_interruptible唤醒中断

路径:kernel-3.18/drivers/input/evdev.c
static void __pass_event(struct evdev_client *client,
             const struct input_event *event)
{
    client->buffer[client->head++] = *event;
    client->head &= client->bufsize - 1;
    
    if (unlikely(client->head == client->tail)) {
        /*
         * This effectively "drops" all unconsumed events, leaving
         * EV_SYN/SYN_DROPPED plus the newest event in the queue.
         */
        client->tail = (client->head - 2) & (client->bufsize - 1);
        /*将event装入client的buffer中,buffer是一个环形缓存区*/ 
        client->buffer[client->tail].time = event->time;
        client->buffer[client->tail].type = EV_SYN;
        client->buffer[client->tail].code = SYN_DROPPED;
        client->buffer[client->tail].value = 0;

        client->packet_head = client->tail;
        if (client->use_wake_lock)
            wake_unlock(&client->wake_lock);
    }

}

分析:到这里,最终将事件传递给了用户端的client结构中buffer中,buffer是一个环形缓存区,等待用户空间来读取
读取大致过程:先调用evdev_open_device打开设备,然后调用evdev_read去读取

路径:kernel-3.18/drivers/input/evdev.c
static ssize_t evdev_read(struct file *file, char __user *buffer,  
              size_t count, loff_t *ppos)  
{  
    /*这个就是刚才在open函数中*/  
    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;  
    /*如果client的环形缓冲区中没有数据并且是非阻塞的,那么返回-EAGAIN,
      也就是try again*/  
    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;  
    /*如果获得了数据则取出来,调用evdev_fetch_next_event*/  
    while (retval + input_event_size() <= count &&  
           evdev_fetch_next_event(client, &event)) {  
        /*input_event_to_user调用copy_to_user传入用户程序中,这样读取完成*/  
        if (input_event_to_user(buffer + retval, &event))  
            return -EFAULT;  
  
        retval += input_event_size();  
    }  
  
    return retval;  
} 

路径:kernel-3.18/drivers/input/input-compat.c
int input_event_to_user(char __user *buffer,
            const struct input_event *event)
{
    if (INPUT_COMPAT_TEST && !COMPAT_USE_64BIT_TIME) {
        struct input_event_compat compat_event;

        compat_event.time.tv_sec = event->time.tv_sec;
        compat_event.time.tv_usec = event->time.tv_usec;
        compat_event.type = event->type;
        compat_event.code = event->code;
        compat_event.value = event->value;

        if (copy_to_user(buffer, &compat_event,
                 sizeof(struct input_event_compat)))
            return -EFAULT;

    } else {
        if (copy_to_user(buffer, event, sizeof(struct input_event)))
            return -EFAULT;
    }

    return 0;
}

分析:该函数中,最终调用input_event_to_use()->copy_to_user()把数据拷贝到用户空间,到此按键上报事件内核流程分析就结束了。

总结

图1
总结一下事件的传递过程:
首先在驱动层中,调用inport_report_key和input_sync,
然后他调用了input core层的input_event,
input_event调用了input_handle_event对事件进行分派,调用input_pass_event,
在这里他会把事件传递给具体的handler层,
然后在相应handler的event处理函数中,封装一个event,
然后把它投入evdev的那个client_list上的client的事件buffer中,
等待用户空间来读取。
最后通过evdev_read->input_event_to_user->copy_to_user把数据拷贝到用户空间

调用函数如下:
 input_event()->input_handle_event() ->input_pass_values()
 ->input_to_handler->handle->handler->event(handle,type, code, value)
 ->evdev_events() ->evdev_pass_values() ->__pass_event()
把数据存在客户端的buffer中,等待读取
最后evdev_read->input_event_to_user->copy_to_user
拷贝到用户空间

总体来说:
Driver(设备驱动层)->Inputcore(输入子系统核心层)
->Event handler(事件处理层)->userspace(用户空间)

Stay hungry,Stay foolish!
荆轲刺秦王

参考文档:
https://blog.csdn.net/u010142953/article/details/46680529
https://www.linuxidc.com/Linux/2011-09/43187p5.htm
https://blog.csdn.net/wh8_2011/article/details/51678336
https://blog.csdn.net/sdkdlwk/article/details/71108226
https://blog.csdn.net/hb9312z/article/details/78414334
http://www.cnblogs.com/myblesh/articles/2367648.html
https://blog.csdn.net/u013604527/article/details/53432623
http://www.cnblogs.com/ant-man/p/9204977.html
https://blog.csdn.net/u010177751/article/details/38520245
https://blog.csdn.net/liyanfei123456/article/details/53196693

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339

推荐阅读更多精彩内容