主从处理机内核简介

来源:互联网 发布:java snmp4j jar 编辑:程序博客网 时间:2024/06/06 04:25
 
主从处理机内核简介
 
摘自Curt Schimmel著《现代体系结构上的UNIX系统——内核程序员的SMP和Caching技术》之第九章
 
第9章
 
本章介绍修改单处理机内核实现,使之没有竞争条件地运行在SMP系统上所采用的最简单的技术:主从处理机内核(master-slave kernel)。
 
9.1 引言
 
       第8章介绍的短期互斥技术是构造单处理机UNIX内核实现的主要基础之一,它不需要把明确的上锁代码编入内核中的许多地方。
       短期互斥技术依赖于这样的事实,即在内核中决不会有一个以上的进程同时执行。在MP系统上做到这一点的一项简单技术是要求所有的内核活动都在一个物理处理器上执行,这个处理器称为主处理器(master)。系统中所有其他的处理器都称为从处理器(slave),它们只能执行用户代码。以用户态执行的敬臣可以在系统中的任何处理器上执行。但是,当进程执行一次系统调用的时候,它就切换到主处理器上。一旦系统调用完成,进程就可以再次在任何处理器上运行。运行在从处理器上的用户态进程所产生的任何陷阱(比如缺页错或者算术异常)也会使得进程切换到主处理器上,因此还要维护内核陷阱处理程序(kernel trap handler)的互斥要求。最后,所有的设备驱动程序(device driver)和设备中断处理程序(device interrupt handler)只能在主处理器上运行。
       从内核的角度来看,主从处理机的编排保留了单处理机执行环境。这就能让单处理机的内核实现只需要很少的修改就可以在MP系统上运行,而且可以在任意数量的处理器上执行。要修改的重要领域之一就是如何将进程分配给各个处理器。有一项简单技术可以做这个事情,即有两个独立的运行队列,一个包含必须运行在主处理器上的内核态进程,而另一个包含运行在从处理器上的用户态进程。在每次现场切换的时候,每个从处理器选择在从运行队列(slave run queue)中有最高优先级的进程,而主处理器选择在内核进程队列(kernel process queue)中有最高优先级的进程。运行在从处理器上的进程在执行一次系统调用或者产生一次陷阱的时候就被放入主处理器的运行队列。当主处理器执行一次现场切换的时候,它正在执行的老进程如果是以用户态执行的,那么就把它放入从队列(slave queue);否则,它就返回主队列(master queue)。
       因为有多个处理器同时把进程排入队列、取出队列或者在队列中搜索进程,所以需要有一种方法来防止竞争条件。运行队列(run queue)是唯一需要显式MP短期互斥技术的数据结构,因为通过让所有的内核代码运行在主处理器,就可以保护所有其他的数据结构。采用自旋锁(spin lock)是提供这样的短期互斥的最简单方法。
 
9.2 自旋锁
 
 
9.3 死锁
 
 
9.4 主从处理机内核的实现
 
       在采用一个主从处理机内核实现的情况下,唯一的临界资源(critical resource)就是两个运行队列。向两个队列加入进程和从两个队列取出进程都必须采用互斥来完成,以防队列被破坏。用一个自旋锁来保护每个队列就能轻而易举地达到这个目的。
 
9.4.1运行队列的实现
 
       假定每个运行队列都作为一个无序链表(unsorted linked list)来实现(优先级队列是一种更好的实现,但是简单的无序链表能让这个例子集中在问题德互斥方面)。将队列中的进程的进程表项链接起来就构成了链表。在这些信息中,进程表项包含有进程的优先级和一个指向链表中下一个元素的指针。
 
后略
 
9.4.2从处理器的进程选择
 
       从处理器只能执行在从运行队列中的进程。当一个从处理器需要选择一个要执行的新进程时,它会执行如图9-11所示的代码片断。
 
       while((newproc = dispatch(&slave_queue)) == NULL)
        ;
