嵌入式系統 Boot Loader 技術內幕

2021-06-03 11:03:06 字數 3520 閱讀 8268

.used )

i++;

continue;}/*

* 當前頁已經是乙個被對映到 ram 的有效位址範圍

* 但是還要看看當前頁是否只是 4gb 位址空間中某個位址頁的別名? */

if(* (u32 *)addr != 0)

/**

當前頁已經是乙個被對映到 ram 的有效位址範圍

* 而且它也不是 4gb 位址空間中某個位址頁的別名。 */

if (memory_map

.used == 0) else } /* end of for (…) */

在用上述演算法檢測完系統的記憶體對映情況後,boot loader 也可以將記憶體對映的詳細資訊列印到串列埠。

3.2.3 載入核心映像和根檔案系統映像

(1) 規劃記憶體占用的布局

這裡包括兩個方面:(1)核心映像所占用的記憶體範圍;(2)根檔案系統所占用的記憶體範圍。在規劃記憶體占用的布局時,主要考慮基位址和映像的大小兩個方面。

對於核心映像,一般將其拷貝到從(mem_start+0x8000) 這個基位址開始的大約1mb大小的記憶體範圍內(嵌入式linux 的核心一般都不操過 1mb)。為什麼要把從 mem_start 到 mem_start+0x8000 這段 32kb 大小的記憶體空出來呢?這是因為  linux 核心要在這段記憶體中放置一些全域性資料結構,如:啟動引數和核心頁表等資訊。

而對於根檔案系統映像,則一般將其拷貝到 mem_start+0x0010,0000 開始的地方。如果用 ramdisk 作為根檔案系統映像,則其解壓後的大小一般是1mb。

(2)從 flash 上拷貝

由於像 arm 這樣的嵌入式 cpu 通常都是在統一的記憶體位址空間中定址 flash 等固態儲存裝置的,因此從 flash 上讀取資料與從 ram 單元中讀取資料並沒有什麼不同。用乙個簡單的迴圈就可以完成從 flash 裝置上拷貝映像的工作:

3.2.4 設定核心的啟動引數

應該說,在將核心映像和根檔案系統映像拷貝到 ram 空間中後,就可以準備啟動 linux 核心了。但是在呼叫核心之前,應該作一步準備工作,即:設定 linux 核心的啟動引數。

linux 2.4.x 以後的核心都期望以標記列表(tagged list)的形式來傳遞啟動引數。啟動引數標記列表以標記 atag_core 開始,以標記 atag_none 結束。每個標記由標識被傳遞引數的 tag_header 結構以及隨後的引數值資料結構來組成。資料結構 tag 和  tag_header 定義在 linux 核心原始碼的include/asm/setup.h 標頭檔案中:在嵌入式 linux 系統中,通常需要由  boot loader 設定的常見啟動引數有:atag_core、atag_mem、atag_cmdline、atag_ramdisk、 atag_initrd等。

比如,設定 atag_core 的**如下:其中,boot_params 表示核心啟動引數在記憶體中的起始基位址,指標 params 是乙個  struct tag 型別的指標。巨集tag_next() 將以指向當前標記的指標為引數,計算緊臨當前標記的下乙個標記的起始位址。注意,核心的根檔案系統所在的裝置id就是在這裡設定的。

下面是設定記憶體對映情況的示例**:

可以看出,在 memory_map[]陣列中,每乙個有效的記憶體段都對應乙個 atag_mem 引數標記。

linux 核心在啟動時可以以命令列引數的形式來接收資訊,利用這一點我們可以向核心提供那些核心不能自己檢測的硬體引數資訊,或者過載 (override)核心自己檢測到的資訊。比如,我們用這樣乙個命令列引數字串"console=ttys0,115200n8"來通知核心以  ttys0 作為控制台,且串列埠採用 "115200bps、無奇偶校驗、8位資料位"這樣的設定。下面是一段設定呼叫核心命令列引數字串的示例**:

請注意在上述**中,設定 tag_header 的大小時,必須包括字串的終止符'\0',此外還要將位元組數向上圓整4個位元組,因為 tag_header 結構中的size 成員表示的是字數。

下面是設定 atag_initrd 的示例**,它告訴核心在 ram 中的什麼地方可以找到 initrd 映象(壓縮格式)以及它的大小:

