曾經和同事一起吃飯,聽他們吐槽程序員壓力太大,我開玩笑說去送外賣會不會更好;但細數了一下,還是程序員香。
我想,能夠選擇程序員作為職業生涯的開始應該算是一種幸運:收入還行,雖然會頂着項目進度的壓力,但只需要吃學習的苦。
現在軟件開發非常熱門,下至各種操作系統、上至五花八門的框架;讓初學者眼花繚亂,很多人很迷茫,擔心學習的某樣東西以后很快被新興事物代替。
我曾聽過亞馬遜創始人Jeff Bezos說過的一句話:
人們經常問,在接下來的10年里,會有什么樣的變化。但是我只問,未來的10年,什么是不變的?——Jeff Bezos
我認為,行業和方向都不是最重要的,按我對《軟技能:代碼之外的生存指南》其中提到的“沖量”的理解,競爭力=選擇+積累。
程序員的三大方向
韋東山將程序員的方向分為3類:專業領域、業務領域、操作系統領域。而按我的理解是:專業領域,業務領域,底層領域。
負責底層領域的人,主要的工作是向其他開發人員提供操作底層硬件的接口,有時候甚至需要搭建業務開發人員程序運行的系統環境,計算軟件運行時所需要的最小資源;配合硬件工程師進行聯調,從軟、硬件2個角度排除解決PCBA上存在的問題;有時候還需要參與硬件設計(主要是CPU外圍),設計行業應用的數據鏈路架構。
從事業務開發的人,需要了解一些操作系統知識,善用各種庫進行,常常使用操作系統的接口(系統調用)進行應用層開發。一般都需要從行業出發、對產品有一定的認識,了解一些業務需求,搞清楚業務的關系。
所以,當領導的人,多是做應用的。一旦鑽入了某個行業,很難換行業。因此,選擇一個好的行業非常重要。——韋東山
對專業開發的人來說,他們常常面對的是業務中某一個核心需求的實現,像科研人員一樣,更多地需要對理論知識有足夠的積累;高數中的很多東西是他們的老朋友;編程語言只是他們實現目標的工具。
這3類工種直接可以沒有明確的界定,只要學習能力足夠強,甚至可以相互轉換,而且這種轉換后的變化可能是更加得心應手的工作。
我們來講講底層領域吧。
底層領域
底層開發需要在於芯片廠商或者板級開發商的BSP,包括3種:
0、BSP開發
1、裸機開發
2、基於操作系統的驅動開發
BSP開發:我曾經以為底層移植需要從通用的BootLoader或者kernel做移植,沒成想實際上這一塊屬於板級供應商的工作。除非是進入到這些供應商從事工作或者進行產品底層的深度定制,否則沒啥機會用到芯片上的編程經驗。
實際上,大部分的移植工作都只是簡單的適配。
裸機開發往往是面對一些對資源要求比較苛刻,無法運行操作系統的場景。例如物聯網領域,尤其是通過電池供電產品;也有一些不需要上系統的原因在於調度和通訊的方式比較簡單,用前后台(輪詢+中斷)系統足以解決這些問題。
而操作系統開發的開發根據系統的不同而不同;嵌入式常見的操作系統很多(過時的WinCE就不說了),由簡到難的有:uCos、FreeRTOS;VxWorks,RT-Thread,Linux...
其中,比較成熟而被人廣泛認識的有:Linux、FreeRTOS、RT-Thead。
我建議學習的路線,“從裸機到系統”:
0、學習一款MCU,例如STM32
1、了解操作系統原理
2、買個資料豐富、有條理而且社區友好的開發板,移植並開發有關的系統
3、在2的基礎上,研究Linux
當然,不僅如此,經常和硬件工程師打交道也是一項工作內容。
操作系統領域所包含的內容,簡單地說,就是制作出一台裝好系統的專用“電腦”,可以分為:
- 為產品規划硬件:按需求、性能、成本和資源選擇主芯片,搭配周邊外設,交由硬件開發人員設計。
- 給單板適配操作系統、編寫驅動以控制外部設備
- 定制維護、升級等整體量產方案
- 為應用開發人員搭建開發環境
- 從系統角度解決疑難雜症
韋東山:我在中興公司上班時,寫驅動的時間其實是很少的,大部分時間是調試:系統調優,上幫APP工程師、下幫硬件工程師查找問題。我們從廠家、網上得到的源碼,很多都是標准的,當然可以直接用。但是在你的產品上也許優化一下更好。比如我們可以把攝像頭驅動和DMA驅動揉合起來,讓攝像頭的數據直接通過DMA發到DSP去。
我們可以在軟件和硬件之間起橋梁作用,對於實體產品,有可能是軟件出問題也可能是硬件出問題,一般是底層系統工程師比較容易找出問題。當硬件、軟件應用出現問題,他們解決不了時,從底層軟件角度給他們出主意,給他們提供工具。再比如方案選擇:芯片性能能否達標、可用的BSP是否完善等等,這只能由負責整個方案的人來考慮,他必須懂底層。
在操作系統領域,對知識的要求很多:
- 外語能力:熟讀各種芯片手冊(我曾見過一種很少見的配置說明,要需要有一定的理解能力)
- 硬件能力:懂硬件知識才能看懂電路圖
- 系統框架:有編寫、移植驅動程序的能力,匯編
- 系統理論:對操作系統本身有一定的理解,才能解決各類疑難問題
- 通信協議:從最簡單的串口到SPI、IIC、CAN上至TCP/IP
- 驅動開發:外設的配置
- 應用開發:C/C++
缺點: 它絕對是一個大坑,沒有興趣、沒有毅力的人慎選:
- 發育緩慢:廣而雜的知識,要學很久;沒有經驗的時候,需要從軟件開發開始學習
- 崗位機會少:絕對比APP的職位少(數量減少、薪資提高)
- 門檻高(當然薪水相對就高)
優點:
- 不擔心行業限制:學好后,行業通殺,想換行就換行;想自己做產品就自己做產品。
- 工作量穩定:不會被經常變動的需求搞得天天加班。(所以說文檔先行,先規划再開發)
驅動開發,我認為適合於這些人:
1、硬件工程師:想轉軟件開發,從底層軟件入門會比較好,硬件經驗能夠用得上;
2、應用開發人員:想深入了解底層的人,不會整天被底層卡脖子;
3、裸機開發人員:面對日益復雜的資源調度處理開始力不從心;有資源能夠上系統的硬件平台可以搞搞。
4、想擁有整體調試能力的人:擁有底層能力對調試來講是一把利刃;
嵌入式軟件中一定有的部分:
-
簡單的OS:內核、驅動、任務。
-
比較龐大的OS:bootloader、內核、驅動、文件系統、應用程序(任務)。
零基礎快速入門
假設您是零基礎,我們規划了如下入門路線圖:
前面的知識,是后面知識的基礎,建議按順序學習。每一部分,不一定需要學得很深入透徹
軟件
C語言:
- 基本語法
- 結構體、指針
- 復雜宏定義
通用:
- 數據結構+算法
應用
PC-Linux(例如Centos、Ubuntu)
- Linux常見命令
- 開發環境配置、搭建
- Linux常見編輯器的使用(vim,emacs)
硬件
- 原理圖、芯片手冊
- 各類通訊協議
我們學習硬件知識的目的在於能看懂原理圖,看懂通信協議,看懂芯片手冊;不求能設計原理圖,更不求能設計電路板。對於正統的方法,你應該這樣學習:
①學習《微機原理》,理解一個計算機的組成及各個部件的交互原理。
②學習《數字電路》,理解各種門電路的原理及使用,還可以掌握一些邏輯運算(與、或等)。
嵌入式學習路線
裸機程序
概述:從簡單的裸機開發入手,先掌握硬件操作。對於基於ARM+Linux的裸機學習,這么做可以學得更深,並且更貼合后續的Linux學習。
Linux驅動開發 = Linux驅動程序軟件框架 + 通過軟件操作硬件
實際上這個部分就是通過軟件操作硬件
:不需要依賴其他框架,你可以按你自己的意願來組織編寫代碼;你只需要知道如何讀寫物理地址即可。(必要時,使用芯片提供的資源對硬件進行控制)
一切從零編寫代碼、管理代碼,可以讓我們學習到更多知識:
實際上這些知識在BootLoader中學習會更有體會
- 需要了解芯片的上電啟動過程,知道第1條代碼如何運行
- 需要掌握怎么把程序讀入內存
- 需要理解內存怎么規划使用,比如棧在哪,堆在哪
- 需要理解代碼重定位
- 需要知道中斷發生后,軟硬件怎么保護現場、跳到中斷入口、調用中斷程序、恢復現場
你會知道:
- main函數不是我們編寫的第1個函數(鏈接地址,加載地址的概念等內容)
- 芯片從上電開始,程序是怎么被搬運執行的(不同芯片加載代碼的差異)
- 函數調用過程中,參數是如何傳遞的(C函數形參列表與匯編寄存器的對應關系)
- 中斷發生時,每一個寄存器的值都要小心對待(異常模式)
- 代碼段、數據段、BSS段(程序在內存中的分布情況)
- 程序的運行:上電復位、代碼重定位、位置無關碼、CPU異常/中斷
- 驅動硬件:時鍾,內存,中斷,GPIO,IIC,SPI,Flash,LCD等(片內資源如何使用)
學習裸機開發的目的有兩個:
1、掌握裸機程序的結構,為后續的u-boot作准備
2、練習硬件知識,即:怎么看原理圖、芯片手冊,怎么寫代碼來操作硬件
后面的u-boot可以認為是裸機程序的集合,我們在裸機開發中逐個掌握各個部件,再集合起來就可以得到一個u-boot了。后續的驅動開發,也涉及硬件操作,你可以在裸機開發中學習硬件知識。
注意:如果你並不關心裸機的程序結構,不關心bootloader的實現,這部分是可以先略過的。
推薦兩本書:杜春蕾的《ARM體系結構與編程》,韋東山的《嵌入式Linux應用開發完全手冊》。
BootLoader
概述:
如果你是軟件工程師,無論是ARM9、ARM11、A8還是A9,對我們來說是沒有差別的。一款芯片,上面有CPU,還有眾多的片上設備(比如UART、USB、LCD控制器)。我們寫程序時,並不涉及CPU,只是去操作那些片上設備。
所以:差別在於片上設備,不在於CPU核;差別在於寄存器操作不一樣。
因為我們寫驅動並不涉及CPU的核心,只是操作CPU之外的設備,只是讀寫這些設備的寄存器。
學習目標:
- uboot框架、修改uboot命令、uboot啟動內核的細節
- 適配uboot的方法(適配相近的硬件板子到實際的板子上,並做修改)
- 讀寫不同的介質下的程序
- 調通網絡,能夠啟動內核
學習方法:
①先學習《從零編寫bootloader》,這可以從最少的代碼理解bootloader的主要功能
②再看書上對u-boot的講解,並結合《分析u-boot 1.1.6的視頻》來理解
③最后,有時間有興趣的話,看《移植一個全新u-boot的視頻》,這不是必須的。
學習程度:
- 理解u-boot的啟動過程,特別是u-boot代碼重定位:怎么從Flash上把自己讀入內存
- 知道bootloader如何給內核傳遞參數
- 知道bootloader是根據“bootcmd”指定的命令啟動內核
- 掌握BootLoader一些調試技巧
內核
描述:內核本身不是我們學習的重點,但是了解一下內核的啟動過程,還是很有必要的:工作中有可能要修改內核以適配硬件,掌握了啟動過程才知道去修改哪些文件。
- 結合代碼分析內核啟動流程
- 配置、適配內核
學習程度:
①知道機器ID的作用,根據機器ID找到單板對應的文件
②知道Makefile、Kconfig的作用,知道怎么簡單地配置內核
③知道怎么修改分區
④作為入門:只求理解,不要求能移植
文件系統
文件系統的學習其實指的是:構建一個根目錄文件系統,此后將其制作為鏡像;這個鏡像的類型是我們平時所說的
概述:
在驅動程序開發階段,我們喜歡搭建一個最小根文件系統來調試驅動;
在開發應用程序時,也需要搭建文件系統,把各種庫、配置文件放進去;
在發布產品時,你還需要修改配置文件,使得產品可以自動運行程序;
甚至你想實現插上U盤后自動啟動某個程序,這也要要修改配置文件;
這一切,都需要你理解根文件系統的構成,理解內核啟動后是根據什么配置文件來啟動哪些應用程序。
學習內容:
- init進程
- 構建最小根文件系統,燒寫到單板上
學習程度:
①理解配置文件的作用
②知道根文件系統中lib里的文件來自哪里
③可以制作、燒寫文件系統映象文件
驅動
字符驅動設備
描述:
對每一個驅動,先了解硬件原理,然后從零寫代碼,從簡單到復雜,逐漸完善它的功能。
以LED、按鍵驅動為例,練習開發過程中碰到的機制:查詢、休眠-喚醒、中斷、異步通知、poll、同步、互斥等等。后續更復雜的驅動程序,就是在這些機制的基礎上,根據硬件特性設計出精巧的軟件框架。
學習內容:
- 設備框架
- 應用與驅動的關系
- 查詢、中斷、休眠喚醒、poll、異步、同步、互斥、阻塞
學習路線:
- 字符設備驅動程序之概念介紹、編寫編譯、測試改進、操作LED
- 字符設備驅動程序之查詢方式的按鍵驅動程序
- 字符設備驅動程序之中斷方式的按鍵驅動:Linux異常處理結構、Linux中斷處理結構、編寫代碼、poll機制、異步通知、同步互斥阻塞、定時器防抖動
- 輸入子系統概念介紹、編寫驅動程序
- 應用程序、庫、內核、驅動程序的關系
- Linux驅動程序的分類和開發步驟
- 驅動程序的加載和卸載
- 字符設備驅動程序開發
- 字符設備驅動程序中重要的數據結構和函數
- LED驅動程序源碼分析
- Linux異常處理體系結構
- Linux異常處理體系結構概述
- Linux異常處理的層次結構
- 常見的異常
- Linux中斷處理體系結構
- 中斷處理體系結構的初始化
- 用戶注冊中斷處理函數的過程
- 中斷的處理過程
- 卸載中斷處理函數
- 使用中斷的驅動程序示例
- 按鍵驅動程序源碼分析、測試程序情景分析
學習方法:
1、沿着數據流向,從應用程序出發,對驅動程序的使用進行情景分析。
所謂情景分析,就是假設應用程序發起某個操作,你去分析其中的運作過程。比如應用程序調用open、read、ioctl等操作時涉及驅動的哪些函數調用。你要思考一個問題:一個應用程序,怎么獲得按鍵信息,怎么去控制LED。把其中數據的流向弄清楚了,對字符驅動程序也就基本理解了。
2、學習異常和中斷時,可以結合書和視頻;對於驅動程序中其他內容的學習,可以不看書。
其他常見驅動
概述:
學習不同的驅動有2個好處:
1、在你工作中遇到同類驅動時提供借鑒
2、供你學習、練習,煅煉閱讀驅動程序的“語感”,提升編寫程序的能力,增加調試經驗
也許有人說:在工作中我們基本上只是移植、修改驅動而已,很少從頭編寫。這話沒錯,但是能修改的前提是理解;想更好地理解,最好的方法是從零寫一個出來。在學習階段,不要怕耗費太多時間,從零開始編寫,慢慢完善它,在這過程中你既理解了這個驅動,也煅煉了能力,做到觸類旁通。
學習內容:
- 輸入子系統、輸出系統(LCD)、USB設備驅動,塊設備(FLASH)驅動、網卡驅動,IIC,SPI,聲卡,攝像頭,熱插拔。
學習方法:
1、再次強調,不能光看不練:一定要寫程序,即使照抄也得寫
2、必學:NAND Flash、Nor Flash、hotplug_uevent機制、IIC、SPI
3、學完之后,強烈建議換一個不同的開發板,嘗試在新板上寫驅動程序。
直到你自認為:
1、給你一個新板,你可以很快實現相關驅動
2、給你一個新硬件,你可以很快給它編寫/移植驅動。
按視頻學習會一切順利,很多問題你可能沒想到、沒想通,換一個新板會讓你真正掌握。
學習路線:
- 驅動程序分層分離概念、總線驅動設備模型
- USB驅動程序
- 塊設備驅動程序
- NAND FLASH驅動程序
- NOR FLASH驅動程序
- 網卡驅動程序
- 移植DM9000C驅動程序
- 內核下的I2C驅動程序
- DMA驅動程序
- hotplug_uevent機制
驅動調試
有一種說法,程序是三分寫七分調。大部分人很快可以寫完程序,但是會把大部分的時間花在調試上面。
驅動調試:
- JTAG原理與調試
- printk的使用
- 打印到proc虛擬文件
- 根據pc值確定出錯的代碼位置、根據棧信息確定函數調用過程
- 自制工具:寄存器編輯器(devmem)
- 修改系統時鍾中斷定位系統僵死問題
應用調試:
- 使用strace命令跟蹤系統調用
- 使用gdb和gdbserver
- 配置修改內核打印用戶態段錯誤信息
- 應用調試之自制系統調用
怎么學習嵌入式Linux操作系統?
學習操作系統理論知識
可以從中了解到一些基礎概念:
1、操作系統常見名詞
2、系統各部分的層次結構
3、任務的調度原理
4、常見的操作系統
...
簡單的操作系統
很多操作系統都不要求芯片有mmu,像FreeRTOS、uCos都是很適合學習的。很容易從中理解到一些操作系統的核心。
學習Linux操作系統
Linux系統本身的知識:
1、操作系統具有進程管理、存儲管理、文件管理和設備管理等功能,這些核心功能非常穩定可靠,基本上不需要我們修改代碼。我們只需要針對自己的硬件完善驅動程序
2、學習驅動時必定會涉及其他知識,比如存儲管理、進程調度。當你深入理解了驅動程序后,也會加深對操作系統其他部分的理解
3、Linux內核中大部分代碼都是設備驅動程序,可以認為Linux內核由各類驅動構成。但是,要成為該領域的高手,一定要深入理解Linux操作系統本身,要去研讀它的源代碼。
在忙完工作,閑暇之余,可以看看這些書:
- 趙炯的《Linux內核完全注釋》,這本比較薄,推薦這本。他后來又出了《Linux內核完全剖析》,太厚了,搞不好看了后面就忘記前面了。
- 毛德操、胡希明的《LINUX核心源代碼情景分析》,此書分上下冊,巨厚無比。當作字典看即可:想深入理解某方面的知識,就去看某章節。