powerpc 编程_PowerPC存储模型和AIX编程

PowerPC体系结构定义了用于存储访问顺序的存储模型,称为“弱一致性”。 该模型提供了提高性能的机会,但是它要求您的软件程序显式地排序对由其他线程或I / O设备共享的存储的访问。 为确保按程序顺序执行这些访问,必须在对共享存储的存储访问之间放置适当的内存屏障指令。 在适当位置不包含内存屏障指令的程序可能会在旧平台上正确执行,但在新平台(例如p690之类的POWER4系统)上可能会失败,因为新处理器会积极地重新排序内存访问以提高性能。 AIX程序员编写设备驱动程序或将共享存储与多线程应用程序一起使用至关重要,请遵循体系结构规则。

我们将为您提供检查代码以确保体系结构合规性所需的信息。 我们从总结PowerPC架构的显着特征开始,并包括伪代码示例来说明这些概念的实际应用。

概念和术语

首先,我们要从体系结构书中提取一些定义,并介绍一些相关的概念。 不用担心-我们不打算描述每个体系结构的细微差别,但是我们希望为本文稍后的讨论提供足够的基础,在这里我们将更直接地展示这些概念如何应用于AIX驱动程序。

术语“ 存储访问”是指由加载,存储,DMA写或DMA读引起的对内存的访问。

需要考虑以下三个顺序:

订购 描述
顺序执行模型定义的顺序 在此模型中,每条指令似乎在下一条指令开始之前完成。 通常,从不访问共享内存的程序的角度来看,似乎按照程序指定的顺序(即程序顺序)执行指令。
指令执行顺序 顺序执行模型不需要处理器按程序顺序执行指令。 即使现代处理器经常以不同的顺序执行指令,但不访问共享内存的程序无法检测到指令序列的执行顺序与程序中指定的顺序不同。
执行存储访问的顺序 执行存储器访问的顺序可能与程序顺序以及导致访问的指令的执行顺序都不同。 考虑一个程序,该程序包含按顺序从位置A,B和C加载的序列。 处理器可能以不同的顺序(例如B,A,C)执行这些指令,并以不同的顺序(例如C,B,A)执行由指令引起的访问。

PowerPC体系结构定义了四个存储控制属性:

  • 通过必填
  • 缓存被抑制
  • 需要内存一致性
  • 守卫

在AIX环境中,程序可以访问:

  • 具有“禁止”和“保护”缓存属性的存储。 例如,I / O适配器上的寄存器或内存。
  • 既不禁止缓存也不禁止保护的存储,但所有访问都一致的存储。 例如,可以缓存并可以推测读取的主存储; 与I / O存储不同,读取主存储不会产生任何副作用。

控制存储访问的排序规则取决于存储位置的控制属性。 在编写设备驱动程序时,通常需要考虑两类存储:

  • 系统内存-具有“需要内存一致性”属性。
  • 设备内存-具有“禁止缓存”和“保护缓存”属性。 这是您通过AIX iomem_att或io_map内核服务可寻址的内存。 在Linux中,这是您通过ioremap服务可寻址的内存。

为简单起见,在本文的其余部分中,我们将使用术语设备内存和系统内存来描述这两个存储类。

如果加载或存储指令的操作数位于设备存储器中,则对操作数的访问可以在单个操作中执行,也可以作为一系列操作执行,具体取决于操作数的对齐方式,操作数的大小。操作数,以及沿处理器和设备内存之间的路径(即,总线上)支持的操作类型。 例如,如果操作数是单个字节,则通过单个操作执行访问。

但是,如果操作数是双字且未在双字边界上对齐,则可以通过三到八个总线操作的序列来执行访问。 取决于设备,这可能是一个问题。

在较新的p系列系统上,由字符串指令,指定不是存储操作数大小的倍数的有效地址的指令,“加载多个”指令或“存储多个”指令引起的存储访问也会受到“卡顿”的影响。 当访问由处理器部分执行,然后在被中断(例如,由外部中断或处理器内部的某些事件中断)后重新启动时,会发生这种情况。 从设备的角度来看,指令的执行可能导致所寻址的存储被多次访问(如果访问引起副作用,则可能导致信息丢失)。 在较新的处理器实现中,字符串和对设备内存的多次操作非常慢(性能较差)。 建议驱动程序避免在这些平台上使用这些说明,并且将所有I / O与设备内存对齐,使其自然界限。

