端口映射访问和内存映射访问之间的区别?


19

谁能解释端口映射和内存映射之间的区别,以及两者都完成了什么?为什么要映射端口,端口在结构上与内存映射有何不同?许多架构同时使用这两种端口有什么原因吗?另外,从这个意义上说,“端口”是什么,因为端口在不同的上下文中可能意味着不同的事物?

示例:端口转发,作为通信端点的端口,“端口映射”。

假设我将OUT写入端口400h(以虚拟机为例)(例如x86-64等)。

如果不在内存中,我该写什么或写在哪里?如何映射“端口”,从这个意义上说是什么?



Answers:


24

内存映射的I / O和端口映射的I / O是I / O的两种补充方法。

内存映射的I / O

在内存映射的系统中,访问I / O设备就像将其作为内存的一部分一样。LoadStore命令被用于读取和写入到I / O设备,就像它们被用于存储执行(端口映射有特殊命令的I / O)。这意味着I / O设备使用与内存相同的地址总线,这意味着CPU可以基于地址的值引用内存 I / O设备。这种方法需要在地址空间中进行隔离:也就是说,为I / O保留的地址不应对物理内存可用。

下面是一个简单的基本计算机系统的图像。在现代系统中,情况要复杂得多。

在此处输入图片说明


端口映射的I / O

根据维基百科

端口映射的I / O通常使用一类特殊的CPU指令专门用于执行I / O。可以在Intel微处理器上找到,并带有IN和OUT指令。这些指令可以读写一到四个字节(outb,outw,outl)到I / O设备。I / O设备具有与常规存储器不同的地址空间,可以通过CPU物理接口上的额外“ I / O”引脚或专用于I / O的整个总线来实现。由于I / O的地址空间与主存储器的地址空间是隔离的,因此有时将其称为隔离的I / O。


至于优缺点:由于外围设备比内存慢,因此共享数据和地址总线可能会减慢内存访问。另一方面,通过内存映射系统提供的I / O简便性,CPU所需的内部逻辑更少,这有助于实现更快,更便宜,功耗更低的CPU。该逻辑与RISC系统的逻辑相似:降低复杂性,获得更专用,更强大的系统,例如,对于嵌入式系统来说非常方便。

相反(再次来自Wiki):

端口映射的I / O指令通常非常有限,通常仅用于CPU寄存器和I / O端口之间的简单加载和存储操作,因此,例如,向端口映射的设备寄存器添加常量将需要三个说明:将端口读取到CPU寄存器,将常数添加到CPU寄存器,然后将结果写回到端口。

我强烈建议您阅读该Wiki文章以获取更多信息。


要回答您的问题之一:

如果不在内存中,我该写什么或写在哪里?

您正在通过数据总线写入I / O接口的寄存器,该总线稍后(准备就绪时)将数据发送到实际的I / O设备。下面是示例I / O设备接口的图像。

在此处输入图片说明


这是为内存映射写的,这意味着什么:“这种方法需要在地址空间中进行隔离:也就是说,为I / O保留的地址不应对物理内存可用。” 您是说为I / O保留的地址在主存储器中,而不对非I / O不可用。显然,IO地址和非IO地址是物理内存。没有诸如内存之类的东西不是物理的。(除非您是威廉·莱恩·克雷格(William Lane craig)的论点的信奉者,即事物可以具有非物理的大脑和记忆!)。
barlop 2014年

1
当您编写“正在通过数据总线写入I / O接口的寄存器时,稍后(准备就绪)它将数据发送到实际的I / O设备。下面是示例I / O的图像设备。” 您不清楚IO“寄存器”是否在设备中,我想如果您将其称为寄存器,听起来就好像在设备中。但是随后您写了“ ..稍后(准备就绪),它将数据发送到实际的I / O设备”,因此不清楚您在说这些内存位置在哪里。它们是否在设备内部,还是设备外部..还是设备外部。
barlop 2014年

1
注意:我从在微处理器讲座中学到的内容以及从Wiki中获得的内容中汇编了这些内容。话虽如此,您在第一条评论中问的意思是:“ IO地址和非IO地址是物理内存”并不像您想的那样清晰。其实(据我所知,据我教)地址码解码器激活指定的IO地址时,IO接口,这意味着当您提供的地址,它应该不会是物理地址,如范围,物理费用为$ 0000-$ 00FF,IO费用为$ 0100-$ 01FF(忽略金额,只关心边界)。
Varaquilex 2014年

