今天分享的文章描繪出了一個真正的嵌入式程序員應該是怎樣的模樣,其實bug菌一直覺得嵌入式程序員是一個程序員世界中的鬼斧神工,他們利用極其簡約的"物件"構(gòu)造出精美的杰作!
好吧,不吹了,看了下面的譯文你就了解了: 寄存器!示波器!胡子! 串口!C! 循環(huán)剃須!胡子!中斷!匯編!等等,我剛剛提到胡子了嗎? 如果我提到“嵌入式程序員”一詞,我們行業(yè)中的大多數(shù)人都會立刻想起一個英雄角色的形象。 一個擁有神秘技能的偉大開發(fā)人員,有著淵博的神秘學的知識,對個人衛(wèi)生的概念持半信半疑的態(tài)度,面部胡子拉碴,像神一樣的形象。 然而,在秘密地研究了幾年這個主題之后,我在這里告訴你,你們的想象統(tǒng)統(tǒng)不正確。 大多數(shù)嵌入式程序員都沒有胡子,做嵌入式程序員也不必連做夢都要使用匯編語言,而且嵌入式程序員也會洗澡。 我還可以告訴你嵌入式編程非常有趣,收獲頗豐,且富有挑戰(zhàn)性。如果你對本文感興趣,那么有可能將來你也會從事這一行。 請注意,以下內(nèi)容可能有點雄心勃勃。我會介紹嵌入式程序應有的樣子,而不是大多數(shù)嵌入式程序?qū)嶋H的樣子。 這可能是一種理想的狀況。突然偶遇嵌入式代碼,你可能覺得非常嚇人,覺得這些代碼都是一些聰明的家伙寫的高端代碼。 但我想要告訴你,你會對Spotify的嵌入式編程感興趣,而優(yōu)秀的代碼正是我們的奮斗目標。 所以,如果你也是一個嵌入式程序員,那么可能本文中介紹的代碼與你每天使用的有出入,我明白你的心情,現(xiàn)實確實如此,但生活可以更加美好。 首先,我們來介紹一下嵌入式系統(tǒng)編程與嵌入式應用程序編程之間的區(qū)別。 嵌入式系統(tǒng)編程 :可能就是你想象中的嵌入式編程。 包括讓嵌入式硬件平臺正常運行所涉及的所有工作:編寫設(shè)備驅(qū)動程序,引導加載程序,移植或編寫操作系統(tǒng),各種位操作,還要考慮到時鐘周期問題。調(diào)試丟失的中斷。 一連盯著示波器幾個小時,埋怨編譯器,討厭中斷控制器,就仿佛它是一個人一樣。時常在午夜驚醒,冷汗淋漓,努力說服自己是貓偷走了中斷信號的正面。不好意思,我跑題了。嵌入式系統(tǒng)編程確實涉及非常底層的東西。 嵌入式應用程序編程 :指的是為資源有限的系統(tǒng)編寫應用程序的技術(shù)。這種開發(fā)更容易,開發(fā)環(huán)境很好。 還可以在臺式計算機上編寫、測試和調(diào)試代碼。雖然有些約束可能很具挑戰(zhàn)性,但是你無需考慮匯編、GPIO引腳或DMA描述符。 然而,你必須考慮內(nèi)存的使用情況,運行環(huán)境,代碼的大小和可移植性。但說真的,難道有人不喜歡這些工作嗎? 本文討論的是嵌入式應用程序編程,在Spotify我們大部分時間從事的都是這種類型的嵌入式編程。1
簡約之美
約束讓我們的生活變得更加有趣。 與完整的管弦樂隊錄制的專輯相比,只用兩根柱子和一杯水錄制的音樂專輯需要更多的創(chuàng)造力。 雖然我對創(chuàng)造力或?qū)Α耙魳贰币辉~有著非常規(guī)的理解。但是, 我想表達的是嵌入式編程很有趣,但是有趣在哪兒呢? 大多數(shù)嵌入式程序的首要約束因素是大小,代碼必須緊湊,常見的編程習慣(代碼需要模塊化,易于維護和測試,還要經(jīng)過測試)仍然適用于嵌入式程序,除此之外還需要將代碼量降到最低,而且還需要保證自給自足。 用一個概括就是: 優(yōu)雅 。良好的嵌入式代碼非常優(yōu)雅。 那么什么是嵌入式編程呢?什么是嵌入式程序員?讓我們繼續(xù)往下看……2
內(nèi)存的利用率--隱形的殺手
嵌入式程序員需要避開現(xiàn)代內(nèi)存管理的概念。你幾乎不可能實現(xiàn)垃圾收集。一個垃圾收集器可能就耗盡了代碼容量的限制。 而且垃圾收集器還需要不時地運行實際的垃圾收集,這會破壞嵌入式程序的實時效果。 你甚至還需要避免常規(guī)的malloc(),調(diào)用malloc()可能會花費大量時間,因為分配器可能必須對分配區(qū)域進行碎片整理,才能釋放出足夠大的內(nèi)存塊來響應請求。 嵌入式程序員很樂意直接管理內(nèi)存,編寫自定義分配器,甚至通過靜態(tài)分配的內(nèi)存塊來杜絕內(nèi)存分配失敗的發(fā)生。 大多數(shù)嵌入式系統(tǒng)和普通計算機之間的一個 重大區(qū)別就在于內(nèi)存的組織方式 。 主流的臺式機和服務(wù)器的處理器架構(gòu)(如Intel x86)使用的編程模型中,代碼和數(shù)據(jù)存儲在同一個地址空間中。 這意味著如果你的機器有64MB的RAM(好大啊,太不可思議了?。?,而你的程序是40MB(令人難以置信?。敲茨愕臄?shù)據(jù)就只剩下24MB(這么多,根本用不完?。。?。 在家的時候,我們稱之為馮·諾伊曼架構(gòu),但在酒吧我們就不敢這么說,因為這會引發(fā)唇槍舌戰(zhàn)。 由于RAM是處理器中功耗最大的一部分,而且由于RAM占用大量芯片面積,因此在許多嵌入式系統(tǒng)使用的模型中,代碼和數(shù)據(jù)分別存儲在不同的存儲器中。 代碼和靜態(tài)數(shù)據(jù)存儲在ROM(通常是閃存或EEPROM)中;而動態(tài)數(shù)據(jù)存儲在RAM中。ROM比RAM便宜很多,因此通常使用的也較多(通常是5-10倍)。 RAM和ROM可以具有單獨的地址空間(哈佛架構(gòu)),也可以映射到統(tǒng)一的單個地址空間(改良版的哈佛架構(gòu),遺憾的是從未被稱為小丑架構(gòu))。 除非你需要編寫引導加載程序或系統(tǒng)內(nèi)升級的功能(在這些情況下,你需要向代碼的存儲空間中寫入),否則一般你不會注意到這兩者之間的差異。 但這確實意味著代碼的大小和RAM的使用率需要單獨計算。換句話說:在嵌入式系統(tǒng)中,代碼大小的限制不同于RAM使用的限制。 請記住,通常RAM的使用是最關(guān)鍵的參數(shù)。3
你所看到的就是一切
嵌入式程序員確實是軟件世界的佼佼者。他們喜歡親手寫程序,創(chuàng)建自己的代碼庫,使用20世紀60年代的語言,使用機械鍵盤。 很少有是現(xiàn)成的庫可以滿足嵌入式系統(tǒng)的特殊限制。雖然有很多JSON解析庫,但很少有庫能夠支持大于RAM容量的文檔的解析。 嵌入式程序員總是樂于嘗試現(xiàn)有的庫,因為他們都很懶。但是,如果沒有合適的庫,那么嵌入式程序員也很樂意重新發(fā)明更小、更快的工具。 由于嵌入式程序員非常重視理解和控制代碼的執(zhí)行和資源的使用,因此幾乎所有代碼都是用C語言編寫的。 有時新的編程語言試圖入侵嵌入式的世界,結(jié)果卻會遭到嵌入式程序員的質(zhì)疑。 這種語言有一些奇特的線程模型嗎?請參見如下有關(guān)并發(fā)的討論。 這種語言有垃圾收集嗎?請參見如下有關(guān)性能的討論。 這種語言的編譯器是否支持人類已知的每種計算機體系結(jié)構(gòu)? 請參見如下有關(guān)可移植性的討論。簡而言之,衡量標準設(shè)定得很高,而且C非常優(yōu)秀。 嵌入式軟件系統(tǒng)往往很好理解 。缺乏第三方的代碼庫和華麗的繼承結(jié)構(gòu),而且還存在嚴格的代碼大小限制,所有這些都保證了代碼很小且易于理解。 在編寫良好的嵌入式代碼中,你在頁面上看到的一切就是所有的代碼。嵌入式程序員無需通過層層地剝離才能弄清楚代碼的實際作用。 這并不是說實際的嵌入式應用程序的邏輯不會非常復雜,但至少我們不需要通過層層疊疊的抽象模式來隱藏這些邏輯。4
并發(fā)
嵌入式開發(fā)人員都不喜歡并發(fā)。對于查爾斯·巴貝奇(可編程計算機的發(fā)明者,計算機的先驅(qū))來說,一次做一件事就足夠了,你也一樣。 大多數(shù)形式的并發(fā)支持都需要不時地保存狀態(tài)并切換到新任務(wù)。 這需要多個堆棧,每個任務(wù)一個,還需要很多的RAM,有時多得不是一點半點?,F(xiàn)如今,堆棧很容易就占用1KB甚至更大的空間。 嵌入的程序員無法忍受這一點。他們寧愿手工處理合作性的任務(wù)切換、非阻塞的I/O、輪詢、回調(diào)、手動任務(wù)調(diào)度、主循環(huán)。 這些都是嵌入式程序員最常用的東西。如果你不了解這些東西的含義,也不用擔心,你可以參考本文末尾推薦的著作。 聰明的讀者可能不禁想問:“難道不是所有類型的的任務(wù)切換都需要保存任務(wù)的狀態(tài)嗎?你豈不是將這些工作從操作系統(tǒng)轉(zhuǎn)嫁到程序員頭上了嗎?” 嵌入式程序員會回答說:“然也!”,或者說:“將工作從操作系統(tǒng)轉(zhuǎn)嫁到程序員頭上,這就是嵌入式編程的工作?。 ?/span> 并發(fā)是一個難題。但是,拋開先入為主的多線程的概念,并發(fā)實際上就簡單多了。你知道你的代碼不會被中斷,因此你很容易掌握執(zhí)行順序。 缺點就在于你需要為I / O等編寫更多的代碼,因為你不能使用阻塞。 前面說過的可憐的嵌入式系統(tǒng)程序員很不走運,對他們而言, 硬件中斷會打亂所有的工作。5
請可移植性
如果你的程序不能在人類所有已知的處理器體系結(jié)構(gòu)上運行,那么你的程序就不算嵌入式程序。
大端、小端、基于堆棧的尋址、基于寄存器的尋址、RISC、 CISC、標量、向量、DSP或PIC,這些都無所謂。
代碼無論放到哪里都可以正常運行。一個字節(jié)包含8比特?沒那么快!嵌入式程序員不會做任何假設(shè),他們會質(zhì)疑所有問題。
如果有一天,客戶要求你的程序在有分段只寫內(nèi)存和分支協(xié)處理器的基于Chewbacca 5000 12.5位堆棧的矢量CPU上運行,那么嵌入式程序員也可以坦然地告訴他:沒問題。
這對你來說意味著什么?瘋狂地刷舊版的C標準吧。
6
易于測試
由于嵌入式程序可以在“設(shè)備外”進行多種類型的測試,因此嵌入式程序員可以在編寫測試代碼的時候,盡情地嘗試現(xiàn)代軟件開發(fā)。
測試代碼可以用Node.JS、Python、或C ++編寫。我們可以在測試代碼中在為堆棧上分配大對象,世上沒有比這更享受的罪惡感了。
為硬件開發(fā)嵌入式程序時,測試絕對至關(guān)重要。你編寫的代碼最終會出現(xiàn)在永遠不會更新的設(shè)備中。
有時你的代碼會被刻錄到ROM芯片中。如果在發(fā)布后才發(fā)現(xiàn)Bug,那成本可就十分昂貴了。
因此,堅決地貫徹現(xiàn)代測試的原則,對編寫易于測試的代碼保持熱情,以及百分百完成測試覆蓋率的決心,是嵌入式程序員非常寶貴的品質(zhì)。
7
性能
對于大多數(shù)程序員來說,性能是一個簡單的問題:“代碼的運行速度夠快嗎?”
如果代碼的運行速度比要求還快,則萬事大吉。
對于嵌入式程序員來說,情況則略為復雜:“代碼的運行速度夠快嗎?實時性夠嗎?功耗是否在計劃內(nèi)?”
除了RAM和代碼大小的限制外,代碼的運行速度還必須夠快。
但不用過快。未使用的周期有助于降低功耗。降低功耗意味著電池壽命更長,產(chǎn)生的熱量更少,而且在下一次硬件升級中可以使用更便宜,更慢的CPU。
我們總是與底層的硬件人員博弈。良好的嵌入式代碼設(shè)計可以促進CPU進入睡眠模式,例如利用顯式輪詢,讓系統(tǒng)可以在輪詢之間休眠。這也意味著代碼不應該因任何原因造成阻塞。
有些嵌入式系統(tǒng)有硬性的實時要求,例如起搏器。在這樣的系統(tǒng)中運行的任何線程或任務(wù)都必須保證能夠在固定的時間段內(nèi)返回。
如果運行時間過長,則系統(tǒng)可能會完全失效。拿起搏器舉個例子,這樣的起搏器會被歸類為“次品”。毋庸置疑,為心臟起搏器寫代碼的工作不適合膽小的人。
大多數(shù)嵌入式媒體應用程序都有軟性的實時要求——如果線程或任務(wù)占用CPU的時間超過應有的時間,那么系統(tǒng)性能會被降級,但不會完全失敗。
音頻或視頻可能會出現(xiàn)故障或卡頓。用戶界面可能會感覺延遲。這絕對不是好事,但也不是災難性的。
編寫具有良好實時特性的代碼非常類似于編寫有助于CPU睡眠模式的代碼,兩者都是利用很短時間片,非常注意循環(huán)長度并且永遠不會阻塞。了解你的代碼路徑。不惜一切代價避免遞歸。
8
怎樣才能成為嵌入式程序員
有理想的嵌入式程序員都有哪些重要的特質(zhì)?
你需要對計算機體系結(jié)構(gòu)的基礎(chǔ)知識有深入的了解。了解內(nèi)存的層次結(jié)構(gòu),吞吐量瓶頸和硬件級別的并發(fā)問題。還有一顆強大的心理掌握尋址的工作原理(指針)。
此外,根據(jù)你需要使用的軟件類型,可能還需要掌握一些行業(yè)的技術(shù)。例如,在Spotify,你需要對網(wǎng)絡(luò)有很好的理解。
如果你覺得你對計算機體系結(jié)構(gòu)的基礎(chǔ)知識沒有深入的了解?
那么請參照下列推薦書籍:
-
《計算機體系結(jié)構(gòu):量化研究方法》
這本書絕對是經(jīng)典之作,寫的非常好。如果你可以理解內(nèi)存層次結(jié)構(gòu)設(shè)計和線程級并行等章節(jié),那么就可以應對內(nèi)存管理、并發(fā)或吞吐量瓶頸相關(guān)的所有問題了。
-
《現(xiàn)代操作系統(tǒng)》
這本是也是經(jīng)典之作。作者寫道:“5年后,每個人都將在他們的200 MIPS,64M SPARCstation-5上運行免費的GNU?!边@本書寫自1991年,但書中的內(nèi)容遠遠超過了作者的預知能力。書中有關(guān)內(nèi)存管理的章節(jié)是內(nèi)存管理方面優(yōu)秀的入門讀物。
-
《C語言程序設(shè)計 K&R》
標準的C編程書。有史以來最優(yōu)秀的編程書籍之一。也可能是有史以來最好的技術(shù)寫作范例之一。當然也是有史以來最暢銷的數(shù)據(jù)。一定要讀!
-
《C語言接口與實現(xiàn)》
這是一本關(guān)于正確使用C的書籍,書中介紹了如何使用最實用的數(shù)據(jù)結(jié)構(gòu)和算法的示例,編寫易于維護和測試的代碼。
-
《C專家編程》
這本書對C語言及其與系統(tǒng)交互進行了深入探討,介紹了內(nèi)存管理、指針算法、編譯器的工作原理、中斷的處理方法。
除此之外,好奇心也是嵌入式程序員身上最重要的品質(zhì),渴望了解事物原理的沖動以及堅韌,愿意全身心地投入到工作中,不斷學習。
作者 | Per Knytt
譯者 | 彎月
責編 | 屠敏
本文為CSDN翻譯,轉(zhuǎn)載請注明來源出處。
版權(quán)歸原作者所有。僅供技術(shù)的傳播和學習討論,如涉及作品版權(quán)問題,請聯(lián)系我進行刪除。免責聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!