订购规则和机制

该体系结构提供了一些适用于存储访问顺序的规则。 此外,执行I / O的有序执行( eieio )和同步( sync )指令可提供明确的控制。

仅当访问是由顺序执行模型所需的指令引起的时,才执行对设备内存的存储访问。 这并不意味着访问必须按照程序顺序进行(例如,您希望从阅读程序清单中获得的顺序),而不能以推测方式执行访问。

如果加载指令取决于前一条加载指令返回的结果,则按程序顺序执行相应的存储访问。

要求对同一存储位置进行原子访问。

到设备存储器的两条存储指令以程序顺序执行。 这是1999年2月进行的体系结构更改。在此之前,该体系结构允许对存储到设备内存的存储进行重新排序。 结果,许多当前驱动程序都具有eieio指令(一分钟内会详细介绍eieio)以控制进行存储访问的顺序。 尽管在原始体系结构定义下添加了这些说明是为了保证体系结构的正确性,但实际上,从来没有任何处理器实现需要这样做。 在进行体系结构更改时,已针对RS / 6000对此进行了确认,最近对Motorola 7400、7410、7450和82xx处理器也进行了确认。 对于开发新代码的驱动程序编写者,我们建议您省略指令,其唯一目的是在两个存储之间提供对设备内存的排序。 对于现有代码,如果正在执行性能工作,则可以删除说明,但请确保对所有受支持的处理器实现执行回归测试 。 在大多数情况下,可以将现有代码保持原样。

同样,仅在所有访问都针对同一设备的情况下,对设备内存的访问顺序才是确定的。 为了将存储订购到两条不同PCI总线上的设备,需要附加的软件协议,例如从一个设备执行加载以确保存储已完成,然后再允许将存储存储到另一条PCI总线上的另一设备。 本文后面的io_flush示例进一步说明了这一点。

所有这些规则都不能防止相对于设备内存中的负载,以程序顺序发出对设备内存的存储,反之亦然 。 相反,当乱序操作可能产生错误结果时,必须使用内存屏障指令eieiosync

内存屏障说明

eieio指令和sync指令均创建一个存储屏障 ,用于对存储访问指令进行eieio 。 内存屏障将指令分为在屏障创建指令之前的指令和在屏障创建指令之后的指令。 由屏障创建指令之前的指令引起的访问必须先于屏障创建指令之后发生的指令所导致的访问。 在同步命令所有存储访问的同时,eieio仅命令存储访问的特定子集。

有几点要牢记。 首先, eieio指令的执行或完成并不意味着由eieio之前的指令引起的存储访问已完成。 指令创建的屏障将确保按屏障顺序进行的所有访问均按指定顺序执行,但是在eieio指令完成很长时间之后,可能不会执行在屏障之前发出的访问。 关于eieio第二个关键方面是它将存储访问指令进一步分为两类:系统存储器指令和设备存储器指令。 访问一类存储器的顺序与访问另一类存储器的顺序无关。 因此,可以保证在eieio指令之后的加载和存储对设备内存的访问是在eieio之前的指令导致的之后eieio 。 同样,对屏障两侧的系统内存的存储访问也要按顺序进行。 但是,如果一个访问是对设备内存的访问,而另一个访问是对系统内存的访问,则eieio 不会影响执行两个访问的顺序。 另外, eieio对将加载排序到系统内存没有任何影响,并且不建议将存储排序到系统内存。 最后一点, eieio对于设备内存访问不是累积的。 如下所述,这与sync指令相反。

