神盾作戰系統計算機(2)─程式語言

──by Captain Picard


(1)  (2)  (3)  (4)  (5)  (6)

CMS-2程式語言

CMS-2(N/UYK-CMS)是1960年代末期美國海軍為NTDS開發的一種嵌入式系統編程語言,CMS意味著「編譯器監控系統」(Compiler Monitor System)。CMS-2是早期開發標準化高階電腦程式語言的嘗試,希望能提高程式碼的可移植性與重用性。在CMS-2出現前,美國海軍船艦上計算機程式幾乎全都是與機器語言對應的組合語言(Assembly),不僅開發極慢,且不易閱讀與維護,並且跟所在的電腦高度綁定,無法直接疑直到不同電腦上編譯使用。

CMS-2由為位於加州聖地牙哥的太平洋艦隊電腦程式設計中心(FCPCPAC)發展,於1968年由計算機科學公司(Computer Sciences Corporation)在Intermetrics的協助下完成實作。CMS-2語言持續發展,最終支援多種美國海軍軍規電腦,包括32位元的 艦載電腦AN/UYK-7與AN/UYK-43,以及16位元的AN/UYK-20、AN/UYK-44艦載電腦乃至於航空機使用的AN/AYK-14 16位元電腦。

對應指令集不同的處理器(例如32位元的UYK-7以及16位元的UYK-20),CMS-2也有不同系列來指定編譯的目標平台。例如,CMS-2M是針對海軍16位元處理器,如航空機使用的AN/AYK-14計算機或AN/UYK-20。

CMS-2的設計主旨是強調模組化,允許對整個系統的部分內容進行獨立編譯。其語言是「陳述句」(statement)為導向的。原始碼採自由格式,在自行安排上有較大自由度。資料類型包括定點數、浮點數、布林值、字元與狀態值,並允許直接引用與操作字元字串及位元字串(bit strings)。程式中亦可包含符號化機器碼,稱為「直接程式碼」(direct code)。
CMS-允許程式設計師直接控制處理暫存器(processor register),以便於處理雷達數據的大量位元組(bits),效率極高。

CMS-2的語法風格類似早期商規語言Cobol和Fortran的綜合體,例如定義一個程序(Procedure)時使用 PROC 關鍵字,並透過EXEC來執行。CMS-2原始碼由兩種基本類型的陳述句(statements)組成:宣告式陳述句 (Declarative statements)向編譯器提供基本控制資訊,定義與特定程式相關的資料結構;而動態陳述句 (Dynamic statements )使編譯器產生可執行的機器碼。CMS-2資料的宣告式陳述句被分組成名為「資料設計」(data designs )的單位,包含對暫時與永久資料儲存區、輸入區、輸出區及特殊資料單位的精確定義。

CMS-2對資料執行動作或計算的動態陳述句被分組為「程序」(procedures)。資料設計與程序進一步組合成CMS-2 程式的「系統元素」,編譯器將這些系統元素結合為一個「編譯時期系統」(compile time system ),可以獨立運作,也可以是更大型程式的一部分。CMS-2支持系統程序(SYS-PROC)與局部程序(LOCAL-PROC),這在 1970 年代是很前衛的設計。

CMS-2的資料單元(Data Units)核心是SWITCH和TABLE,可精確控制電腦內部記憶體位址,必須在程式裡指定每一個欄位(Field)佔用多少空間(以Bits為單位),使之能直接處理雷達追蹤檔案(Track Files)。

ADA語言

1.起源與歷史

在1970 年代中期,美國國防部(DoD)對於軍方內部存在過多種類編程語言的情況感到擔憂(此時已經多達450種),其中許多語言已經過時或依賴特定硬體,且沒有任何一種支援信息安全的模組化程式設計。在1975年,國防部成立了一個名為「高階語言工作小組」(HOLWG)的編組,希望尋找或建立一種普遍適用於美國國防部及英國國防部需求的程式語言,來減少軍種語言的數量。