@barlop对于第二个问题,您就在这里,我的意思是“下面是示例I / O设备接口的图像”。我在刚刚进行的修改中更正了此问题。我希望编辑消除混乱。如果没有,请问更多。我会尽力回答。
Varaquilex 2014年

1
@barlop在我所遇到的大多数帖子和文学作品中,内存位置的含义都是模棱两可的。他们要么假设您已经知道,要么他们真的不知道。我相信答案就是LDD3 ch的这些话。9:“ I / O存储器只是RAM状区域的一个区域,设备通过总线将其提供给处理器。” (添加斜体),即存储器和/或寄存器在设备上。I / O端口和I / O内存只是系统使这些位置对软件可用的两种方式。
orodbhen

2

在内存映射的I / O(简称MMIO)中,设备实际上是通过访问内存的指令来访问的。每个设备都会获得特定的内存地址。但是,当您尝试读取或写入内存的这一部分时,某个设备(可能是北桥)只是将其发送到相关设备。即使计算机没有足够的内存来存储该地址(因为MMIO地址通常很高),物理内存也没有关系(在查看主板时即会看到该内存,即RAM)。 )甚至都没有关系。如果您有足够的RAM用于该地址,则它要么映射到更高的非I / O地址,要么就丢失了,这意味着您无法在其中读写。

端口映射的I / O(简称PMIO)非常不同。您具有用于读取和写入端口的不同指令。有一个端口地址空间,就像内存地址空间一样,其中地址是实际上与设备通信的I / O地址,或者只是无效的。从本质上讲,PMIO可以被认为是具有用于I / O的单独存储器地址空间的MMIO。


“ PMIO本质上可以被认为是具有用于I / O的单独存储器地址空间的MMIO。” -地址空间是一个简单而基本的概念,为什么您要对您本来合理的答案进行误导类比?这种误导性的类比并不会改变事实:例如,除了“内存地址空间”之外,可能还有其他地址空间。
木屑

1

使用“ I / O信号”和“内存映射”之类的名称,一切都变得比实际复杂得多,因此给人的印象是它有更多的东西,并且涵盖了高级主题。现在的趋势是人们将其视为新事物。但这远非如此。即使是1830年代的巴贝奇(Babbage)驾驶他的打印机,这也需要一个I / O信号,尽管它是由轴和齿轮完成的。例如,在2000年前的《亚历山大英雄》的机器中或在回到希腊时代的剧院中,他们总是从一组不同的绳索中拉出一根绳索来控制灯光或风景,每条绳索就像一条输入和输出线,就像就这么简单,地址就是“哪条线”,即我们选择的是什么东西,内存或设备,

尽管大型主机计算机在机柜中充满了建筑物,但在40年代就使用了64位之类的东西,因此在很久以前就使用了相同的I / O映射,例如Konrad Zuse和他的房间大小的计算机使用浮动这个点在1930年代大约有十进制的20位数字,并且必须驱动诸如打印机,各种灯泡指示器和开关之类的东西。但是在微型微处理器上,情况有所不同,直到60年代才开始构想,直到1971年才建成。所有这些技术都是在80年代使用8位逻辑的,在70年代用于4位的微处理器,在60年代使用2位,后来被使用。在90'的16bit中 s当每个人都开始购买计算机时,由于它现在已经在他们面前,因此第一次开始讨论此I / O和内存映射主题,并且它似乎是随着Internet的出现而出现的新事物;然后我们在00年代使用32位计算机,而在10年代使用64位计算机,这导致了对存储数据线的无休止的讨论。为了回答您的问题,我将谈论电子爱好者在30到40年前购买的芯片,就像我当时那样,从那时起,事情变得如此先进,以至于我无法使用后来的芯片来制造,但是现在的原理是一样的,门只藏在更大的黑盒芯片内,这些芯片结合了其他引脚,这些引脚可以并行处理这些操作(例如,启用多个八进制锁存器,

好吧,我对所有新语言一无所知,也不了解现代PC上的情况,但是我可以告诉您过去使用芯片制造计算机时的情况。

简单来说,所有I / O映射和内存映射的意思是,如果您串起一堆灯泡示例以进行一些庆祝活动,并且有电线连接到每个灯泡上,并称为灯泡的内存位置,(即灯泡代表RAM中的内存,开启或关闭,如果选择位置0,则会得到导线0,位置1,导线1,位置2导线2,依此类推)如果添加了更多导线,例如一根导线是钟形,则该特定位置不是存储器是使用OUT命令输出到以使其响铃的设备。但是从计算机的角度来看,它被视为内存位置,因为它以相同的方式连接到MPU。如果添加了另一条线,该线是您在外部操作的开关,则这是I / O设备,这是对PC的IN指令。因此,这称为I / O映射I / O。

现在在计算机上,总线上的导线代表地址线或数据线,但它们是二进制的,即,使用2根导线,您可以拥有00 01 10 11,即4种组合2 ^​​ 2,因此,使用8条线2 ^ 8 = 256, 20行2 ^ 20 = 1048576和30行2 ^ 30 = 1073741824(1 gig)有30行的可能性。因此,这就是为什么它被称为MAPPED的原因,而不仅仅是说I / O和内存,而是说I / O映射,以及内存映射,因为您将电线映射为一个COMBINATION y对它们进行二进制编码。因此,如果说您有2条线,4种组合,则它们不能仅连接到灯泡上(更不用说MPU的微小电压所要求的电流放大以及防止反馈电流),但是2条线具有通过解码器(我们以前使用138将3行解码为8行,使用164将4条二进制行解码为16行。)一旦通过解码器,这两条线,例如A0和A1(地址0和地址1(LINES)),就变成了您要驱动的特定灯泡的4条线(开或关)(在计算机上是MEMORY),但是在某些情况下,这些位置改为选择某些输入/输出设备,然后说“用我”,即像存储一样,一旦定位,则数据以一种或另一种方式传递(使用聪明的三态逻辑切断途中的电压)每次)在数据总线D0..7或D0..31或计算机上的任何大小的数据上(您有2位,4位,8位,16位,32位,64位,128位,256位,计算机,无论什么)您正在建造的计算机)。因此,数据自然会从数据线传入或传出到内存或I / O设备(如果已映射到​​内存),但是不应与IN / OUT指令混淆,此IN和OUT表示在某些其他I / O存储器块中,MPU内专门为I / O分配的特殊I / O存储块,即(未映射存储器)此I / O空间并非总能获得在某些微处理器上,例如,我认为我们没有在6502上安装它,但是在z80上安装了它。更具艺术性的芯片仅使用内存映射(例如,在游戏机等中),更明智但无趣的(留在书中)的芯片也用于I / O空间。内存映射的I / O合并了内存寻址(对于RAM来说是超快的),因此速度非常快,因此图形类型的计算机仅使用I / O的内存映射来提高速度。I / O映射的I / O被分配给rs232等慢速端口或并行端口,并使用IN OUT命令。e。(未映射内存),这种I / O空间并不总是在某些微处理器上获得的,例如,我认为我们没有在6502上拥有它,但是在z80上拥有它。更具艺术性的芯片仅使用内存映射(例如,在游戏机等中),更明智但无趣的(留在书中)的芯片也用于I / O空间。内存映射的I / O合并了内存寻址(对于RAM来说是超快的),因此速度非常快,因此图形类型的计算机仅使用I / O的内存映射来提高速度。I / O映射的I / O被分配给rs232等慢速端口或并行端口,并使用IN OUT命令。e。(未映射内存),这种I / O空间并不总是在某些微处理器上获得的,例如,我认为我们没有在6502上拥有它,但是在z80上拥有它。更具艺术性的芯片仅使用内存映射(例如,在游戏机等中),更明智但无趣的(留在书中)的芯片也用于I / O空间。内存映射的I / O合并了内存寻址(对于RAM来说是超快的),因此速度非常快,因此图形类型的计算机仅使用I / O的内存映射来提高速度。I / O映射的I / O被分配给rs232等慢速端口或并行端口,并使用IN OUT命令。对于I / O空间来说,更明智但又不有趣的(留在书中)的芯片也同样适用。内存映射的I / O合并了内存寻址(对于RAM来说是超快的),因此速度非常快,因此图形类型的计算机仅使用I / O的内存映射来提高速度。I / O映射的I / O被分配给rs232等慢速端口或并行端口,并使用IN OUT命令。对于I / O空间来说,更明智但又不有趣的(留在书中)的芯片也同样适用。内存映射的I / O合并了内存寻址(对于RAM来说是超快的),因此速度非常快,因此图形类型的计算机仅使用I / O的内存映射来提高速度。I / O映射的I / O被分配给rs232等慢速端口或并行端口,并使用IN OUT命令。