sync指令创建的障碍更加全面,因为它对所有存储访问进行了排序,而与类无关。 sync指令有两种版本。

  • 第一种形式是权重sync ,或者通常简称为sync 。 适配器设备驱动程序经常需要这种形式,以便通过驱动程序关键部分对I / O适配器的访问来对系统内存访问进行排序。 执行sync确保了前面的所有指令sync指令已经在之前完成的sync指令完成,且没有后续指令开始,直到后sync指令完成。 这并不意味着以前存储的访问已在之前完成的sync指令完成。 在保证后续加载可以查看存储结果之前,尚未执行存储访问。 同样,仅当存储不再更改要返回的值时才执行加载。

    sync指令创建的内存屏障提供“累积”排序。 这意味着在处理器P0上执行的程序观察到在处理器P1上执行的程序执行的访问然后执行sync指令之后,在处理器P0上执行的程序对其执行访问之后,将对其他处理器执行随后的存储访问。观察到已由处理器P1上执行的程序执行。 例如,在处理器P3上执行的程序将看不到存储访问的序列,该序列将与在处理器P0上执行的程序观察和执行的序列的顺序相冲突。

    重量级sync发生在AIX内核的解锁服务中(例如, unlock_enable )。 设备驱动程序通常在离开中断处理程序之前先进行unlock_enable ,并且潜在的沉重sync确保了对设备内存的存储访问相对于其他处理器而言是正确可见的。

  • sync指令的第二种形式是轻量级sync或lwsync 。 该表格用于控制仅对系统内存的存储访问的顺序。 它创建用于访问设备存储器的存储器屏障。 这种sync发生在内核的unlock_mem服务(例如, unlock_enable_mem )内。 需要由不同CPU共享的系统内存访问之间进行协调的软件,而无需考虑对设备内存的访问相关排序,可以使用lwsync 。 本文档后面的示例显示了设备驱动程序中lwsync的其他潜在用途。

下表总结了内存屏障指令的使用:

存储障碍和存储访问顺序
指令->存储访问1 启用缓存(“系统内存”) 禁止和保护缓存(“设备内存”)
同步2 lwsync 3 英雄4 同步2 lwsync 3 英雄4
加载/加载 推荐的 不适用 不适用
加载/存储 推荐的 不适用 不适用
存储/加载 没有 不适用 不适用
商店/商店 推荐的 注6 注5 不适用 注5
笔记:
  1. 当两次访问均使用相同的内存属性存储时,将应用术语“是”,“推荐”和“不适用”
  2. sync (sync 0) 所有访问进行排序,而与内存属性无关(例如,它将对启用了缓存的存储排序禁止缓存的负载)。
  3. lwsync (sync 1)对禁止缓存的访问的顺序没有影响。 它仅应用于订购对启用了缓存的存储的访问。
  4. eieio不会对具有不同存储属性的访问进行排序(例如,如果将eieio置于启用了缓存的存储区和禁止缓存的存储区之间,则访问仍然可以按照程序指定的顺序执行)
  5. 不管是否被屏障指令分开,两个存储到禁止缓存的存储都按照程序指定的顺序执行。
  6. 尽管eieio将命令定购到支持缓存的存储中,但lwsync是用于此目的的首选指令。 建议不要将 eieio用于此目的。

同步指令

同步指令包括指令同步( isync ),加载和保留( lwarx/ldarx )以及存储条件( stwcx./stdcx. )指令。

AIX使用“加载,保留和存储条件”指令来实现应用程序和设备驱动程序所需的高级同步功能。 应用程序和设备驱动程序应使用AIX提供的接口,而不是直接使用“加载,保留和存储条件”指令。

isync指令使处理器完成所有先前的指令的执行,然后丢弃isync之后的指令(可能已经开始执行)。 执行isync后,以下指令将开始执行。 isync用于锁定代码中,以确保在授予锁定之前,不执行进入关键部分后的加载(由于处理器中的攻击性乱序或推测性执行)。

缓存管理说明

PowerPC体系结构提供了许多高速缓存管理指令。 这些包括dcbf (数据高速缓存块刷新)和dcbst (数据高速缓存块存储)。 根据底层实现的不同,使用这些指令在适配器DMA启动之前刷新数据高速缓存行可能会对性能产生正面或负面影响。 无论是否存在这些指令,I / O都是一致的。 重要的是要注意dcbfdcbst 不能确保任何有关指令完成或排序的事情。 它们受先前描述的可见性和订购规则的约束。 出于存储访问排序的目的,它们被视为负载(来自系统内存)。 如果设备驱动程序在导致DMA到达刷新位置的设备内存访问之前使用dcbfdcbst ,则将需要插入sync指令。 有关更多说明,请参见“拉”适配器模型部分。

在对设备驱动程序进行性能优化时,建议您检查缓存管理指令的所有先前使用情况。 可能由于误解而产生了某些用法,这些误解是出于一致性原因需要指令,或者指令具有类似于sync属性。 如果这些指令旨在提高性能,则应进行分析以确定这些指令是否应在可在多种实现上运行或仅在某些实现上运行的通用驱动程序源库中使用。 通常,首选结果是单个驱动程序源库,该库没有用于唯一实现的特殊代码。

