04.16 放下偏見,原來嵌入式程序員如此“妖嬈”!

你不知道的入式程序員!

放下偏見,原來嵌入式程序員如此“妖嬈”!

作者 | Per Knytt

譯者 | 彎月

出品 | CSDN(ID:CSDNnews)

寄存器!示波器!鬍子! 串口! C! 循環剃鬚!鬍子!中斷!彙編!等等,我剛剛提到鬍子了嗎?

如果我提到“嵌入式程序員”一詞,我們行業中的大多數人都會立刻想起一個英雄角色的形象。

一個擁有神秘技能的偉大開發人員,有著淵博的神秘學的知識,對個人衛生的概念持半信半疑的態度,面部鬍子拉碴,像神一樣的形象。

然而,在秘密地研究了幾年這個主題之後,我在這裡告訴你,你們的想象統統不正確。

大多數嵌入式程序員都沒有鬍子,做嵌入式程序員也不必連做夢都要使用匯編語言,而且嵌入式程序員也會洗澡。

我還可以告訴你嵌入式編程非常有趣,收穫頗豐,且富有挑戰性。如果你對本文感興趣,那麼有可能將來你也會從事這一行。

請注意,以下內容可能有點雄心勃勃。我會介紹嵌入式程序應有的樣子,而不是大多數嵌入式程序實際的樣子。

這可能是一種理想的狀況。突然偶遇嵌入式代碼,你可能覺得非常嚇人,覺得這些代碼都是一些聰明的傢伙寫的高端代碼。

但我想要告訴你,你會對Spotify的嵌入式編程感興趣,而優秀的代碼正是我們的奮鬥目標。

所以,如果你也是一個嵌入式程序員,那麼可能本文中介紹的代碼與你每天使用的有出入,我明白你的心情,現實確實如此,但生活可以更加美好。

首先,我們來介紹一下嵌入式系統編程與嵌入式應用程序編程之間的區別。

嵌入式系統編程:可能就是你想象中的嵌入式編程。

包括讓嵌入式硬件平臺正常運行所涉及的所有工作:編寫設備驅動程序,引導加載程序,移植或編寫操作系統,各種位操作,還要考慮到時鐘週期問題。調試丟失的中斷。

一連盯著示波器幾個小時,埋怨編譯器,討厭中斷控制器,就彷彿它是一個人一樣。時常在午夜驚醒,冷汗淋漓,努力說服自己是貓偷走了中斷信號的正面。不好意思,我跑題了。嵌入式系統編程確實涉及非常底層的東西。

嵌入式應用程序編程:指的是為資源有限的系統編寫應用程序的技術。這種開發更容易,開發環境很好。

還可以在臺式計算機上編寫、測試和調試代碼。雖然有些約束可能很具挑戰性,但是你無需考慮彙編、GPIO引腳或DMA描述符。

然而,你必須考慮內存的使用情況,運行環境,代碼的大小和可移植性。但說真的,難道有人不喜歡這些工作嗎?

本文討論的是嵌入式應用程序編程,在Spotify我們大部分時間從事的都是這種類型的嵌入式編程。

簡約之美

約束讓我們的生活變得更加有趣。

與完整的管絃樂隊錄製的專輯相比,只用兩根柱子和一杯水錄製的音樂專輯需要更多的創造力。

雖然我對創造力或對“音樂”一詞有著非常規的理解。但是,我想表達的是嵌入式編程很有趣,但是有趣在哪兒呢?

大多數嵌入式程序的首要約束因素是大小,代碼必須緊湊,常見的編程習慣(代碼需要模塊化,易於維護和測試,還要經過測試)仍然適用於嵌入式程序,除此之外還需要將代碼量降到最低,而且還需要保證自給自足。

用一個概括就是:優雅。良好的嵌入式代碼非常優雅。

那麼什麼是嵌入式編程呢?什麼是嵌入式程序員?讓我們繼續往下看……

內存的使用率——隱藏的殺手

嵌入式程序員需要避開現代內存管理的概念。你幾乎不可能實現垃圾收集。一個垃圾收集器可能就耗盡了代碼容量的限制。

而且垃圾收集器還需要不時地運行實際的垃圾收集,這會破壞嵌入式程序的實時效果。

你甚至還需要避免常規的malloc(),調用malloc()可能會花費大量時間,因為分配器可能必須對分配區域進行碎片整理,才能釋放出足夠大的內存塊來響應請求。

嵌入式程序員很樂意直接管理內存,編寫自定義分配器,甚至通過靜態分配的內存塊來杜絕內存分配失敗的發生。

大多數嵌入式系統和普通計算機之間的一個重大區別就在於內存的組織方式。

主流的臺式機和服務器的處理器架構(如Intel x86)使用的編程模型中,代碼和數據存儲在同一個地址空間中。

這意味著如果你的機器有64MB的RAM(好大啊,太不可思議了!),而你的程序是40MB(令人難以置信!),那麼你的數據就只剩下24MB(這麼多,根本用不完啊!)。