现在,如果您不是替换两根电线,而是实际上替换了原先用于灯泡的两根电线,并拿走了其中的一些灯泡,并用其他东西代替了它们,例如,一只钟上的铃铛,另一只上的开关,则现在不再引用(选择)分别使用IN和OUT指令,则通过访问选择这些电线(最初是灯泡)的特定内存位置来引用它们。因此,这是内存映射的I / O。

内存映射的I / O意味着通常到达内存(RAM)的实际地址总线也连接到其他解码器(逻辑解码器),并且当它感测到地址信号的特定二进制组合时,它会产生高电平输出,(例如,如果您负载了和而不是门,并且您说过,如果使用了A0..A20引脚或您的地址总线是任何大小,那么,如果没有,那么等等),那么此高电平信号将使能锁存器, (对于特定设备,例如串行端口,并行端口),此锁存器然后将数据总线上的数据通过,直达I / O设备。这用于写入I / O设备。I / O设备以相反的方式进行读取,将数据传回,如果我没记错的话,它会将完全相同的地址代码组合发送到地址线上。

我认为,今天它必须以这种方式工作,只是它们将只是更多的数据和地址线。

您实际上是将I / O连接到地址线。因此,I / O有效地映射到内存空间中,就好像它是内存一样。但是另一个锁存器使地址引脚无法同时访问ram,因此不会在同一条线上获得两个地址或数据源的电压,这会损坏芯片。

有了IN和OUT指令,我们40年前就在z80芯片上使用了它。这是针对特殊情况的,其中芯片实际上以不同的方式处理I / O,即,它不是内存映射的。(即,使用内存映射时,您只是读取或写入内存位置,但是使用IN和OUT,您已经在告诉CPU这是I / O信号而不是内存)。因此,使用IN / OUT指令,它具有自己的I / O地址空间(这是RAM内存的额外空间),看起来像是这个I / O Ram,具有一组相同的地址,除了您是通过连接到这些I / O地址的解码器直接访问该设备,并且您不是从标准地址引脚访问该I / O设备,这是针对IN / OUT指令的。

在这种情况下,最好将其作为字母和数字的ASCII代码字符串传递。这些命令与在循环为字符串长度的循环中使用IN和OUT指令的情况完全相同。

如果要访问例如PC扬声器,则只需使用OUT一次传递一个数据即可。

如果您正在从并行端口读取数据,那么您将进行IN,并使用该代码作为端口的I / O地址。写入它,例如通过电子信号驱动旧打印机或机器人,您将使用OUT命令。并行端口和串行端口(旧的RS232)是使用的典型端口。RS232是串行数据,只允许输入或输出一位,因此,如果您从rs232读取数据,则只有1位相关字节,与输出相同。rs232的最大波特率约为17kHz,但是在过去,这些波特率用于驱动电子设备很多,我曾经建立rs232电路,例如读取电压或驱动PIC微控制器。每个端口都被命名为例如COM1 COM2 COM3 COM4,并且它们具有I / O地址。我不确定在这里有什么用,但是它们类似于例如3F8h 378h(h =十六进制地址)

我不确定现代的端口,但是如果您要写入USB,则很可能是内存映射的I / O,可以提高速度。

我认为PS / 2键盘端口使用IN指令从键盘读取数据。它取代了旧的RS232,但我相信它的规格略有不同。

磁盘驱动器通常是内存映射的,大概现在仍然如此,即您不使用IN / OUT指令驱动磁盘驱动器,它们会太慢。但是无论如何,端口都很慢,所以没关系,例如,与所需的数据速率(例如,硬盘所需的200 MB /秒)相比,打印机的数据速率要低得多。对于扬声器,它只需要声音频率的大约10或20倍,例如20kHz对于蜂鸣器就足够了,因此它是I / O。慢的东西使用I / O,IN / OUT指令。因此,USB现在可能已映射到内存,您必须对其进行检查。