作为驱动程序编写者的最终缓存管理说明,PowerPC体系结构的较早版本包括dcbi (数据缓存块无效)指令。 驱动程序编写者切勿使用此指令。 它的性能可能很差,使用不当可能会导致安全隐患,并且在较新的平台上,这将导致AIX无法处理的非法指令中断。 如果您认为需要刷新缓存,请使用dcbf而不是dcbi

非原子访问

PowerPC体系结构指出,在由单个指令(不是原子访问)引起的存储访问中,不应假设任何排序。 并且没有控制该顺序的方法。 如前所述,在使用对设备内存的多个,字符串或未对齐的存储访问时,应该非常谨慎(如果没有其他原因,出于性能原因,请完全避免在较新的平台上使用)。 在新平台上,使用这些指令访问设备内存(甚至对齐的多个或字符串指令)可能会导致对齐中断,从而导致通过软件对访问进行仿真,从而导致性能比对齐的加载或存储慢数百倍。指令。

基本的AIX支持

AIX操作系统提供了一系列基本功能,这些功能基于前面描述的指令来执行共享内存同步所需的重要操作。 这些包括锁,信号量,原子更新原语等。它们可以作为系统调用,内核服务或库来使用。 在AIX / pSeries SMP系统上执行的大多数应用程序已经使用各种可用库来安全地管理共享内存通信。 此类库的示例包括:

  • AIX pthread库
  • ESSL SMP库
  • 编译器SMP运行时库

即便如此,仍有一些应用程序代码实例需要确保发生正确的同步,如以下一些示例所示。

AIX适配器设备驱动程序通常需要关注对设备内存和系统内存的CPU访问,通常与它们控制的适配器对系统内存的DMA访问结合在一起。 以下示例显示了在架构上兼容的方法来解决常见问题。

例子

全局线程标志

一个线程可能需要通过将共享标志变量设置为特定值来向其他线程指示它已经完成了部分计算。 直到将所有计算出的数据存储到系统内存中之后,才能进行该标志的设置。 否则,某个其他线程(在另一个处理器上)可能会看到该标志已设置并访问尚未更新的数据位置。 为了防止这种情况,必须在数据存储和标志存储之间放置一个synclwsync 。 在这种情况下, lwsync是首选指令。

< compute and store data >
..
lwsync
<store flag>

等待信号信号的标志已存储

在此示例中,使用isync来防止处理器使用该块中的过时数据。 isync防止在标记设置之前推测执行访问数据块。 结合前面的加载,比较和条件分支指令, isync保证分支所依赖的加载(标志的加载)在isync之后发生的任何加载(来自共享的加载)之前执行块)。 isync不是内存屏障指令,但是负载比较条件分支isync序列可以提供此排序属性。 几个其他示例将进一步说明此顺序。

Loop:   load global flag 
                Has flag been set? 
          No:  goto Loop 
          Yes: continue 

          isync 

                <use data from shared block>

需要注意的是一个sync指令可能已被使用,但一个isync是一个更昂贵的指令,在后性能的影响方面。

“拉”适配器型号

设备驱动程序通常使用d_map内核服务与I / O适配器共享系统内存的一部分。 通常,设备驱动程序会在系统内存中填写一些命令元数据,然后对I / O适配器(IOA)进行内存映射的I / O(PIO)。 I / O适配器依次通过DMA读取命令特定信息的系统内存地址(从系统内存中“拉” cmd信息)并执行命令。 需要sync才能正确编程此序列:

store cmd_info to system memory addr X;           /* shared storage */ 
    __iospace_sync();         /* compiler built-in for sync instruction */ 
    store new_cmd_trigger to device memory addr Y;    /* pio to device */ 

            device does a DMA read to memory location X as a result of the PIO

在没有任何中间存储屏障指令的情况下(例如,在上述示例中没有sync ),在p690上观察到此序列失败。 延迟可能是这样的:适配器的PIO和适配器的DMA可以在CPU的存储到内存位置X进入一致性域之前发生。 结果是适配器读取过时的数据。 请注意,一个eieio指令将不足以在这种情况下,因为eieio订单存储访问设备存储器和系统存储器分开。 同样,使用dcbfdcbstdcbst存储到系统内存并不能消除sync的需要-如果不进行sync ,适配器DMA仍然可以获取过时的数据。

“推”适配器型号

其他I / O适配器更多地依赖于“推”方案,在该方案中,CPU PIO在启动命令之前将所有命令信息都存储到设备内存中。 由于该体系结构的早期版本(或至少是驱动程序社区对此的理解)不需要按程序顺序向设备内存中存储两次存储,因此在当前的AIX驱动程序代码中通常会看到如下序列:

store cmd_info1 to device memory addr X; 
    __iospace_eieio();        /* compiler built-in for eieio instruction */ 
    store start_cmd to device memory addr Y;

第一家商店设置了一个新命令。 第二个存储启动命令。 如果没有按顺序将存储交付给IOA,则IOA可能会在正确编程之前启动新命令。 既然该体系结构显然要求按照程序顺序执行上述存储,那么eieio就没有必要了。

加载/存储重新排序

在设备驱动程序的协议中,可能会有带有IOA的实例,必须先写入一个寄存器,然后才能从另一个寄存器读取有效数据。 这种情况下需要使用eieio

store enable_read_val to device memory address X 
        __iospace_eieio();  /* the above store needs to be presented to 
                             * the IOA before the following load is 
                             * presented to the IOA. 
                             */ 
        val = load from device memory address Y

io_flush

有时,驱动程序需要确保对设备内存的存储访问已完成。 几年前,AIX遇到了此问题的一个实例,并在解决方案中生成了io_flush宏。 在中断控制器使用与中断IOA不同的PCI总线的系统上,在CPU上执行此典型执行顺序时,观察到以下情况:

  1. dd interrupt handler loads from device interrupt register
  2. dd interrupt handler stores to device register to clear interrupt
  3. dd interrupt handler returns to system external interrupt subsystem, indicating interrupt was serviced
  4. system external interrupt subsystem stores to interrupt controller, to clear interrupt level into processor

到中断控制器的存储在发生到设备的存储之前完成。 结果,设备仍在向中断控制器显示其中断,并导致了第二个中断。 到操作系统开始处理第二个中断时,以上步骤2中的原始存储已完成,因此设备驱动程序报告其设备未在中断,并导致了无人认领的中断。 为了解决这个问题,需要一个代码序列来确保步骤2中的存储在启动步骤4中的存储之前真正完成。 解决此问题的一种方法是使用io_flush宏,该宏当前位于inline.h AIX头文件中。 在AIX驱动程序中断处理程序中的用法可能如下所示:

pio read to see if its your intr 
   return INTR_FAIL if not your intr 

   pio write to clear intr 

   eieio to enforce pio ordering of last write before subsequent pios 

   val = pio read from same address space as the pio write (it must 
be to address space owned by the same PCI bridge).  By spec, the 
bridge must complete the above write to clear the interrupt before 
completing this read 

   io_flush(val);  /* make sure the read completes */ 

   return INTR_SUCC 

   system external interrupt subsystem will pio write to MPIC or PPC interrupt controller

目的是在系统将pio写入中断控制器之前,先进行pio写入,以清除对适配器的中断(在示例中,该pio在另一个PCI主机桥或PHB下)。 eieio不能保证这一点,它只能保证写入顺序离开一致性域(到达PHB)。 因此,使用读取操作来刷新第一次写入操作(一次在PHB上,加载不能通过存储),并且io_flush确保读取操作完成(通过基于读取值的条件分支),并io_flush读取操作(通过isync )一个真正激进的处理器开始执行存储在中断控制器中的系统外部中断子系统的指令序列的机会。

DMA完成

通常,IOA DMA将数据写入与其设备驱动程序共享的系统内存中,然后发出中断。 因为该中断可以绕过IOA写入的DMA数据,所以在处理该中断时,该数据可能尚未存储在系统内存中。

RPA指定响应于DMA写操作之后发生的加载操作,在返回数据之前,将PHB缓冲区中的所有DMA写数据写入内存。 这意味着设备驱动程序必须在访问作为DMA写目标的系统内存位置之前,对其IOA进行PIO加载。 这样的负载自然会出现在大多数中断服务程序的开头,其形式是来自IOA的中断状态寄存器的负载,以确定所呈现的中断类型。

但是设备驱动程序需要防止对共享内存进行重新排序或以推测方式执行的访问,否则设备驱动程序对共享内存块的访问仍会返回陈旧数据。 必须先执行来自IOA的用于将数据刷新到内存的加载,然后再从共享内存块进行任何加载。

