现在的位置: 首页 > 综合 > 正文

《unix systems for modern architectures》笔记—SMP和锁(一)

2013年10月02日 ⁄ 综合 ⁄ 共 2946字 ⁄ 字号 评论关闭
文章目录

一.MP相关

      相对于UP而言,MP可以采用多个CPU来缓解制造更高速度CPU的需求,在单位时间内能执行多项任务,不仅如此,多处理机系统可以调整CPU的数量来进行扩张,从而与应用环境相匹配。例如,对于终端用户和客户来说,可以通过计算需求的扩大而增加CPU数量来升级系统,就是一种很吸引人的方案。此外,还有可能提高系统的可用性。如果一个CPU发生故障,那么剩余的CPU就可以继续发挥作用(取决于系统的设计,在android中,就有MP decision进程来做相关设计),从而保证系统的可用性,只是性能会有所降低。这就提供一定程度的容错能力,在诸如在线交易处理的环境中,系统停机会造成收入上的损失,就需要容错能力。(想起主备倒换)。
      调整UP实现在MP上实现,主要是解决3个主要领域的问题:系统稳定性,性能和外部编程模型(提供给用户空间的接口,不感知MP还是UP).
      
	

             1. SMP存储器模型--顺序存储模型

  	在这种模型下,CPU是按照程序次序来执行所有的loadstore指令,即按照它们在程序的顺序指令流中出现的次序来执行的,而且从主存储子系统和其 他CPU的角度来看,loadstore指令也是顺序对主存储器产生影响的。如果编译器或者优化器不改变程序中指令的次序,一直都是使用这个顺序存储模型,但是优化的存在,所有代码中存在barrier的使用。

             2. 原子读-改-写操作

	顺序存储模型定义从CPU或者I/O设备(通过DMA)到主存储器的单次度或者写操作为原子读或者写操作(这里暂时不讨论cache相关的内容)。
	从这点上看,在MP体系下,等待总线被授予给指定CPU的时候有可能引入延迟。比如,如果一条访问存储器的指令在等候轮到它占用总线的时候被拖延了,那么指令似乎仅仅是花了更多时间来执行而已。从这一点来看,对于存储器和总线来说,其带宽要大于或者等于所有CPUI/O设备能够产生的存储器流量之和。如果不如此,CPU就不能以峰值性能来执行,因为它们在试图访问存储器的时候会被频繁的延迟,而总线引起的延迟对于运行在系统上的软件来说是透明的。
	虽然存储器操作是原子和顺序的,但是来自多个CPU同时的存储操作,其相对的次序并不是确定的。
例如对于共享内存上的某个位置,一个CPU在读,一个CPU在写,读到的可能是这个位置上的老数据也可能是新数据,因为不能保证先发生读操作还是写操作。对于这种race condition,对于存储器操作是原子和顺序也是无法解决的.
	因为在SMP系统中经常需要对共享存储器的地址访问进行同步,所以大多数实现都通过原子的读--写(read-modify-write)操作来对此进行硬件支持。最典型的要数test-and-set.
 

二.UP 互斥

              主要可以归结为3类。短期互斥、中断互斥、长期互斥。

             1.短期互斥

	短期互斥是指防止短临界段中的竞争条件,当内核处于更新其数据结构之一的期间,就会发生这样的临界段。对于单核而言,一次只能执行一个进程,如果发生抢占才有可能出现上述的竞争条件。只有当内核态的进程允许时,才能从当前的进程现场切换到另外一个进程,例如,在使用spin_lock时,是有使用disable_preempt的,在释放锁就是spin_unlock,才调用enable_preempt,对于这样的锁资源就属于短期互斥. 在短期互斥中,不要忽略一点,中断的存在。如果出现中断之后,在中断返回时,检查到need_schedule1,此时也会发生调度,此时,也有可能出现上述的竞争条件,所以适度的spin_lock_irqsave是需要的。(从这一点来看,就不属于短期互斥的定义?)

             2.中断互斥

	中断互斥就是在中断处理程序中执行的代码访问或者更新了由非中断的代码使用的同一数据结构,就会出现竞争条件,此时需要spin_lock_irqsave。但是,这只是在UP中比较有效,因为spin_lock_irqsave禁的是local_irq,在MP下同样有可能存在效率的问题,并且不能保护资源,但是不会导致递归死锁。

             3.长期互斥

      	长期互斥相对于中断互斥和短期互斥最大的特点是没有disable_preempt,也就是系统允许抢占便于能够运行其他进程。一旦被抢占需要一种机制来唤醒原进程资源的就位机制,unix内核以sleepwakeup来实现这种类型的互斥。
      	 Sleep挂起调用它的进程,直到指定的事件发生为止(即将进程从run_list中移除下来),而wakeup用于发出一个特殊事件将等待该事件的所有进程的唤醒。
       	对于wakeup而言,等待此事件的进程可能有多个或者一个或者无进程。要唤醒一个进程,wakeup搜索sleep记录中哪些进程在哪些时间上休眠的那张表,将所有符合给定事件的进程都加入到运行队列。在scheduler执行的时候,会选择一个优先级较高的进程获取到资源(互斥),而其余进程如果发现对像是否定的,那么这些进程就返回睡眠,等待所持有锁的新进程唤醒。
       	对资源的占有特性,就决定了多读锁的出现。多读锁在后面介绍。另外,这里也存在一个竞争关系,在后面会介绍设置进程blocked的方式解决。
 

三.在MP上使用UP互斥策略的问题

           1.短期互斥

       	短期互斥的实现是基于禁止抢占而言的,但是MP体系本质上违反了短期互斥中禁止抢占的前提。即使一个MP系统的内核能禁止一个特殊的处理器的进程能被另外一个进程抢占,但是其他core上的进程也可能出现UP上短期互斥所述的问题

           2.中断互斥

       	UP的中断互斥手段在MP上也可能不会发生正常的功能,spin_lock_irqsave只是禁止本地中断,中断可能传递给其他处理器处理。根据硬件设计的不同,中断可以传递给系统中的任何一个CPU,或者始终被定向到一个CPU上,但是无论上那种情况,只要该中断处理函数不是在同一个CPU上运行,如果不使用自旋锁,就不能真正的保护数据,而在使用自旋锁的情况,就是性能问题的考虑。

           3.长期互斥

	MP系统下,以原有的sleepwakeup函数实现的长期互斥也不能解决所有的正常工作问题。如果MP体系上两个进程同时检测到资源被释放,目前无进程在使用,则此二个进程均能获取到资源,从而违反了互斥的原则。
	另外,MP下存在在UP下所说的问题,如果一个进程A检测锁并且开始执行sleep函数之间(还未挂到队列中),另外一个进程B释放了锁,在这种情况下,B释放锁调用的wakeup函数啥也不做,因为队列中此时为空。而第一个进程A此时加入到队列中,进程休眠,不清楚什么时候才会被唤醒,除非又有新的进程C获取锁,然后释放锁从能导致进程A能够被唤醒,期间的时间多久是无法决定的。
	这也就是代码mutex_lock__mutex_lock_common函数中,
             
	会通过_set_task_state状态为TASK_UNINTERRUPTIBLE的原因,后面会详细说明。
 
 
 
 

抱歉!评论已关闭.