图9-11 选择要运行的新进程的从处理器代码
 
       为了减少争用,可以对图9-10所示的dispatch例程进行修改,在获得锁之前先检测队列的状况。可以把图9-12中的代码加入到例程的开头部分,在队列为空的时候提早返回。
 
     Proc_t *
    dispatch( queue_t *q)
    {
       …
       if (q->q_head == NULL)
           return NULL;
       …
图9-12 修改后的dispatch例程
 
9.4.3主处理器的进程选择
 
       因为主处理器既可以运行内核态进程,也可以运行用户态进程,所以它可以从任何一个队列中选择进程,如图9-13所示。
 
    do {
       if ((newproc = dispatch( &master_queue )) == NULL)
           newproc = dispatch( &slave_queue );
    } while( newproc == NULL );
 
图9-13选择要运行的新进程的主处理器代码
 
9.4.4时钟中断处理
 
       在主从实现中的每个处理器都要接受和处理它自己的时钟中断(clock interrupt),这就能让每个处理器跟踪当前正在执行的时间配额(time quantum)。为了保持单处理机短期互斥策略,所有和时钟中断相关的普通内核活动,比如跟踪每天的时间、执行alarm系统调用、重新计算进程优先级等等,都是由主处理器来处理的。在从处理器上的时钟中断处理程序只能检查应该执行一次现场切换的地情形。这会在3中情况下发生:在当前进程的时间配额过时的时候,在向从运行队列中加入了一个优先级更高的进程的时候(传统的UNIX实现通过设置标志来交流这种情况,在时钟中断处理程序中可以检查这个标志),或者向该进程发送一个信号的时候。在前两种情况下,当前正在执行的进程会被放回到从运行队列中。在第三种情况下,必须将该进程切换到主处理器上,以便无需冒着有竞争条件的风险就能运行处理信号所需的内核代码。
 
9.5 性能考虑
 
正如在前面的几小节中所看到的那样,主从MP内核的实现很直观,而且在概念上也很简单。它既能满足系统完整性的要求,又能保持8.1.1小节中讨论过的单处理机外部编程模型。但是,一个重要问题是它将如何执行。
在理想情况下,随着系统中加入更多的处理器,SMP的整体系统吞吐量将等于处理器的数量和单个处理器吞吐量的积。一个MP实现能够在多大的程度上接近这个理想值则取决于3个主要因素:硬件体系结构、应用作业的混合情况以及内核的实现。
我们考虑在一个主从MP实现上运行两种不同的基准(benchmark)。第一个基准由一组完全限于运算(compute bound)的进程所构成。一旦被启动执行,它们不会产生缺页错、也没有系统调用,更不执行I/O操作。随着给系统加入更多的处理器,在系统吞吐量上,这样的基准会表现出几乎理想的线性增长。因为基准在用户态花掉了它全部的时间,所以完全利用上了所有的从处理器。另一方面,第二个基准由相同数量的进程所组成,不同之处在于这些进程都是限于系统调用的(system call bound),它显示出了相反的结果。在这种场合下,基准中的所有进程都需要不断得到内核的服务。既然只有主处理器才能提供这种服务,那么在整个基准中,从处理器都处于空闲状态。类似的,限于I/O(I/O bound)的基准也会显示出采用主从处理机内核的MP系统没有改善性能,因为从处理器增加的CPU吞吐量并不会加速I/O操作。
由此可以得到结论,对于高度交互性(或者I/O密集型)的应用环境来说,因为这些应用有大量的系统调用和I/O活动,所以采用主从实现是一种糟糕的选择。但主从实现对限于计算的科学应用环境来说则是一个良好的选择。
 
9.5.1主从处理机内核的改进
 
       通过放松所有的系统调用都在主处理器上执行这一要求,就可以改善主从处理机内核实现的性能。任何只能返回一条内核信息的系统调用都可以由从处理器来执行,而且很保险。系统调用getpid就是一个例子,因为它只返回在进程的生命期内都不会改变的一个值。
       类似的,任何系统调用如果只会修改对于进程来说是私有的数据(也就是说,从来不被任何别的进程所修改),那么它也能在从处理器上运行。如果只有一个进程修改数据,就不会造成竞争。
       虽然这些修改很容易做到,但是它们不太可能对一个主从系统的整体性能有显著的影响,因为这些系统调用并不常用,而且它们也不会占用多少内核的CPU时间量。
       因为将所有的内核活动都拴到一个处理器的做法在主从实现中式一个限制因素,所以改善系统性能的唯一方法就是由可能让多个处理器同时在内核中执行。
 
9.6 小结
 
       不经过修改,单处理机内核就不能在MP系统上运行。UP内核所使用的短期互斥技术依赖于这样的事实,即内核从来不会同时执行一个以上的进程。MP系统保持这个策略有效的一种途径就是将所有的内核活动都限制在系统中的一个处理器上。这个主处理器服务于所有的系统调用、中断和任何其他的内核活动。系统中其他的处理器是从处理器,它们只能在进程处于用户态时才能执行它们。一旦运行在从处理器上的一个进程启动一次系统调用,或者产生一个陷阱,那么它就必须被切换到主处理器上。
       在这样的一种实现中,唯一的临界资源就是运行队列。需要有一项技术把多个处理器对运行队列的访问串行化,从而防止发生竞争条件。自旋锁是一种简单的MP互斥原语,它可以用于这个目的。自旋锁可以通过更新一个存储器位置的原子操作来实现,于是,在任何时刻,只有一个处理器能成功地获得锁。一旦某个处理器获得了自旋锁,那么所有其他试图获得该锁的处理器就会处于忙等待状态,直到该锁被释放为止。
       如果以一种嵌套的方式来使用锁,而且所有的处理器都没有以相同的次序来上锁,那么就会导致死锁。为了防止死锁,所有的处理器都必须以相同的次序来锁定嵌套锁。如果一个占有自旋锁的进程执行了一次现场切换,或者如果一个中断处理程序试图获得已经由被中断的进程占有的自旋锁,也会导致死锁。
       在主从处理机内核中,主处理器会成为系统整体性能的限制因素。一旦主处理器饱和了,那么再增加更多的从处理器业不会改善性能,因为它们在一般的情况下无法分担内核的负载。有一些简单的系统调用可以放到从处理器上运行,因为没有别的进程会同时修改这些调用所引用的数据。但是因为它们不是占用主处理器大多数时间的系统调用,所以把它们放到从处理器上运行并不会明显改善性能。只有让内核活动并行地在多个处理器上执行,才能显著地提高性能。[]