最好的解决方案是使用io_flush构造。 io_flush宏中的isync遵循基于要加载的值的条件分支。 为了完成条件分支,必须完成来自IOA的负载。 并且isync保证(从系统内存地址开始)后续加载将在条件分支所需的加载之后执行。

interrupt_handler: 

   val = load from IOA interrupt status register 

  io_flush(val); 

  cmd_result = load from system memory address 
               that was written by IOA prior to interrupting

根据中断处理程序的逻辑流程,可以使用一个io_flush宏调用来解决此DMA完成问题以及先前描述的“中断结束”竞赛。

索引命令完成

这个有点复杂的示例从根本上类似于已经描述的“等待标志”和“ DMA完成”示例。 某些设备驱动程序遵循的模型中,系统内存中有状态缓冲区列表。 每当命令完成时,IOA都会更新状态缓冲区,然后更新共享内存中的ioa_index变量以指示IOA写入的最新状态缓冲区。 设备驱动程序维护一个单独的dd_index变量,该变量指示驱动程序已处理的最后一个状态缓冲区。 驱动程序比较两个索引变量的值,以查看另一个命令是否已完成,并且它需要处理(由IOA写入)到下一个状态缓冲区的结果:

volatile int ioa_index;   /* written by IOA */ 
         int dd_index;    /* written by driver */

volatile struct cmd_status buffer[NUM_STATUS_BUFS];    /* written by IOA */ 
         struct cmd_status status_var;     /* local variable */ 

 while (dd_index != ioa_index) {

      status_var = buffer[dd_index]; 
      <process status_var>

      next_index(dd_index);  /* increment dd_index, handle wrapping 
                              * back to start of list 
                              */ 
 }

这种类型的逻辑流程也容易受到重新排序和推测执行的影响。 根据RPA,将对IOA的两个DMA写操作(cmd_status信息后跟新的ioa_index)进行排序。 但是,在加载并比较ioa_index信息之前,必须不允许处理器读取cmd_status位置,否则处理器可能会加载过时的cmd_status信息。 应避免的流程是:

read dd_index 
      read buffer[dd_index] 
                            IOA DMA write to buffer[dd_index] 
                            IOA DMA write to ioa_index 
      read ioa_index 
      compare dd_index and ioa_index, which no longer match

将溶液再次是遵循负载需要排序(的负载ioa_index )与比较-分支的iSync序列,诸如由提供的一个io_flush

while (dd_index != ioa_index) { 

      io_flush(ioa_index); 
      status_var = buffer[dd_index]; 
      <process status_var> 

      next_index(dd_index); 
 }

请注意,根据编译器生成的特定汇编顺序,可能已经存在compare和branch,在这种情况下,仅需要插入isync。

设备控制时序循环

当驱动程序需要在设备的PIO之间延迟时,会发生另一种有趣的情况。 一个示例可能是做一个PIO存储,以使IOA向子设备断言某些信号,然后驱动程序需要延迟以确保在发出第二个PIO存储解除断言之前,该信号被断言了一个最小周期。 该架构指定的order访问的,但避免了描述相对于真正的任何操作time 。 因此,两个PIO存储将按顺序传送到设备,但是两个存储可以按顺序循环传送(即,存储之间没有延迟)。 该解决方案是类似于io_flush的方法-一个负载到相同的设备(具有eieio用于排序的负荷,如果它不是以相同的设备地址作为事先存储)和数据相关性需要的初始存储和前后要插入延迟代码,以使驱动程序直到确定初始存储已完成才开始延迟。

结合了sync和lwsync的示例

假设一个I / O编程模型,其中设备驱动程序正在创建要由I / O设备执行的命令的列表,在该环境中,在I / O设备获取信息的同时可以将命令添加到列表中列表中的命令。

假定设备驱动程序已在系统内存中建立了五个命令的列表,执行了重量级的sync指令,然后执行了对设备内存的存储,以使设备启动对命令列表的处理。

在设备获取所有命令之前,设备驱动程序开始将另外三个命令附加到列表中。 假设每个命令元素由两个命令/数据双字和一个链接到下一个元素的双字指针组成(空指针指示列表的末尾)。 为了确保设备读取的命令是一致的,设备驱动程序必须执行三个存储以更新新的列表元素,然后执行sync ,然后执行更新前一个元素中的指针的存储以将新元素添加到列表中。 因为所有这些访问都是对系统内存的访问,所以lwsync是首选指令。

