陳虹宇,胡 術,董志強,李科磊,殷 源
(1.四川大學計算機學院,四川 成都 610064;2.四川大學國家空管自動化系統技術重點實驗室, 四川 成都 610045;3.空軍裝備研究院雷達與電子對抗所, 北京 100039)
我國成都、西安區域管制中心引進了西班牙英德拉(Indra)系統,該系統配有模擬仿真訓練系統,使用與真實系統一樣的設備與人機界面來訓練管制人員。Indra系統是目前中國引進的最先進的航管系統?;诒疚奶岢龅姆椒ㄗ灾餮邪l的系統與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.