嵌入式linux技術到產品的一些考量

嵌入式linux技術到產品的一些考量

  俗話說“學以致用”,“學”的最終目的是“用”, 特別是技術,如果所學不能運用到實際產品中,那麼學習也失去了意義。 從“學”到“用”還是有一段距離要走的,本文章討論一下嵌入式linux技術到產品中的一些考量。
  ps: 一本書(zlg)上的,覺得寫的不錯,就整理了一下摘過來了。


1. 做最適合的系統

  貼合硬件, 量身定製。《登徒子好色賦》中用“增之一分則太長,減之一分則太短;著粉則太白,施朱則太赤” 形容一個女子恰到好處的美。 做產品又何嘗不是如此? 根據系統軟硬件需求,對系統進行精簡,不但可以減小系統體積,還能帶來系統啓動加快,增強系統穩定性。 做出貼合硬件的系統,通常包含兩方面:內核和文件系統,在特殊情況下,還會對Bootloader 進行貼合調整。
  從評估板到實際產品,往往需要進行裁剪。 因爲通常的評估板系統,出於通用性考慮,往往會集成較多功能軟件,而實際產品通常功能明確,不需要冗餘的軟件。 例如目前很多處理器都帶了音頻接口,評估板廠商在設計評估板的時候,往往會實現音頻功能,如果某個產品最終無需音頻功能,則需要將音頻裁掉。一方面需要在內核中去掉音頻相關驅動,另一方面需要在文件系統中去除音頻相關的庫、軟件以及音頻文件。
  從評估板到實際產品,還有可能需要增加功能。 評估板功能較多,僅僅是針對處理器本身外設而言,評估板會儘可能的把處理器外設資源引出,但也不可能涵蓋選用這個處理器的產品的全部功能。 例如一個產品可能需要進行復雜算法處理,而 ARM 本身沒有這麼強的運算能力,通常可能會通過本地並行總線外擴一個 DSP 來實現。在驅動層需要編寫一個驅動,實現 ARM 和 DSP 之間的相互通信和數據傳輸;在應用層需要結合驅動編寫程序, 實現最終目的。
  從評估板到實際產品, 也有可能需要進行功能修改。 產品和評估板往往有巨大差異,進行功能修改也是不可避免的。可能是引腳功能複用上的修改,這種情況比較常見,例如某對引腳的功能可在 GPIO/CAN/UART 之間切換,評估板作爲 CAN 功能,而某個產品需要作爲UART 使用,這就需要在內核中對這對引腳進行復用修改。也有可能是驅動功能上的修改和完善,這類情況也不少,評估板的驅動有的僅僅完成了基本功能測試,或者隱含一些 BUG,在實際產品中,需要完善這些功能,修復已知 BUG。


2. 做可靠的系統

  系統可靠性是一個軟硬件緊密相關的綜合課題, 僅僅硬件或者軟件單方面都不可能徹底解決可靠性問題,只有兩方面協同處理才能做到。不過在這裏不展開硬件方面的討論,假定硬件已經是滿足可靠性要求,在此基礎上討論系統軟件和可靠性處理。目前絕大部分處理器都支持直接從 NAND 啓動, 由於 NAND 容量大、 價格低,所以在產品設計中很受青睞,通常將系統啓動、存儲介質都設計爲 NAND。 但由於 NAND 本身的特性,在使用過程中不可避免出現位反轉情況。儘管 ECC 能糾正一定範圍內的位反轉, 但也會出現 ECC 無法解決的意外。 解決這類問題, 系統層面可從兩個方面入手:分區域保護和雙備份。

2.1 分區域保護

  對 NAND 進行合理規劃,如 Bootloader、內核、文件系統等不同分區,並對各不同分區設置不同的 mask_flags。對於 Bootloader、內核分區,可以設置 mask_flags 爲MTD_WRITEABLE,禁止該 MTD 分區的可寫屬性,實現這些分區只讀, 防止在系統中意外誤操作這些分區。
  設置了 mask_flags 的分區,在進入系統後,將無法用 flash_erase 命令擦除該分區, 也無法改寫該分區的數據, 從而保護該分區的數據。

[root@zlg ~]# flash_erase /dev/mtd0 0 0
flash_erase: error!: /dev/mtd0
error 13 (Permission denied)

  對於文件系統分區,則不能通過 mask_flags 來設置只讀實現分區保護。不過可以通過在內核 cmdline 中增加 ro 選項實現只讀系統保護。例如:

[root@xxx ~]# cat /proc/cmdline
ubi.mtd=5 root=ubi0:rootfs rootfstype=ubifs ro console=ttyO0,115200n8 mem=256M

  加了 ro 啓動的系統都是隻讀的,對文件系統進行任何改寫都會提示“Read-only file system”,例如:

[root@xxx ~]# touch a
touch: a: Read-only file system

  對這樣的只讀系統,如果要進行系統修改,可用 mount 命令經系統重新以可寫方式掛載:

[root@xxx ~]# mount -o remount,rw /
[root@xxx ~]# touch a

  操作完畢,再用 mount 命令將系統掛載爲只讀,實現系統保護:

[root@xxx ~]# mount -o remount,ro /
[root@xxx ~]# touch a
touch: a: Read-only file system

  如果一個系統全部都是隻讀屬性,在實際應用中會很不友好,如果用戶需要存儲數據,建議單獨分出一個 MTD 分區爲好,並將這個 MTD 分區單獨掛載到文件系統的某個目錄,如/opt 目錄。單獨掛載了 MTD 分區的這個目錄的讀寫權限是不受 cmdline 的 ro 所控制的,也就是說,即使通過 cmdline 實現了根文件系統只讀, 但這個目錄也是可寫的。

2.2 雙備份

  系統分區域保護能夠在很大程度上避免軟件意外操作可能引起的系統數據損壞,但不能解決 NAND 特性引起的數據損壞或者外部環境導致的數據損壞。 如果一旦出現這樣的數據損壞,很有可能導致系統無法運行或者而運行出錯。採用多重備份或者雙備份方式可以在很大程度上避免系統起不來的意外。對 Bootloader 區域和內核區域,可以考慮採用多重備份和雙備份的方式, 增強系統的抗損壞性。
  進行雙備份的鏡像文件, 需要進行校驗,啓動過程中對讀取到的鏡像進行校驗,如果校驗通過則運行該鏡像,否則讀取下一個備份鏡像並校驗, 如果備份鏡像也被損壞導致校驗失敗,則系統掛起。


3. 做用戶滿意的系統

  通常來說,一個用戶滿意的系統,除了功能和性能這兩方面的基本要求之外,其他的生產、測試、維護等方面,也都是需要考量的因素。
  易於生產的系統。一旦產品研發完成,將會交付工廠進行批量生產。除了進行硬件生產和測試之外,還需要燒寫系統、安裝應用程序、系統測試或者進行產品配置等。整個過程要經過多道工序,在產品設計特別是軟件和系統方面,要多爲批量生產考慮,加速批量生產的速度。 對於系統燒寫,通常按照如下順序來選擇:貼片前燒錄器燒寫、貼片後脫機燒寫、貼片後在線燒寫。
  貼片前燒寫速度最快, 適合於大批量生產, 需要藉助燒錄器完成。通過燒錄器軟件製作燒錄工程,可以實現快速批量燒寫。但由於 Linux系統各存儲分區對 NAND 使用方式有差異,以及文件系統的特性,燒錄器並不一定能夠很好支持。在經過驗證可實現的情況下,這種方式是最佳選擇。
  貼片後脫機燒寫, 可以脫離上位機, 但並不適用於所有處理器,只有支持 SD/TF 卡等可移動介質啓動的處理器纔可以實現。製作好啓動卡和鏡像,配合一定的外部引腳設置,實現上電後自動完成燒寫,最終給出燒寫完成指示。
  貼片後在線燒寫,需要上位機配合,效率較低,但可能是最常用的一種方式了。 這種方式最終實現也與處理器密切相關,根據處理器的特性來實現燒寫方式。可能需要將 Bootloader、內核和文件系統等分開燒寫,也有可能需要用到多種方式, 如 JTAG、串口、 USB、網口等各種接口。但是對於一個特定的處理器,在設計量產工序的時候,儘量簡化工序和操作步驟,能實現腳本自動化或者半自動化的一定要儘量實現,最大程度上減小人工干預。 例如 TeraTerm 終端軟件就支持腳本功能,靈活使用該特性能夠簡化操作步驟並減少出錯,從而提高生產效率。
  易於測試的系統。 測試包括兩方面內容,一是出廠前產品功能測試,二是在用戶端進行產品功能自測。用戶端功能自測不是必須的,但出廠前產品功能測試是必不可少的。 出廠前功能測試又分兩種,一種是單項功能測試,另一種是整機老化測試。 無論是單項功能測試還是老化測試,操作過程一定要簡便,程序設計必須考慮自動化、批量測試的可能性, 因爲這些程序的使用對象是工廠的測試人員,一定要做到用最簡單的操作來完成測試,測試結果要能提供可讀報表,便於進行測試結果彙總。
  易於維護的系統。維護系統包括兩方面,一方面是對底層系統的維護,另一方面是對應用程序的維護。通常來說,底層系統維護相對較少,但也不排除會有這種需求。對於底層系統,主要是內核和驅動,對有可能需要進行後期升級的部分,建議編譯成內核模塊,進入系統後再進行加載,後續一旦需要維護或者升級,可以在應用中進行升級替換,而無需重燒內核。應用程序維護,可實現的手段較多,例如可以通過網絡進行遠程升級,或者通過 U 盤、SD/TF 卡等進行本地升級,甚至可以通過 2G/3G/4G、 ZigBee 進行無線升級等等。無論通過何種方式升級,都需要考慮升級失敗能恢復舊版本程序或者能夠進行再次升級,避免升級失敗導致系統故障。


4. 快速啓動

  系統啓動時間的長短直接影響到產品的用戶體驗, 幾乎所有產品開發者和用戶都希望系統啓動時間越短越好,甚至期望能夠做到無操作系統環境的那樣秒啓動。 當然,引入操作系統後,對系統啓動時間是會有很大影響的,但系統經過優化,也是可以做到比較快速啓動的。對嵌入式 Linux 系統啓動速度進行優化,可以從 Bootloader、內核以及文件系統等方面着手。

4.1 精簡 Bootloader

  如果系統能支持內核 XIP,這樣可以無需 Bootloader,可以省略不少時間。但是如果系統不支持內核 XIP,必須通過 Bootloader 引導,那就只能對 Bootloader 進行瘦身了。
 儘量精簡 Bootloader 的功能, 把用不到的功能和命令去掉, 特別是開機硬件自檢功能。如果所使用的 Bootloader 中有諸如對以太網、 USB 等外設進行自檢功能,則可將這些功能去掉,這樣能縮短啓動時間。 就 U-Boot 而言, 如果啓動過程中,無需以太網、 SD 卡、 USB等功能,可以在配置頭文件中將這些外設定義去掉,同時去掉以太網、 SD、 USB 相關的命令。 這樣會引入一個問題,去掉這些功能和命令後,對開發工作會帶來影響,解決辦法是調試階段的 U-boot 和產品發佈的 U-Boot 分開配置,編譯成不同的鏡像,在開發階段用完整功能的 U-Boot,在產品發佈階段用快速啓動版本的 U-Boot。
  另縮減 Boot 過程中的等待時間,例如 U-Boot 的 Autoboot 過程可以被中斷,通常默認有 3 秒的等待時間。 在發佈版本的 U-Boot 中,可將這個等待時間設置爲 0。但是這會帶來無法進入 U-Boot 命令行,如果確定後期無需再進入命令行進行操作那倒也無大礙。如果還希望能進入命令行,還有一個變通的辦法就是將原來的等待時間單位從“秒”調整爲“百毫秒”或者“十毫秒”,這樣既能縮短等待時間,也能在必要的時候進入命令行。 修改的函數是<common/main.c>的 abortboot(int bootdelay)函數, 程序清單所示程序是將等待時間單位修改爲“十毫秒”的範例。

static __inline__ int abortboot(int bootdelay)
{
	int abort = 0;
	#ifdef CONFIG_MENUPROMPT
		printf(CONFIG_MENUPROMPT);
	#else
		printf("Hit any key to stop autoboot: %2d ", bootdelay);
	#endif
	……
	while ((bootdelay > 0) && (!abort)) {
		int i;
		--bootdelay;
		/* 循環 100 次 */
		for (i=0; !abort && i<100; ++i) {
			if (tstc()) { /* we got a key press */
			abort = 1; /* don't auto boot */
			bootdelay = 0; /* no more delay */
			......
			}
			//udelay(10000); //原來爲 udelay(10000), 10 毫秒, 總體單位是 100x10 毫秒, 即 1 秒
			udelay(100); //現在修改爲 udelay(100),總體單位是 100x100 微秒,即 10 毫秒
		}
		printf("\b\b\b%2d ", bootdelay);
	}
	putc('\n');
}

  修改後,等待時間變得很短,出現“Hit any key to stop autoboot:”提示信息後再按鍵盤
按鍵通常來不及中斷 Autoboot,就進不了命令行,需要提前按着鍵盤按鍵不鬆開,直到進入
命令行。

4.2 精簡內核

  對於非 XIP 的內核,內核相關的時間有兩方面: Bootloader 搬運內核的時間和內核自解壓後以及運行時間。
非 XIP 內核被 Bootloader 加載到內存特定地址後才能運行,內核鏡像文件的大小直接影響加載時間, 減小內核體積, 就能減少加載時間,從而縮短啓動整體時間。
  內核被搬運到內存後,開始自解壓並運行, 這段時間的長短與內核所包含的功能有直接關係,內核功能越複雜,所需要處理的事情越多,需要的時間也就越長。縮減這個階段的時間,就要從功能上對內核進行精簡和處理。
  精簡內核的一般思路:一是把冗餘不必要的功能去掉,二是把能推後加載的功能和驅動編譯爲模塊,進入系統後再加載。
  裁剪冗餘功能,需要緊貼硬件和產品需求進行裁剪。假如一個系統沒有 CAN 功能,就把 CAN 驅動和相應的協議裁剪掉。如果產品無需用到 WiFi,則不要在內核中選中 WiFi 相關協議和驅動模塊支持。 另外,對於開發完畢的產品,在內核中把各驅動模塊的調試支持功能去掉,以及在 Kernel Hacking 中關閉各種系統調試功能。
  合理模塊化, 只在內核中保留必要的模塊和驅動,像處理器內置串口、 NAND 驅動、系統 RTC 等這樣內核必備功能,通常建議靜態編譯在內核中。而對於網卡、 CAN、鍵盤、UVC 攝像頭或者聲卡等這些外設,或者一些功能模塊例如 IPv6、 Wireless 相關的協議、以及 FAT 文件系統等,這些都可以編譯爲內核模塊,在進入系統後再加載。
  精簡內核, 還可以打開 Kernel Hacking 中的“Show timing information on printks”功能,這樣在啓動過程中能很清楚的知道哪個地方耗時長,可以有針對性的進行時間優化。
  在實際調試時稍微注意一下就可以發現,對於內核顯示的啓動所用時間,與實際掐表測試的時間是有差異的,這個問題不是內核打印的時間不準確,而是處在串口打印信息這個環節。嵌入式 Linux 調試串口波特率通常爲 115200,也有 38400 這樣更低波特率的,打印內核 LOG 信息會耗費大量時間,從而延長了實際啓動時間。要避免這個問題,可以在內核啓動參數中增加 quiet 參數, 將這些信息屏蔽。例如:

Kernel command line: ubi.mtd=5 root=ubi0:rootfs 
rootfstype=ubifs ro console=ttyO0,115200n8 mem=128M quiet 

4.3 精簡根文件系統

  內核啓動後期,會尋找並掛載根文件系統。 成功掛載根文件系統後,將啓動根文件系統的 init 程序, 並完成一系列系統初始化和服務的啓動,最終進入 Shell 或者用戶程序,影響這段時間的長短有幾方面因素:一方面是根文件系統鏡像的格式;另一方面是根文件系統本身體積的大小,這直接關乎掛載時間;還有就是 init 程序以及根文件系統所啓動的服務和程序的多少。
  根文件系統鏡像格式的選擇,須根據硬件系統所採用的存儲介質類型來選擇,可參考第11.2 小節,選擇最佳匹配的文件系統鏡像格式。 不同類型格式的文件系統鏡像,體積會有差異,在掛載時間上也有很大差異,例如同樣是 NAND FLASH, JFFS2 鏡像掛載時間最長,且掛載時間與文件系統體積有直接關係,而 YAFFS2 時間較短, UBIFS 格式掛載時間最短。
  對某些格式的文件系統而言,文件系統體積的大小直接影響掛載時間,如 JFFS2 文件系統,但對於 YAFFS2 影響較小,對 UBIFS 文件系統則幾乎無影響。 如果系統採用了 JFFS2文件系統, 就必須考慮文件系統的大小。
  init 進程是內核啓動的第一個用戶級進程, 它開始運行後,通過執行一些管理任務來結束引導進程,例如檢查文件系統、清理臨時目錄、啓動各種服務,爲每個終端和虛擬控制檯啓動 getty 等。 System V init 是傳統的 Linux init 程序,近年來逐漸淡出,在不同的發行版中分別被 Systemd init和 upstart所替代。在嵌入式 Linux 中使用更多的是 Busybox init,與 System V、 Systemd 以及 upstart 相比, Busybox init 啓動過程會簡單一些。
  在不影響系統功能的情況下,減少系統服務可以減少啓動時間;如果一些系統服務在日後會用到, 可以推後啓動,保證用戶程序優先啓動。
  特別說明一下設備文件生成和管理。 現在內核和系統支持生成動態設備節點,通常在用戶態用 udev 或者 mdev 來產生和管理系統設備節點。 動態設備節點很方便動態的管理系統所支持的外設,特別是熱插拔設備, 但動態生成設備節點會增加啓動時間,如果對於一個外設相對固定的系統,可以不採用動態設備管理,而改用靜態設別節點,能節省一些啓動時間。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章