下面是設定 atag_ramdisk 的示例**,它告訴核心解壓後的 ramdisk 有多大(單位是kb):

最後,設定 atag_none 標記,結束整個啟動引數列表:

3.2.5 呼叫核心

boot loader 呼叫 linux 核心的方法是直接跳轉到核心的第一條指令處,也即直接跳轉到 mem_start+0x8000 位址處。在跳轉時,下列條件要滿足:

1. cpu 暫存器的設定:

● r0=0;

● r1=機器型別 id;關於 machine type number,可以參見linux/arch/arm/tools/mach-types。

● r2=啟動引數標記列表在 ram 中起始基位址;

2. cpu 模式:

● 必須禁止中斷(irqs和fiqs);

● cpu 必須 svc 模式;

3. cache 和 mmu 的設定:

● mmu 必須關閉;

● 指令 cache 可以開啟也可以關閉;

● 資料 cache 必須關閉;如果用 c 語言,可以像下列示例**這樣來呼叫核心:

注意,thekernel()函式呼叫應該永遠不返回的。如果這個呼叫返回,則說明出錯。

4. 關於串列埠終端

在 boot loader 程式的設計與實現中,沒有什麼能夠比從串列埠終端正確地收到列印資訊能更令人激動了。此外,向串列埠終端列印資訊也是乙個非常重要而又有效的除錯手段。但是,我們經常會碰到串列埠終端顯示亂碼或根本沒有顯示的問題。造成這個問題主要有兩種原因:(1) boot loader 對串列埠的初始化設定不正確。(2) 執行在 host 端的終端**程式對串列埠的設定不正確,這包括:波特率、奇偶校驗、資料位和停止位等方面的設定。

此外,有時也會碰到這樣的問題,那就是:在 boot loader 的執行過程中我們可以正確地向串列埠終端輸出資訊,但當boot loader 啟動核心後卻無法看到核心的啟動輸出資訊。對這一問題的原因可以從以下幾個方面來考慮:

(1) 首先請確認你的核心在編譯時配置了對串列埠終端的支援,並配置了正確的串列埠驅動程式。

(2) 你的 boot loader 對串列埠的初始化設定可能會和核心對串列埠的初始化設定不一致。此外,對於諸如 s3c44b0x 這樣的cpu, cpu 時鐘頻率的設定也會影響串列埠,因此如果 boot loader 和核心對其 cpu 時鐘頻率的設定不一致,也會使串列埠終端無法正確顯示資訊。

(3) 最後,還要確認 boot loader 所用的核心基位址必須和核心映像在編譯時所用的執行基位址一致,尤其是對於uclinux 而言。假設你的核心映像在編譯時用的基位址是 0xc0008000,但你的 boot loader 卻將它載入到 0xc0010000 處去執行,那麼核心映像當然不能正確地執行了。

5. 結束語

boot loader 的設計與實現是乙個非常複雜的過程。如果不能從串列埠收到那激動人心的 "uncompressing linux.................. done, booting the kernel……"核心啟動資訊,恐怕誰也不能說:"嗨,我的 boot loader 已經成功地轉起來了!"。

深入嵌入式系統的 BootLoader

黑公尺 設定核心的啟動引數 是啊 我原來以為只要傳乙個大小就可以了,不用管幾段的。結果掛載ramdisk的時候 就是識別不到第2個控制器上的映像。但是uboot確是可以使用的。呵呵 這也是經驗吧 記憶體段tag的設定 static void setup memory tags bd t bd 初始化...

嵌入式Bootloader小述

bootloader的概念 bootloader就是在作業系統核心執行之前執行的一段程式,類似於pc機中的bios程式。bootloader的功能就是完成硬體裝置的初始化 建立記憶體空間的對映圖的功能,將系統的軟硬體環境帶到乙個合適的狀態,為最終呼叫系統核心做好準備。嵌入式中的bootloader一...

嵌入式 BootLoader 技術內幕

在專用的嵌入式板子執行 gnu linux 系統已經變得越來越流行。乙個嵌入 式 linux 系統從軟體的角度看通常可以分為四個層次 引導引導程式。包括固化在韌體 firmware 中的 boot 可選 和 boot loader 兩大部分。linux 核心。特定於嵌入式板子的定製核心以及核心的啟動...