在将三个元素添加到命令列表之后,设备驱动程序必须执行重量级sync指令,然后执行存储到设备存储器的存储,该操作通知设备已将其他命令附加到列表中。 请注意,在此示例中,设备可能已经开始在将第二个命令元素存储到设备内存之前开始获取三个新命令元素,但是lwsync指令确保了设备将读取一致的值。 需要在设备内存中进行第二次存储,以处理以下情况:在设备初次通过命令列表时看不到三个新命令,并且再次需要重量级sync以强制执行命令列表和命令之间的系统内存更新。存储到设备以重新开始处理列表。

使用C的“ volatile”属性

设备驱动程序经常需要访问共享存储(即其他程序线程或I / O设备读取或写入的存储)。 To ensure correct program operation, it is worth emphasizing the importance for device driver writers to correctly use the C "volatile" attribute. You usually need the "volatile" attribute:

  1. when the value in some variable (for example, a location in system or device memory) is examined or set asynchronously, or
  2. when it is necessary to ensure that every load from and store to device memory is performed in device memory, due to side-effects that result from the accesses

So the "volatile" attribute is usually required with memory-mapped I/O. Additional examples where "volatile" must be used include:

  • locations shared between processes (or processors)
  • storage-based clocks or timers
  • locations or variables accessed or modified by signal handlers (which the primary code examines)
  • a lock cell (a special case of the first case)

The IBM compiler generates a load or store appropriately for every reference to a "volatile" memory location and does not reorder such references. The result is that the contents of a volatile object are read from memory each time its value is used, and are written back to memory each time the program modifies the object.

It's usually wrong to copy a pointer to a volatile object to a pointer to a non-volatile object, and then use the copy. The code sequence below is incorrect, and caused a bug in a Microchannel driver in an older version of AIX. The problem is that a "pointer to volatile char" is assigned to two other "pointer to char" variables, which are then used to touch the device registers. Because these pointers are not declared with the volatile attribute, their order was swapped by the compiler's instruction scheduler, and resulted in buggy behavior by the adapter.

The specific sequence was:

volatile char *pptr; 
     char *poscmd, *posdata, poll; 
         :       : 
     pptr = .... 
     poscmd = pptr + 6; 
     posdata = pptr + 3; 
     pptr += 7; 
     *poscmd = 0x47;       /*  This store gets interchanged with */ 
     poll = *posdata;      /*  this load by the compiler         */

结论

We've described and illustrated how the underlying PowerPC storage architecture can impact AIX application and device driver code. The underlying architecture hasn't changed, but newer implementations based on the POWER4 processor are more aggressive in exploiting the architecture for performance gains. You need to examine any implicit software assumptions about order of execution and I/O latencies in your code and change it if the implicit assumptions don't match the architectural guarantees.

Appendix A: Implementation notes

The C and C++ compilers from IBM provide built-in functions ( __iospace_sync and __iospace_eieio ) to generate sync and eieio instructions inline. The compiler will use a sync for __iospace_eieio() if the compile mode is not PowerPC (for example, the compiler generates eieio for __iospace_eieio() only when -qarch=ppc). New releases of these compilers are planned to provide a richer set of built-ins which will permit convenient generation of isync and lwsync .

Assembler-coded functions can be written which contain just the required instruction and a return.

The AIX header file inline.h provides the appropriate symbols and pragmas to inline the instructions, but it's not a shipped header, so it's available only to developers with access to the AIX build environment. However, the techniques used in inline.h can be replicated. It uses a facility of the compiler which permits generation of any instruction in-line. This facility is known as "mc_func" (machine-code function), and requires specification of the instruction(s) in hex. This facility has been used to replace a call to an external assembler-coded function with the instruction(s) contained in the function, without changing the program semantics of the call.