理解它的更好方法是这样。在80年代的旧计算机上,有时您想控制自己制造的某些设备,并且没有输出端口的规格(因为那时候制造商将其隐藏起来,以便某些公司,例如操纵杆和墨盒公司)可以在市场交易)。您要做的是打开计算机,然后将导线焊接到地址总线上的某些点,例如,将三根导线焊接到电路中的某个点,并保持安全距离(以免因热而损坏芯片),通过电路板布局布线的点到微处理器上的引脚A15,A7和A1。而且,您通常还必须连接一条MREQ线(一条内存请求线和/或RD / WR线以发出更整洁的信号,并将其添加到逻辑与非逻辑中,但是,如果您很聪明,可以只使用地址线来完成),然后连接这三根线+这个额外的Ready类型信号(例如MREQ RD或WR线以提供一些有效的低电平或高电平(这可能需要额外的NOT)此处表示数据已经准备就绪)通过一个4输入与门,它通过200欧姆电阻将输出提供给led,您拥有的是自己的存储器,将高速I / O映射到led灯,您可以通过SR锁存器或D型锁存器进行锁存,以将其存储在某个电路板上的外部1位存储器中。这里15是32K线,7是64线,1是2线(二进制以2的幂表示,所以A1是2 ^ 1,A7是2 ^ 7,而A15是2 ^ 15),如果已寻址的地址32768 + 64 + 2 = 32834 = F041(十六进制),在汇编器中的旧MPU上使用LDA或STA或LD,您将输出到该LED,如果电阻约为100欧姆,它将亮起。因此,您已经完成了内存映射的I / O,就这么简单,您现在可以通过将其焊接到相同的mpu地址线来实现。但是由于电路的精致,您现在不会这样做。但是,您也可以加入数据线D0..7(在过去)或现在在旧的486 PC上说32位的d0..31。然后,如果您通过对值8的累加器进行加载(现在移动ax,现在为8)来在机器代码中寻址该位置,或者将该累加器值存储到地址位置(mov F041h,ax累加器),那么今天您甚至会导致请注意,示例中的8是数据总线上的数据,在这种情况下,我们不传递数据,而只是启用特定的设备(如果选择了THAT I / O设备,则LED亮,在这里,只是一个LED),因此,在此示例中,MOV ax,8指令的编号没有关系,例如可以是mov ax,243,然后在执行mov F041h时仍将启用F041h线上的LED,因为因为我们使用相同的地址。您会看到,有地址线,也有数据线。因此,当您在COM1中寻址3F8或任何地址时,I / O内存映射只是向端口(例如ps / 2)发送信号,而and门正在检查线路上是否有1110000100,即11是3 1000是F,0100是8,请参见二进制到十六进制的转换。如果高电压出现在那些有1的位位置,则将端口(例如rs232或ps / 2)设置为活动状态,即被启用,这将通过CE芯片启用信号或CS芯片启用锁存器选择简单。在8条指令中,它可能是mov ax,243,然后在执行mov F041h时仍会启用F041h线上的LED,因为我们使用的是相同的地址。您会看到,有地址线,也有数据线。因此,当您在COM1中寻址3F8或任何地址时,I / O内存映射只是向端口(例如ps / 2)发送信号,而and门正在检查线路上是否有1110000100,即11是3 1000是F,0100是8,请参见二进制到十六进制的转换。如果高电压出现在那些有1的位位置,则将端口(例如rs232或ps / 2)设置为活动状态,即被启用,这将通过CE芯片启用信号或CS芯片启用锁存器选择简单。在8条指令中,它可能是mov ax,243,然后在执行mov F041h时仍会启用F041h线上的LED,因为我们使用的是相同的地址。您会看到,有地址线,也有数据线。因此,当您在COM1中寻址3F8或任何地址时,I / O内存映射只是向端口(例如ps / 2)发送信号,而and门正在检查线路上是否有1110000100,即11是3 1000是F,0100是8,请参见二进制到十六进制的转换。如果高电压出现在那些有1的位位置,则将端口(例如rs232或ps / 2)设置为活动状态,即被启用,这将通过CE芯片启用信号或CS芯片启用锁存器选择简单。因为我们使用相同的地址。您会看到,有地址线,也有数据线。因此,当您在COM1中寻址3F8或任何地址时,I / O内存映射只是向端口(例如ps / 2)发送信号,而and门正在检查线路上是否有1110000100,即11是3 1000是F,0100是8,请参见二进制到十六进制的转换。如果高电压出现在那些有1的位位置,则将端口(例如rs232或ps / 2)设置为活动状态,即被启用,这将通过CE芯片启用信号或CS芯片启用锁存器选择简单。因为我们使用相同的地址。您会看到,有地址线,也有数据线。因此,当您在COM1中寻址3F8或任何地址时,I / O内存映射只是向端口(例如ps / 2)发送信号,而and门正在检查线路上是否有1110000100,即11是3 1000是F,0100是8,请参见二进制到十六进制的转换。如果高电压出现在那些有1的位位置,则将端口(例如rs232或ps / 2)设置为活动状态,即被启用,这将通过CE芯片启用信号或CS芯片启用锁存器选择简单。11是3 1000是F,0100是8,请参见二进制到十六进制的转换。如果高电压出现在那些有1的位位置,则将端口(例如rs232或ps / 2)设置为活动状态,即被启用,这将通过CE芯片启用信号或CS芯片启用锁存器选择简单。11是3 1000是F,0100是8,请参见二进制到十六进制的转换。如果高电压出现在那些位为1的位位置,则将端口(例如rs232或ps / 2)设置为活动状态,即被启用,这将通过CE芯片启用信号或CS芯片启用锁存器选择简单。