在家的時候,我們稱之為馮·諾伊曼架構,但在酒吧我們就不敢這麼說,因為這會引發唇槍舌戰。

由於RAM是處理器中功耗最大的一部分,而且由於RAM佔用大量芯片面積,因此在許多嵌入式系統使用的模型中,代碼和數據分別存儲在不同的存儲器中。

代碼和靜態數據存儲在ROM(通常是閃存或EEPROM)中;而動態數據存儲在RAM中。 ROM比RAM便宜很多,因此通常使用的也較多(通常是5-10倍)。

RAM和ROM可以具有單獨的地址空間(哈佛架構),也可以映射到統一的單個地址空間(改良版的哈佛架構,遺憾的是從未被稱為小丑架構)。

除非你需要編寫引導加載程序或系統內升級的功能(在這些情況下,你需要向代碼的存儲空間中寫入),否則一般你不會注意到這兩者之間的差異。

但這確實意味著代碼的大小和RAM的使用率需要單獨計算。換句話說:在嵌入式系統中,代碼大小的限制不同於RAM使用的限制。請記住,通常RAM的使用是最關鍵的參數。

你所看到的就是一切

嵌入式程序員確實是軟件世界的佼佼者。他們喜歡親手寫程序,創建自己的代碼庫,使用20世紀60年代的語言,使用機械鍵盤。

很少有是現成的庫可以滿足嵌入式系統的特殊限制。雖然有很多JSON解析庫,但很少有庫能夠支持大於RAM容量的文檔的解析。

嵌入式程序員總是樂於嘗試現有的庫,因為他們都很懶。但是,如果沒有合適的庫,那麼嵌入式程序員也很樂意重新發明更小、更快的工具。

由於嵌入式程序員非常重視理解和控制代碼的執行和資源的使用,因此幾乎所有代碼都是用C語言編寫的。

有時新的編程語言試圖入侵嵌入式的世界,結果卻會遭到嵌入式程序員的質疑。

這種語言有一些奇特的線程模型嗎?請參見如下有關併發的討論。

這種語言有垃圾收集嗎?請參見如下有關性能的討論。

這種語言的編譯器是否支持人類已知的每種計算機體系結構?

請參見如下有關可移植性的討論。簡而言之,衡量標準設定得很高,而且C非常優秀。

嵌入式軟件系統往往很好理解。缺乏第三方的代碼庫和華麗的繼承結構,而且還存在嚴格的代碼大小限制,所有這些都保證了代碼很小且易於理解。

在編寫良好的嵌入式代碼中,你在頁面上看到的一切就是所有的代碼。嵌入式程序員無需通過層層地剝離才能弄清楚代碼的實際作用。

這並不是說實際的嵌入式應用程序的邏輯不會非常複雜,但至少我們不需要通過層層疊疊的抽象模式來隱藏這些邏輯。

併發

嵌入式開發人員都不喜歡併發。對於查爾斯·巴貝奇(可編程計算機的發明者,計算機的先驅)來說,一次做一件事就足夠了,你也一樣。

大多數形式的併發支持都需要不時地保存狀態並切換到新任務。

這需要多個堆棧,每個任務一個,還需要很多的RAM,有時多得不是一點半點。現如今,堆棧很容易就佔用1KB甚至更大的空間。

嵌入的程序員無法忍受這一點。他們寧願手工處理合作性的任務切換、非阻塞的I/O、輪詢、回調、手動任務調度、主循環。

這些都是嵌入式程序員最常用的東西。如果你不瞭解這些東西的含義,也不用擔心,你可以參考本文末尾推薦的著作。

聰明的讀者可能不禁想問:“難道不是所有類型的的任務切換都需要保存任務的狀態嗎?你豈不是將這些工作從操作系統轉嫁到程序員頭上了嗎?”

嵌入式程序員會回答說:“然也!”,或者說:“將工作從操作系統轉嫁到程序員頭上,這就是嵌入式編程的工作啊!”

併發是一個難題。但是,拋開先入為主的多線程的概念,併發實際上就簡單多了。你知道你的代碼不會被中斷,因此你很容易掌握執行順序。

缺點就在於你需要為I / O等編寫更多的代碼,因為你不能使用阻塞。

前面說過的可憐的嵌入式系統程序員很不走運,對他們而言,硬件中斷會打亂所有的工作。

可移植性

如果你的程序不能在人類所有已知的處理器體系結構上運行,那麼你的程序就不算嵌入式程序。

大端、小端、基於堆棧的尋址、基於寄存器的尋址、RISC、 CISC、標量、向量、DSP或PIC,這些都無所謂。

代碼無論放到哪裡都可以正常運行。一個字節包含8比特?沒那麼快!嵌入式程序員不會做任何假設,他們會質疑所有問題。

如果有一天,客戶要求你的程序在有分段只寫內存和分支協處理器的基於Chewbacca 5000 12.5位堆棧的矢量CPU上運行,那麼嵌入式程序員也可以坦然地告訴他:沒問題。

