`
isiqi
  • 浏览: 16027120 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

linux输入子系统(8)--input core

阅读更多

<!--[if !supportLists]-->第3章 <!--[endif]-->输入子系统核心层

上面两章分别讲了Linux输入子系统的设备驱动层和事件处理层,这两层的实现都是建立在输入核心层的基础之上的。核心层负责管理所有的资源并连接驱动层和事件处理层。

<!--[if !supportLists]-->3.1 <!--[endif]-->input core初始化

输入子系统的核心层的实现都在driver/input/input.c文件中,初始化函数如<!--[if supportFields]><span lang=DE style='mso-ansi-language:DE'><span style='mso-element:field-begin'></span> REF _Ref282522496 \h <span style='mso-element:field-separator'></span></span><![endif]-->程序清单 3.1<!--[if gte mso 9]><xml> <w:data>08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003200380032003500320032003400390036000000</w:data> </xml><![endif]--><!--[if supportFields]><span lang=DE style='mso-ansi-language: DE'><span style='mso-element:field-end'></span></span><![endif]-->所示。

程序清单 <!--[if supportFields]><span style='mso-bookmark:_Ref282522496'></span><span style='mso-element:field-begin'></span><span style='mso-bookmark:_Ref282522496'><span lang=EN-US><span style='mso-spacerun:yes'>&nbsp;</span>STYLEREF 1 \s <span style='mso-element:field-separator'></span></span></span><![endif]-->3<!--[if supportFields]><span style='mso-bookmark: _Ref282522496'></span><span style='mso-element:field-end'></span><![endif]-->.<!--[if supportFields]><span style='mso-bookmark:_Ref282522496'></span><span style='mso-element:field-begin'></span><span style='mso-bookmark:_Ref282522496'><span lang=EN-US> SEQ </span></span><span style='mso-bookmark:_Ref282522496'><span style='font-family:黑体;mso-ascii-font-family: Arial'>程序清单</span><span lang=EN-US> \* ARABIC \s 1 <span style='mso-element: field-separator'></span></span></span><![endif]-->1<!--[if supportFields]><span style='mso-bookmark:_Ref282522496'></span><span style='mso-element:field-end'></span><![endif]--> input core初始化

/* driver/input/input.c */

static const struct file_operations input_fops = {

.owner = THIS_MODULE,

.open = input_open_file,

};

static int __init input_init(void)

{

int err;

err = class_register(&input_class); <!--[if supportFields]><span lang=DE><span style='mso-element:field-begin'></span> = 1 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=DE><span style='mso-element:field-end'></span></span><![endif]-->

if (err) {

printk(KERN_ERR "input: unable to register input_dev class\n");

return err;

}

err = input_proc_init(); <!--[if supportFields]><span lang=DE><span style='mso-element:field-begin'></span> = 2 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=DE><span style='mso-element:field-end'></span></span><![endif]-->

if (err)

goto fail1;

err = register_chrdev(INPUT_MAJOR, "input", &input_fops); <!--[if supportFields]><span lang=DE><span style='mso-element:field-begin'></span> = 3 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=DE><span style='mso-element:field-end'></span></span><![endif]-->

if (err) {

printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);

goto fail2;

}

return 0;

fail2: input_proc_exit();

fail1: class_unregister(&input_class);

return err;

}

这个函数主要做了三件事:

<!--[if supportFields]><span lang=EN-US><span style='mso-element:field-begin'></span><span style='mso-spacerun:yes'>&nbsp;</span>= 1 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=EN-US><span style='mso-element:field-end'></span></span><![endif]-->注册一个input_class类。

<!--[if supportFields]><span lang=EN-US><span style='mso-element:field-begin'></span><span style='mso-spacerun:yes'>&nbsp;</span>= 2 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=EN-US><span style='mso-element:field-end'></span></span><![endif]-->/proc/bus/input下面创建两个节点deviceshandlers,通过打印这两个节点的内容可以查看输入子系统的设备驱动层信息和处理层信息。

<!--[if supportFields]><span lang=EN-US><span style='mso-element:field-begin'></span><span style='mso-spacerun:yes'>&nbsp;</span>= 3 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=EN-US><span style='mso-element:field-end'></span></span><![endif]-->申请注册输入子系统的主设备号13,并注册文件结构体input_fops

input core申请了主设备号,那么所有打开输入设备的处理请求都会传给input_fops的打开函数处理input_open_file,这个函数的实现如<!--[if supportFields]><span lang=EN-US><span style='mso-element:field-begin'></span> REF _Ref282608598 \h <span style='mso-element:field-separator'></span></span><![endif]-->程序清单 3.2<!--[if gte mso 9]><xml> <w:data>08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003200380032003600300038003500390038000000</w:data> </xml><![endif]--><!--[if supportFields]><span lang=EN-US><span style='mso-element:field-end'></span></span><![endif]-->所示。

程序清单 <!--[if supportFields]><span style='mso-bookmark: _Ref282608598'><span style='mso-bookmark:_Ref282608592'></span></span><span style='mso-element:field-begin'></span><span style='mso-bookmark:_Ref282608598'><span style='mso-bookmark:_Ref282608592'><span lang=EN-US><span style='mso-spacerun:yes'>&nbsp;</span>STYLEREF 1 \s <span style='mso-element:field-separator'></span></span></span></span><![endif]-->3<!--[if supportFields]><span style='mso-bookmark:_Ref282608598'><span style='mso-bookmark:_Ref282608592'></span></span><span style='mso-element:field-end'></span><![endif]-->.<!--[if supportFields]><span style='mso-bookmark:_Ref282608598'><span style='mso-bookmark:_Ref282608592'></span></span><span style='mso-element:field-begin'></span><span style='mso-bookmark:_Ref282608598'><span style='mso-bookmark:_Ref282608592'><span lang=EN-US> SEQ </span></span></span><span style='mso-bookmark:_Ref282608598'><span style='mso-bookmark:_Ref282608592'><span style='font-family:黑体;mso-ascii-font-family:Arial'>程序清单</span><span lang=EN-US> \* ARABIC \s 1 <span style='mso-element:field-separator'></span></span></span></span><![endif]-->2<!--[if supportFields]><span style='mso-bookmark:_Ref282608598'><span style='mso-bookmark:_Ref282608592'></span></span><span style='mso-element:field-end'></span><![endif]--> input_open_file

/* driver/input/input.c */

static int input_open_file(struct inode *inode, struct file *file)

{

struct input_handler *handler;

const struct file_operations *old_fops, *new_fops = NULL;

int err;

lock_kernel();

/* No load-on-demand here? */

handler = input_table[iminor(inode) >> 5]; <!--[if supportFields]><span lang=DE><span style='mso-element:field-begin'></span> = 1 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=DE><span style='mso-element:field-end'></span></span><![endif]-->

if (!handler || !(new_fops = fops_get(handler->fops))) {

err = -ENODEV;

goto out;

}

if (!new_fops->open) {

fops_put(new_fops);

err = -ENODEV;

goto out;

}

old_fops = file->f_op;

file->f_op = new_fops; <!--[if supportFields]><span lang=DE><span style='mso-element:field-begin'></span> = 2 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=DE><span style='mso-element:field-end'></span></span><![endif]-->

err = new_fops->open(inode, file); <!--[if supportFields]><span lang=DE><span style='mso-element:field-begin'></span> = 3 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=DE><span style='mso-element:field-end'></span></span><![endif]-->

if (err) {

fops_put(file->f_op);

file->f_op = fops_get(old_fops);

}

fops_put(old_fops);

out:

unlock_kernel();

return err;

}

这个函数做了以下工作:

<!--[if supportFields]><span lang=EN-US><span style='mso-element:field-begin'></span><span style='mso-spacerun:yes'>&nbsp;</span>= 1 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=EN-US><span style='mso-element:field-end'></span></span><![endif]-->根据次设备号从input_table取出handler

<!--[if supportFields]><span lang=EN-US><span style='mso-element:field-begin'></span><span style='mso-spacerun:yes'>&nbsp;</span>= 2 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=EN-US><span style='mso-element:field-end'></span></span><![endif]-->handler对应的文件结构体赋给struct file *file

<!--[if supportFields]><span lang=EN-US><span style='mso-element:field-begin'></span><span style='mso-spacerun:yes'>&nbsp;</span>= 3 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=EN-US><span style='mso-element:field-end'></span></span><![endif]-->调用文件结构体的open函数。

<!--[if !supportLists]-->3.2 <!--[endif]-->输入子系统的整体结构

input core有两个链表表头input_dev_listinput_handler_list,分别用来连接input_dev(设备驱动)结构和input_handler(事件处理)结构,另外还有一个input_table[8]数组,可以存放8个支持文件操作的handler指针。

另外从1.4<!--[if gte mso 9]><xml> <w:data>08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003200380032003100350033003300330030000000</w:data> </xml><![endif]-->2.2<!--[if gte mso 9]><xml> <w:data>08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003200380032003100350038003600370037000000</w:data> </xml><![endif]-->两节中可以看出input_devinput_handler注册的时候都是将自己注册进input core的链表,然后从对方的链表中寻找可以匹配的节点建立连接。并且可以建立一对多的连接。连接后的结构图如<!--[if supportFields]><span lang=DE style='mso-ansi-language:DE'><span style='mso-element:field-begin'></span> REF _Ref282529523 \h <span style='mso-element:field-separator'></span></span><![endif]--> 3.1<!--[if gte mso 9]><xml> <w:data>08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003200380032003500320039003500320033000000</w:data> </xml><![endif]--><!--[if supportFields]><span lang=DE style='mso-ansi-language: DE'><span style='mso-element:field-end'></span></span><![endif]-->所示。

图中有两个输入设备,key_dev代表一个按键设备,mice_dev代表一个鼠标设备。根据<!--[if supportFields]><span lang=DE style='mso-ansi-language:DE'><span style='mso-element:field-begin'></span> REF _Ref282529849 \r \h <span style='mso-element:field-separator'></span></span><![endif]-->2.3.1<!--[if gte mso 9]><xml> <w:data>08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003200380032003500320039003800340039000000</w:data> </xml><![endif]--><!--[if supportFields]><span lang=DE style='mso-ansi-language: DE'><span style='mso-element:field-end'></span></span><![endif]-->节的分析我们知道,mice_devkey_dev都可以和evdev_handler匹配连接,而只有mice_dev可以和mousedev_handler匹配连接。


<!--[if supportFields]><span style='mso-bookmark:_Ref282529523'></span><span style='mso-element:field-begin'></span><span style='mso-bookmark:_Ref282529523'><span lang=EN-US><span style='mso-spacerun:yes'>&nbsp;</span>STYLEREF 1 \s <span style='mso-element:field-separator'></span></span></span><![endif]-->3<!--[if supportFields]><span style='mso-bookmark: _Ref282529523'></span><span style='mso-element:field-end'></span><![endif]-->.<!--[if supportFields]><span style='mso-bookmark:_Ref282529523'></span><span style='mso-element:field-begin'></span><span style='mso-bookmark:_Ref282529523'><span lang=EN-US> SEQ </span></span><span style='mso-bookmark:_Ref282529523'><span style='font-family:黑体;mso-ascii-font-family: Arial'>图</span><span lang=EN-US> \* ARABIC \s 1 <span style='mso-element:field-separator'></span></span></span><![endif]-->1<!--[if supportFields]><span style='mso-bookmark: _Ref282529523'></span><span style='mso-element:field-end'></span><![endif]--> input core结构


<!--[if supportFields]><span style='mso-bookmark:_Ref282534100'></span><span style='mso-element:field-begin'></span><span style='mso-bookmark:_Ref282534100'><span lang=EN-US><span style='mso-spacerun:yes'>&nbsp;</span>STYLEREF 1 \s <span style='mso-element:field-separator'></span></span></span><![endif]-->3<!--[if supportFields]><span style='mso-bookmark: _Ref282534100'></span><span style='mso-element:field-end'></span><![endif]-->.<!--[if supportFields]><span style='mso-bookmark:_Ref282534100'></span><span style='mso-element:field-begin'></span><span style='mso-bookmark:_Ref282534100'><span lang=EN-US> SEQ </span></span><span style='mso-bookmark:_Ref282534100'><span style='font-family:黑体;mso-ascii-font-family: Arial'>图</span><span lang=EN-US> \* ARABIC \s 1 <span style='mso-element:field-separator'></span></span></span><![endif]-->2<!--[if supportFields]><span style='mso-bookmark: _Ref282534100'></span><span style='mso-element:field-end'></span><![endif]--> evdevevdev_client的关系


连接input_devinput_handler的结构体input_handle往往作为一个更大结构体的成员。对evdev_handler来说这个大结构体是struct evdev。这个结构体负责管理打开该设备节点的所有线程。每个打开设备的线程用一个struct evdev_client结构体表示。它们的关系如<!--[if supportFields]><span lang=DE style='mso-ansi-language:DE'><span style='mso-element:field-begin'></span> REF _Ref282534100 \h <span style='mso-element:field-separator'></span></span><![endif]--> 3.2<!--[if gte mso 9]><xml> <w:data>08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003200380032003500330034003100300030000000</w:data> </xml><![endif]--><!--[if supportFields]><span lang=DE style='mso-ansi-language: DE'><span style='mso-element:field-end'></span></span><![endif]-->所示。

<!--[if !supportLists]-->3.3 <!--[endif]-->输入子系统实例分析-触摸屏

<!--[if supportFields]><span lang=DE style='mso-ansi-language: DE'><span style='mso-element:field-begin'></span><span style='mso-spacerun:yes'>&nbsp;</span>REF _Ref282534487 \r \h <span style='mso-element:field-separator'></span></span><![endif]-->3.1<!--[if gte mso 9]><xml> <w:data>08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003200380032003500330034003400380037000000</w:data> </xml><![endif]--><!--[if supportFields]><span lang=DE style='mso-ansi-language: DE'><span style='mso-element:field-end'></span></span><![endif]-->节已经分析过,input core会在/proc/bus/input下建立两个文件,deviceshandlers,读取它们的内容可以得到设备的信息。

在开发板上运行“cat /proc/bus/input/devices”命令,得到以下输出:


[root@zlg /]# cat /proc/bus/input/devices

I: Bus=0019 Vendor=0001 Product=0002 Version=0100 <!--[if supportFields]><span lang=DE style='mso-font-kerning:0pt'><span style='mso-element:field-begin'></span> = 1 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=DE style='mso-font-kerning:0pt'><span style='mso-element:field-end'></span></span><![endif]-->

N: Name="LPC32xx Touchscreen" <!--[if supportFields]><span lang=DE style='mso-font-kerning:0pt'><span style='mso-element:field-begin'></span> = 2 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=DE style='mso-font-kerning:0pt'><span style='mso-element:field-end'></span></span><![endif]-->

P: Phys=lpc32xx/input0 <!--[if supportFields]><span lang=DE style='mso-font-kerning:0pt'><span style='mso-element:field-begin'></span> = 3 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=DE style='mso-font-kerning:0pt'><span style='mso-element:field-end'></span></span><![endif]-->

S: Sysfs=/class/input/input0 <!--[if supportFields]><span lang=DE style='mso-font-kerning:0pt'><span style='mso-element:field-begin'></span> = 4 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=DE style='mso-font-kerning:0pt'><span style='mso-element:field-end'></span></span><![endif]-->

U: Uniq=

H: Handlers=mouse0 event0 <!--[if supportFields]><span lang=DE style='mso-font-kerning:0pt'><span style='mso-element:field-begin'></span> = 5 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=DE style='mso-font-kerning:0pt'><span style='mso-element:field-end'></span></span><![endif]-->

B: EV=b <!--[if supportFields]><span lang=DE style='mso-font-kerning:0pt'><span style='mso-element:field-begin'></span> = 6 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=DE style='mso-font-kerning:0pt'><span style='mso-element:field-end'></span></span><![endif]-->

B: KEY=400 0 0 0 0 0 0 0 0 0 0 <!--[if supportFields]><span lang=DE style='mso-font-kerning:0pt'><span style='mso-element:field-begin'></span> = 7 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=DE style='mso-font-kerning:0pt'><span style='mso-element:field-end'></span></span><![endif]-->

B: ABS=1000003

<!--[if supportFields]><span lang=DE style='mso-font-kerning:0pt'><span style='mso-element:field-begin'></span> = 8 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=DE style='mso-font-kerning:0pt'><span style='mso-element:field-end'></span></span><![endif]-->

可以看出开发板上只有一个输入设备“LPC32xx Touchscreen”,这是一个触摸屏。各行的含义如下:

<!--[if supportFields]><span lang=EN-US><span style='mso-element:field-begin'></span><span style='mso-spacerun:yes'>&nbsp;</span>= 1 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=EN-US><span style='mso-element:field-end'></span></span><![endif]-->这一行对应input_devstruct input_id成员的内容,参考<!--[if supportFields]><span lang=EN-US><span style='mso-element:field-begin'></span> REF _Ref282001696 \h <span style='mso-element:field-separator'></span></span><![endif]-->程序清单 1.4<!--[if gte mso 9]><xml> <w:data>08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003200380032003000300031003600390036000000</w:data> </xml><![endif]--><!--[if supportFields]><span lang=EN-US><span style='mso-element:field-end'></span></span><![endif]-->

<!--[if supportFields]><span lang=EN-US><span style='mso-element:field-begin'></span><span style='mso-spacerun:yes'>&nbsp;</span>= 2 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=EN-US><span style='mso-element:field-end'></span></span><![endif]-->这一行对应input_dev的名字。

<!--[if supportFields]><span lang=EN-US><span style='mso-element:field-begin'></span><span style='mso-spacerun:yes'>&nbsp;</span>= 3 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=EN-US><span style='mso-element:field-end'></span></span><![endif]-->这一行对应input_devphys成员。

<!--[if supportFields]><span lang=EN-US><span style='mso-element:field-begin'></span><span style='mso-spacerun:yes'>&nbsp;</span>= 4 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=EN-US><span style='mso-element:field-end'></span></span><![endif]-->这一行对应该设备在/sys下的路径。

<!--[if supportFields]><span lang=EN-US><span style='mso-element:field-begin'></span><span style='mso-spacerun:yes'>&nbsp;</span>= 5 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=EN-US><span style='mso-element:field-end'></span></span><![endif]-->这一行对应和该设备建立连接的handler。可以看到有两个handler与设备建立了连接,这与我们上文的分析一致。对应的两个节点分别是mouse0event0

<!--[if supportFields]><span lang=EN-US><span style='mso-element:field-begin'></span><span style='mso-spacerun:yes'>&nbsp;</span>= 6 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=EN-US><span style='mso-element:field-end'></span></span><![endif]-->这一行对应该设备支持的事件,和input_devevbit成员一致。由此看出该设备支持三类事件:按键、绝对坐标和同步事件。

<!--[if supportFields]><span lang=EN-US><span style='mso-element:field-begin'></span><span style='mso-spacerun:yes'>&nbsp;</span>= 7 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=EN-US><span style='mso-element:field-end'></span></span><![endif]-->这一行对应该设备支持的按键,和input_devkeybit成员一致。该设备只支持一个按键:BTN_TOUCH

<!--[if supportFields]><span lang=EN-US><span style='mso-element:field-begin'></span><span style='mso-spacerun:yes'>&nbsp;</span>= 8 \* GB2 <span style='mso-element:field-separator'></span></span><![endif]--><!--[if supportFields]><span lang=EN-US><span style='mso-element:field-end'></span></span><![endif]-->这一行对应该设备支持的绝对值事件,和input_devabsbit成员一致。该设备支持X轴绝对坐标、Y轴绝对坐标和压力(ABS_PRESSURE)。


下面看看handler的信息:


[root@zlg /]# cat /proc/bus/input/handlers

N: Number=0 Name=kbd

N: Number=1 Name=mousedev Minor=32

N: Number=2 Name=evdev Minor=64


可以看到有三个已经注册的handler,分别处理键盘事件、鼠标事件和一般事件。其中后两个占用设备号,起始的次设备号分别是3264。这里与上面的分析相符。


用户程序一般不从触摸屏直接读取数据,使用触摸屏一般要通过tslib的接口。下面看一看qt界面的配置文件:


export QWS_MOUSE_PROTO=Tslib:/dev/input/event0

export TSLIB_CALIBFILE=/etc/pointercal

export TSLIB_CONFFILE=/mnt/tslib-lib/etc/ts.conf

export TSLIB_PLUGINDIR=/mnt/tslib-lib/lib/ts

export QTDIR=/mnt/qt-4.6-arm-lib

export LD_LIBRARY_PATH=$QTDIR/lib

export QT_QWS_FONTDIR=$QTDIR/lib/fonts

export QT_PLUGIN_PATH=$QTDIR/plugins

./hello -qws -font wenquanyi


第一行的意思是配置鼠标的驱动程序是tslib,使用的设备节点是/dev/input/event0

现在概括一下触摸屏在输入子系统中的使用方式:系统中有三个可用的handler,分别对应键盘、鼠标和一般事件,触摸屏设备的input_dev可以和后两个建立连接,对应event0mouse0这两个设备节点,同时tslib使用event0读取触摸屏的事件消息。

可以看出触摸屏的事件是直接传给tslib的,不需要象鼠标一样建立一个专门的handler

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics