`
yuanlanjun
  • 浏览: 1185415 次
文章分类
社区版块
存档分类
最新评论

ARM_S3C2440中断分析

 
阅读更多

ARM S3C2440中断分析

1.什么是中断

所谓中断,是指CPU在正常运行程序时,由于内部/外部事件或由程序预先安排的事件,引起CPU中断正在运行的程序,而转到为内部/外部事件或为预先安排的事件服务的中断程序中去,服务完毕,再返回去执行刚才被中断的程序。

2.什么是中断优先级

中断优先级是指,中断源被响应和处理的优先等级。设置优先级的目的是为了在有多个中断源同时发出中断请求时,CPU能够按照预定的顺序(如:按事件的轻重缓急处理)进行响应并处理。

3.什么是中断嵌套

中断嵌套是指当CPU正在处理某个中断源即正在执行中断服务程序时,会出现优先级更高的中断源申请中断,为了使更急的中断源及时得到服务,需要暂时中断(挂起)当前正在执行的级别较低的中断服务程序,去处理更高级别的中断源,待执行完毕后再返回来执行低优先级的中断服务程序。但中断级别低的中断源不能中断级别高的中断服务,这就是中断嵌套,并且称这种中断嵌套方式为完全嵌套方式。

4.什么是中断向量

中断向量是中断服务程序的入口地址,中断向量一般是固定的,我们需要把我们写好的中断服务程序(ISR)的入口地址写道中断向量表中,这样在发生中断时,CPU就会自动跳转到中断向量表中找到它要执行的中断服务程序了。

5.什么是硬中断,什么是软中断

硬中断是由外部事件引起的因此具有随机性和突发性;软中断是执行中断指令产生的,无面外部施加中断请求信号,因此中断的发生不是随机的而是由程序安排好的。

S3C2440/S3C2410中断体系结构:

6.ARM体系 CPU的7种工作模式:

· 用户模式(usr): ARM处理器正常的程序执行状态
· 快速中断模式(fiq): 用于高速数据传输或通道处理
· 中断模式(irq):用于通用的中断处理
· 管理模式(svc):操作系统使用的保护模式
· 数据访问终止模式(abt): 当数据或指令预取时进入该模式,可用于虚拟存储及存储保护
· 系统模式(sys) :运行具有特权操作系统任务
· 未定义指令中止模式(und): 当为定义的指令执行时进入该模式,可用于支持硬件协处理的软件仿真

可以通过软件来进行模式切换,或者发生各类中断,异常时CPU自动进入相应的模式。除用户模式外,其余的6中工作模式都属于特权模式。大多数程序运行于用户模式,进入特权模式是为了处理中断,异常,或者访问被保护的系统资源。

ARM920T的寄存器分为7组,其每个工作模式都拥有对应的寄存器组。其中有些寄存器是共用的,有些寄存器在不同模式下有自己的副本。当切换到另一个 工作模式时,那个工作模式的寄存器副本将被使用;这些寄存器成为备份寄存器(下图中使用灰色三角形标记的寄存器)


R0-R15可以直接访问,R13-R15稍有特殊,R13又称为 栈指针寄存器,通常用于保存栈指针。R14又称为程序连接寄存器或连接寄存器,比如当执行BL子程序调用指令时,R14得到调用前的程序计数器 PC(R15)的备份,以供子程序返回之用。当发生中断和异常时,对应的R14_svc、R14_irq、R14_fiq、R14_abt和 R14_und保存R15返回值,R15是程序计数器PC。
PC(程序计数器),CPSR(当前程序状态寄存器)寄存器是程序执行时必不可少的寄存器。每组寄存器都有自己独立的SPSR,R14是为了保存CPSR和PC进入异常前的值。

7.ARM920T异常处理的过程——从寄存器值的变化和地址跳转两个角度

7.1 ·ARM920T异常处理的过程——寄存器值变化角度

ARM920T CPU 将在异常(包括中断)的进入和退出时自动完成的工作:
在处理异常前,必须保存当前处理器的状态(PC和CPSR值),当中断程序处理程序完成后,原来的程序能够继续执行。
处理一个异常:
· 将下一条指令的地址(PC)保存在相应的LR(R14)寄存器中。如果异常是从ARM状态进入,则保存在LR(R14)寄存器中的下一条指令的地址。如果是从Thumb状态进入,则保存在LR寄存器中的是当前PC的偏移值。它能使程序异常返回后从正确的位置重新开始。这就意味着异常处理不需要确定异常是从何种状态进入的。
· 将CPSR复制到相应的SPSR中
· 将CPSR的工作模式位设置为这个异常对应的工作模式
· 令PC值等于这个异常模式在异常向量表中的地址,即跳转去执行异常的向量表中的相应指令
退出一个异常:
· 前面进入异常工作模式时,连接寄存器中保存了前一个工作模式的下一个指令的地址,将它减去一个适当的值(图二)后赋给PC寄存器。
· 将SPCR值复制回CPSR
进入/退出异常模式PC地址

CPSR各位意义:
· T位:置位时,CPU处于Thumb状态,否则处于ARM状态
· 中断禁止位: I位和F位属于中断禁止位,他们被置位时,IRQ中断,FIQ中断分别被禁止。
· 工作模式: 表明CPU当前处于什么工作模式,可以编写这些位,使CPU进入指定的工作模式。


中断处理过程:
· 中断控制器汇集各类外设发出的中断信号,然后通知CPU
· CPU保存程序的运行环境(保存相应寄存器的值),调用中断服务程序(ISR)来处理这些中断。
· 在ISR中通过读取中断控制器,外设的相关寄存器来识别这个是那个中断,并进行相应的处理
· 清除中断: 通过读写中断控制器和外设的相关寄存器来实现
· 最后恢复被中断程序的运行环境(即上面保存的各个寄存器等),继续执行

7.2 ARM920T异常处理的过程——地址跳转变化角度

通常嵌入式处理器都有一张中断向量表,当中断出现时,必须调用向量表,向量表一般为与0 地址处,只要在对应的地址上写上跳转指令就实现了中断向量表。
ARM9的中断向量表
地址 异常
0x0000 0000 复位
0x0000 0004 未定义指令
0x0000 0008 软件中断
0x0000 000c 中止(预取指令)
0x0000 0010 中止(数据)
0x0000 0014 保留
0x0000 0018 IRQ(外部中断请求)
0x0000 001c IQ(快速中断请求)
中断处理的过程如下图:

8.S3C2440S3C2410中断相关寄存器寄存器设置

中断相关寄存器的设置包括中断控制寄存器设置和引脚(IO)模式的设置,在这里我只介绍各个环节设计到的寄存器及其功能,而对于具体的设置我不做详细介绍,请参考芯片手册(interruptController And IO Ports)

S3C2440S3C2410中断控制寄存器

S3C2440S3C2410中断控制寄存器有5大类:
· 源待决寄存器(SRCPND)
当中断源产生中断信号时,在SRCPND相应位置位,至于CPU相应哪个中断不是SRCPND的工作。
· 中断模式寄存器(INTMOD)
该寄存器是设置中断模式是FIQ还是IRQ
· 中断掩码寄存器(INTMSK)
该寄存器是中断使能,当置位时,屏蔽该中断信号。
· 优先级寄存器(PRIORITY)
该寄存器是设置IRQ模式下中断的优先级
· 中断源待决寄存器(INTPND)
该寄存器的值决定CPU响应哪个中断,即只有一位被置位,但是该寄存器值一般是不用设置的(清楚中断的需要设置),该寄存器的值是系统自动计算得出来的值
这五类寄存器值的设置里,中断掩码寄存器和优先级寄存器设置是比较复杂的。中断模式寄存器、中断掩码寄存器、中断源待决寄存器涉及到子寄存器(子寄存器说法不很准确)的设置,就是关于EINT8_EINT23中断掩码寄存器的设置和中断源待决寄存器的设置

注:INTOFFSET寄存器用来表示INTPND寄存器中 那个被置位了,即INTPND寄存器中位[X]为1时INTOFFSET寄存器的值为X(X为0~31)上图展示了中断处理框图(硬件实现角 度)Request source(with sub -register)表示INT_RXD0,INT_TXD0等中断源(S3C2440中有15个),他们不同于Request source(without sub-regiser).假如Request source(with sub -register)中的中断源被触发后,SUBSRCPND寄存器的相应位置1,如果此中断没有被INTSUBMSK寄存器屏蔽,它在SRCPND寄存 器中的相应位置1,之后的过程和Request source(without sub-regiser)一样了。

补充材料:

一、中断是什么

中断的汉语解释是半中间发生阻隔、停顿或故障而断开。那么,在计算机系统中,我们为什么需要“阻隔、停顿和断开”呢?

举个日常生活中的例子,比如说我正在厨房用煤气烧一壶水,这样就只能守在厨房里,苦苦等着水开——如果水溢出来浇灭了煤气,有可能就要发生一场 灾难了。等啊等啊,外边突然传来了惊奇的叫声“怎么不关水龙头?”于是我惭愧的发现,刚才接水之后只顾着抱怨这份无聊的差事,居然忘了这事,于是慌慌张张 的冲向水管,三下两下关了龙头,声音又传到耳边,“怎么干什么都是这么马虎?”。伸伸舌头,这件小事就这么过去了,我落寞的眼神又落在了水壶上。

门外忽然又传来了铿锵有力的歌声,我最喜欢的古装剧要开演了,真想夺门而出,然而,听着水壶发出“咕嘟咕嘟”的声音,我清楚:除非等到水开,否则没有我享受人生的时候。

这个场景跟中断有什么关系呢?

如果说我专心致志等待水开是一个过程的话,那么叫声、电视里传出的音乐不都让这个过程“半中间发生阻隔、停顿或故障而断开”了吗?这不就是活生生的“中断”吗?

在这个场景中,我是唯一具有处理能力的主体,不管是烧水、关水龙头还是看电视,同一个时间点上我只能干一件事情。但是,在我专心致志干一件事情 时,总有许多或紧迫或不紧迫的事情突然出现在面前,都需要去关注,有些还需要我停下手头的工作马上去处理。只有在处理完之后,方能回头完成先前的任务,“ 把一壶水彻底烧开!”

中断机制不仅赋予了我处理意外情况的能力,如果我能充分发挥这个机制的妙用,就可以“同时”完成多个任务了。回到烧水的例子,实际上,无论我在 不在厨房,煤气灶总是会把水烧开的,我要做的,只不过是及时关掉煤气灶而已,为了这么一个一秒钟就能完成的动作,却让我死死地守候在厨房里,在10分钟的 时间里不停地看壶嘴是不是冒蒸气,怎么说都不划算。我决定安下心来看电视。当然,在有生之年,我都不希望让厨房成为火海,于是我上了闹钟,10分钟以后它 会发出“尖叫”,提醒我炉子上的水烧开了,那时我再去关煤气也完全来得及。我用一个中断信号——闹铃——换来了10分钟的欢乐时光,心里不禁由衷地感叹: 中断机制真是个好东西。

正是由于中断机制,我才能有条不紊地“同时”完成多个任务,中断机制实质上帮助我提高了并发“处理”能力。它也能给计算机系统带来同样的好处: 如果在键盘按下的时候会得到一个中断信号,CPU就不必死守着等待键盘输入了;如果硬盘读写完成后发送一个中断信号,CPU就可以腾出手来集中精力“服务 大众”了——无论是人类敲打键盘的指尖还是来回读写介质的磁头,跟CPU的处理速度相比,都太慢了。没有中断机制,就像我们苦守厨房一样,计算机谈不上有 什么并行处理能力。

跟人相似,CPU也一样要面对纷繁芜杂的局面——现实中的意外是无处不在的——有可能是用户等得不耐烦,猛敲键盘;有可能是运算中碰到了0除 数;还有可能网卡突然接收到了一个新的数据包。这些都需要CPU具体情况具体分析,要么马上处理,要么暂缓响应,要么置之不理。无论如何应对,都需要 CPU暂停“手头”的工作,拿出一种对策,只有在响应之后,方能回头完成先前的使命,“把一壶水彻底烧开!”

先让我们感受一下中断机制对并发处理带来的帮助。

让我们用程序来探讨一下烧水问题,如果没有“中断”(注意,我们这里只是模仿中断的场景,实际上是用异步事件——消息——处理机制来展示中断产 生的效果。毕竟,在用户空间没有办法与实际中断产生直接联系,不过操作系统为用户空间提供的异步事件机制,可以看作是模仿中断的产物),设计如下:

void StayInKitchen()

{

bool WaterIsBoiled = false;

while ( WaterIsBoiled != true )

{

bool VaporGavenOff = false;

if (VaporGavenOff )

WaterIsBoiled = true;

else

WaterIsBoiled = false;

}

// 关煤气炉

printf(“Close gas oven.\n”);

// 一切安定下来,终于可以看电视了,10分钟的宝贵时间啊,逝者如斯夫…

watching_tv();

return;

}

可以看出,整个流程如同我们前面描述的一样,所有工作要顺序执行,没有办法完成并发任务。

如果用“中断”,在开始烧水的时候设定一个10分钟的“闹铃”,然后让CPU去看电视(有点难度,具体实现不在我们关心的范围之内,留给读者自行解决吧:>)。等闹钟响的时候再去厨房关炉子。

// 闹钟到时会执行此程序

void sig_alarm(int signo)

{

//关煤气炉

printf(“Close gas oven.\n”);

}

void watching_tv()

{

while(1)

{

// 呵呵,悠哉悠哉

}

}

int main()

{

// 点火后设置定时中断

printf(“Start to boil water, set Alarm”);

if (signal( SIGALRM, sig_alrm ) == SIG_ERR)

{

perror("signal(SIGALRM) error");

return -1;

}

// 然后就可以欣赏电视节目了

printf(“Watching TV!\n”);

watching_tv();

return 0;

}

这两段程序都在用户空间执行。第二段程序跟中断也没有太大的关系,实际上它只用了信号机制而已。但是,通过这两个程序的对比,我们可以清楚地看到异步事件的处理机制是如何提升并发处理能力的。

Alarm定时器:alarm相当于系统中的一个定时器,如果我们调用alarm(5),那么5秒钟后就会“响起一个闹铃”(实际上靠信号机制 实现的,我们这里不想深入细节,如果你对此很感兴趣,请参考Richard Stevens不朽著作《Unix环境高级编程》)。在闹铃响起的时候会发生什么呢?系统会执行一个函数,至于到底是什么函数,系统允许程序自行决定。程 序员编写一个函数,并调用signal对该函数进行注册,这样一旦定时到来,系统就会调用程序员提供的函数(CallBack函数?没错,不过在这里如何 实现并不关键,我们就不引入新的概念和细节了)。上面的例子里我们提供的函数是sig_alarm,所做的工作很简单,打印“关闭煤气灶”消息。

上面的两个例子很简单,但很能说明问题,首先,它证明采用异步的消息处理机制可以提高系统的并发处理能力。更重要的是,它揭示了这种处理机制的 模式。用户根据需要设计处理程序,并可以将该程序和特定的外部事件绑定起来,在外部事件发生时系统自动调用处理程序,完成相关工作。这种模式给系统带来了 统一的管理方法,也带来无尽的功能扩展空间。


为什么要中断

计算机系统实现中断机制是非常复杂的一件工作,再怎么说人都是高度智能化的生物,而计算机作为一个铁疙瘩,没有程序的教导就一事无成。而处理一个中断过程,它受到的限制和需要学习的东西太多了。

首先,计算机能够接收的外部信号形式非常有限。中断是由外部的输入引起的,可以说是一种刺激。在烧水的场景中,这些输入是叫声和电视的音乐,我 们这里只以声音为例。其实现实世界中能输入人类CPU——大脑的信号很多,图像、气味一样能被我们接受,人的信息接口很完善。而计算机则不然,接受外部信 号的途径越多,设计实现就越复杂,代价就越高。因此个人计算机(PC)给所有的外部刺激只留了一种输入方式——特定格式的电信号,并对这种信号的格式、接 入方法、响应方法、处理步骤都做了规约(具体内容本文后面部分会继续详解),这种信号就是中断或中断信号,而这一整套机制就是中断机制。

其次,计算机不懂得如何应对信号。人类的大脑可以自行处理外部输入,我从来不用去担心闹钟响时会手足无措——走进厨房关煤气,这简直是天经地义 的事情,还用大脑想啊,小腿肚子都知道——可惜计算机不行,没有程序,它就纹丝不动。因此,必须有机制保证外部中断信号到来后,有正确的程序在正确的时候 被执行。

还有,计算机不懂得如何保持工作的持续性。我在看电视的时候如果去厨房关了煤气,回来以后能继续将电视进行到底,不受太大的影响。而计算机则不 然,如果放下手头的工作直接去处理“意外”的中断,那么它就再也没有办法想起来曾经作过什么,做到什么程度了。自然也就没有什么“重操旧业”的机会了。这 样的处理方式就不是并发执行,而是东一榔头,西一棒槌了。

那么,通用的计算机系统是如何解决这些问题的呢?它是靠硬件和软件配合来协同实现中断处理的全过程的。我们将通过Intel X86架构的实现来介绍这一过程。

CPU执行完一条指令后,下一条指令的逻辑地址存放在cs和eip这对寄存器中。在执行新指令前,控制单元会检查在执行前一条指令的过程中是否有中断或异常发生。如果有,控制单元就会抛下指令,进入下面的流程:

1. 确定与中断或异常关联的向量i (0~55)

2. 寻找向量对应的处理程序

3. 保存当前的“工作现场”,执行中断或异常的处理程序

4. 处理程序执行完毕后,把控制权交还给控制单元

5. 控制单元恢复现场,返回继续执行原程序

让我们深入这个流程,看看都有什么问题需要面对。

1、异常是什么概念?

在处理器执行到由于编程失误而导致的错误指令(例如除数是0)的时候,或者在执行期间出现特殊情况(例如缺页),需要靠操作系统来处理的时候, 处理器就会产生一个异常。对大部分处理器体系结构来说,处理异常和处理中断的方式基本是相同的,x86架构的CPU也是如此。异常与中断还是有些区别,异 常的产生必须考虑与处理器时钟的同步。实际上,异常往往被称为同步中断。

2、中断向量是什么?

中断向量代表的是中断源——从某种程度上讲,可以看作是中断或异常的类型。中断和异常的种类很多,比如说被0除是一种异常,缺页又是一种异常, 网卡会产生中断,声卡也会产生中断,CPU如何区分它们呢?中断向量的概念就是由此引出的,其实它就是一个被送通往CPU数据线的一个整数。CPU给每个 IRQ分配了一个类型号,通过这个整数CPU来识别不同类型的中断。这里可能很多朋友会寻问为什么还要弄个中断向量这么麻烦的东东?为什么不直接用 IRQ0~IRQ15就完了?比如就让IRQ0为0,IRQ1为1……,这不是要简单得多么?其实这里体现了模块化设计规则,及节约规则。

首先我们先谈谈节约规则,所谓节约规则就是所使用的信号线数越少越好,这样如果每个IRQ都独立使用一根数据线,如IRQ0用0号线,IRQ1 用1号线……这样,16个IRQ就会用16根线,这显然是一种浪费。那么也许马上就有朋友会说:那么只用4根线不就行了吗?(2^4=16)。

这个问题,体现了模块设计规则。我们在前面就说过中断有很多类,可能是外部硬件触发,也可能是由软件触发,然而对于CPU来说中断就是中断,只 有一种,CPU不用管它到底是由外部硬件触发的还是由运行的软件本身触发的,因为对于CPU来说,中断处理的过程都是一样的:中断现行程序,转到中断服务 程序处执行,回到被中断的程序继续执行。CPU总共可以处理256种中断,而并不知道,也不应当让CPU知道这是硬件来的中断还是软件来的中断,这样,就 可以使CPU的设计独立于中断控制器的设计,这样CPU所需完成的工作就很单纯了。CPU对于其它的模块只提供了一种接口,这就是256个中断处理向量, 也称为中断号。由这些中断控制器自行去使用这256个中断号中的一个与CPU进行交互,比如,硬件中断可以使用前128个号,软件中断使用后128个号, 也可以软件中断使用前128个号,硬件中断使用后128个号,这与CPU完全无关了,当你需要处理的时候,只需告诉CPU你用的是哪个中断号就行,而不需 告诉CPU你是来自哪儿的中断。这样也方便了以后的扩充,比如现在机器里又加了一片8259芯片,那么这个芯片就可以使用空闲的中断号,看哪一个空闲就使 用哪一个,而不是必须要使用第0号,或第1号中断号了。其实这相当于一种映射机制,把IRQ信号映射到不同的中断号上,IRQ的排列或说编号是固定的,但 通过改变映射机制,就可以让IRQ映射到不同的中断号,也可以说调用不同的中断服务程序。

3、什么是中断服务程序?

在响应一个特定中断的时候,内核会执行一个函数,该函数叫做中断处理程序(interrupt handler)或中断服务程序(interrupt service routine(ISR))。产生中断的每个设备都有相应的中断处理程序。例如,由一个函数专门处理来自系统时钟的中断,而另外一个函数专门处理由键盘产 生的中断。

一般来说,中断服务程序要负责与硬件进行交互,告诉该设备中断已被接收。此外,还需要完成其他相关工作。比如说网络设备的中断服务程序除了要对 硬件应答,还要把来自硬件的网络数据包拷贝到内存,对其进行处理后再交给合适的协议栈或应用程序。每个中断服务程序根据其要完成的任务,复杂程度各不相 同。

一般来说,一个设备的中断服务程序是它的设备驱动程序(device driver)的一部分——设备驱动程序是用于对设备进行管理的内核代码。

4、隔离变化

不知道您有没有意识到,中断处理前面这部分的设计是何等的简单优美。人是高度智能化的,能够对遇到的各种意外情况做有针对性的处理,计算机相比 就差距甚远了,它只能根据预定的程序进行操作。对于计算机来说,硬件支持的,只能是中断这种电信号传播的方式和CPU对这种信号的接收方法,而具体如何处 理这个中断,必须得靠操作系统实现。操作系统支持所有事先能够预料到的中断信号,理论上都不存在太大的挑战,但在操作系统安装到计算机设备上以后,肯定会 时常有新的外围设备被加入系统,这可能会带来安装系统时根本无法预料的“意外”中断。如何支持这种扩展,是整个系统必须面对的。

而硬件和软件在这里的协作,给我们带来了完美的答案。当新的设备引入新类型的中断时,CPU和操作系统不用关注如何处理它。CPU只负责接收中 断信号,并引用中断服务程序;而操作系统提供默认的中断服务——一般来说就是不理会这个信号,返回就可以了——并负责提供接口,让用户通过该接口注册根据 设备具体功能而编制的中断服务程序。如果用户注册了对应于一个中断的服务程序,那么CPU就会在该中断到来时调用用户注册的服务程序。这样,在中断来临时 系统需要如何操作硬件、如何实现硬件功能这部分工作就完全独立于CPU架构和操作系统的设计了。

而当你需要加入新设备的时候,只需要告诉操作系统该设备占用的中断号、按照操作系统要求的接口格式撰写中断服务程序,用操作系统提供的函数注册该服务程序,设备的中断就被系统支持了。

中断和对中断的处理被解除了耦合。这样,无论是你在需要加入新的中断时,还是在你需要改变现有中断的服务程序时、又或是取消对某个中断支持的时候,CPU架构和操作系统都无需作改变。

5、保存当前工作“现场”

在中断处理完毕后,计算机一般来说还要回头处理原先手头正做的工作。这给中断的概念带来些额外的“内涵”。注一“回头”不是指从头再来重新做, 而是要接着刚才的进度继续做。这就需要在处理中断信号之前保留工作“现场”。“现场”这个词比较晦涩,其实就是指一个信息集,它能反映某个时间点上任务的 状态,并能保证按照这些信息就能恢复任务到该状态,继续执行下去。再直白一点,现场不过就是一组寄存器值。而如何保护现场和恢复场景是中断机制需要考虑的 重点之一。

每个中断处理都要经历这个保存和恢复过程,我们可以抽象出其中的步骤:

1. 保存现场

2. 执行具体的中断服务程序

3. 从中断服务返回

4. 恢复现场

上面说过了,“现场”看似在不断变化,没有哪个瞬间相同。但实际上组成现场的要素却不会有任何改变。也就是说,只要我们保存了相关的寄存器状 态,现场就能保存下来。而恢复“现场”就是重新载入这些寄存器。换句话说,对于任何一个中断,保护现场和恢复现场所做的都是完全相同的操作。

既然操作相同,实现操作的过程和代码就相同。减少代码的冗余是模块化设计的基本准则,实在没有道理让所有的中断服务程序都重复实现这样的功能, 应该将它作为一种基本的结构由底层的操作系统或硬件完成。而对中断的处理过程需要迅速完成,因此,Intel CPU的控制器就承担了这个任务,非但如此,上面的所有步骤次序都被固化下来,由控制器驱动完成。保存现场和恢复现场都由硬件自动完成,大大减轻了操作系 统和设备驱动程序的负担。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics