陈虹宇,胡 术,董志强,李科磊,殷 源
(1.四川大学计算机学院,四川 成都 610064;2.四川大学国家空管自动化系统技术重点实验室, 四川 成都 610045;3.空军装备研究院雷达与电子对抗所, 北京 100039)
我国成都、西安区域管制中心引进了西班牙英德拉(Indra)系统,该系统配有模拟仿真训练系统,使用与真实系统一样的设备与人机界面来训练管制人员。Indra系统是目前中国引进的最先进的航管系统。基于本文提出的方法自主研发的系统与Indra配备的模拟仿真训练系统类似,提供了对航空管制员的训练功能。其运行控制需求复杂,不仅要求提供对多个组别的模拟飞行员席位和模拟管制员的组队训练,还要求提供对提供服务的模拟服务程序均衡地分配计算资源的能力。针对航管仿真训练系统的相关训练特点,本文设计并完成了基于航管仿真训练系统的运行控制系统。
航管仿真训练系统是用于培养和提高空中交通区域及进近雷达管制人员管制技能的主要手段和标准设备,能够逼真地模拟包括设备、用户界面等所有与训练相关的对象。通过实时模拟不同空中交通状态,以提供一个接近真实的工作环境来训练管制员,并且提供必要的手段完成这一模拟训练过程的准备、运行、记录、回放以及评估等。当代大型航管仿真训练系统可以同时进行多个组别、不同训练内容、科目的训练,其基本组成如图1所示。该系统均由UNIX操作系统主机构成,仿真软件采用跨平台设计,可以运行在Solaris、Linux等主流操作系统上。
Figure 1 ATC simulation training system图1 航管仿真训练系统组成图
进行航管模拟训练的基本过程如下:在进行仿真训练时,教员席位准备训练数据,将训练数据分发给参训主机后,开启训练过程,主要完成对参训的模拟机长席位和模拟雷达管制席位的角色指定,完成一个模拟机长席位和一个或多个模拟雷达管制席位的组队,在模拟训练服务器中启动相应组别的多个模拟服务进程。图1中,模拟机长P1和模拟雷达管制席位R1和R2组成第一组,通过和模拟训练服务器交互完成第一组模拟训练任务。模拟训练服务器集群中部署相同的进程,可以同时运行一组或多组训练服务程序,为各组模拟机长和模拟雷达管制席位的运行提供支撑。教员席位通过启动训练服务进程、模拟机长和模拟管制员席位完成训练组的开启,在训练完成后退出相应的训练组进程。
为了简化系统的部署,系统设计时各训练组的主机(含模拟训练服务器、模拟机长和模拟管制员席位)均部署一样的进程,只在运行前由教员席位先进行训练数据准备,然后将不同训练组的训练数据按组分发到参训主机的train_num(即训练组号)目录下,然后用命令行参数-Tnum(num为训练组号)启动训练的各进程,进程通过读取相应的训练数据目录运行不同训练组。
从以上需求来看,大型航管仿真训练系统对运行控制提出了较高的要求,需合理地调度模拟训练服务器的计算资源,灵活地组合训练组教员和模拟雷达主机,安全地启动和退出训练组。
运行控制最基本的要求是对组成训练组的各进程的启动、停止进行准确的控制,并且监控其运行状态。为此,设计在系统每个主机上部署Agent代理进程,通过教员席位负责运行调度的调度模块Scheduler对不同主机Agent发出相应的启动、关闭命令,完成训练组的运行控制。
为了(1)在模拟训练服务器中均衡运行不同训练组进程;(2)在不同席位中按模拟飞行员、模拟管制员的不同角色启动进程,航管模拟训练系统中进程以组作为调度的最小粒度。图2为三组训练服务进程通过三台模拟训练服务器为五个训练组提供服务的实例图。不同进程组的运行情况,用二元组〈进程组编号/训练组编号〉对应一个实际运行的进程组,被称为训练进程组。
Figure 2 Distribution of process group and training group in services clusters图2 服务集群中进程组与训练组的分布
进程组运行控制的基础是通过Agent实现对组内进程进行启动和退出的操作。通过一个进程对其他进程进行启动有多种方式,系统实验了三种启动方式:
(1)以fork+子进程中exec方式将进程作为Agent的子进程启动[1],启动后立即获得被启动进程的pid,后续可使用kill(pid,0)方式检查进程是否在运行。该方式的缺点是:①子进程将继承父进程Agent的诸多运行环境,如打开的文件描述符、控制终端、信息输出终端等,使父、子进程的输出将打印在同一个终端,因而无法分辨;②可能导致出现僵死进程,耗费系统资源。
(2)改进上述方法采用两次fork,可避免僵死进程的出现[2]。但是,该方法:①具有方法(1)中描述的①同样的缺点;②对使用shell脚本启动而在退出时使用实际运行pid进行管理的进程(如Java进程),该方法不能处理;③对启动后立即异常退出的进程,虽然在启动时就获取了pid,但由于该进程没有稳定运行,可能会对Agent的状态判断产生短暂的负面影响。
(3)使用 system()启动。system()函数执行了三步操作:①system()通过调用fork()由当前终端窗口的shell产生子进程;②子进程则调用execl()来启动一个全新的程序;③在父进程中调用waitpid()等待子进程结束。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略[3]。system()方式的另一个好处是可以衍生执行多种启动方式,如在某个终端中执行进程(如Linux环境下的终端仿真程序Gnome Terminal)等。这种方式的缺点是不能立即获得被启动进程的pid,需要使用进程定位技术获得pid。
目前主要基于第三种方式,为了更好地适应进程组运行控制的多样化需求,也提供了其他几种方式。
在使用system()启动时,既可以将子进程的控制台输出与父进程的控制台输出在一起,也可以启动一个终端窗口,将子进程的输出输出到该窗口;但是,该方法在启动时无法立即获取被启动进程的pid。本系统采用的策略是,在system()启动后的适应性时间(一般是该进程完成必要的初始化并开始稳定运行)后,使用与操作系统平台相关的进程定位方法获取进程pid[4]。如在Linux系统下,访问/proc的虚拟文件系统,/proc下以数字命名的子目录是进程目录,通过进程pid来访问该子目录,查看子目录中名为cmdline的文件,可以获得相关进程的启动时间、用户时间、系统时间等。
在指定训练组关闭时,各主机需要安全退出该训练组的相关进程,传统的做法是由Agent调用kill()函数对进程发出SIGKILL(值为9)信号。但是,在模拟训练服务器中,如模拟仿真核心需要在退出时将训练过程中生成的评估信息等存入数据库,如果强行退出将导致数据丢失。本系统可对进程的退出方式进行配置,对于需要保存数据的进程,先发出SIGTERM(值为15)信号等待适应性时间后,如该进程仍未退出,才最后对其发出SIGKILL信号[2]强制退出。
除了训练组相关的应用进程外,模拟训练系统中有一些进程需要长时间运行,直到系统完全关机。这类进程包括:基于NTP协议的对时进程、打印服务进程、消息中间件、集群管理进程、训练日志进程、系统监控进程等。这些进程可以放在Agent启动前运行,完成主机对时、初始化打印队列等系统的环境准备工作。如Agent在启动后发现这些进程没有启动,将首先启动这些进程。本系统将这些进程按主机分成不同的服务进程组,并由Agent确保其连续运行,当这些进程发生异常退出时,将自动重启该进程,以保证服务提供的持续性。
Agent进程通过网络接收教员席Scheduler模块发出的基本运行控制命令,这些命令包括:关闭全系统主机、停止全系统运行、启动本机指定进程组、退出本机指定进程组、对运行时异常退出的进程执行重启操作、按默认方式启动进程组以及关闭指定进程等。基于进程的运行控制要求,Agent应具有按照命令启动、退出、管理一台主机上一个或多个训练进程组的功能。Agent采用了划分命令队列、启动队列、重启队列和退出队列的管理方式实现了复杂的运行控制。每个队列设置一个定时器驱动,每秒钟轮询队列一次,执行控制命令。
命令队列接收教员席Scheduler模块传入的控制报文并解析命令。解析命令后,根据其类型,最终将其加入启动队列、重启队列,或是退出队列中执行。由于命令的执行要耗费一定的时间,驱动器每次只从队首取出一条命令,待这条命令执行完毕后将其从队列中删除。
5.1.1 优先级划分
命令队列区分了各种命令的优先顺序,为每个命令定义了不同的优先级及不同的处理函数。命令优先级PRI_MODE如下所示,其中优先级越高,数值越低。
enum PRI_MODE
EM_SHUTDOWN= 1,∥关机命令
EM_CLOSE_ALL_PROC= 2,∥关闭所有进程
EM_CLOSE_SVR_PROC= 3,/*关闭所有服务进程 */
EM_CLOSE_APP_PROC= 4,/*关闭所有应用进程*/
EM_START_DEFAULT_PROC= 5,/*启动默认进程*/
EM_CLOSE_PROC= 6,∥关闭某应用进程
EM_START_PROC= 7,∥开启某应用进程
升压调节器采用的是SP6641B,它具有很高的电池转换效率能够满足多个设备的电力供应,而且还具有极低的静态电流。
};
Figure 3 Flow chart of process management 图3 进程管理流程图
引入优先级主要是为了避免如下情况的发生:教员席位可以不断发出各种运行操作命令,这些命令的执行和完成都需花费一定时间,在这个时间里如果教员席位不断恶意地发出命令,则系统有可能会不断关闭和启动,影响系统稳定性。所以,Agent收到命令后, 应先根据命令报文的类型及当前队列中命令,判断是将新命令加入命令队列,还是忽略这个命令。对PRI_MODE中的前四种命令,处理规则为:(1)当前没有要执行的命令,直接将新命令加入到命令队列;(2)当这些命令加入时,删除所有低优先级命令;(3)当这些命令加入时如果有更高优先级命令存在,则放弃加入。这样能让教员在系统完全退出、就绪的状态下进行下一个系统级操作。而后三种命令的判断规则为:(1)当前没有要执行的命令,直接将新命令加入到命令队列;(2)当前有正在执行的命令,新命令的类型是启动默认进程,若新命令的优先级最高,则新命令加入队列中,但不删除队列之前的命令;(3)当前有正在执行的命令,新命令的类型是关闭某应用进程,若新命令的优先级最高,则新命令加入队列中,并且删除当前命令队列中对同一组进程的启动命令,但不删除队列之前的其他命令;(4)当前有正在执行的命令,新命令的类型是启动某应用进程,若命令队列中存在关机命令、关闭所有进程的命令,则忽略新命令,否则,将其加入队列。
5.1.2 进程信息管理
由于命令队列在执行命令时,将命令中涉及到的进程交付给了其他三个队列,故其本身并不知道进程的执行结果,因此把进程划分为九种状态。各队列根据进程的执行情况,调整进程的状态。例如,进程暂时不能被加入到启动队列中(一种情况是,需等到退出队列中的进程全部退出后,进程才能加入启动队列),这时进程被设置为PS_TOSTART状态;在进程成功加入启动队列后,进程被设置为PS_INSTART状态;启动队列中的进程成功启动后,又被设置为PS_RUNNING状态。图3简要描述了进程在启动列表、重启列表及退出列表中的转换过程。
进程状态PROC_STATE如下所示:
enum PROC_STATE
{
PS_NONE= 1,∥初始化状态
PS_INSTART= 2,∥在启动队列中
PS_TOSTART= 3,∥要加入到启动队列
PS_STARTING= 4,∥正在启动
PS_RUNNING= 5,∥运行中
PS_TOEXIT= 6,∥要加入到退出队列
PS_STOPPED= 7,∥已经停止
PS_INEXIT= 8,∥在退出队列中
PS_TERMING= 9∥正在停止
};
除了进程状态的设置外,系统还设计了专门的进程管理器。进程管理器存储了Agent系统中所有服务进程和应用进程的最新信息,包括进程状态信息。因此每次进程状态发生变化时,都要及时修改进程管理器中相关信息。通过定时器驱动定时检查所有应用进程的状态,对已经退出的进程,将其从进程管理器中删除;对等待启动和等待关闭进程,将其加入到启动或退出队列中。由于运行中的进程可能会发生异常终止的情况,进程管理器将正常退出的进程标记为KILLED(数值1),异常退出的进程的标记为UNKILLED(数值0)。通过查看进程退出标记的值及检测进程是否还在运行中,来判断进程有无异常终止。对异常终止的进程,进程管理器将其加入到重启列表中重新启动。
启动队列接收命令队列和进程管理器的进程启动请求,将待启动进程加入到队列中。与命令队列不同,启动队列一次处理完所有到来的请求,而不是逐一处理。启动和重启队列中的进程状态只能是INSTART和TOSTART两者之一,启动成功的进程状态修改为PS_RUNNING,并从该队列中移除。进程执行启动动作后,若超过了60秒仍未启动成功,则将进程加入到重启队列中等待下一次启动,同时从启动队列中删除。启动队列和重启队列中的进程要求无重复;同时,启动进程之前需判断进程的存活状态,使用kill(pid,0)得到进程是否已经在运行,确保不会再次启动已经在运行的进程。
由于Agent支持在一台主机上进行不同训练组的切换功能,如席位主机从模拟飞行员切换为模拟雷达管制席位,这样在启动新进程组时,若本机上前一组进程仍然在运行,需要进行进程组的切换。进程组的切换要求先将前一个训练组的进程加入退出队列并杀死,再将待启动的新训练组进程加入启动队列进行启动。因此,在命令队列将待启动进程加入启动队列之前,需保证退出队列的稳定态(即待退出的进程已经全部杀死)。如果退出队列是非稳定态,则通过定时器驱动不断检测其状态,直到达到稳定态,才能将待启动进程加入启动队列中。
命令队列处理退出进程组、退出进程、全系统关闭、训练组切换等命令时,需要杀死一个或多个进程,所有待杀死的进程被放入到退出队列中。Agent提供了两种杀死进程的方式:一种基于SIGKILL信号,一种基于SIGTERM信号。由于SIGTERM信号不能立即杀死进程[2],故设置超时时间用于判断SIGTERM是否超时。超时后,使用SIGKILL信号立即杀死进程。在对进程发出SIGTERM信号后,该进程继续保留在退出队列中,同时记录下SIGTERM信号的发出时间,进程状态也由PS_INEXIT变为PS_TERMING。退出队列的定时器轮询队列时,若检查到状态为PS_TERMING的进程在超时时间之内死亡,则将其从退出队列中移除;若检查到该进程已经超时却仍然存活,则对该进程发出SIGKILL信号,强制杀死进程并将该进程移出退出列表。与启动队列类似,对退出队列中进程发出杀死信号之前,也要判断其存活状态,避免对已死亡进程的不必要操作。
当采用进程定位方式管理进程时,如果对操作系统中所有的进程进行定时的轮询,会导致Agent的CPU占用率增高。为了减少Agent的CPU占用率,可以采用将驱动器运行时间间隔加长的方法,但这样会降低进程检测的灵敏度。经过优化的Agent设计为:在上述四个队列均为空时(这是实际系统运行时大部分时间的状态),将这种状态定义为系统稳定态,只对被进程管理器管理的进程用kill(pid, 0)进行存活的判断,该方法有效降低了CPU占用率。
系统中各主机的Agent定时向Scheduler模块发送训练组的信息和状态,每秒钟更新一次。更新的信息包括:发送主机、是否处于稳定态、当前CPU使用率、当前主机运行了的分组数目、当前本机上运行的分组分别是哪些等。这个报文只在Agent处于稳定状态的时候进行发送,如果主机目前状态不稳定,则会选择发送上一次稳定态的信息;同时,指示当前的主机状态为不稳定,以便Scheduler暂时不使用这些信息进行进程组的启动。Scheduler在启动后和运行中如果发现某个训练组发生组缺失导致该训练组不能正常运行,将选择在某个训练服务器上重启该缺失的训练进程组。图4是对Agent与Scheduler关系的简单描述。
Figure 4 Flow chart of scheduler processing 图4 Scheduler处理流程图
对于运行于模拟仿真服务器上的各进程组,Scheduler在:(1)存在进程异常退出;(2)某台服务器CPU占用率高于阈值;(3)训练组启动后需要对当前的服务器运行的训练服务进程组在多个服务器间进行调度,尽可能保持各航管模拟训练服务器的CPU负载均衡。Scheduler在负载均衡算法运算后根据结果执行调度动作,目前执行调度切换的时间为3秒,可以满足系统的需要。
Scheduler采用如下方法进行调度[5~8]:
(1)接收Agent报告的各训练服务器中各进程的CPU占用率,并得到以进程组为单位的CPU占用率,本系统中使用的占用率为小数点以后两位。
(2)检查各服务器CPU占用率是否存在较大的差值,如果存在则进行负载均衡动作即进入第三步。
(3)定义一个系统中不可能出现的单机最大负载值MAX_LOAD,当系统满足了进行负载均衡切换的条件后,首先在集群系统中查找当前负载小于参数所指定负载(MAX_LOAD)的服务器中的最高负载的服务器,由它们组成一个“最高负载服务器链表”,然后依次遍历链表的每个服务器上的进程组。假设某最高负载服务器上运行的第T个训练组的第N号进程组,于是在当前运行的服务器组中查找具有最低负载的服务器。如果找到则计算两台服务器切换前后的负载差值,如果切换后的差值小于切换前的则说明切换后负载更趋向均衡,那么将该进程放入进行切换的队列中,依此类推,直至遍历完该主机上的所有进程组为止。如果在该主机的遍历过程中发生了进程组的变化,那么则跳出对“最高负载服务器链表”的循环,因为由于发生了切换,所以此时在最高负载服务器链表中的服务器负载总和可能已经不再是系统中的最高负载了,因此重新进行处理,进入下一次递归,此时传递的参数仍然是MAX_LOAD,直至遍历完所有最高负载主机上的进程组。如果遍历完成而没有发生切换,那么就将此时系统中的最高负载总值作为参数传递到下一次递归运算中。递归的终点是:在服务器组中再也找不到具有比传入参数更小的负载总值的服务器了。此刻训练服务器组中各主机的负载达到了最大程度的平衡。图5作为算法流程图详细描述了这一步的算法流程与步骤。
在推选出各服务器要进行切换的服务器进程组以后,Scheduler会通知这些进程组完成进程运行状态检查点的保存,并使用调用各服务器Agent完成检查点信息到目标服务器的传输,在状态完成传递以后,统一发起进程组在目标机的启动工作。在进程组跨主机迁移的过程中,Scheduler对外部对集群的控制命令不予处理,对于Agent心跳报文也只记录收到的时间,这样做的目的是为了避免切换过程中引起新的调度。
Figure 5 Flow chart of training server load balancing algorithm图5 训练服务器负载均衡算法流程图
如4.1节的方法一所述,以fork+子进程中exec方式将进程作为Agent的子进程启动的方式可能导致出现僵死进程。当子进程终止时,它释放资源,并且发送SIGCHLD信号通知父进程。父进程接收SIGCHLD信号,返回子进程的状态,并且释放系统进程表资源。如果子进程先于父进程终止,而父进程fork()之前既没有按照SIGCHLD信号调用处理函数waitpid()等待子进程结束,又没有显式忽略该信号,则子进程成为僵死进程,无法正常结束。此时即使是以超级用户身份调用kill-9命令也不能杀死僵死进程。直到其父进程结束,子线程成为孤儿进程,由守护进程init领养孤儿进程,当孤儿进程结束时init为其释放进程表资源。
实现系统时发现,即使处理了SIGCHILD信号,调用waitpid()等待子进程结束,当被关闭进程组的进程过多时,如达到几十个,由于UNIX系统对同一信号没有排队机制,总有几个子进程无法正常关闭,出现僵死进程。不仅耗费系统资源,也可能因为僵死进程没有释放系统资源,如网络通讯的文件描述符绑定的网络端口,导致同一进程无法再次启动,给系统的进程管理带来很大麻烦。经过多次尝试新方法,最终4.1节的方法三良好地适应了系统进程组控制管理的需求。
以上介绍了基于代理的航管仿真训练系统运行控制的设计与实现,通过对命令队列、启动队列、重启队列和退出队列的设计,通过对收到的命令按优先级进行处理,配合负载均衡算法,有效处理了航管训练系统中运行控制的问题,也实现了对进程运行状态信息的监控。
在航管仿真训练系统的后续研发中,随着训练功能的日益丰富,对基于代理的运行控制提出了更多的要求,其XML表述的配置内容更加丰富,对于命令的处理也由单纯的依靠优先级判断变为基于有限状态机进行命令和运行状态转换,系统功能日益完善和提升。
[1] Ferber J, Gutknecht O. A meta-model for the analysis and design of organization in multi-agent systems[C]∥Proc of the 3rd International Conference on Multi-Agent Systems (ICMAS’98), 1998:258-266.
[2] Raymond E S. The art of Unix programming[M]. 2nd edition. Beijing:Electronic Industry Press, 2006.(in Chinese)
[3] Stevens W R. Advanced programming in the Unix environment[M]. Beijing:Posts & Telecom Press, 2000.(in Chinese)
[4] Stevens W R. Unix network programming volume 2:Interprocess communications[M]. Beijing:Posts & Telecom Press, 2010.(in Chinese)
[5] Lin Jun-xuan, Hu Shu. System and process information access in tru64 Unix server[J]. Computer Applications, 2006, 26(z2):316-318.(in Chinese)
[6] Forrest S,Hofmeyr S A,Somayaji A,et al.A sense of self for Unix processes[C]∥Proc of the 1996 IEEE Symposium on Security and Privacy, 1996:120-128.(in Chinese)
[7] Xiang Jian-jun, Bai Xin, Zuo Ji-zhang. A multipletask load balancing algorithm used in real-time cluster system[J]. Computer Engineering, 2003, 29(12):36-38.(in Chinese)
[8] Sekar R, Venkatakrishnan V N. One-way isolation:An effective approach for realizing safe execution environments[C]
∥Proc of the ISOC Network and Distributed Systems Symposium, 2005:265-278.(in Chinese)
[9] Bass L, Clements P C, Kazman R. Software architecture in practice[M].2nd ed. Beijing:Tsinghua University Press, 2003.(in Chinese)
附中文参考文献:
[2] Raymond E S. UNIX编程艺术[M]. 第二版.北京:电子工业出版社,2006.
[3] Stevens W R. UNIX高级环境编程[M].第二版.北京:人民邮电出版社,2000.
[4] Stevens W R. UNIX网络编程(卷二)进程间通信[M]. 北京:人民邮电出版社,2010.
[5] 林俊萱,胡术.Tru64 Unix服务器中系统及进程信息的获取方法[J]. 计算机应用,2006, 26(z2):316-318.
[6] Forrest S,Hofmeyr S A,Somayaji A,等.自我感觉UNIX进程[C]∥ IEEE计算机学会安全与隐私研讨会, 1996:120-128.
[7] 向建军,白欣,左继章.一种用于实时集群的多任务负载均衡算法[J]. 计算机工程,2003, 29(12):36-38.
[8] Sekar R, Venkatakrishnan V N. 单向隔离:实现安全的执行环境的有效途径[C]∥国际互联网协会网络与分布式系统安全座谈会, 2005:265-278.
[9] Bass L, Clements P C, Kazman R.软件构架实践[M].第二版.北京:清华大学出版社,2003.