這對你來說意味著什麼?瘋狂地刷舊版的C標準吧。

易於測試

由於嵌入式程序可以在“設備外”進行多種類型的測試,因此嵌入式程序員可以在編寫測試代碼的時候,盡情地嘗試現代軟件開發。

測試代碼可以用Node.JS、Python、或C ++編寫。我們可以在測試代碼中在為堆棧上分配大對象,世上沒有比這更享受的罪惡感了。

為硬件開發嵌入式程序時,測試絕對至關重要。你編寫的代碼最終會出現在永遠不會更新的設備中。

有時你的代碼會被刻錄到ROM芯片中。如果在發佈後才發現Bug,那成本可就十分昂貴了。

因此,堅決地貫徹現代測試的原則,對編寫易於測試的代碼保持熱情,以及百分百完成測試覆蓋率的決心,是嵌入式程序員非常寶貴的品質。

性能

對於大多數程序員來說,性能是一個簡單的問題:“代碼的運行速度夠快嗎?”

如果代碼的運行速度比要求還快,則萬事大吉。

對於嵌入式程序員來說,情況則略為複雜:“代碼的運行速度夠快嗎?實時性夠嗎?功耗是否在計劃內?”

除了RAM和代碼大小的限制外,代碼的運行速度還必須夠快。

但不用過快。未使用的週期有助於降低功耗。降低功耗意味著電池壽命更長,產生的熱量更少,而且在下一次硬件升級中可以使用更便宜,更慢的CPU。

我們總是與底層的硬件人員博弈。良好的嵌入式代碼設計可以促進CPU進入睡眠模式,例如利用顯式輪詢,讓系統可以在輪詢之間休眠。這也意味著代碼不應該因任何原因造成阻塞。

有些嵌入式系統有硬性的實時要求,例如起搏器。在這樣的系統中運行的任何線程或任務都必須保證能夠在固定的時間段內返回。

如果運行時間過長,則系統可能會完全失效。拿起搏器舉個例子,這樣的起搏器會被歸類為“次品”。毋庸置疑,為心臟起搏器寫代碼的工作不適合膽小的人。

大多數嵌入式媒體應用程序都有軟性的實時要求——如果線程或任務佔用CPU的時間超過應有的時間,那麼系統性能會被降級,但不會完全失敗。

音頻或視頻可能會出現故障或卡頓。用戶界面可能會感覺延遲。這絕對不是好事,但也不是災難性的。

編寫具有良好實時特性的代碼非常類似於編寫有助於CPU睡眠模式的代碼,兩者都是利用很短時間片,非常注意循環長度並且永遠不會阻塞。瞭解你的代碼路徑。不惜一切代價避免遞歸。

怎樣才能成為嵌入式程序員?

有理想的嵌入式程序員都有哪些重要的特質?

你需要對計算機體系結構的基礎知識有深入的瞭解。瞭解內存的層次結構,吞吐量瓶頸和硬件級別的併發問題。還有一顆強大的心理掌握尋址的工作原理(指針)。

此外,根據你需要使用的軟件類型,可能還需要掌握一些行業的技術。例如,在Spotify,你需要對網絡有很好的理解。

如果你覺得你對計算機體系結構的基礎知識沒有深入的瞭解?

那麼請參照下列推薦書籍:

  • 《計算機體系結構:量化研究方法》
  • 這本書絕對是經典之作,寫的非常好。如果你可以理解內存層次結構設計和線程級並行等章節,那麼就可以應對內存管理、併發或吞吐量瓶頸相關的所有問題了。
  • 《現代操作系統》
  • 這本是也是經典之作。作者寫道:“5年後,每個人都將在他們的200 MIPS,64M SPARCstation-5上運行免費的GNU。”這本書寫自1991年,但書中的內容遠遠超過了作者的預知能力。書中有關內存管理的章節是內存管理方面優秀的入門讀物。
  • 《C語言程序設計 K&R》
  • 標準的C編程書。有史以來最優秀的編程書籍之一。也可能是有史以來最好的技術寫作範例之一。當然也是有史以來最暢銷的數據。一定要讀!
  • 《C語言接口與實現》
  • 這是一本關於正確使用C的書籍,書中介紹瞭如何使用最實用的數據結構和算法的示例,編寫易於維護和測試的代碼。
  • 《C專家編程》
  • 這本書對C語言及其與系統交互進行了深入探討,介紹了內存管理、指針算法、編譯器的工作原理、中斷的處理方法。

除此之外,好奇心也是嵌入式程序員身上最重要的品質,渴望瞭解事物原理的衝動以及堅韌,願意全身心地投入到工作中,不斷學習。

原文:https://labs.spotify.com/2019/04/09/an-opinionated-anthropology-of-the-embedded-programmer-its-habits-and-habitat/

本文為CSDN翻譯,轉載請註明來源出處。


分享到:


相關文章: