zoukankan      html  css  js  c++  java
  • input子系统分析之三:驱动模块

    内核版本:3.9.5

    本节将以even handler来分析设备的注册和打开的过程,分析之前不妨回顾一下上节介绍的数据结构.

    结合前两节分析可知,input子系统分为3层,最上一层是event handler,中间层是input core,底层是input driver.input driver把event report到input core层,input core对event进行分发,传到 event handler,相应的event handler层把event 放到event buffer中,等待应用程序读取!这就是input的基本思想.

    那么我我们来看,Linux模块机制告诉我们在设备注册之前必须先初始化INPUT子系统,这个工作由input_init()函数来完成.在drivers/input.c中:

     1 static int __init input_init(void)
     2 {
     3     int err;
     4 
     5     err = class_register(&input_class);//注册input类
     6     if (err) {
     7         pr_err("unable to register input_dev class
    ");
     8         return err;
     9     }
    10 
    11     err = input_proc_init();//创建/proc中的项
    12     if (err)
    13         goto fail1;
    14 
    15     err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
    16                      INPUT_MAX_CHAR_DEVICES, "input");/*注册设备,主设备号为INPUT_MAJOR,就是13,后面注册的输入设备都使用该主设备号*/
    17     if (err) {
    18         pr_err("unable to register char major %d", INPUT_MAJOR);
    19         goto fail2;
    20     }
    21 
    22     return 0;
    23 
    24  fail2:    input_proc_exit();
    25  fail1:    class_unregister(&input_class);
    26     return err;
    27 }
    28 
    29 static void __exit input_exit(void)
    30 {
    31     input_proc_exit();
    32     unregister_chrdev_region(MKDEV(INPUT_MAJOR, 0),
    33                  INPUT_MAX_CHAR_DEVICES);
    34     class_unregister(&input_class);
    35 }
    36 
    37 subsys_initcall(input_init);
    38 module_exit(input_exit);

    subsys_initcall和module_exit宏相信大家都很熟悉,这俩函数也很简单,没什么看的.

    在入口函数里面创建了一个input_class类,其实就在/sys/class下创建了一个目录input.当然对于一个新设备,可以注册进一个class也可以不注册进去,如果存在对应class的话注册进去更好.另外在/proc创建了入口项,这样就可以/proc目录看到input的信息,然后就注册设备,可以看出输入子系统的主设备号是13,在这里并没有生成设备文件.只是在/dev/目录下创建了input目录,以后所有注册进系统的输入设备文件都放在这个目录下.

    那么接下来看看怎么注册input设备的.我们需要在设备驱动层中完成输入设备的注册,通过调用input_register_device()函数来完成,该函数的一个重要任务就是完成设备与事件驱动的匹配.

     1 int input_register_device(struct input_dev *dev)
     2 {
     3     static atomic_t input_no = ATOMIC_INIT(0);
     4     struct input_devres *devres = NULL;
     5     struct input_handler *handler;
     6     unsigned int packet_size;
     7     const char *path;
     8     int error;
     9 
    10     if (dev->devres_managed) {
    11         devres = devres_alloc(devm_input_device_unregister,
    12                       sizeof(struct input_devres), GFP_KERNEL);
    13         if (!devres)
    14             return -ENOMEM;
    15 
    16         devres->input = dev;
    17     }
    18 
    19     /* Every input device generates EV_SYN/SYN_REPORT events. */
    20     __set_bit(EV_SYN, dev->evbit);
    21 
    22     /* KEY_RESERVED is not supposed to be transmitted to userspace. */
    23     __clear_bit(KEY_RESERVED, dev->keybit);
    24 
    25     /* Make sure that bitmasks not mentioned in dev->evbit are clean. */
    26     input_cleanse_bitmasks(dev);
    27 
    28     packet_size = input_estimate_events_per_packet(dev);
    29     if (dev->hint_events_per_packet < packet_size)
    30         dev->hint_events_per_packet = packet_size;
    31 
    32     dev->max_vals = max(dev->hint_events_per_packet, packet_size) + 2;
    33     dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);
    34     if (!dev->vals) {
    35         error = -ENOMEM;
    36         goto err_devres_free;
    37     }
    38 
    39     /*
    40      * If delay and period are pre-set by the driver, then autorepeating
    41      * is handled by the driver itself and we don't do it in input.c.
    42      */
    43     init_timer(&dev->timer);
    44     if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
    45         dev->timer.data = (long) dev;
    46         dev->timer.function = input_repeat_key;
    47         dev->rep[REP_DELAY] = 250;
    48         dev->rep[REP_PERIOD] = 33;
    49     }
    50 
    51     if (!dev->getkeycode)/*没有定义设备的getkeycode函数,则使用默认的获取键值函数*/
    52         dev->getkeycode = input_default_getkeycode;
    53 
    54     if (!dev->setkeycode)/*没有定义设备的setkeycode函数,则使用默认的设定键值函数*/
    55         dev->setkeycode = input_default_setkeycode;
    56 
    57     dev_set_name(&dev->dev, "input%ld",
    58              (unsigned long) atomic_inc_return(&input_no) - 1);/*设定dev的名字*/
    59 
    60     error = device_add(&dev->dev);/*添加设备*/
    61     if (error)
    62         goto err_free_vals;
    63 
    64     path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
    65     pr_info("%s as %s
    ",
    66         dev->name ? dev->name : "Unspecified device",
    67         path ? path : "N/A");
    68     kfree(path);
    69 
    70     error = mutex_lock_interruptible(&input_mutex);
    71     if (error)
    72         goto err_device_del;
    73 
    74     list_add_tail(&dev->node, &input_dev_list);/*将设备添加到input_dev_list设备链表*/
    75 
    76     list_for_each_entry(handler, &input_handler_list, node)
    77         input_attach_handler(dev, handler);/*遍历input_handler_list,试图与每一个handler进行匹配*/
    78 
    79     input_wakeup_procfs_readers();
    80 
    81     mutex_unlock(&input_mutex);
    82 
    83     if (dev->devres_managed) {
    84         dev_dbg(dev->dev.parent, "%s: registering %s with devres.
    ",
    85             __func__, dev_name(&dev->dev));
    86         devres_add(dev->dev.parent, devres);
    87     }
    88     return 0;
    89 
    90 err_device_del:
    91     device_del(&dev->dev);
    92 err_free_vals:
    93     kfree(dev->vals);
    94     dev->vals = NULL;
    95 err_devres_free:
    96     devres_free(devres);
    97     return error;
    98 }

    第76,77行对于每个input_dev,遍历input_handler_list,调用input_attach_handler,根据input_handler的id_table判断能否支持这个input_dev.

     1 static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
     2 {
     3     const struct input_device_id *id;
     4     int error;
     5 
     6     id = input_match_device(handler, dev);
     7     if (!id)
     8         return -ENODEV;
     9 
    10     error = handler->connect(handler, dev, id);/*执行handler的connect,建立handler与设备之间的联系*/
    11     if (error && error != -ENODEV)
    12         pr_err("failed to attach handler %s to device %s, error: %d
    ",
    13                handler->name, kobject_name(&dev->dev.kobj), error);
    14 
    15     return error;
    16 }

    匹配的具体过程如下:

     1 static const struct input_device_id *input_match_device(struct input_handler *handler,
     2                             struct input_dev *dev)
     3 {
     4     const struct input_device_id *id;
     5 
     6     /*遍历handler的id_table与device进行匹配*/
     7     for (id = handler->id_table; id->flags || id->driver_info; id++) {
     8 
     9         /*根据flags的标志位,按需要匹配相应的字段*/
    10         if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
    11             if (id->bustype != dev->id.bustype)//总线类型不匹配
    12                 continue;
    13 
    14         if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
    15             if (id->vendor != dev->id.vendor)//生产厂商不匹配
    16                 continue;
    17 
    18         if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
    19             if (id->product != dev->id.product)//产品不匹配
    20                 continue;
    21 
    22         if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
    23             if (id->version != dev->id.version)//版本不匹配
    24                 continue;
    25 
    26         if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX))//匹配所有的事件类型
    27             continue;
    28 
    29         /*匹配所有事件的子事件*/
    30         if (!bitmap_subset(id->keybit, dev->keybit, KEY_MAX))
    31             continue;
    32 
    33         if (!bitmap_subset(id->relbit, dev->relbit, REL_MAX))
    34             continue;
    35 
    36         if (!bitmap_subset(id->absbit, dev->absbit, ABS_MAX))
    37             continue;
    38 
    39         if (!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX))
    40             continue;
    41 
    42         if (!bitmap_subset(id->ledbit, dev->ledbit, LED_MAX))
    43             continue;
    44 
    45         if (!bitmap_subset(id->sndbit, dev->sndbit, SND_MAX))
    46             continue;
    47 
    48         if (!bitmap_subset(id->ffbit, dev->ffbit, FF_MAX))
    49             continue;
    50 
    51         if (!bitmap_subset(id->swbit, dev->swbit, SW_MAX))
    52             continue;
    53 
    54         if (!handler->match || handler->match(handler, dev))
    55             return id;//匹配成功,返回id
    56     }
    57 
    58     return NULL;
    59 }

    这里接下来就到input_handler的connect了,看来注册input_handler是一道绕不过去的坎儿.那么我们来看具体的event handler的注册,在drivers/input/evdev.c中:

     1 static const struct input_device_id evdev_ids[] = {
     2     { .driver_info = 1 },    /* Matches all devices */
     3     { },            /* Terminating zero entry */
     4 };
     5 
     6 MODULE_DEVICE_TABLE(input, evdev_ids);
     7 
     8 static struct input_handler evdev_handler = {
     9     .event        = evdev_event,
    10     .events        = evdev_events,
    11     .connect    = evdev_connect,
    12     .disconnect    = evdev_disconnect,
    13     .legacy_minors    = true,
    14     .minor        = EVDEV_MINOR_BASE,
    15     .name        = "evdev",
    16     .id_table    = evdev_ids,
    17 };
    18 
    19 static int __init evdev_init(void)
    20 {
    21     return input_register_handler(&evdev_handler);
    22 }
    23 
    24 static void __exit evdev_exit(void)
    25 {
    26     input_unregister_handler(&evdev_handler);
    27 }
    28 
    29 module_init(evdev_init);
    30 module_exit(evdev_exit);

    这里一些相关的代码我也顺便贴出来了,再来一个:

     1 int input_register_handler(struct input_handler *handler)
     2 {
     3     struct input_dev *dev;
     4     int error;
     5 
     6     error = mutex_lock_interruptible(&input_mutex);
     7     if (error)
     8         return error;
     9 
    10     INIT_LIST_HEAD(&handler->h_list);
    11 
    12     list_add_tail(&handler->node, &input_handler_list);
    13 
    14     list_for_each_entry(dev, &input_dev_list, node)
    15         input_attach_handler(dev, handler);
    16 
    17     input_wakeup_procfs_readers();
    18 
    19     mutex_unlock(&input_mutex);
    20     return 0;
    21 }

    这个函数不用多说了,注册event handler这个input_haner的实例,并且在注册之后迅速遍历了一下input_dev_list链表,查找所有的input_dev设备,看这个input_handler是否支持它.那么回到我们上面的遗留问题看看input_handler的connect函数.对于event handler来说,这个函数就是evdev_connect.在drivers/input/evdev.c中:

     1 static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
     2              const struct input_device_id *id)
     3 {
     4     struct evdev *evdev;
     5     int minor;
     6     int dev_no;
     7     int error;
     8 
     9     minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
    10     if (minor < 0) {
    11         error = minor;
    12         pr_err("failed to reserve new minor: %d
    ", error);
    13         return error;
    14     }
    15 
    16     evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
    17     if (!evdev) {
    18         error = -ENOMEM;
    19         goto err_free_minor;
    20     }
    21 
    22     INIT_LIST_HEAD(&evdev->client_list);
    23     spin_lock_init(&evdev->client_lock);
    24     mutex_init(&evdev->mutex);
    25     init_waitqueue_head(&evdev->wait);
    26     evdev->exist = true;
    27 
    28     dev_no = minor;
    29     /* Normalize device number if it falls into legacy range */
    30     if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
    31         dev_no -= EVDEV_MINOR_BASE;
    32     dev_set_name(&evdev->dev, "event%d", dev_no);
    33 
    34     /*初始化handle,每个evdev中都有一个handle*/
    35     evdev->handle.dev = input_get_device(dev);/*这里就将handle的dev指针指向了input_dev*/
    36     evdev->handle.name = dev_name(&evdev->dev);
    37     evdev->handle.handler = handler;/*这里将handle的handler指向了当前的input_handler.注意本函数evdev_connect,可能是在在输入设备注册的时候
    38     在input_register_device函数中调用input_attach_handler的时候调用;也可能是在输入设备的处理方法input_handler时在input_register_handler
    39     函数中也会用到input_attach_handler函数,就会调用本函数.这里就很明显了,本函数就将input_handler和input_dev都放在input_handle中统一管理*/
    40     evdev->handle.private = evdev;
    41 
    42     /*初始化evdev中的内嵌device*/
    43     evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
    44     evdev->dev.class = &input_class;
    45     evdev->dev.parent = &dev->dev;
    46     evdev->dev.release = evdev_free;
    47     device_initialize(&evdev->dev);
    48 
    49     /*注册handle,主要将handle链接到input_dev和handler的h_list链表中去*/
    50     error = input_register_handle(&evdev->handle);
    51     if (error)
    52         goto err_free_evdev;
    53 
    54     cdev_init(&evdev->cdev, &evdev_fops);
    55     evdev->cdev.kobj.parent = &evdev->dev.kobj;
    56     error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
    57     if (error)
    58         goto err_unregister_handle;
    59 
    60     error = device_add(&evdev->dev);
    61     if (error)
    62         goto err_cleanup_evdev;
    63 
    64     return 0;
    65 
    66  err_cleanup_evdev:
    67     evdev_cleanup(evdev);
    68  err_unregister_handle:
    69     input_unregister_handle(&evdev->handle);
    70  err_free_evdev:
    71     put_device(&evdev->dev);
    72  err_free_minor:
    73     input_free_minor(minor);
    74     return error;
    75 }

    至此设备的注册完成!对应event handler,在/dev/input中将多出一个event(x)设备文件,对应一个evdev实例,应用程序打开它的话也就意味着通过event handler来和设备驱动层传递事件.

    第54~60行这几行字符设备初始化,这里之后再将字符设备注册,然后我们打开event(x)设备文件的时候实际上就调用evdev_fops里定义的open回调函数.相应的操作函数也在evdev_fops中定义了.我们来看看evdev_fops.同样在drivers/input/evdev.c中:

     1 static const struct file_operations evdev_fops = {
     2     .owner        = THIS_MODULE,
     3     .read        = evdev_read,
     4     .write        = evdev_write,
     5     .poll        = evdev_poll,
     6     .open        = evdev_open,
     7     .release    = evdev_release,
     8     .unlocked_ioctl    = evdev_ioctl,
     9 #ifdef CONFIG_COMPAT
    10     .compat_ioctl    = evdev_ioctl_compat,
    11 #endif
    12     .fasync        = evdev_fasync,
    13     .flush        = evdev_flush,
    14     .llseek        = no_llseek,
    15 };

    首先来看打开event(x)设备文件,evdev_open函数.

     1 static int evdev_open(struct inode *inode, struct file *file)
     2 {
     3     struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);
     4     unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);
     5     struct evdev_client *client;
     6     int error;
     7 
     8     /*每当一个应用程序打开该文件都会生成一个client*/
     9     client = kzalloc(sizeof(struct evdev_client) +
    10                 bufsize * sizeof(struct input_event),
    11              GFP_KERNEL);
    12     if (!client)
    13         return -ENOMEM;
    14 
    15     client->bufsize = bufsize;
    16     spin_lock_init(&client->buffer_lock);
    17     client->evdev = evdev;
    18     evdev_attach_client(evdev, client);/*将client链入evdev的client_list中*/
    19 
    20     error = evdev_open_device(evdev);
    21     if (error)
    22         goto err_free_client;
    23 
    24     file->private_data = client;
    25     nonseekable_open(inode, file);
    26 
    27     return 0;
    28 
    29  err_free_client:
    30     evdev_detach_client(evdev, client);
    31     kfree(client);
    32     return error;
    33 }

    第20行调用evdev_open_device来打开evdev(如果将inpit_dev抽象为一个父对象,这其实就是一个子对象,或者说这个结构封装了一个具体的实例化的input_dev).

     1 static int evdev_open_device(struct evdev *evdev)
     2 {
     3     int retval;
     4 
     5     retval = mutex_lock_interruptible(&evdev->mutex);
     6     if (retval)
     7         return retval;
     8 
     9     if (!evdev->exist)
    10         retval = -ENODEV;
    11     else if (!evdev->open++) {/*如果是第一次打开该设备,则要执行输入设备的open*/
    12         retval = input_open_device(&evdev->handle);
    13         if (retval)
    14             evdev->open--;
    15     }
    16 
    17     mutex_unlock(&evdev->mutex);
    18     return retval;
    19 }

    这里终于看到了输入子系统核心层的接口input_open_device,此函数就是内核为我们做好的初始化input_dev的函数接口.

     1 int input_open_device(struct input_handle *handle)
     2 {
     3     struct input_dev *dev = handle->dev;
     4     int retval;
     5 
     6     retval = mutex_lock_interruptible(&dev->mutex);
     7     if (retval)
     8         return retval;
     9 
    10     if (dev->going_away) {
    11         retval = -ENODEV;
    12         goto out;
    13     }
    14 
    15     handle->open++;
    16 
    17     if (!dev->users++ && dev->open)/*如果是第一次打开此设备(当前input_dev的使用者数为0),并且input_dev中定义的open函数不为空*/
    18         retval = dev->open(dev);//执行input_dev中定义的open,完成设备的初始化
    19 
    20     if (retval) {
    21         dev->users--;
    22         if (!--handle->open) {
    23             /*
    24              * Make sure we are not delivering any more events
    25              * through this handle
    26              */
    27             synchronize_rcu();
    28         }
    29     }
    30 
    31  out:
    32     mutex_unlock(&dev->mutex);
    33     return retval;
    34 }

    至于具体的如何初始化input_dev,这个是具体的输入设备去实现的.我们这里不分析了.现在来看看,对于一个event(x)设备文件的.

     1 static ssize_t evdev_read(struct file *file, char __user *buffer,
     2               size_t count, loff_t *ppos)
     3 {
     4     struct evdev_client *client = file->private_data;
     5     struct evdev *evdev = client->evdev;
     6     struct input_event event;
     7     size_t read = 0;
     8     int error;
     9 
    10     if (count != 0 && count < input_event_size())
    11         return -EINVAL;
    12 
    13     for (;;) {
    14         if (!evdev->exist)/*evdev没有定义返回函数,直接返回吧,因为我们下面必须要用到这个函数*/
    15             return -ENODEV;
    16 
    17         if (client->packet_head == client->tail &&
    18             (file->f_flags & O_NONBLOCK))/*无数据并且是非阻塞状态,则直接返回,说明没什么要处理的*/
    19             return -EAGAIN;
    20 
    21         /*
    22          * count == 0 is special - no IO is done but we check
    23          * for error conditions (see above).
    24          */
    25         if (count == 0)
    26             break;
    27 
    28         while (read + input_event_size() <= count &&
    29                evdev_fetch_next_event(client, &event)) {
    30 
    31             if (input_event_to_user(buffer + read, &event))
    32                 return -EFAULT;
    33 
    34             read += input_event_size();
    35         }
    36 
    37         if (read)
    38             break;
    39 
    40         if (!(file->f_flags & O_NONBLOCK)) {/*如果是可阻塞状态的话,则等待在wait队列上.直到有数据要被处理,当前进程才被唤醒.这很好理解,既然是
    41         输入设备,读的话比如读按键,那么必须要有硬件设备有按键按下才会返回按键值,这里还是处于事件处理层,应用程序在这里休眠,那么谁来唤醒?
    42         当然是有按键按下才去唤醒,因此这个工作就交给了设备驱动层,那么找到这个唤醒呢,直接去找不好找,那么可以直接搜索evdev->wait,搜索结果
    43         可知evdev->wait在evdev_event()函数中被唤醒*/
    44             error = wait_event_interruptible(evdev->wait,
    45                     client->packet_head != client->tail ||
    46                     !evdev->exist);
    47             if (error)
    48                 return error;
    49         }
    50     }
    51 
    52     return read;
    53 }

    注释中说的很清楚,evdev_event()会唤醒此处的读按键进程.那么evdev_event()又是被谁调用?显然是设备驱动层,现在看一个设备层例子,内核中有个按键的例子,gpiokey.c,这只是个例子不针对任何设备,在gpiokey.c终端处理函数里面.

     1 static void evdev_pass_values(struct evdev_client *client,
     2             const struct input_value *vals, unsigned int count,
     3             ktime_t mono, ktime_t real)
     4 {
     5     struct evdev *evdev = client->evdev;
     6     const struct input_value *v;
     7     struct input_event event;
     8     bool wakeup = false;
     9 
    10     event.time = ktime_to_timeval(client->clkid == CLOCK_MONOTONIC ?
    11                       mono : real);
    12 
    13     /* Interrupts are disabled, just acquire the lock. */
    14     spin_lock(&client->buffer_lock);
    15 
    16     for (v = vals; v != vals + count; v++) {
    17         event.type = v->type;
    18         event.code = v->code;
    19         event.value = v->value;
    20         __pass_event(client, &event);
    21         if (v->type == EV_SYN && v->code == SYN_REPORT)
    22             wakeup = true;
    23     }
    24 
    25     spin_unlock(&client->buffer_lock);
    26 
    27     if (wakeup)
    28         wake_up_interruptible(&evdev->wait);
    29 }
    30 
    31 /*
    32  * Pass incoming events to all connected clients.
    33  */
    34 static void evdev_events(struct input_handle *handle,
    35              const struct input_value *vals, unsigned int count)
    36 {
    37     struct evdev *evdev = handle->private;
    38     struct evdev_client *client;
    39     ktime_t time_mono, time_real;
    40 
    41     time_mono = ktime_get();
    42     time_real = ktime_sub(time_mono, ktime_get_monotonic_offset());
    43 
    44     rcu_read_lock();
    45 
    46     client = rcu_dereference(evdev->grab);
    47 
    48     if (client)
    49         evdev_pass_values(client, vals, count, time_mono, time_real);
    50     else
    51         list_for_each_entry_rcu(client, &evdev->client_list, node)
    52             evdev_pass_values(client, vals, count,
    53                       time_mono, time_real);
    54 
    55     rcu_read_unlock();
    56 }
    57 
    58 /*
    59  * Pass incoming event to all connected clients.
    60  */
    61 static void evdev_event(struct input_handle *handle,
    62             unsigned int type, unsigned int code, int value)
    63 {
    64     struct input_value vals[] = { { type, code, value } };
    65 
    66     evdev_events(handle, vals, 1);
    67 }

    好了,就贴到这里,input子系统的大体脉络以及比较清楚了.

  • 相关阅读:
    30岁女IT工程师感叹:靠这工具,把报表做成养老工作,月薪快3W
    直播丨墨天轮邂逅MySQL之父,腾讯云CDB/CynosDB技术揭秘之自主可控、前沿探索
    主备库内存不一致的Data Guard环境搭建全过程
    每日一题丨2020.05.27
    Redis Python 客户端
    Hack The Box——Sauna
    数据分析工具测评!被Excel打过的“耳光”,现在可以还回去了
    FORM中的MOAC控制
    网上看到的一张图,销售-客户各层次关系表
    Oracle WorkFlow(工作流)(一)
  • 原文地址:https://www.cnblogs.com/jason-lu/p/3156411.html
Copyright © 2011-2022 走看看