For the convenience of external developers (who don't have access to inline.h), here are the file contents that relate to the material that we've covered. Simply copy these lines into your C source to use the mc_func facility to inline calls to isync, eieio, sync, lwsync, or io_flush:

void isync(void);
void eieio(void);
void SYNC(void);
void LWSYNC(void);
 
#pragma mc_func isync	{ "4c00012c" }          /* isync */
#pragma mc_func eieio	{ "7c0006ac" }		/* eieio */
#pragma mc_func SYNC    { "7c0004ac" }          /* sync  */
#pragma mc_func LWSYNC  { "7c2004ac" }          /* lwsync  */
 
#pragma reg_killed_by isync
#pragma reg_killed_by eieio
#pragma reg_killed_by SYNC
#pragma reg_killed_by LWSYNC
 
/*
 * The following is used on PCI/PowerPC machines to make sure that any state
 * updates to I/O ports have actually been flushed all the way to the device.
 * A read from something on the same bridge, an operation on the value read,
 * and a conditional branch and an isync are needed to guarantee this.  This 
 * inline will do the last three parts.  More generically, the inline can also 
 * be used to ensure that the load of the val parameter is performed 
 * before storage accesses subsequent to the io_flush sequence are performed.
 */
void io_flush(int val);
 
#pragma mc_func io_flush { \
"7c031800"	/* not_taken:	cmp	cr0, r3, r3	*/ \
"40a2fffc" 	/*		bne-	cr0, not_taken	*/ \
"4c00012c" 	/*		isync			*/ \
}
#pragma reg_killed_by io_flush     gr3,cr0

Appendix B: Combining, merging and collapsing

Besides ordering, you should also consider the possibilities of combining, merging, and collapsing. The following definitions come from PCI, but it's useful to think of the concepts at all stages between the processor and the I/O adapter. In PCI, combining occurs when sequential, non-overlapping writes are converted into a single (multiword) transaction. Byte merging occurs when a sequence of individual memory writes are merged into a single word access, and it's not permitted if one of the bytes is accessed by more than one of the writes in the sequence. The implied order is preserved in combining, but in byte merging the order is not necessarily preserved (a sequence in which bytes 3, 1, 0, and 2 in the same word-aligned address are written can be byte merged into a single transaction). Finally, collapsing occurs if a sequence of memory writes to the same location are collapsed into a single bus transaction.

Here is a brief summary of the rules for combining, merging, and collapsing.

Combining of sequential writes (non-overlapping) where the implied ordering is maintained:

  • Processor architecture - allowed except if separated by a sync or if guarded and separated by an eieio (must be sequential writes from an ordering standpoint)
  • PCI architecture - allowed unconditionally to PCI memory space and encouraged, not allowed to I/O or config; implied ordering must be maintained
  • RPA architecture - says everything in the coherency domain will implement the PowerPC semantics - that includes Hubs but not bridges
  • Hub implementations - sync or eieio to guarded space will prevent combining at Hub since they are part of coherency domain
  • Bridge implementations - Combining appropriate and encouraged by PCI architecture to PCI memory address space, not allowed to I/O or config (per PCI architecture)
  • Device drivers - will have to prevent it to the PCI memory space by use of some method other than sync or eieio (since bridges allowed to combine) if the PCI device cannot handle it and will have to prevent it to PCI I/O space by use of sync or eieio to guarded space (config space not a problem due to use of RTAS call)
  • Firmware - needs to use sync or eieio to guarded space to the config space

Merging of a sequence of individual memory writes into a single word (to be merged, the individual memory writes cannot overlap and cannot address the same byte, but reordering is allowed):

  • Processor architecture - processor architecture does not allow this if the operations are non-sequential or overlapping, or if separated by a sync or eieio with the guarded bit on, but does allow it if sequential, non-overlapping, and not separated by sync or eieio with guarded bit
  • PCI architecture - disallowed to non-prefetchable memory and to I/O spaces and config space, allowed to prefetchable memory spaces; PCI architecture doesn't require that PHBs implement non-prefetchable versus non-prefetchable spaces
  • RPA architecture - doesn't require the bridges implement prefetchable versus non-prefetchable
  • Bridge implementations - given the last two bullets, our platforms had better not implement merging to any of the PCI address spaces
  • Device drivers - device driver has to prevent processor from merging if to PCI non-prefetchable memory space or to PCI I/O space by use of sync or eieio to guarded space (config space not a problem due to use of RTAS call)
  • Firmware - needs to use sync or eieio to guarded space to access the config space

You don't have to worry about the collapsing of a sequence of individual memory writes to the same location into one bus transaction. Each individual store in a sequence of stores to the same location in device memory will be delivered to the adapter (for example, a sequence of stores to a FIFO buffer will produce the expected result). See the section above that discusses "volatile" objects.


翻译自: https://www.ibm.com/developerworks/systems/articles/powerpc.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值