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