如何寫出軍工級的代碼?
INTEGRITY 178B,最開始為了B1B(核彈轟炸機)設計,後來被用作B2,F16,F22,F35(都是戰鬥機)的操作系統,再後來也被用於民航(787 A380)的操作系統。這是我們公司(Green Hills Software,我們sponsor h1b,歡迎廣大在美國讀CS的同學前來應聘!簡歷可以發到我的郵箱gaotian@ghs.com或者公司招聘郵箱jobs@ghs.com,私貨夾藏完畢!)的一個產品。
迄今為止唯一一個過了EAL 6+的軟體(應該絕後了,因為6+好像由於太難達到取消了)。
有人說軍工軟體比較low,從科技進步的角度說,這是毫無疑問的。拿INTEGRITY 178B來說,這個操作系統沒有動態分配內存的功能,沒有動態分配address space的功能,沒有動態增加task的功能。scheduler裡面提前分配好了每個task可以佔用的時間,甚至包括所有的中斷可能佔用的時間。這種OS的效率可以說低得令人髮指。更不要說這個OS根本沒有什麼USB啊藍牙之類的driver了。
然而從另一個角度說,這是一個無比先進的技術。現在的OS大多是priority分配,任務有優先順序。而優先順序分配資源的特點在於,只保證最高優先順序task的資源。換言之,當你最高優先順序的task開始吃資源(內存泄漏或者死循環)時,基本上其他task就廢掉了。而這種情況在飛機上是不允許出現的。說30ms需要響應一次,就必須響應,晚了30ms飛機可能就掉下來了(尤其對於極其依賴發動機控制,空氣動力學不穩定的戰鬥機)。而這種「低端」的操作系統,在設計的正確的情況下,是可以滿足飛機需求的。畢竟戰鬥機飛行員不太需要在飛機上聽歌。
而這個OS的審批過程也是歷盡艱辛(畢竟是要上核彈轟炸機的),用一個過程來管中窺豹,這個OS給了NSA做白箱檢查,也就是上交全部源代碼,讓他們找bug和vulnerability,是否會被hack,他們花了18個月,沒有找到任何缺口,這才讓用。對比一下,linux kernel大概是3天左右出一個 vulnerability(Linux Linux Kernel : CVE security vulnerabilities, versions and detailed reports, 感謝一下 @冰橙 指出的我的錯誤,和提供的鏈接)。
這個OS過了審批之後,再想修改其中的一行代碼,需要層層上報,獲得批准。絕不是一個approve from manager就完事的。當然這個OS似乎之後幾乎沒改過。
當然,這個操作系統,由於顯而易見的原因,我是沒見過的:)
他的兄弟,INTEGRITY的民用版,我是見過的。這個OS目前大量被用在汽車裡做核心系統,據我了解toyota近幾年的車應該都用的INTEGRITY。當然INTEGRITY也有很多其他領域的比較追求安全性而非效率和酷炫的用戶,比如醫療器械之類的。
不過即便是INTEGRITY的民用版,我也沒有commit code的資格。(而且似乎還限制賣到敵對國家(伊朗伊拉克朝鮮古巴還有。。你懂的。。)
當然了,這些故事都是我們老闆在公開場合講過的,又非常基礎,太深入的東西我也不懂了,就算懂估計也不能亂說了,哈哈!看到有人提到友商的系統了,作為競爭對手我也來說兩句:
軍工(和航天)代碼沒那麼神秘,就是需要准守一些特定的規範,規範就是 @高天 的回答里寫的那些。
簡單點說,就是整個操作系統的運行必須是確定的:
不能有動態內存分配,不能有動態任務創建、刪除……總之一切不確定性的東西都不允許有;
很多人都只有x86的CPU有個超線程的概念吧,一個核上跑兩個線程,但超線程會影響兩個線程執行的確定性,在軍工航天領域,超線程必須關掉;
現代操作系統都支持虛擬內存吧,但缺頁中斷的執行時間是不確定的,所以必須關掉虛擬內存,必要的時候關掉頁表也是可以的。代碼的確定性可以大概理解為:如果天上有個衛星,上面的OS某個任務跑了10秒,地面上有一個一模一樣硬體配置的計算機同樣的OS,也跑了10秒,在輸入狀態一致的情況下,兩個計算機里的東西應該是一模一樣的。
軍工航天類的代碼要求的規範和限制比較多,比如必須符合某某規範才能在飛行器上使用(具體的看這個回答:如何評價中航計算機所研製的天脈操作系統? - 嵌入式系統)等等。
要求多並不意味著代碼質量一定很高,因為要求多、限制多,代碼往往看起來比較奇怪。而且由於歷史原因,如果一個代碼能工作,輕易是不允許改的,改動代碼要經過的審查流程往往比改代碼麻煩多了。比如現在個人電子市場早就進入64位時代了,但軍工航天領域很多還是32位的,這些代碼要是放到64位系統里肯定掛,但因為軍工航天不需要這些,所以就不能改,也不需要改。
不要對軍工代碼有什麼神秘感,其實一點都不神秘,就是要求多,測試嚴,審查多。在規範允許的範圍內,代碼寫的再爛也沒關係。
另外,由於code review是要花錢的,所以代碼一般都不長,否則對於OS廠商來說,code review的成本就太高了(註:第三方公司負責review,一行大概幾美元)。
美國軍工行業用的Integrity是GreenHills的,這個公司要求所有寫代碼的人必須是美國公民或者持美國綠卡的人,公司不大,全靠國防部養著,我估計知乎上沒有人能說清楚具體的細節。
國內大部分用的是VxWorks的改版,說是改版,可以理解為合理的學習、複製等等,安全性倒不是問題,因為能拿到的東西都是有源碼的,細節就不展開說了。
如果題主對軍工代碼有興趣,可以先研究一下ARINC的規範,這東西跟軍工上用的差不太多,不過要找到能支持這種規範的開發環境就很麻煩了,有興趣題主可以自己構造一個,難倒是不難,就是用起來很不舒服。軍工級接觸起來有些困難。但是汽車工業早有標準,即MISRA標準,有C和C++兩種。汽車也是關係到生命安全的產品,其代碼質量要求也是非常之高的,後來航空工業指定相關軟體編碼標準時也參考了MISRA標準。
MISRA標準的篇幅不算太大,但也不適合粘貼到這裡來。題主可以自行尋找相關文檔,中英文的都有。MISRA標準在某些方向比如字符集上規範的很細緻,但另一方面,對於嵌入式系統的常見玩法卻沒有什麼定義。所以我下面補充一下嵌入式系統的一些考量。
嵌入式實時系統需要在指定時間內做出響應,所以編程上要避免一些方法的使用。比如遞歸,遞歸的過程沒有一個全局的控制,導致遞歸的深度無法控制。當遞歸深度過大時,執行時間無法預測,且對stack和heap的佔用也無法預測。所以對安全性和實時性有要求的系統是不能用遞歸的,如果要實現類似功能,可以用遞推,這樣就可以有一個全局的控制。
另一方面是中斷。中斷髮生時,會導致當前執行程序被掛起。期間保存工作寄存器和恢復工作寄存器也會浪費大量時間。所以也會整個系統的實時性無法確保。所以實時系統也會避免中斷的使用,轉而使用輪詢。即一個循環函數中去檢測各個中斷標誌位是否發生,並調用對應函數來處理,同時確保每個函數的靜態分析結果的運行時間是可控的。早期一些電腦遊戲也是使用此類模式開發的,稱為tick模型。
有其他回答提到了動態內存分配,這也是實時系統儘力避免的。動態內存分配和釋放也是個時間難以預測的過程。所以一些嵌入式操作系統甚至將其作為一個備選模塊,而不是默認功能。比如FreeRTOS就提供了4種動態內存分配和釋放演算法的實現。用戶可以選擇實際使用哪一種。內存利用效率高和分配釋放速度快是矛盾的。而避開不用動態內存分配也是有辦法的,就是大量的使用static變數。這樣這個變數僅在啟動時分配內存,運行中不會被釋放。不過static變數應該在高層函數,或專用的初始化代碼里聲明,而不應該在底層函數里聲明。底層函數還是應該盡量接受傳入的參數,只有這樣才能避免並發問題,以及提高靈活性。static變數的使用也避免了全局變數的混亂。F-35 (JSF)的C++編碼規範,拿去,不謝
http://www.stroustrup.com/JSF-AV-rules.pdf軍工軟體沒寫過。工業級的軟體寫了一些。主要幾個區別。
- 硬實時。 @高天已經說了。不光是響應時間有要求,對於響應的延遲也有要求,對延遲的穩定性也有要求。
- 過程可靠性。當發生比特錯誤時,應確保程序不會產生異常的硬體輸出。此處不考慮多比特錯誤同時發生的情況。
- 備份、恢復、仲裁。核心的變數有備份和校驗,發生錯誤時可從備份中恢復出正確的數據。當數據十分重要時,在不同的硬體中做多份儲存,最安全的備份甚至是物理斷電、按需通電的以確保不發生誤操作。
- 代碼審核。針對一個模塊,審核團隊獨立完成功能後,再對研發團隊完成的代碼做審查。這意味著一個模塊實際上被完成了兩次。
之前有個人和我撕,我說當年東風5上用加法累積求和來替代乘法運算減少運算量,當年背景數字計算機才剛應用,有個人不信說一個乘法器就可以解決問題,數字電路還搞不定一個簡單的乘法運算,當時我也不知道怎麼反駁,最近在看工程式控制制論時候才發現,裡面的確有提當年(書出版年代已經是80年代)因為數字計算機運算能力太弱,簡單的微分積分也要進行變化讓計算機能夠運算,東風5研製的年代是60年代,那個時候的數字計算機運算能力能用現在的計算機眼光能衡量么?數字計算機一般是只有加法和減法的指令的,靠數值演算法實現微分和積分,不是么?
具體的不清楚,不過可以說幾件小事情,就當是分享下(本來以為沒人看的,但是這二天收到幾十個感謝和點贊,還是挺感動的,會繼續添加更新補充,歡迎關注)註:最新消息,就在上周四,阿波羅11號登月源代碼正式對外公布!有興趣的同學可以看看! 1 很多人都以為全世界寫代碼最多的企業是微軟,其實不是的,是洛克希德馬丁,下屬的一家信息部門寫的代碼總行數超過了微軟。 2 寫軍工代碼也要考慮版本迭代問題,比如洛克希德馬丁的F35項目,其系統代碼行數遠遠超出最初設計行數,軟體包臃腫,並且版本修改了很多次,原因是因為F35要同時滿足空軍和海軍的需求,滿足不同的使用環境,後期出了很多不可控因素,導致系統需要兼容很多使用環境,據說F35的軟體代碼量是至今為止的軍工項目中量最大的。 3 軍工項目,尤其是在航天領域,一旦在任務進行中,一個BUG出現的結果可能就是火箭發射失敗或者是太空任務的失敗,雖然也能進行遠程調控,但是仍然要杜絕在軟體的最終版本中出現bug,保證是零bug的,當然了某些原因需要養bug以好給自家的技術支持人員混口飯吃,我不想說什麼,因此執行航天任務的代碼私以為首先要保證高可靠性,否則要遠距離進行代碼的調試和修改,是一件多麼困難的事,日本的H2B火箭、歐洲的阿麗亞娜4火箭、美帝的宇宙神和那個spacex的火箭等,都出現過因為控制系統的一個BUG出現,導致最後火箭發射失敗,貌似我們國家航天領域的控制系統都是用的VXWORKS(f當然了,是我們拿到了源代碼然後改過了),即使除了軍工領域,很多民用領域也有這種要求,例如現在的無人駕駛汽車,完完全全可以按照軍工編碼標準來要求,要做到零BUG,一個BUG可能就是車毀人亡。出於航天任務的特殊性和高可靠性,航天領域的OS內核都有任務調度,支持優先順序任務搶佔,支持任務的中斷,尤其是實時性,OS必須能夠保證快速響應任務,具體體現在 1)能夠在規定的時間內對任務作出響應2) 可以調度一切資源來完成任務3)可以控制實時任務間的同步在這裡岔開一段,在我印象中最深刻的一次航天任務就是阿波羅登月,關鍵部分就是阿波羅飛船在月球上空控制登月艙降落,這對任務實時性有極高要求,降落的過程必須嚴格控制時間,稍微有點差錯登月艙就會直接砸到月球上,而且那個年代,還用的彙編語言,難度可想而知。
4 看到其他答案都是從航天領域來講的,就想到一點,NASA的CIP,協作式信息管理系統(Collaborative Information Portal)有興趣了解詳細內容可以在下面評論告訴我,我會補充
5 軍工領域,例如航空領域,四代戰鬥機(新標準,即傳統的三代戰鬥機)的標配有一項就三軸四餘度飛控系統,這個對軟體要求很高,那我們當年是怎麼解決這個問題的呢,其實部分原因就是當年的珍珠和平計劃,殲八2 改項目雖然最後失敗,但是當年的中國技術人員也是通過這個項目才接觸到當年的先機戰機信息系統該怎麼做標準,怎麼編寫相關的操控軟體,美帝還是給了很大的幫助的,然後我們就自己搞出了四餘度飛控系統。(當然以色列也有部分幫助) 6 至於那個高票答案說的B1B的INTEGRITY系統代碼一直被沿用,被後面的F16,B2,F22等沿用,其實原因也很簡單呀,因為美國的軍工體制下,不是說一家軍工企業獲得一個項目,所有的工作都在這家企業完成,而是分包制度,比如說F16,雖然說是洛克希德馬丁的產品,但是F16下面的很多子系統被分包給了其他的企業,雷神也參與了、波音也參與了,這樣子做的目的就是一方面不讓出現一家獨大,餓死其他企業,另一方面也能控制和降低成本,最大化利用現有技術,可能美國很多的戰鬥機上的系統就是一家系統承包的,例如雷神(不一定,我只是打個比方),在這種情況下,一個系統被用了幾十年也是很正常的,更何況F16/F22/F35三個項目都是洛克希德馬丁作為承包商承包的,沿用一種戰鬥機操控系統不是情理之中么。 7 在我心裡,其實挺佩服毛子寫的軍工代碼的,為什麼呢?因為毛子的軍工領域,有很多模電部分就算了,帶數電和計算機的,其運算能力也是弱的可憐,內存還是停留在幾M甚至幾百KB的程度,要在很弱的硬體水平下完成高要求作戰任務,這對代碼就有要求了,如何能在保證任務前提下寫出最簡潔的代碼,舉幾個簡單的例子,蘇俄的S-300系列防控系統,因為要求雷達能夠同時跟蹤大於N個目標,並且同時控制N枚導彈,這對數據處理要求比較高,但是S300系統里的計算機配置卻很弱,真的很弱,但是毛子卻能夠做到這一點了,S300算是世界上最好的防空導彈系統吧?再看看愛國者那個計算機配置,好的不是一點點,相同的還有現代級驅逐艦,整個控制系統的是一台很大很老佔了一個艙室的計算機控制,但是同樣的這台計算機運算能力也是弱的可憐,但就是這麼弱的計算機要負責為現代級的雷達系統、火控系統等提供運算,毛子的技術人員是怎麼為這些系統編程的,相比較之下美國的伯克級上的宙斯盾系統採用的計算機配置,不能說最頂尖,但是遠遠的甩毛子一千條街。 8 印象中,寫代碼比較厲害的幾家軍工企業,除了洛克希德馬丁,其次還有雷神公司,美國的大部分導彈就是出自雷神系統,還有部分轟炸戰鬥機的控制部分也是由雷神操刀,法國的達索公司,英國的BAE集團,編程水平都挺可以的。 9美國的X 47B無人機在航母上自主起飛,據說那家無人機的系統代碼行數是1700萬行(具體的還有待考證,有知道的歡迎指正) 10再講一個小細節,當年的SU27引進回來後,我們想使用國產武器,但是問題來了,蘇27的火控系統無法支持國產武器,系統完全不兼容,如何我們硬是把整個系統給換掉,又會導致其他問題,沒辦法換系統,當時的解決辦法,就是逆向破解系統,弄到系統源代碼,可是我們不可能找俄羅斯幫忙的,俄羅斯怎麼可能會幫我們做這種事呢?後來怎麼解決的,恩,毛二烏克蘭出現了!神奇的烏克蘭,幫助我們逆向破解了系統,然後那批蘇27也能使用國產武器了。 11 其實一家先進戰鬥機的氣動性能,除了取決於氣動外形設計,還有一點就是要有一套先進的飛控軟體,蘇式戰鬥機通過無限挖掘氣動外形潛力,其三代戰鬥機蘇27擁有很高的機動性能,但是,美國的戰鬥機,在氣動外形這一塊並沒有刻意追求,而是在飛控軟體上下功夫,最典型的就是F22,寶石柱信息系統在當年是超越時代的存在,即使放到現在也一點也不落後,在飛控軟體和大推力矢量發動機的幫助下,F22能作出許多逆天的飛行動作,比如我們的殲20,飛控軟體也一定很複雜吧,為什麼蘇聯和俄羅斯搞5代戰鬥機沒有做鴨翼,隱身和氣動性能是一點,還有個原因,這對飛控系統要求太高太高,對計算機的運算能力要求又高,而當年蘇聯的計算機普遍黑大粗,放在地面和艦艇上這一問題還不突出,但是在狹小的飛機上,這一問題就很致命了,我想這也是蘇聯的飛機設計過於追求外形設計而不是在飛控系統上下功夫的原因之一,蘇聯和俄羅斯的技術人員編程能力我覺得是絕對可以的,俄羅斯到現在的編程水平也不用懷疑,但是再好的代碼也不能彌補硬體上的弱勢吧。 12 導彈這塊,嵌入式編程這塊,導彈的一個核心部件就是導航控制系統,又要談到蘇聯和美國的比較了,可以發現一個現象,蘇聯的導彈普遍做的比較粗大,無論是洲際導彈、彈道導彈、還是反艦導彈、地空導彈、空空導彈等等,基本上都要比美國的粗大一點(不是絕對),原因除了蘇聯追求大射程和威力外,還有一個很現實原因,蘇聯的電子水平(又是這個梗),很多型號的導彈還在用模電,體型普遍要比數電做的要大一號(但是模電的好處就是穩定,不怕核輻射,蘇聯人就是這麼想的),而蘇聯,還在苦苦的執著於模電,一直到蘇聯末期才開始搞數電,這在大型導彈上還不吃虧,但是空空導彈、地空導彈等對體積有要求過高的地方,問題就很嚴重了,但是蘇聯人另闢蹊徑,用了一個挺巧妙的思路,就是把導彈捕捉的數據信號統統的傳回到地面計算機,讓地面計算機進行運算處理再返回給導彈,如今有的小型機器人也是這樣子做的,這樣就解決計算機占導彈內部體積的問題了。然後蘇聯這一方面做到極致的就是SN-A-22花崗岩反艦導彈,同時發射N枚,其中一枚作為控制導彈彈,承擔主要的運算工作,將數據傳輸給剩下的導彈,來發動攻擊,頗具有人工智慧的感覺,但是具體怎麼通過編程做到的,就不得而知了,但是這種導航控制,的確很巧妙。
所以我想到,現在的嵌入式領域,很糾結於體積、處理器運算能力、和能耗問題,也可以借不一定要很複雜、很高明,但可以做的很巧妙。 14 岔開一個話題,其實早年因為硬體問題,軍工領域因為作戰環境的特殊性對硬體本身要求偏高要保證壽命、抗過載和輻射等,導致硬體性能過低,內存和存儲容量小的可憐,運算能力也弱,蘇聯早年選擇模電路線也是有原因的,所以軍工領域的OS和軟體都是專門編寫的,內核要做的精簡、軟體要編的簡潔,例如那個VXWORKS,內核做的很小,但是到了90年代往後,軍工領域開始出現一個趨勢,就是直接採用商業貨架產品,意思就是說直接用商業的計算機硬體,硬體性能上漲帶來的一個結果就是OS和軟體也能做的更大,例如有把linux內核裁剪過來用的軍工產品,比較有代表性的就是阿利伯克級驅逐艦,較新的批次上的計算機和伺服器是直接用的戴爾的貨架產品,帶來的一個好處就是計算機整個運算性能得到提升(還有成本降低什麼),宙斯盾系統的軟體也做的越來越複雜,包括支持攔截彈道導彈,F35上的很多計算機也是拿的民用商品,說這點的意思就是,軍工級別的代碼標準也不會是一成不變的,隨著硬體性能提升,當年的標準或許就不使用了。 15 再講一個有意思的小細節,其實大家知道最早的計算機是為了二戰時候為了支持彈道運算誕生的,那最早的操作系統出現也是有原因的,當年蘇聯搞出了洲際導彈,美國為了應對搞出了攔截導彈,那個年代要攔截導彈必須要進行複雜的運算、計算機要連接和處理雷達數據、控制引導導彈發射跟蹤和通信系統,需要連接幾個單元,在那個局面下,最初的那種沒有操作系統、還要採用計算機指令和彙編語言的計算機顯然不能滿足任務需求,然後美帝的技術人員搞出了最早的最原始的那種操作系統,雖然用現在眼光看,已經low的不行,但是那個年代的確實實在在解決了問題,雖然最後那個防空導彈項目也失敗了。蘇聯那邊也差不多,搞出了橡皮套防空系統,也是帶OS的計算機在背後控制。自己總結下軍工代碼的要求1 可靠性是第一位,因為戰場上,一個bug的出現代價可能就是死亡,即使在民用領域,我們也應該嚴謹要求自己。2 能夠根據設計目標和任務要求來制定代碼的要求,盡量做到滿足任務的需求特點。3 軍工級別的代碼,也不用做的很高大上,也不用一定要用到多高大上的編程技巧,但是編程思路可以靈活一些、巧妙一些,如何在保證前面二點的要求下用巧妙的思路最好的解決任務要求,不一定非要偏執於一條路這也是軍工代碼給的一些啟示。軍工級的規範標準往往代表著落後與low :)
---------------------------------------------------------------我參加的是非作戰部分而是日常安保相關。所以和其他答主提到的嚴謹耐操什麼的也完全搭不上關係 ,就是舊,沒錢升級:)我理解的題主的問題是,「如何寫出安全性、健壯性等可以達到軍工產品級別的代碼」,而不是不同的軍工產品有哪些。如果是這樣的話,題主可以從安全編程入手,首先理解編程語言、常見的編程漏洞及利用方式,然後有針對性地避免這些問題。
IEC61508及其各領域的子規範
本行業就是EN50128
當然本行業不是軍工,但大多數要求都是一致的。推薦電影The Pentagon Wars,讓你看看美帝的先進的軍工級項目管理
http://youtu.be/aXQ2lO3ieBA某天某大佬給我一份需求文檔,讓我實現一下,大概就是DSP做一下濾波和調製之類的,後來大佬給我付錢的時候才順便說,這個是雷達上面用的東西,並沒有覺得有什麼特殊的。有些指標要求高了,但是給的器件也很牛逼啊,實現上並沒有覺得太難。之前幾個小夥伴還給潛水艇搞了特種電源來著。
注意答題分寸,上網不涉密。
該問題可自行查詢軍標。
單任務,高穩定,極端環境,頂事兒,抗造,耐操。以上是用戶的要求。
軍標就這麼簡單。曾經有一天,我接了一個項目,給多少(黑魚)條做個板卡,我曾經有機會寫裡面的驅動,但是,我不是碼農,我的時間都浪費在跟軍代表抽煙的房間里了。。。。
軍工有很多種,有些外包出去的你的不敢想像。
有過相應經驗的飄過。。。
軍工碼的確沒有特別的,特別的地方 @高天 已經指出。
這裡再說一下根本的區別:就是技術老。飛機上導彈上使用的code必須是有高度確定性的,這就意味著動態分配這些一切都不可能,而且Cache,Out of Order,Branch Pridiction都是干擾因素。甚至Malloc都不能使用的情況下,思考問題的方式全變了。
如果你寫過VHDL或者其他硬體描述語言的話,你基本就明白這種區別了。因此程序設計都是先寫出State Machine,然後填Code。IDE使用Rational Rhapsody, Rational Rose, Rational DOORS.編程語言C/C++/Ada/Assembly
有人問為什麼需要Ada,其實為的是證明DO178中的Full Independency。這裡指的無關性說的是不能同一團隊,不能同一地點, 不能同一語言,不能同一編譯器,不能同一庫。因此Ada這種反人類的語言到今天還沒死乾淨。
標準的話可以去查找MIL-STD-882E(免費下載), DO178C(非免費), DO254(非免費)。其實還是有個軍工級的作息規律比較靠譜