HOLWG審查了當時存在的各種程式語言,在1977年提出結論報告,認為雖然許多現成語言都通過正式審查,但沒有任何一種現有語言符合國防部的需求規範。美國國防部在1978年在「國防部通用高階語言計畫」中建立了軍種通用的程式語言規範,這些規範歷年來依序出現的代號包括「稻草人」(Strawman)「木頭人」(Woodenman)、「錫人」(Tinman)和「鐵人」(Ironman ))。整理出來的規範聚焦於嵌入式電腦應用的需求,並強調可靠性、可維護性和效率,還包含了例外處理設施、執行時期檢查和平行運算。

由於沒有任何現成的編成語言能滿足這些標準,因此國防部展開競標,發展一種更接近目標的語言。總共有四個團隊參與提案,包括Benjamin Brosgol公司領導的Intermetrics(提案代號為Red)、由法國電腦科學家Jean Ichbiah領導的Honeywell(提案代號為Green) 、由John Goodenough領導的SofTech(提案代號Blue)和由 Jay Spitzen領導的SRI International (提案代號Yellow)。

經過國防部審查後,Red和Green兩個提案進入第二階段。1979年5月,美國國防部正式選擇由Honeywell團隊的「Green 」提案獲勝,並以英國在19世紀的計算機領域先驅──勒芙蕾絲伯爵夫人(Augusta Ada King,通常被稱為 Ada Lovelace )的中間名而命名為ADA。由Jean Ichbiah領導的團隊到1987年完成美國國防部的設計合約,最後完成的ADA語言緊密(雖然並非完全)遵循了國防部最終的「鐵人」規範。ADA的軍用標準參考手冊於1980年的12月10日(勒芙蕾絲伯爵夫人的生日)獲得批准,編號為ANSI/MIL-STD-1815 ,以向勒芙蕾絲伯爵夫人的出生年份致敬,這是ADA發佈的第一個版本。

ADA早期開發過程吸引了整個程式設計領域的極大關注。其支持者預測它可能成為通用程式設計的主流語言,不僅限於國防相關工作。第一個通過驗證的ADA實作是NYU Ada/Ed編譯器,於1983年4月11日獲得認證。到了 1980 年代末和 1990 年代初,ADA編譯器的效能有所提高,但仍無法充分發揮其能力方,包括其任務模型(tasking model)與大多數即時系統程式所習慣的模型不同。到1991 年,美國國防部開始要求所有軟體開發工作都使用ADA,即「ADA強制令」(ADA mandate );然而實務上的困難,使該規則經常獲得豁免。由於接著國防部開始採納商用現成品(COTS)技術,必須使用商務上主流的C++語言,因此「ADA強制令」於1997年被實質取消。

其他北約(NATO)國家也存在類似要求:由於ADA高安全性的特性,有些國家規定涉及指揮與控制等功能的系統必須使用ADA。在瑞典、德國和加拿大等國,ADA曾是國防相關應用的法定或首選語言。

歷史

最早的ADA主要是針對嵌入式系統。1995年出現的ADA95修訂版超越了最初的「鐵人」(Steelman)需求,除了嵌入式系統外,也鎖定了通用型系統,並增加了支援物件導向程式設計(OOP)的功能。

第一版ADA在1980年以ANSI/MIL-STD 1815;修訂版的ANSI/MIL-STD 1815A在1983年發佈,修正了第一版的一些一致性錯誤。在1987年,MIL-STD 1815A未經進一步修改即成為ISO標準,通稱為ADA 83 (依據ANSI採納日期),有時也被稱為ADA 87 (依據 ISO 採納日期)。

ADA 95於1995年2月發佈,Intermetrics 的 S. Tucker Taft在 1992 年至 1995 年間設計,改良了對系統、數值、金融與物件導向程式設計支援。ADA 95是第一個ISO標準的物件導向程式語言。隨後ADA的重大發佈包括2007年3月9日的ADA 2005(ISO/IEC 8652:1995/Amd 1:2007,因新標準的設計工作於2004年完成)、2012年12月發佈的ADA 2012(ISO/IEC 8652:2012)以及2023年5月2日發佈的ADA 2022等。

特徵

ADA 是一種結構化、靜態強型別、指令式、物件導向的高階程式語言,深受Pascal及其他語言啟發。ADA支持「契約設計」(DbC),具有極強的型別系統、顯式並行(explicit concurrency)、任務(tasks )、同步訊息傳遞、受保護物件以及非決定性。ADA以安全著稱,能透過編譯器發現執行時期錯誤,進而提升程式碼的安全型與可維護性。

ADA具備模組化編程機制,即使用軟件包(packages )、執行時期檢查、平行處理(任務、同步訊息傳遞、受保護物件與非決定性選擇語句)、例外處理以及泛型。ADA 95 增加了對物件導向程式設計的支援,包括動態派發。

ADA是為了開發極大型軟體系統而設計的;使用的依賴包(packages)可以分開編譯,也可以在沒有實作的情況下單獨編譯
檢查一致性。這使得項目在實作開始之前的設計階段,就能及早發現問題。語法上,ADA盡量減少執行基本操作的方式選擇,並偏好使用英文關鍵字(例如or、else、and、then)而非符號(例如 || 和 &&)。ADA仍使用基本的算術運算子 +、-、* 和 /,但避免使用其他符號。ADA程式碼區塊由declare、begin 和 end 等單字界定,其中 end在大多數情況下後面會接著它所關閉的區塊關鍵字(如 if...end if、loop ... end loop)。這在條件區塊的情況下,可以避免C或Java其他高階語言中,在巢狀if表達式配對中發生懸空else的錯誤的問題。

ADA的動態記憶體管理具備高階且型別安全的特性,沒有泛型或無型別指標,也不會隱含宣告任何指標型別。所有動態記憶體分配與釋放必須透過顯式宣告的「存取型別」(access types )來進行。每個存取型別都關聯一個「存儲池」(storage pool ),負責處理記憶體管理的底層細節;程式設計師可以使用預設存儲池,也可以定義新的存儲池。語言還提供了編譯時期與執行時期的「可達性檢查」(accessibility checks),確保存取值(或pointer)的壽命不,會超過它所指向物件的型別壽命。

雖然ADA語意允許對不可達物件進行自動垃圾回收(garbage collection),但大多數實作預設不支援此功能,因為這會導致即時系統中出現不可預測的行為。ADA支援一種有限形式的「基於區域的記憶體管理」,銷毀存儲池同時也會銷毀該池中的所有物件。

ADA一大特點是極強大的錯誤檢查機制,編譯器支援執行時期檢查,以防止存取未分配的記憶體、緩衝區溢位錯誤(buffer overflow errors (N/UYK-Ada))、範圍違規、差一錯誤(off-by-one errors)、陣列存取錯誤及其他可偵測的程式錯誤。為了提高執行效率,這些檢查可以被停用,但它們通常能被高效地編譯。

長處

ADA具備支援安全關鍵(safety-critical)系統的特性,包括編譯器支持檢查執行時期錯誤。基於這些理由,許多關鍵系統(critical systems)都由ADA開發,在這些系統中任何異常都可能導致非常嚴重的後果如傷亡、傷害或重大的財務損失,例如航空電子、空中交通管制、鐵路、銀行、軍事與太空技術。例如波音777客機的主要線傳飛控軟體,包括歐洲戰鬥機(Eurofighter)、瑞典SAAB JAS-39獅鷲(Gripen)戰鬥機、美國F-22戰鬥機的線傳飛控系統,美國F-14D雄貓戰鬥機的替代飛行控制系統(DFCS)、歐洲亞利安(Ariane)4/5 商業火箭的控制系統等,都用ADA撰寫。加拿大自動化空中交通系統(Canadian Automated Air Traffic System)的原始碼是100萬行的ADA軟體,具備先進的分散式處理、分散式資料庫以及物件導向設計。此外,英國下一代「臨時未來區域控制工具支援」(iFACTS) 空中交通管制系統也是使用 SPARK ADA 設計與實作的。法國TGV高速鐵路的TVM 駕駛室內訊號系統,以及巴黎、倫敦、香港與紐約市的地鐵系統,也都使用了ADA語言撰寫的控制系統。

相較於C++、Java等業界常見的編譯式物件導向高階語言,ADA許多特性無人能及,包括有效確保程式的正確或者多個併行程序的精準同步等。

1.平行程序的同步

