随着对实时系统可靠性、性能和可扩展性要求的提高,多处理器的应用场合日益增多。解决紧耦合共享内存问题,以及多任务并行执行问题的目的是确保多处理器(SMP)系统设计中能够安全有效地共享系统资源,本文从对称性、旋转锁机制、操作系统的结构和中断管理等方面阐述了SMP系统的设计策略,为将一个单处理器RTOS扩展为支持SMP的系统提供参考。
一般来说,当系统中采用了n个处理器时,应该无需改变任何代码就能得到比采用1个处理器时快n倍的处理速度。然而,实际情况并非如此简单。
多处理器这一概念已经出现了几十年,但只是最近,随着对所谓“高度可用”的CPU密集型系统(即正常运行时间为99.999%的系统)的需求增大,导致业界出现了几种类型的多处理器系统,例如,多处理器系统就广泛应用在3D图像处理等应用中,由专用的多处理器芯片(例如Clearspeed公司的Fuzion 150)来完成音频/视频压缩和解压功能。此外,这些系统还被用于高带宽的网络通信交换机/路由器设计中,例如在专用多处理器上实现特殊网管特性设计(利用Broadcom公司的Mercurian系列可编程网络处理器中带有SB-1芯片的SB1250)。
本文着重介绍一种特殊类型的多处理器系统-紧耦合共享内存对称式多处理器(SMP),并讨论典型的实时操作系统是怎样支持这种多处理器的。
SMP配置
在我们的SMP配置中,所谓“紧耦合”就是指各处理器内核个体之间距离很近,并通过一个通用高速总线实现物理互连。这些处理器内核通过一个公共I/O总线接口共享一个全局内存模块(即共享内存)以及诸多外围设备。
SMP系统和分布式多处理器环境二者之间的差异是我们必须了解的一个问题。在一个分布式多处理器系统中,处理单元个体通常以单独节点的形式存在,每个这样的节点中的处理器类型可以不同,并且配备有它自己的内存和I/O设备。每个处理器可以运行它自己的操作系统,只通过一种互连方式,利用消息或旗语来与其它处理器同步。这种互连方式可以是以太网,也可以是Infiniband这样的更专用的高速互连方式。
而紧耦合共享内存SMP系统则不同。在SMP系统中,所有的处理器都运行操作系统的同一个副本,该操作系统可协调每个相似的CPU上同时发生的活动。由于这些紧耦合CPU所访问的是一个公共内存区,所以它们必须通过一种基于低延时共享内存的通信机制来实现相互之间的同步。
SMP系统的对称性
SMP系统定义为通过一个公共高速总线,将多个相同的处理器与诸如内存和I/O设备等一系列外围设备连接起来的系统。这里使用“对称”一词是因为,每个处理器在访问共享内存和外围设备时,访问机制相同。
图1所示为一个SMP系统,其中包含2个相同的处理器(P1和P2),一个全局内存模块,以及磁盘控制器和网络接口等I/O设备。这2个处理器通过一个公共高速总线共享存储设备。通常,二者通过一个高速总线接口与每一台外围设备间均有连接。
在SMP系统中,各处理器必须通过竞争的方式访问总线(例如对全局存储器进行读或写)。如果P1已经占据了总线,正在接收从网络接口送来的数据,那么P2就必须等待P1的操作结束之后才能访问总线,向磁盘中写入数据。这样一来,只要有任何一个设备正忙于访问内存,进行读写操作,那么所有其它想要访问内存的设备都只能进入总线等待状态。
然而,每一个这样的设备中通常都会专门保留一些局部存储器,以便让设备自己使用。例如一台I/O设备(以一个常用的网络接口控制器为例) 中就保留了一部分局部存储器,用来对那些在网络上接收到之后,还没有发送到(通常通过DMA方式)全局内存中去,让P1或P2深入处理的数据进行缓冲或预处理。
在SMP系统中,除了每个CPU均有的中断控制器以外,通常还另有一个中断控制器(例如英特尔的IO-APIC),用于将外围设备发出的中断请求信号送给某个CPU,以实现该设备所请求的服务。
由于采用了对称式硬件结构,SMP系统的软件设计得以独立于任何由设备类型或其它与硬件相关的方面决定的专用互联方案,其中包括系统中处理器的确切数目。
为何要采用SMP?
在我们具体考虑RTOS是如何支持一个SMP系统之前,先了解一下采用SMP系统所带来的好处:首先,在完成一组任务时,两个处理单元并行运行比一个处理器单独运行的速度快许多。同样道理,四个处理器并行运行时,速度又要比两个处理器快。其次,一个SMP系统如果能通过添加更多的处理内核和/或外围设备来达到并行执行更多任务的目的,那么这种系统就提供了一个具备较强生命力的可扩展的平台。最后,即使有一个处理器出现了致命故障,其它的处理器也能无缝地接管它的工作,降低系统的停工时间。
此外,采用SMP系统还有一个好处,那就是通过操作系统来采用SMP系统相对较为简单。因为当操作系统设计得当时,能使硬件中确切的处理单元数对应用程序员透明。于是操作系统就能不进行任何针对SMP的修改而运行现有的应用软件。
与单处理器系统类似,在SMP系统上运行抢占型RTOS就能保证在任何时刻,系统中运行的都是优先级最高的任务。但与单处理器系统不同的是,多处理器系统中每个处理器都能运行一项任务,因而整个系统就能同时运行多个任务。当一项优先级较高的任务就绪时,它会抢占比其优先级低的任务的运行资源。
在一个通常运行在单处理器系统中的多线程应用中,RTOS通过互斥原理限制了任何一个任务对全局资源的访问。而加入旋转锁(spin lock)之后,该原理也适用于多处理器系统。当我们在这类多处理器系统中使用RTOS时,主要需考虑旋转锁的数目以及采用哪种内核结构恰当。首先,我们来看旋转锁。
旋转锁
旋转锁是最基本的SMP原语。在一个支持SMP系统的内核中采用旋转锁可保证数据的完整性。表1中采用伪码实现了一种简单的旋转锁。
SPINLOCK sp_lock是一种内核数据结构。运行在任意一个处理器上的某个任务如果成功调用了lock()函数,就可获得sp_lock,退出“spin”循环,并得以对“spin”所保护的共享资源进行独占性访问。当该任务对共享资源的访问结束后,它会调用unlock()函数,将被锁定的资源解锁。在这段时间内,所有其它正在运行中并正企图获取该资源的任务(运行在其它处理器上的任务)都将空转,并等待sp_lock被重置为0。但当sp_lock被释放时,只有一个正在等待的任务可以成功地获得它。
下面我们根据图2中给出的例子来讨论旋转锁在4处理器环境下是如何工作的。首先,T1在进入其临界区前获得了该锁。当T1还在访问共享资源时,运行在另一个处理器上的T2发出了对同一个锁的请求,并开始空转,等待T1释放该锁。随后,T3也调用了lock()并开始空转。这时,T2和T3均处于空转状态,等待获取该锁。当T1结束了其临界代码的执行并释放该锁后,T3获得了该锁,尽管它提出请求的时间比T2还晚。在这一系列操作的过程中,并不存在任何上下文环境切换。
旋转锁被上一个任务释放之后,下一个正在空转并请求获得旋转锁的任务就立即俘获到这一信息,并退出while循环,继续执行其内核程序。如果RTOS支持带有优先级的旋转锁,那么一旦T1释放了旋转锁,空转任务中优先级最高的任务就会获得该锁。
旋转锁的另一种工作方式就是在它被释放后,让空转的任务休眠一段时间,切换为运行另一个用户模式的任务。一旦该任务结束后旋转锁被释放,系统就会调用一个wakeup()函数,唤醒所有正在休眠的、向该旋转锁发出了请求的任务。
表2给出了一种实现旋转锁及其相关休眠-唤醒功能的伪码。这种实现方式更适用于完成长期互斥。否则,如果某任务占据旋转锁的时间很短,那么这种休眠-唤醒所造成的上下文环境切换开销就会将通过切换到其它任务(而不是象表1中一样,仍然让运行某任务的处理器空转等待获得旋转锁)而获得的性能提高全部抵消。
以上是旋转锁的基本工作原理,下面我们将阐述如何用旋转锁来保证一个多处理器内核中数据的完整性。
操作系统的结构
支持SMP硬件的操作系统可能有几种结构。修改一个单处理器内核使其支持多处理器系统的一种最简单的方法,就是将整个操作系统视为一个不可分割的整体,并限定所有内核模式的操作均运行于同一个处理器(即主处理器)上,而另一个处理器(即从处理器)只用来执行用户模式的操作。这样得到的软件结构就不再是对称的。
在一个从处理器上运行的某个任务也可以通过进入一个任务队列来申请内核服务,该队列中的任务均等待着在主处理器上以内核模式执行。如果主处理器正空闲,那么等待队列中优先级最高的任务就能够运行。当被申请的内核服务执行完毕时,如果某个任务仍在等待执行,那么它就进入第二个队列,该队列中的任务均为用户模式任务,需要在某个从处理器上执行。
注意,这两个队列(一个用来管理申请在主处理器上执行的任务,另一个用来管理申请在任意一个从处理器上执行的任务)都必须由旋转锁保护,因为不同处理器上并行运行的多个任务可能会同时要求访问这两个队列。而采用两个旋转锁就可以避免出现与这两个队列相关的竞争条件。
其它的内核数据结构则不需要旋转锁保护,因为内核操作通常都在主处理器上执行。因而,只要提到内核,那么该操作仍然是在一个单处理器环境中运行。只有用户级任务才用得到从处理器提供的并行机制。
主-从结构的缺点在于只有当大多数任务都以用户模式执行时,这种结构才能体现其优越性。当应用中所涉及的大部分是内核模式操作时,主-从结构会将所有任务中的内核模式操作均分配在主处理器上执行。因此,系统的总体性能就和单处理器系统的性能相差无几。而且,如果出现了几个任务同时请求内核业务的情况,那么这些任务进出运行队列所带来的开销就会急剧降低系统的整体性能。
在一个主-从结构的RTOS中,如果应用包含了40%的内核模式操作,那么增加第二个CPU最多能将系统性能改善30%。但随着内核模式操作占整个CPU利用时间的比例增加,以及更多的任务进出运行队列,该应用的性能很快就会退化。
粗加锁方式
主-从型内核的一个精妙之处就在于其粗加锁方式(giant lock)结构。粗加锁将整个操作系统当作一个由旋转锁保护的整体单片电路,但又不会将内核模式的操作局限在某一个特定的处理器上。
任何一个要求内核模式操作的任务都可以获得粗加锁,并且继续在它正在使用的处理器上运行。但在某个特定的时刻,这种内核模式的操作仍然只能在某一个处理器上运行。因此,当某项任务占用着粗加锁时,其它所有等待获得内核服务的任务都只能空闲。
在SMP系统的对称型硬件结构中不存在主处理器或从处理器这样的概念,因此这种RTOS设计使得任意一个处理器都可以执行内核模式的操作。在这种结构中,消除了切换到另一个处理器所需的开销,但等待获得粗加锁的任务必须空转。而在一个主-从结构的内核中,在当前任务位于主处理器队列中等待时,从处理器可以切换到另一种模式-用户模式运行。
图2给出的就是一个这样的设计,其中整个内核被当作一个整体,由一个旋转锁提供保护。注意,T2耗费了很长时间在空转中等待T3释放粗加锁。因此,如果T1和T3先后延长它们占用该粗加锁的时间,那么T2将继续空转,并永不可能进行任何有意义的操作-尽管它实际上仍在某个处理器上运行。因此,如果有几个任务都在等待获得内核服务,那么这些任务将像在单处理器系统中一样一个接一个地连续执行,而不会象我们选择多处理器系统时所期望的那样并发执行。
这就是粗加锁内核结构的本质缺点。即当几个任务同时需要进行一段长时间的内核模式操作时,采用粗加锁的多处理器并不能真正提高应用的性能。粗加锁结构的这种较差的可扩展能力以及它对中断延迟的不利影响,最终导致大多数支持多处理器的RTOS都没有采用这种设计。
粗加锁内核是细加锁设计的一种极端情况。为了提供优秀的扩展能力,SMP操作系统需要为不同的内核子系统配备单独的锁。所谓的内核细加锁设计就允许各内核子系统同时在不同的处理器上分别以线程方式运行,因而这种设计能在一个应用中的各个任务之间实现更好的并行性。
内核细加锁
设计内核细加锁(fine-grained locking)结构的目的是为了让不同处理器上运行的任务能够同时执行内核模式操作。采用这种结构的内核叫做线程化内核(threaded kernel)。这是通过对不同的内核子系统分别采用旋转锁来实现的,以便企图访问这些子系统的任务能够并发执行。
加锁机制的粒度(granularity)决定了最大可并发执行的内核线程数。例如,大多数操作系统的核心调度器和文件系统组件都是相对内核服务独立的。因此,采用两个不同的旋转锁就能保护这两个子系统。这样,当某项任务正在读/写一个较大的文件,操作中涉及耗时的磁盘I/O时,它无需阻断另一个企图访问调度器的任务就可完成任务。于是,为这两个子系统分别配备一个旋转锁就得到一个线程化的内核,这时两个不同的任务就能同时以内核模式执行。
如图3所示,当T1忙于磁盘I/O时,T2可以激活优先级较高的T3,从而使得这段时间内,T2和T3能够进行一些有意义的操作,而不是空转。注意,T3在它自己操作失灵之前先释放了它获得的旋转锁。如果T3不释放这个旋转锁,那么就将导致所有其它企图获得该旋转锁的任务永远空转。
内核的这种细加锁机制在很大程度上增大了用户任务在执行时的并行性,这对促进CPU的利用和提高整个应用的数据吞吐量有很大帮助。
然而,为每个不同的内核子系统分别配备额外的旋转锁后,为了保证每个子系统内的互斥性,必须对每个旋转锁分别管理,这就引入了多个旋转锁的管理开销。
同时,设置多个旋转锁还使系统出现了死锁的可能。例如,在图3所描述的情景下,结果可能出现:
1. T1获得SP_LOCK1
2. T2获得SP_LOCK2
3. 然后T2企图获得SP_LOCK1,因而在空转中等待T1释放SP_LOCK1
4. 最后T1企图获得SP_LOCK2,因而在空转中等待T2释放SP_LOCK2
这就导致整个系统因死锁而挂起。当操作系统中的旋转锁数目更多时,这一问题会变得更加复杂(这个问题可通过实现区分优先级的旋转锁或优先继承协议1来解决,前者必须限定最高优先级)。
基于以上原因,我们在管理多旋转锁所带来的开销和应用中期望得到的并行度之间必须进行仔细权衡。这时,最好能够确定哪些内核子系统需要单独的旋转锁。在仔细选择出一组应单独受旋转锁保护的内核子系统后,RTOS设计通常就能为其SMP实现(针对某些特定类型的应用)提供较强的扩展能力。
细加锁机制的应用
MontaVista Linux专业版是Linux 2.4内核的派生产品,内带一个完全抢占调度器。它的SMP内核中就采用了细加锁机制,以便改善其可扩展性。这样设计之后,用户任务就能在不同的处理器上以单独的内核模式线程并发执行。
在其它更流行的商业RTOS厂商中,QNX和Wind River也宣称支持多处理器。在对多处理器的支持方面,Wind River的VxWorks或VxMP均有同步原语,该同步原语基于各节点上所运行的不同VxWorks之间的消息传递接口,目的主要针对涉及电路板级互联的多板设计,而不是传统的SMP设计。
QNX则支持操作系统为微核结构而非整体结构的SMP设计。其内核自身支持一组核心服务,如调度器或中断管理器,而其它诸如网络I/O(包括协议堆栈)等服务则以单独的进程方式运行。这就使内核模式操作的运行时间非常短,从而在实现SMP加锁机制时,可将微核看作一个单一的实体。欲获知更多有关QNX及其微核结构的信息,请参看QNX系统结构手册2。
中断管理
作为RTOS设计的一个重要方面,对系统定时器和I/O设备等各种中断驱动设备的服务机制要专门讨论。以系统定时器为例,系统时钟中断所造成的延时直接关系到系统的调度延时,而调度延时则决定了RTOS内核的实时性能。
在一个典型的单处理器RTOS中,为了避免在访问同一个全局共享资源的任务和中断句柄之间出现潜在竞争条件,当一个任务正在执行其临界区代码时,该任务会暂时禁止各种中断。然而在一个多处理器系统中,仅仅禁止中断并不足以避免出现竞争条件。
假设出现这样的情况,当列表3所示的中断句柄Timer_ISR()正在接受服务时,运行在另一个处理器上的任务Task_1禁止了中断,并进入其临界代码区。这时,系统时钟中断句柄Timer_ISR()和Task_1()同时运行,而且均要访问全局变量“time”。于是,Task_1()和Timer_ISR()之间就出现了竞争条件,Task_1()所读取的“time”变量当前的值为秒级,而实际值则在微秒级。
为了避免在SMP系统中的任务和中断服务程序(ISR)之间出现竞争条件,任务和ISR二者均必须获得一个旋转锁才能访问它们的临界区所用到的共享资源,即二者必须实现互斥访问。
列表4给出的是修改后的Task_1()和Timer_ISR()的伪码。如果ISR发现已有其它程序在使用旋转锁,那么它将在空转中等待其它任务或ISR释放该旋转锁。这对中断延时以及整个系统对外部事件的响应均有影响。
在主-从处理器配置中,所有中断句柄均要求内核模式的操作,所以它们均只能在主处理器上运行,这种方式潜在地避免了以上竞争条件。但在粗加锁设计和内核细加锁设计中,我们必须在中断句柄程序中实现这种旋转锁保护功能。
在内核细加锁设计中,旋转锁对中断延时的影响非常不利。因为在这种系统中,所有任务和ISR都必须通过竞争来获取唯一的内核旋转锁。
但在一个SMP系统中,如果某应用涉及大量的I/O操作,而这些I/O操作中又有很大部分均为需要内核模式操作的中断驱动数据传输任务,那么通常必须采用内核细加锁设计才能满足合理的并行性。
本文小结
由于紧耦合共享内存的SMP系统具备以上种种独特功能,所以可以用这种系统来代替当前的单处理器系统,以便为那些CPU占用比例较大并且实用性较高的设计提供可扩展的平台。
将一个单处理器RTOS扩展为支持SMP的系统其实并不困难,通常只需在操作系统中加入一些基本的原语(如旋转锁等)。只要我们设置好旋转锁,再选择一种恰当的内核结构,用户级的任务就能在多处理器上并行运行,并能同时安全有效地竞争共享资源。设计者应根据应用所需的并行度和正确管理旋转锁所能承受的开销来决定使用旋转锁的数目。
从一个单处理器系统过渡到SMP系统后,系统的整体数据吞吐量并不一定会有所提高,但各种多线程应用和可并行化的算法却能够大大加快运行速度。
作者:Srinivas Dharmasanam
工程师
Pillar Data Systems公司
Email: the_srinivas@








京公网安备 11011202001138号
