1.5.4 各任务的实现
路由器的软件功能被分解、划分成几个相互独立的任务之后,就很容易在支持多任务的嵌入式操作系统上实现了。假设我们的路由器是在Hello China上进行开发的,而Hello China支持完善的多任务(多线程)机制和任务同步机制,因此,在实现的时候,我们定义6个线程对象用来代表实现路由器功能的6个功能模块。
__KERNEL_THREAD_OBJECT* lpIpForwarding; //Ip forwarding thread. __KERNEL_THREAD_OBJECT* lpOspf; //OSPF thread. __KERNEL_THREAD_OBJECT* lpBgp; //BGP thread. __KERNEL_THREAD_OBJECT* lpTelnet; //Telnet server thread. __KERNEL_THREAD_OBJECT* lpConsole; //COM interface thread. __KERNEL_THREAD_OBJECT* lpTcpUdp; //TCP/UDP thread.
Hello China在完成自身初始化后,就应该启动上述6个任务了。在目前的实现中,Hello China的所有初始化功能都是在__init函数中完成的。因此,一个很好的选择就是修改__init函数,在该函数的尾部(这时候所有操作系统功能都已经初始化)创建并启动上述线程。相关代码如下。
VOID __init() { ... ... lpIpForwarding= KernelThreadManager.CreateKernelThread((__COMMON_ OBJECT*) &KernelThreadManager, IpForwarding, NULL, 0L, 0L, NULL); if(NULL==lpIpForwarding) //Can not create kernel thread. goto __TERMINAL; // //Create other 5 threads here. // ... ... }
这样,当Hello China完成自身的初始化后,上述路由器功能的6个线程就会被启动,目标系统就具备路由器的功能了。
对于每个线程的实现相对来说就比较容易了,因为嵌入式操作系统提供了大量的基础设施,每个具体的功能模块可以充分利用这些基础设施来实现自身功能,比如内存分配、线程同步、消息传递、定时器等功能。我们以IP转发线程为例,说明如何利用操作系统提供的同步机制来实现IP转发功能。在我们的实例中,IP转发功能是作为一个单独的线程来实现的,该线程维护一个本地转发队列,队列中存储了等待转发的数据报文。队列中的数据报文,是由接口驱动程序添加的,一旦接口驱动程序接收到一个IP报文,则会把该IP报文添加到队列中。一旦队列中存在IP报文,IP转发线程就开始工作,依次检查IP队列中的每个报文,根据报文的目的IP地址,查找路由表,然后从查找出的接口上发送出去。若队列中没有IP报文,则IP转发线程进入阻塞状态,以节约系统资源。因此,该线程需要有一个事件对象来配合实现同步功能。该线程的功能描述如下。
__EVENT* lpHasPacket=NULL; VOID IpForwarding() { ... ... lpHasPacket=ObjectManager.CreateObject(&ObjectManager, NULL, OBJECT_TYPE_EVENT); //Create event object to synchronize itself. if(NULL==lpHasPacket) //Can not create object. goto __TERMINAL; while(TRUE) { lpHasPacket->WaitForThisObject((__COMMON_OBJECT*) lpHasPacket); //Wait for packet to reach. while(queue is not empty) { get a ip packet from the queue; look up routing table; forward the packet according to routing table; } lpHasPacket->ResetEvent((__COMMON_OBJECT*)lpHasPacket); } ... ... }
上述代码中,IpForwarding函数首先创建一个事件对象,作为IP报文队列的指示器,然后进入一个无限循环。在循环的开始,等待创建的事件对象,若事件对象处于非信号状态,则会导致IP转发线程进入阻塞状态。一旦接口驱动程序接收到一个IP报文,则驱动程序会把IP报文挂到IP转发线程的发送队列,然后设置(SetEvent)事件对象。设置事件对象的结果是唤醒IP转发线程,IP转发线程一旦被唤醒,则依次检查转发队列中的IP报文,并查找路由表,完成报文的转发,直到IP队列变为空(所有IP报文处理完毕),然后转发线程复位事件对象,这样在循环的开始处转发线程又会阻塞自己,等待IP报文的再次到达。
其他线程的实现与此类似。需要说明的是,对于共享数据结构,比如路由表的访问,需要有一个互斥体对象来进行访问同步。比如在路由器的实现中,定义一个互斥体对象,来完成对路由表的互斥访问,代码如下。
__MUTEX* lpRtMutex=NULL; ... ... lpRtMutex->WaitForThisObject((__COMMON_OBJECT*)lpRtMutex); modify routing table; lpRtMutex->ReleaseMutex((__COMMON_OBJECT*)lpRtMutex); ... ...
这样,共享路由表数据结构的各线程之间共享该互斥体对象。在访问路由表的时候,首先获得互斥体对象,然后再进行修改,修改完毕之后,释放互斥体对象。这样可确保路由表的一致性。
上述这个路由器实例,非常简单,仅仅是为了说明如何利用嵌入式操作系统开发一个应用。实际的路由器其功能远不止这些,而且相互之间的关系更加复杂,但开发的基本方法和思路却与此类似。