對於軍事系統而言,ADA嚴格的類型檢查和內建的「工作」(Tasking)處理能力,非常適合處理雷達追蹤、目標分配等需要分時多工且絕對不能錯誤的即時任務。如果以C++實作這種多線程(Threads)併行的程式,需要依賴外部Library(如Pthreads),而在ADA中Task是原生語法。

ADA支持併發處理 (Rendezvous),支援讓兩個獨立執行的任務程序精確同步:例如,「雷達追蹤任務」與相關的「火控任務」兩個程序可以通過Rendezvous,保證兩者在數據交換完成前的絕對同步的,可以有效防止高速運算中不同程序發生競逐情況(Race Condition)。如改用C/C++語言執行,需要開發者自己實作複雜的Lock或Semaphore機制來正確同步。

2.強大的數值範圍檢查與例外處理

ADA內建「範圍檢查」與「例外處理」 (Exceptions),允許定義非常精確的類型,例如宣告一個浮點數變數時就限定數值範圍在特定區間(例如儲存飛彈航向的浮點數在0至360.0度間);如果程式算出的飛彈航向變成了361度,Ada在編譯或執行當下就會立即報錯;反觀C++等商用高階語言就不具備此種能力,如果使用者自己在程式邏輯沒有阻擋,錯誤的數值會繼續流向其他次系統(如飛彈引信),進而引發連鎖的災難。

3.結構化的記憶體存取

ADA的Representation Clauses允許開發者直接定義變數在記憶體中的位元佈局(Bit-level layout);如果在C++通過pointer實作,就必須通過位元運算來正確讀取記憶體中相關資料的格式,比較不直觀且容易出錯。對於需要存取AN/SPY-1D雷達暫存器資訊的底層驅動程式而言,Representation Clauses提供了很高的實用性。

4.嚴謹的結構定義

ADA的「契約式設計」 (Design by Contract)理念嚴格定義程式結構的嚴謹性。ADA的強型別(Strong Typing)可以避免將整數加入浮點數,例如將公尺(整數)變數誤加到英尺(浮點數)變數,否則編譯器會報錯;先前Mariner 1火箭就曾發生程式一個參數符號錯誤,導致火箭墜毀爆炸。即便之後美軍軟體開發者改用C++等商規語言,在實作層面上也繼續依循ADA 的嚴謹性,例如使用強大的模板類(Template Classes)來防止單位換算錯誤。

ADA在神盾系統的應用

在神盾Baseline 5的現代化過程中,許多核心模組(如計算機輔助調度)開始從以往的CMS-2轉用ADA 83編寫。而從神盾Baseline 6/7時期(引進AN/UYQ-70顯控台等商規現成技術)時,核心軟體都由ADA撰寫;許多原本在UYK-7/43等舊軍規電腦的CMS-2核心戰術火控邏輯,在ADA程式中「封裝」起來;即使換到 COTS商規伺服器上,這些底層代碼仍能穩定運行。

另外,神盾發展反彈道飛彈(BMD)初期,很大一部分攔截演算法使用ADA 95實作,因為當時ADA對於浮點運算的精確度與即時中斷的處理已經很可靠,而此時商用的C++在此尚未成熟。
  

在1980年代末到1990年代神盾系統從CMS-2過渡到ADA的時期,引進「物件導向」的概念,這才使得之後進一步「虛擬化」與「軟體解耦」成為可能。如果沒有ADA時期建立的嚴謹程式結構,若直接將軟體從最初嵌入式系統的CMS-2跳到C++ ,可能會導致系統極其不穩定。

雖然到2010年代完全商規化的神盾系統(如神盾Baseline 9以及TI-12/TI-16硬體架構),軟體已經全面轉向C++、Java(用於人機介面)等商規主流高階物件導向語言,但先前用ADA撰寫的軟體並沒有完全消失。美國海軍在神盾系統現代化過程建立的通用軟體庫 (Common Source Library,CSL)中,仍有部分經過數十年驗證、極其穩定的底層火控邏輯是ADA撰寫(有些甚至可追溯到更早的CMS-2時代),只是被封裝在C++的Wrapper下繼續運作。

此外,在某些涉及關鍵安全(Safety-Critical)領域的環節如飛彈控制,SPARK Ada(ADA 的一個衍生應用,可進行數學證明)依然是確保軟體開發「絕不出錯」的首選。

商規C/C++語言進入軍用系統

雖然美國國防部曾寄望ADA語言成為將來所有美軍軟體系統的主流,但其開發流程複雜而緩慢,其任務模型(tasking model)與大多數即時系統程式所習慣的模型不同,之後並未成為軍方或商規的主流系統。相反地,隨著Unix/POSIX等商規工作站在民間普及,1990年代初期軍方非核心系統(如氣象、地圖顯示、行政系統)開始使用C語言編寫。使用C語言的開發速度比ADA快數倍,而且更容易找到人才。

1990年代加州矽谷IT產業快速成長(包括Intel、Microsoft崛起),美國軍方也放棄研發週期長且完全不符經濟效益的專屬軍規系統,改引進與民間同步的商規現成(COTS)系統,因此C/C++也自然更廣泛地進入美軍的軟體開發領域。

例如,神盾Baseline 6(約1990年代末)正式引進COTS技術的AN/UYQ-70工作站,雖然核心的戰術決策、火控導引軟件仍都由ADA、CMS-2撰寫的軟體並執行在舊的軍規UYK-43電腦上,但由UYQ-70承擔的顯示、通信等部分都是C/C++語言撰寫。到2000年代初的Baseline 7,商規組件的計算機單元(主要是UYQ-70系列的工作站作為運算單元)正式進入核心戰術火控領域(例如反彈道飛彈任務的攔截運算),程式自然變成C/C++的天下。

C/C++語言雖然在安全特徵上不如ADA,但其指針(Pointer)對記憶體的直接存取,在需要處理大量傳感器原始數據(Raw Data)時是最有效率的操作。更重要的是,廣大的民間開發社群為C/C++實作了上百萬個高效的C Library(包含TCP/IP通信協議、圖形界面等),使用社群稀少的ADA根本沒有同等的資源,各國軍方發現使用ADA的開發與維護成本高到無法忍受。而且IT相關科系畢業的人才中,C/C++語言是普遍的人才,但只有極少的人接觸過ADA。 

 

ADA與C++程序的協作

在現代神盾系統的CSL庫的架構下,ADA撰寫的軟體資源通常被視為「受信賴的核心模組」,與C++ 撰寫的程式主要透過以下兩種方式溝通:

1.兩程式的語言綁定 (Language Binding):在ADA端結合外部C/C++程式使用pragma Export,而在C/C++程式端綁定外部ADA軟體則使用extern。這種作法讓新撰寫的C++ 軟體可以像呼叫內部函式一樣,去執行已經穩定使用了30年、以ADA撰寫的火控運算邏輯。

