2.1 Cisco IOS XR内核
Cisco IOS XR技术精要
Cisco IOS XR是一款基于微内核、高度分布的操作系统。Cisco IOS XR中使用的微内核是一种由QNX Software Systems公司开发的QNX Neutrino实时操作系统(RTOS),其使用的内核是轻量级的,仅提供了少量必要的服务。该系统负责终端处理、调度、任务交换、内存管理、同步、进程间通信等工作。微内核系统不包括如设备驱动器、文件系统和网络栈之类的系统服务;这些服务是通过内核外的独立进程来执行的,可以像其他应用那样重新启动。
微内核是一种兼容POSIX(可移植操作系统接口)的内核。POSIX定义了兼容POSIX的OS必须遵循的OS规范和针对API和OS服务的测试组。基于POSIX兼容的内核开发的应用和服务可以方便地移植到另一个兼容POSIX的内核上。
基于微内核的OS必要的一点就是模块性。微内核提供了极具高度的模块性。OS是由一组通过微内核来管理、通过消息传送服务相结合的协作进程来执行工作的。每种进程运行在各自的地址空间中,不会因其他进程而造成内存数据损坏。微内核架构中有一个重要的因素就是快速内容交换能力,该能力提高了模块性的灵活程度。因为内容交换使用的CPU开销很低,它可以使每种应用和服务最大化地使用各自的进程并运行在各自的地址空间中。例如,Cisco IOS XR中,BGP、OSPF、OSPFv3、RIBv4、RIBv6等应用会执行在不同的进程中。而且,如果路由器上配置了多个OSPF进程,那么系统会将每个进程分配进与其他进程完全分离的单独进程实例中。这种快速内容交换能力使更优的模块性成为可能,这里的快速内容交换能力是指由QNX提供、Cisco加强的微内核和有效进程间通信的快速能容交换能力。进程间通信内容将在本章后续小节中做更详细的介绍。
2.1.1 线程
如图2-1所示,OS是由一组通过微内核管理的协同进程组成的。微内核提供了线程调度、抢占,以及到进程的同步服务。微内核还扮演着消息传送“通道”的角色。微内核和进程管理器共同构成了procnto进程。每种进程运行在单独的地址空间中,并且重启不会影响到其他进程。
在开发一款应用时,经常需要考虑并发地执行多种算法。这种并发性可以通过使用一个进程中的多个线程来实现。线程(thread)是执行与调度操作的最小单元。换句话说,一个进程(process)包含了多个相关线程,并且定义了线程可以执行的地址空间。每个进程至少带有一个线程。有关线程将在2.1节中做详细的介绍。
如读者所见,在例2-1中的show processed threadname 120命令输出中,IOS XR的BGP进程具有多个线程,每个线程各负其职,包括输入、输出、导入等。在下例的输出中,120表示BGP进程的作业ID,作业ID(JID)是分配给每个进程的唯一编号,更多内容将在2.1节中做更详细的介绍。
例2-1 BGP进程的线程名称
图2-2给出了最为常见的线程状态以及各状态之间的转换。内侧的圆圈实际上代表了两种截然不同的状态:ready和running。线程状态可以从ready转变成running,反之亦然。处于running状态的线程同样可以转变成图2-2中所示的其他任意一种状态。
Cisco IOS XR微内核使用一种抢占的、基于优先级的、非自适应的调度算法。每个线程分得一个优先级。调度程序负责基于已分配的优先级选择下一个执行的线程。处于ready状态中优先级最高的线程将被将被选择为running。每个优先级级别都会有一个ready状态的先进先出(FIFO)队列。
空闲线程是procnto进程中的一种特殊线程,因为只有这种线程的优先级为0并使用FIFO调度机制。而且,空闲线程只会处于running或ready状态,并且永远不会主动地交出CPU控制权。不过,由于其使用的优先级最低,所以空闲进程可以被任何处于ready状态的其他进程所抢占。
running状态的线程可能由于系统呼叫(如内核呼叫、异常或硬件中断)、被阻断、被抢占,或主动交让操作而转变成某种不同的状态。如果running状态的线程被更高优先级的线程抢占了,将会被放入同优先级ready队列的最前端。另一种情况,如果是由于线程的时隙用尽或是主动交让进程的原因,将会被放入同优先级ready队列的最末端。时隙(timeslice)指的是当同一优先级的ready队列中,如果一个或多个线程为running状态时,每个running状态的线程可以执行的最大时间长度。
当线程需要等待某个事件的发生时(如回复消息),running线程会被阻断。被阻断的线程将会永久地进入对应的阻断状态,直到阻断消失。当进程的阻断结束后,线程通常都会被放入同优先级ready队列的最末端。当然也有一些例外。
例如,如果一个服务器线程正在等待一个客户端请求,将会进入阻断(block)状态。假设受阻断的服务器线程的优先级为10,而非阻断的客户端线程的以优先级20发送请求并等待回应。在这种情况下,服务器线程将会解除阻断,并将客户端线程转变为阻断的reply状态。如果服务器线程以10的优先级放入ready队列,不过还有多个ready状态的线程优先级为15,这样的话,即便是客户端线程的优先级为20,也会影响到客户端的相应时间。这个问题被称作优先级倒置。为了防止优先级倒置,微内核使用了优先级继承机制,可以使服务器线程的优先级临时地增加成匹配客户端线程的值(20),并将服务器线程放入客户端优先级(20)使用的ready队列中。
2.1.2 调度算法
微内核中提供了如下3种调度算法来满足不同场景的需要。
FIFO调度。
轮询调度。
偶发调度。
在FIFO调度中,除非线程主动交让控制权或被更高优先级线程抢占,否则线程会一直工作下去。在Cisco IOS XR中,只有procnto进程(内核)中的空闲线程使用FIFO调度算法。
IOS XR中的大多数其他进程都使用轮询(round-robin)调度,这种调度方法限定了每个线程得到资源控制权的最大时间长度(时隙)。使用轮询调度的线程会一直运行,直到其主动交让控制权、被更高优先级线程抢占,或待其时隙用尽。
偶发(sporadic)调度算法允许线程的优先级在下降为低优先级之前,在补充间隔(replenishment interval)内使用常规优先级运行一段时长(预算时间,budget time)。图2-3说明了偶发调度算法是如何工作的。
假设从t=0ms开始,线程T1和T2准备就绪,其他所有线程处于阻断状态。此外,假设T1为偶发调度线程,其常规优先级为30,低优先级为10,预算时间为20ms,补充时间为50ms。T2为轮询调度线程,优先级为20。实例如下。
1.由于T1的优先级比T2高,所以T1在t=0ms时被优先调度得到服务。
2. 在t=5ms时,带有40优先级的线程T3阻断结束,并变为ready状态。
3. 由于T3带有比T1还高的优先级,所以T3抢占T1得到服务。抢占的结果是,T1进入优先级30的ready队列的最前端。
4.在t=10ms时,T3线程被阻断并交还CPU控制权。此时优先级为30的T1线程和优先级为20的T2线程均处于ready状态。因此,较高优先级的T1在t=10ms时得到服务。
5. 当t=25ms时,T1线程的预算时间已经用完,由于偶发调度的机制,其优先级被降低成10。此时的T2线程处于ready状态并带有20的优先级,所以,T2抢占了T1,从25ms开始得到服务。
6.在t=50ms时,补充间隔的50ms到期,T1线程的优先级恢复成了常规优先级30,于是,又从T2线程上将控制权抢占了回来。
7.当t=70ms时,新补充间隔中的预算时间20ms又被用完,优先级再次降为10,T2再次抢占T1得到服务。
例2-2给出了show process pidin命令的部分输出。输出中列出了线程,以及相应的进程ID(pid)、线程ID(tid)、进程名称、优先级、调度算法和进程状态。“prio”一列给出了线程的优先级和调度算法。在线程调度符号中,f表示FIFO,r表示轮询,?表示偶发调度。例2-2中,共有两个线程使用FIFO调度;procnto线程1和2,这两个都是内核的空闲线程。每个CPU都会有一个空闲进程。由于此命令是在CRS-16/S的RP上执行的,该RP上共有两块CPU处理器,所以显示出了两个空闲线程,每个CPU一个。例2-2中还显示了eth_server进程中的线程3、4、7使用了偶发调度,所有其他线程使用的是轮询调度。
例2-2 show processes pidin命令输出
2.1.3 同步服务
微内核提供了一种基于消息传送的IPC同步机制。这种消息传送服务可以不经过任何中间组件,直接从发送线程的地址空间中将消息拷贝给接收线程。消息的内容和格式对内核来说是透明的。IPC内容将在2.3节中做更详细的介绍。
微内核除了提供了消息传送IPC机制之外,还可以开发出其他使用共享内存空间的IPC机制。不过,访问共享内存空间必须要求同步来确保数据的一致性。例如,如果某个线程正试图访问连接列表,而另一线程正在对列表进行更新,那么很可能会导致灾难性的结果。为了解决这一问题,微内核加入了mutex、condvar和semaphore同步机制。
相互排外锁,即mutex,用于保证在线程之间单独地访问共享数据。在线程访问共享数据之前,应该先获得(锁住)mutex。当完成在共享数据上的操作之后,线程会释放mutex。任意时间上,只能有一个线程获得mutex。如果某个线程试图锁住已经被其他线程锁住的mutex,那么该线程将进入阻断状态,直到mutex解锁并获得为止。当线程释放mutex时,具有最高优先级等待获得mutex的线程将解除阻断并成为新的mutex拥有者。
如果高优先级的线程试图锁住已经被低优先级线程锁住的mutex,那么当前拥有mutex的线程的优先级将会增加到同高优先级的阻断线程一样的优先级值。这种机制被称为优先级继承(priority inheritance),用来解决优先级倒置的问题。优先级继承与优先级倒置的内容在之前的客户端/服务器线程交互部分中有过介绍。
条件变量(condvar)用于等待某些条件的执行(例如超时)。线程在满足特定条件之前一直处于阻断状态。condvar通常与mutex按如下方式结合使用:
锁住mutex;
等待condvar;
执行动作(操作共享数据);
解锁mutex。
另一种同步机制被称为信号量(semaphore),线程状态由semaphore是否为正数来决定。如果semaphore为正数,线程将解除阻断访问资源,semaphore值减1。每次post操作会使semaphore值增加1。可以使用semaphore借助信号控制器来唤醒线程。线程通过semaphore发出一个wait操作,表示等待信号。信号控制器通过semaphore执行post操作,唤醒被semaphore阻断的线程。
像本节先前例2-2中显示的那样,命令show process pidin location< r/s/m >可以列出每个线程的状态。表2-1给出了一份进程可能获得的状态列表。
回到例2-2的输出,可以看到,某些线程正处于reply状态,这表示客户端线程正处于阻断状态并等待服务器响应。如果进程一直卡在阻断状态,很可能是(Client/Server)进程或应用出了问题。不过,并不是路由器上所有的阻断进程都是有问题的,因为在进程的选择过程中,阻断状态是一种可预期的行为。有些进程卡在阻断状态可能会导致应用不做响应。理解生产网络中路由器的进程行为是很有必要的。
如果想要查看所有阻断线程,可以使用命令show processes blocked location< r/s/m>,如例2-3所示。建议在数秒内多使用几次,这样可以确定某个阻断进程是由于错误被阻断还是执行常规IPC交换时的正常阻断。某些进程,如ksh和devc-conaux设计出来即是阻断状态。ksh作为客户端进程与devc-conaux(服务器进程)进行通信。在用户提供Console服务器上的输入信息之前,线程会一直处于阻断状态。更具体地讲,ksh等待Console或auxiliary端口上的输入信息并向devc-conaux返回系统消息。当devc-conaux回复ksh,进程状态将从阻断变为reply。
例2-3 受阻断进程