在锁存器上是E Enable引脚或OE低电平有效输出使能。也就是说,在上述示例中,我们使用地址来选择(通过解码)我们要使用的I / O设备(即,在该示例中,如果选择了该I / O设备,则LED点亮。因此,这是使能线然后,一旦选择了I / O设备,则通过旧的八进制锁存器373从数据总线(过去的D0..7或现在的64位计算机现在为D0..63)传递数据。如今,这些是D型触发器电路,将数据存储在触发器内部。在高电平有效时钟沿时,数据通过并被存储。该时钟沿将来自数据信号上的“ DATA RDY”信号,它有各种各样的名称,我不知道现在的名字,所以对于64位,我们有8个八进制锁存器。它们使用双向锁存器以双向或三态控制数据,因此当不使用I / O设备时,数据线处于高阻抗状态。因此,因此选择在地址线上具有组合的I / O设备,这是数字,例如OUT 3F8h中的3f8h,7,而此处的示例7中的数据就是在数据线上传递的数据。 OUT命令将数据传递给数据锁存器,再传递给I / O设备。如果您有IN,您将执行一个命令,例如IN 3f8h,800h((我希望,但我不知道x86汇编器的语法)),我的意思是,对于IN,您是从数据线(选择了地址后,例如3f7h,它选择了该I / O设备),该数据来自I / O设备,通过数据锁存器中的D型触发器(数据总线线路的每一位一个),并输入到MPU微处理单元上的D0..7或(在现代PC上为D0..63)引脚)。在此示例中,我将IN 3f8h设置为800h,以表明一旦数据进入,它将被存储到地址800h中。我认为x86的语法是不同的,您可能必须在IN 3f8h,ah或类似的方法中进行操作,即首先将传入的数据放入寄存器,然后将MOV 800h,ah即将数据移入内存在RAM中的位置(如果要存储它),或者用ah等做其他操作。ah是一个示例寄存器,可以是any,al,bh,bl等,但是检查语法,每个汇编器系统都是稍有不同,我不是x86方面的专家。同样,我使用3f8h作为示例I / O地址,有数百个,

而当您访问内存(例如70年代的RAM,例如64字节静态RAM和动态RAM,80年代的8K SRAM和DRAM)时,在90年代,现在每行SIMMS都有几兆字节(单行存储模块)是以包含DIMM的DDR模块,双列直插式内存模块的形式出现的,我没有检查过,但是如果不是I / O地址,则最新的毫无疑问每个小芯片上的每个DDR都有几个GB)(非常很少有地址是I / O地址,如今,内存比现代PC上的I / O数百万倍甚至更多。),您仍然对内存使用相同的读写数据指令,但实际上驱动某些寻找这些位的外部逻辑电路,这些地址和数据引脚直接连接到RAM芯片。

在机器代码中,I / O和存储器寻址看起来都是相同的,就像它们都是存储器访问一样,但是实际情况在实际的电子电路中完全不同。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.