3.基於資料分佈服務(Data Distribution Service,DDS)的中介方式(The Middleware Approach):在TI-12/16硬體架構的虛擬化環境中,不同的ADA模組與C++ 模組可能被封裝在不同的容器(Container)或虛擬機中各自運作,這些容器之間以標準信息格式在DDS網路上交換(例如ADA撰寫的模組完成射控解算後,將結果數據打包成標準化的DDS訊息發送到網路,另一端執行C++程式的顯示服務或飛彈射控模組從DDS網路抓取對應主題(Topic)的信息數據即可,完全與這些數據從什麼語言撰寫的軟體產生。如此就實現程式語言層級的解耦(decoupled)。

將程式從ADA語言轉換到C++,並不是只有轉換原始碼重新編譯這麼簡單,因為兩者的多進程同步以及記憶體管理有許多根本不同。在神盾Baseline 7發展到Baseline 9軟體轉換過程中,發生過幾次測試事故;一個著名案與記憶體管理 (GC) 有關。在一次海上攔截測試事件中,SPY-1雷達的螢幕突然被大量不存在的假目標充滿,系統算力瞬間飽和。

當時,由ADA撰寫的舊有核心處理程序有ADA支持的嚴格記憶體分層,但是新撰寫負責顯示的C++中介軟體處理來自於ADA程序的目標物件時,沒有正確釋放C++端的指標(Pointer)。進行高強度測試、同時追蹤50個目標時,這個C++程序發生了記憶體堆疊溢位 (Stack Overflow)。雖然ADA撰寫的核心邏輯還在跑,但C++撰寫的 顯示層崩潰了。這次事件的教訓是在進行跨語言程式溝通時,C++必須使用「智慧指針 」(Smart Pointers)」來模擬 Ada 的安全性。

另一次案例是與多程序同步,ADA的Rendezvous機制支持讓兩程序同步,而C++ 的多線程通常是非同步的。在一次模擬對抗飽和攻擊的測試中,一個由C++撰寫的數據分派模組因為沒等到由ADA火控模組的「確認回覆」,就無限期地掛佔據了整個通訊鏈路。

而在神盾虛擬化之後,原本ADA程式的核心火控任務(Task)被包裝成container或虛擬機在Linux虛擬化環境執行時,海軍必須確保ADA的Task在Linux內核中依然擁有最高執行權。

例如,一個低優先級的C++繪圖任務佔用了某個系統資源鎖,而高優先級的ADA火控任務需要這個資源;在普通的Linux作業系統中,Kernel會等C++任務執行完才會載入ADA火控任務來執行。在Linux作業系統加上PREEMPT_RT實時補丁(Real-time Patch)之後,Linux會暫時將C++任務的優先級提昇到與ADA任務相同,讓它盡快跑完並釋放資源鎖,使ADA任務的延遲最小化。
而對於需要硬中斷(Hard IRQ)的任務,更需要PREEMPT_RT補定才能實現。一般的Linux內核在執行某些系統調用時是不能被中斷的,這對於飛彈導引等關鍵火控工作而言是致命的。而加上PREEMPT_RT補丁讓Linux內核幾乎變得「全透明」;例如,ADA的任務收到來自雷達的硬中斷 (Hard IRQ)指令時,就能強行中斷正在運行的Linux內核程式碼,確保雷達導引指令在10微秒(μs)內發出。

而邁入虛擬化環境之後,透過虛擬機或容器(container)的隔離,就更能保障不同程式碼撰寫的應用安全地運作。例如 TI-16硬體架構利用了Linux Container (LXC) 或Docker 的早期軍用變體,其中ADA核心容器運行在一個經過極度精簡、具備實時補丁的獨立CPU核心上;而運行C++程式的 業務容器則運行在其他核心,透過資料分送服務(DDS)與ADA容器通訊。通過虛擬機的隔離,即便C++撰寫的介面程式當機,底層Linux虛擬平台的隔離機制也能確保其他虛擬機(如ADA撰寫的火控)不受影響,繼續原本的火控導引工作。

神盾Baseline 10:考慮引進Rust語言

2. 在2020年代美國海軍發展神盾Baseline 10 之後,美國海軍研究辦公室(ONR)與洛克希德·馬丁公司(Lockheed Martin)討論是否要引進從C++系列新興的Rust語言。

Rust在維持等同於C++效能之上,又內建了一些類似過去ADA專屬的安全機制。例如 Rust 的核心特性「所有權」(Ownership)系統,能從編譯階段就杜絕掉C++操作指標容易發生的 記憶體洩漏 (Memory Leak) 與懸空指標 (Dangling Pointer)問題;先前開發神盾Baseline 9時期,使用C++撰寫與研究往往發生這種問題。而Rust因為不需要類似Java的圾回收機制(Garbage Collection),意味沒有「停頓時間」,對於攔截高超音速飛彈這種需要微秒級確定性(Determinism)的任務至關重要。因此在開發處理新興高超音速威脅(在大氣層上緣飛行、具備高超音速飛行以及氣動變軌能力而讓彈道更難預測)的新演算法時,Rust 會是首選,能保證程式碼在極端高負載下絕對不會因為記憶體錯誤而當機。

如同先前神盾系統從軍規主機到商規主機以及從CMS-2、ADA一路到C++、Java的過渡,即便引進Rust語言撰寫軟體,也不會一夕之間拋棄過去就有的數百萬行代碼。可能的策略是優先將Rust用於任務關鍵性最高的模組,如處理外部網路數據的接口、處理高超音速目標的彈道解算等。而另一種可能是跨語言工具鏈 (C-FFI),發展一些讓Rust 與舊有ADA/C++ 程式碼無縫溝通的中介。 

(1)  (2)  (3)  (4)  (5)  (6)