Shared Memory(共享内存)
共享内存允许一个或多个进程通过同时出现在它们的虚拟地址空间的内存通讯。这块虚拟内存的页面在每一个共享进程的页表中都有页表条目引用。但是不需 要在所有进程的虚拟内存都有相同的地址。象所有的系统V IPC对象一样,对于共享内存区域的访问通过key控制,并进行访问权限检查。内存共享之后,就不再检查进程如何使用这块内存。它们必须依赖于其他机制, 比如系统V的信号灯来同步对于内存的访问。
每一个新创建的内存区域都用一个shmid_ds数据结构来表达。这些数据结构保存在shm_segs向量表中。Shmid_ds数据结构描述了这 个共享内存取有多大、多少个进程在使用它以及共享内存如何映射到它们的地址空间。由共享内存的创建者来控制对于这块内存的访问权限和它的key是公开或私 有。如果有足够的权限它也可以把共享内存锁定在物理内存中。
参见include/linux/sem.h
每一个希望共享这块内存的进程必须通过系统调用粘附(attach)到虚拟内存。这为该进程创建了一个新的描述这块共享内存的vm_area_struct数据结构。进程可以选择共享内存在它的虚拟地址空间的位置或者由Linux选择一块足够的的空闲区域。
这个新的vm_area_struct结构放在由shmid_ds指向的vm_area_struct列表中。通过vm_next_shared和vm_prev_shared把它们连在一起。虚拟内存在粘附的时候其实并没有创建,而发生在第一个进程试图访问它的时候。
在一个进程第一次访问共享虚拟内存的其中一页的时候,发生一个page fault。当Linux处理这个page fault的时候,它找到描述它的vm_area_struct数据结构。这里包含了这类共享虚拟内存的处理例程的指针。共享内存的page fault处理代码在这个shmid_ds的页表条目列表中查找,看是否存在这个共享虚拟内存页的条目。如果不存在,它就分配一个物理页,并为它创建一个 页表条目。这个条目不但进入当前进程的页表,也存到这个shmid_ds。这意味着当下一个进程试图访问这块内存并得到一个page fault的时候,共享内存错误处理代码也会让这个进程使用这个新创建的物理页。所以,是第一个访问共享内存页的进程使得这一页被创建,而随后访问的其他 进程使得此页被加到它们的虚拟地址空间。
当进程不再需要共享虚拟内存的时候,它们从中分离(detach)出来。只要仍旧有其他进程在使用这块内存,这种分离只是影响当前的进程。它的 vm_area_struct从shmid_ds数据结构中删除,并释放。当前进程的页表也进行更新,使它共享过的虚拟内存区域无效。当共享这块内存的最 后一个进程从中分离出的时候,共享内存当前在物理内存中的页被释放,这块共享内存的shmid_ds数据结构也被释放。
如果共享的虚拟内存没有被锁定在物理内存中的话会更加复杂。在这种情况下,共享内存的页可能在系统大量使用内存的时候交换到了系统的交换磁盘。共享内存如何交换初和交换入物理内存在第3章中有描述。
Chapter 6
PCI
Peripheral Component Interconnect(PCI),好像它的名字暗示的一样,是描述如何通过一个结构化和可控制的方式把系统中的外设组件连接起来的一个标准。标准的 PCI Local Bus规范描述了系统组件电气连接的方法和它们行为的方法。本章探讨Linux核心如何初始化系统的PCI总线和设备。
图6.1是一个PCI基础的系统的逻辑图。PCI总线和PCI-PCI桥(bridge)是系统组件联系在一起的粘合剂。CUP和video设备连 在主要的PCI总线,PCI总线0数据结构的队列?PCI设备,PCI-PCI桥把主总线连接到次PCI总线,PCI总线1。按照PCI规范的术语, PCI总线1描述成为PCI-PCI桥的下游而PCI总线0是桥的上游。连接在次PCI总线上的是系统的SCSI和以太网设备。物理上桥、次要PCI总线 和这两种设备可以在同一块PCI卡上。系统中的PCI-ISA桥支持老的、遗留的ISA设备,本图显示了一个超级I/O控制芯片,控制键盘、鼠标和软驱。
6.1 PCI Address Space(PCI地址空间)
CPU和PCI设备需要访问它们所共享的内存。这些内存让设备驱动程序控制这些PCI设备并在它们之间传递信息。一般地共享的内存包括设备的控制和 状态寄存器。这些寄存器用于控制设备和读取它的状态。例如:PCI SCSI设备驱动程序可以读取SCSI设备的状态寄存器,判断它是否可以向SCSI磁盘写一块信息。或者它可以写入控制寄存器让它关闭的设备开始运行。
CPU的使用的系统内存可以用作这种共享内存,但是如果这样的话,每一次PCI设备访问内存,CPU都不得不停顿,等待PCI设备完成。对于内存的 访问通常有限制,同一时间只能有一个系统组件允许访问。这会使得系统速度降低。允许系统的外部设备在一个不受控的方式下访问主内存也不是一个好主意。这会 非常危险:一个恶意的设备会让系统非常不稳定。
外部设备由它们自己的内存空间。CPU可以访问这些空间,但是设备对于系统内存的访问受到严格的控制,必须通过DMA(Direct Memory Access直接内存存取)通道。ISA设备可以访问两种地址空间:ISA I/O(输入/输出)和ISA内存。PCI由三中:PCI I/O、PCI内存和PCI配置空间(configuration space)。CPU可以访问所有的地址空间其中PCI I/O和PCI内存地址空间由设备驱动程序使用而PCI配置空间由Linux和心中的PCI初始化代码使用。
Alpha AXP处理器没有对于除了系统地址空间之外的地址空间的天生的访问模式。它需要使用支持芯片来访问象PCI配置空间这样的其他地址空间。它使用了一个地址空间的映射方案,从巨大的虚拟地址空间中偷出一部分映射到PCI地址空间。
6.2 PCI Configuration Headers(PCI配置头)
系统中的每一个PCI设备,包括PCI-PCI桥都由一个配置数据结构,位于PCI配置地址空间中。PCI配置头允许系统识别和控制设备。这个头位 于PCI配置地址空间的确切位置依赖于设备使用的PCI拓扑。例如,插在PC主板一个PCI槽位的一个PCI显示卡配置头会在一个位置,而如果它被插到另 一个PCI槽位则它的头会出现在PCI配置内存中的另一个位置。但是不管这些PCI设备和桥在什么位置,系统都可以发现并使用它们配置头中的状态和配置寄 存器来配置它们。
通常,系统的设计使得每一个PCI槽位的PCI配置头都有一个和它在板上的槽位相关的偏移量。所以,举例来说,板上的第一个槽位的PCI配置可能位 于偏移0而第二个槽位的在偏移256(所有的头都一样长度,256字节),依此类推。定义了系统相关的硬件机制使得PCI配置代码可以尝试检查一个给定的 PCI总线上的所有可能的PCI配置头,试图读取头中的一个域(通常是Vendor Identification 域)得到一些错误,从而知道那些设备存在而那些设备不存在。PCI Local Bus规范描述了一种可能的错误信息:试图读取一个空的PCI槽位的Verdor Identification和Device Indentification域时候返回0xFFFFFFFF。
图6.2显示了256字节的PCI配置头的布局。它包括以下域:
参见include/linux/pci.h
Vendor Identification 唯一的数字,描述这个PCI设备的发明者。Digital的PCI Vendor Identification 是0x1011而Intel是0x8086。
Device Identification 描述设备自身的唯一数字。例如Digital的21141快速以太网设备的设备标识符是0x0009。
Status 此域给除了设备的状态,它的位的含义由PCI Local Bus规范规定。
Command 系统通过写这个域控制这个设备。例如:允许设备访问PCI I/O内存。
Class Code 标识了设备的类型。对于每一种设备都有标准分类:显示、SCSI等等。对于SCSI的类型编码是0x0100。
Base Address Registers 这些寄存器用于确定和分配设备可以使用的PCI I/O和PCI内存的类型、大小和位置。
Interrupt Pin PCI卡的物理管脚中的4个用于向PCI总线传递中断。标准中把它们标记为A、B、C和D。Interrupt Pin域描述了这个PCI设备使用那个管脚。通常对于一个设备来说这时硬件决定的。就是说每一次系统启动的时候,这个设备都使用同一个中断管脚。这些信息 允许中断处理子系统管理这些设备的中断。
Interrupt Line PCI配置头中的Interrupt Line域用于在PCI初始化代码、设备驱动程序和Linux的中断处理子系统之间传递中断控制。写在这里的数字对于设备驱动程序来讲是没有意义的,但是 它可以让中断处理程序正确地把一个中断从PCI设备发送到Linux操作系统中正确的设备驱动程序的中断处理代码处。Linux如何处理中断参看第7章。
6.3 PCI I/O and PCI Memory Address(PCI I/O和PCI内存地址)
这两种地址空间用于设备和CPU上运行的Linux核心的它们的设备驱动程序通讯。例如:DECchip 21141快速以太网设备把它的内部寄存器映射到了PCI I/O空间。然后它的Linux设备驱动程序通过读写这些寄存器来控制设备。显示驱动程序通常使用大量的PCI内存空间来放置显示信息。
直到PCI系统建立起来并使用PCI配置头中的Command域打开了设备对于这些地址空间的访问为止,设备都无法访问这些空间。应该注意的是只有PCI配置代码读写PCI配置地址,Linux的设备驱动程序只是读写PCI I/O和PCI内存地址。
6.4 PCI-ISA Bridges(PCI-ISA桥)
这种桥把对于PCI I/O和PCI内存地址空间的访问转换成为ISA I/O和ISA内存访问,用来支持ISA设备。现在销售的多数系统都包括几个ISA总线插槽和几个PCI总线插槽。这种向后的兼容的需要会不断减少,将来 会有只有PCI的系统。在早期的Intel 8080基础的PC时代,系统中的ISA设备的ISA 地址空间(I/O和内存)就被固定下来。甚至一个S5000 Alpha AXP基础的计算机系统的ISA软驱驱动器的ISA I/O地址也会和第一台IBM PC一样。PCI规范保留了PCI I/O和PCI内存的地址空间中的较低的区域保留给系统中的ISA外设并使用一个PCI-ISAPCI内存访问转换为ISA访问。
6.5 PCI-PCI Bridges(PCI-PCI桥)
PCI-PCI桥是特殊的PCI设备,把系统中的PCI总线粘和在一起。简单系统中只有一个PCI总线,当时单个PCI总线可以支持的PCI设备的 数量有电气限制。使用PCI-PCI桥增加更多的PCI总线允许系统支持更多的PCI设备。这对于高性能的服务器尤其重要。当然,Linux完全支持使用 PCI-PCI桥的使用。
6.5.1 PCI-PCI Bridges: PCI I/O and PCI Memory Windows
PCI-PCI桥只向下游传递对于PCI I/O和PCI内存读和写的一个子集。例如在图6.1中,只有读和写的地址属于SCSI或者以太网设备的时候PCI-PCI桥才会把读写的地址从PCI总 线0传递到总线1,其余的都被忽略。这种过滤阻止了不必要的地址信息遍历系统。为了达到这个目的,PCI-PCI桥必须编程设置它们必须从主总线向次总线 通过的PCI I/O和PCI内存地址空间访问的基础(base)和限制。一旦系统中的PCI-PCI桥设置好,只要Linux设备驱动程序只是通过这些窗口存取PCI I/O和PCI内存空间,PCI-PCI桥是不可见的。这是个重要的特性,使得Linux的PCI设备驱动程序的作者的日子好过了。但是它也让Linux 下的PCI-PCI桥在一定程度上需要技巧才能配置,我们不久就会看到。
6.5.2 PCI-PCI Bridges: PCI Configuration Cycles and PCI Bus Numbering(PCI-PCI桥:PCI配置cycle和PCI总线编号)
既然CPU的PCI初始化代码可以定位不在主PCI总线上的设备,必须有一种机制使得桥可以决定是否把配置cycle从它的主接口传递到次接口上。 一个cycle就是它显示在PCI总线上的地址。PCI规范定义了两种PCI地址配置格式:类型0和类型1,分别在图6.3和图6.4中显示。类型0的 PCI配置cycle不包含总线号,被这个PCI总线上的所有的PCI设备解释用于PCI地址配置。配置cycle的位32:11看作是设备选择域。设计 系统的一个方法是让每一个位选择一个不同的设备。这种情况下为11可能选择槽位0的PCI设备,位12选择槽位1的PCI设备,依此类推。另一种方法是把 设备的槽位号直接写到位31:11中。一个系统使用哪一种机制依赖于系统的PCI内存控制器。
类型1的PCI配置cycle包括一个PCI总线号,这种配置循环被除了PCI-PCI桥之外的所有PCI设备忽略。所有看到了类型1的PCI配置 cycle的PCI-PCI桥都可以把这些信息向它们的下游传送。一个PCI-PCI桥是否忽略PCI配置循环或者向它的下游传递,依赖于这个桥是如何配 置的。每一个PCI-PCI桥都有一个主总线接口号和一个次总线接口号。主总线接口离CPU最近而次总线接口是离CPU最远的。每一个PCI-PCI桥都 还有一个附属总线编号,这是在第二个总线接口之外可以桥接的最大的PCI总线数目。或者说,附属总线编号是PCI-PCI桥下游的最大的PCI总线编号。 当PCI-PCI桥看到一个类型1的PCI配置cycle的时候,它做以下事情:
如果指定的总线编号不在桥的次总线编号和总线的附属编号之间就忽略它。
如果指定的总线编号和桥的次总线编号符合就把它转变成为类型0的配置命令
如果指定的总线编号大于次要总线编号而小于或等于附属总线编号,就不改变地传递到次要总线接口上。
所以,如果我们希望寻址图6.9的拓扑中总线3上的设备1,我们必须从CPU生成一个类型1的配置命令。桥1不改变地传递到总线1,桥2忽略它但是桥3把它转换成一个类型0的配置命令,并把它发送到总线3,使设备1响应它。
每一个独立的操作系统负责在PCI配置阶段分配总线编号,但是不管使用哪一种编码方案,对于系统中所有的PCI-PCI桥,以下陈述都必须是正确的:
所有位于一个PCI-PCI桥后面的PCI总线的编码都必须在次总线编号和附属总线编号之间(包含)
如果违背了这条规则,则PCI-PCI桥将无法正确地传递和转换类型1的PCI配置cycle,系统无法成功地找到并初始化系统中的PCI设备。为 了完成编码方案,Linux按照特定的顺序配置这些特殊设备。参看6.6.2节对于Linux PCI桥和总线编码方案的描述以及一个可以工作的例子。
6.6 Linux PCI Initialization(Linux PCI初始化过程)
Linux中PCI初始化代码分为三个逻辑部分:
PCI Device Driver 这个伪设备驱动程序从总线0开始查找PCI系统,定位系统中所有的PCI设备和桥。它建立一链接的数据结构的列表,描述系统的拓扑。另外,它还为系统中所有的桥编码。
参见drivers/pci/pci.c and include/linux/pci.h
PCI BIOS 这个软件层提供了PCI BIOS ROM规范中描述的服务。即使Alpha AXP没有BIOS服务,在Linux核心也有提供了相同的功能的等价代码。
参见arch/*/kernel/bios32.c
PCI Fixup 系统相关的整理代码,整理和系统相关的在PCI初始化最后的内存疏松的情况。
参见arch/*/kernel/bios32.c
6.6.1 Linux Kernel PCI Data Structures(Linux核心的PCI数据结构)
当Linux核心初始化PCI系统的时候它建立反映系统真实的PCI拓扑结构的数据结构。图6.5显示了数据结构之间的关系,它用来描述了图6.1中示例的PCI系统。
每一个PCI设备(包括PCI-PCI桥)都用一个pci_dev的数据结构描述。每一个PCI总线用一个pci_bus的数据结构描述。结果是一 个PCI总线的树型结构,每一个总线上有粘附着一些子PCI设备。因为一个PCI总线只能通过PCI-PCI桥达到(除了主PCI总线,总线0),每一个 pci_bus都包括一个它要通过的PCI设备的指针(这个PCI-PCI桥)。这个PCI设备是这个PCI总线的父总线的一个子设备。