對于嵌入式開發(fā)團隊來說,更快的嵌入式應用上市時間確實是一個重要的考慮因素。但是,如何開發(fā)一種方法來更快地交付應用程序,同時將質(zhì)量和安全性作為優(yōu)先事項?在這種情況下,OTA軟件更新經(jīng)理Mender.io背后的產(chǎn)品工程團隊經(jīng)歷了為開發(fā)Mender的嵌入式客戶端和服務器部分選擇最佳編程語言的過程。在這個評估過程中,Go、C和C++入圍候選名單。本文解釋了從這個評估過程中吸取的一些教訓,以及為什么最終選擇Go開發(fā)Mender的客戶端嵌入式應用程序。
雖然這可能很主觀,但Go對于嵌入式開發(fā)來說是一種非常有效的語言。當涉及到網(wǎng)絡(luò)編程時,尤其如此,網(wǎng)絡(luò)編程在某種程度上是每個連接的設(shè)備或應用程序的一部分。Go是谷歌為滿足谷歌開發(fā)者的需求而創(chuàng)建的,它的開發(fā)主要是為了解決其生態(tài)系統(tǒng)中的復雜性爆炸問題。因此,高效編譯、高效執(zhí)行和易于編程是開發(fā)Go背后的三個主要原則,因為這三個原則以前并不是在同一種主流語言中都可用。
然而,我們要強調(diào)的是,Go不能被視為C的替代品,因為在很多地方都需要C,例如在開發(fā)實時操作系統(tǒng)或設(shè)備驅(qū)動程序時。
嵌入式開發(fā)的嚴格要求
建立架構(gòu)后,修補程序產(chǎn)品工程團隊評估哪種語言最適合開發(fā)修補程序應用程序。該系統(tǒng)應該由運行在嵌入式設(shè)備上的客戶端和作為客戶端連接的中心點的服務器組成。因此,我們對語言有幾個要求:
由于客戶端應用程序?qū)⒃谇度胧皆O(shè)備上運行,編譯后的二進制文件需要盡可能小。
它需要與Linux的嵌入式發(fā)行版Yocto一起工作。
安裝客戶端的復雜性應該很低,從而限制外部依賴性和庫。
因為它可以在不同的設(shè)備上運行,所以該語言必須在不同的架構(gòu)上編譯。
運行修補程序客戶端應用程序的設(shè)備將是物聯(lián)網(wǎng)或聯(lián)網(wǎng)設(shè)備,因此該語言需要訪問聯(lián)網(wǎng)庫。
選擇新語言的要求還包括一些非功能性要求:
我們組織中盡可能多的程序員需要能夠理解這種語言。
在用C編寫的現(xiàn)有應用程序之間共享和重用現(xiàn)有代碼以及在客戶端和服務器應用程序之間重用代碼必須盡可能容易。
開發(fā)速度也是考慮因素之一——我們面臨著快速添加新功能的持續(xù)壓力。
比較Go與C
Go還因為其極其豐富的標準庫而被選中進行嵌入式開發(fā),這使得開發(fā)速度更快,尤其是與C相比。Go繼承了C、C++和Python的許多元素,包括表達式語法、控制流語句、數(shù)據(jù)結(jié)構(gòu)、接口、指針、按值傳遞概念、參數(shù)解析語法、字符串處理和垃圾收集。通過其優(yōu)化的編譯器,Go可以在嵌入式設(shè)備上自然運行。
Go確實有一些缺點,它們的分量不足以克服它的優(yōu)勢,比如開發(fā)速度和語言構(gòu)建的簡易性,但它們確實是我們決策的考慮因素。
Go與C的大小比較
就大小而言,Go比C更笨重,這是它的幾個缺點之一。如果你把“helloworld”作為最小的應用程序,你可以在Go中編寫并使用內(nèi)置的println函數(shù),在去掉調(diào)試符號后,它的大小剛好超過600kB。如果包含fmt包及其依賴項,那么大小將增加到1.5MB。
與C相比,如果你正在構(gòu)建一個動態(tài)鏈接庫,那么它僅為8kB。如果是靜態(tài)的,那么大小會增加到800KB以上,這比Go二進制文件還要大得驚人。
Go與C的速度比較
編譯的Go代碼通常比C可執(zhí)行文件慢。Go是完全垃圾收集的,這本身會減慢速度。使用C,你可以精確地決定要為變量分配內(nèi)存的位置,以及是在堆棧上還是在堆上。在Go中,編譯器試圖對變量的分配位置做出明智的決定。例如,你可以看到變量將被分配到哪里(go build-gcflags-m),但不能強制編譯器僅使用堆棧。
然而,說到速度,我們不能忘記編譯速度和嵌入式開發(fā)人員速度。Go提供了極快的編譯速度;例如,15000行Go客戶端代碼需要1.4秒才能編譯。Go非常適合并發(fā)執(zhí)行(goroutines和channel),前面提到的豐富標準庫涵蓋了大多數(shù)基本需求,因此開發(fā)速度更快。
匯編和交叉匯編
有兩個Go編譯器可以使用:最初的一個叫做gc。它是默認安裝的一部分,由Google編寫和維護。第二個叫做gccgo,是GCC的前身。使用gccgo,編譯速度極快,大型模塊可以在幾秒內(nèi)編譯完成。如前所述,Go默認情況下是靜態(tài)編譯的,因此只創(chuàng)建一個二進制文件,而不需要額外的依賴關(guān)系或虛擬機。可以使用-linkshared標志創(chuàng)建和使用共享庫。通常,Go不需要構(gòu)建文件,可以通過一個簡單的“Go build”命令進行構(gòu)建,但也可以將Makefile用于更高級的構(gòu)建過程。
除此之外,在交叉編譯方面,Go支持大量的操作系統(tǒng)和體系結(jié)構(gòu)。
Go中的調(diào)試和測試
許多開發(fā)人員為了在程序執(zhí)行時了解程序內(nèi)部的情況,將使用GDB。對于高度并發(fā)的Go應用程序,GDB在調(diào)試它們時遇到了一些問題。幸運的是,有一個名為Delve的專用Go調(diào)試器更適合于此目的。只熟悉GDB的嵌入式開發(fā)人員應該能夠在大多數(shù)情況下使用它而不會出現(xiàn)任何問題。
將測試和單元測試添加到Go代碼中非常容易。測試內(nèi)置于該語言中,只需創(chuàng)建一個帶有“test”后綴的文件,向函數(shù)添加測試前綴,導入測試包,然后運行“go test”即可。所有測試都將自動從源代碼中提取并相應執(zhí)行。
并發(fā)支持
Go中很好地支持并發(fā)。它有兩個內(nèi)置機制:goroutine和channel。goroutine是輕量級線程,大小只有2kB。創(chuàng)建goroutine非常簡單:只需在函數(shù)前面添加“go”關(guān)鍵字,它就會同時執(zhí)行。Go有自己的內(nèi)部調(diào)度器,goroutine可以根據(jù)需要復用到OS線程中。通道是在gorroutine之間交換消息的“管道”,可以是阻塞的,也可以是非阻塞的。
Go利大于弊
我們使用Go的經(jīng)驗顯示了優(yōu)點和缺點。從負面來說,社區(qū)中有很多外部庫,但質(zhì)量參差不齊,你需要非常小心地使用哪種庫。隨著語言的成熟和被采用,這一點正在大大提高。
當你的Go代碼中有一些C綁定時,事情通常會變得更加復雜,特別是交叉編譯,如果不導入整個C交叉編譯基礎(chǔ)結(jié)構(gòu),就無法輕松完成。
使用Go進行嵌入式開發(fā)仍然有很多好處。從C和/或Python轉(zhuǎn)換到Go是快速而簡單的,并在幾天內(nèi)實現(xiàn)高效。Go提供了非常好的工具和100多個包的標準庫。你可以輕松地設(shè)置和控制運行時設(shè)置,例如要使用多少OS線程(GOMAXPROCS),或者是否要打開或關(guān)閉垃圾收集(GOGC=off)。庫和代碼可以很容易地在服務器和客戶端開發(fā)團隊之間共享。