tft每日頭條

 > 圖文

 > cortex-m3定義的内核圖是怎樣的

cortex-m3定義的内核圖是怎樣的

圖文 更新时间:2024-08-24 03:08:39

“ARM 程序”是指在 ARM 系統中正在執行的程序,而非保存在 ROM 中的 bin 映像(image)文件。這一點清注意區别。

一個 ARM 程序包含 3 部分: RO, RW 和 ZI。

  • RO 就是隻讀數據,是程序中指令和常量;
  • RW 是可讀寫的數據,程序中已初始化變量;
  • ZI 是程序中未初始化的變量和初始化為 0 的變量。

由以上 3 點說明可以理解為:

  • RO 就是 readonly;
  • RW 就是 read/write;
  • ZI 就是 zero。

1.1 ARM 芯片的啟動過程概述

cortex-m3定義的内核圖是怎樣的(Cortex-M3内核芯片進階之啟動過程詳解)1

圖 3.1 ARM 芯片的啟動過程詳解

注意,以上的過程并非絕對的,不同的 ARM 架構或是不同的代碼以上的執行過程是不同的。

複位處理程序是在彙編器中編寫的短模塊,系統一啟動就立即執行。複位處理程序最少要為應用程序的運行模式初始化堆棧指針。對于具有本地内存系統(如緩存、 TCM、 MMU 和MPU)的處理器,某些配置必須在初始化過程的這一階段完成。複位處理程序在執行之後,通常跳轉到__main 以開始 C 庫初始化序列。

__main 負責設置内存,而__rt_entry 負責設置運行時環境。 __main 執行代碼和數據複制、解壓縮以及 ZI 數據的零初始化。然後,它跳轉到__rt_entry,設置堆棧和堆、初始化庫函數和靜态數據,并調用任何頂級 C 構造函數。然後, __rt_entry 跳轉到應用程序的入口 main()。主應用程序結束執行後, __rt_entry 将庫關閉,然後把控制權交還給調試器。函數标簽 main()具有特殊含義。 main()函數的存在強制鍊接器鍊接到__main 和__rt_entry 中的初始化代碼。如果沒有标記為 main()的函數,則沒有鍊接到初始化序列,因而部分标準 C 庫功能得不到支持。

1.2 結合代碼來看 ARM 芯片的啟動過程

1.2.1 調試環境的搭建及測試代碼

使用 Keil for ARM( uVision4)的軟件模拟,工程的硬件設定為 LPC1700 系列,不使用microlib。這裡不使用 microlib,則系統自動加載标準 C Library,這樣我們才能看到标準的 ARM芯片的标準啟動過程。随後我們會對 microlib 進行探讨。測試代碼如下:

程序清單 3.1 啟動過程測試代碼#include "LPC17xx.h" /* LPC17xx 外設寄存器 */int main (void){

SystemInit();//系統初始化

while (1) {}

}

我們的測試代碼使用的是一段最簡單的代碼,代碼本身隻包含 CMSIS 标準的必備文件,即stdint.h、 core_cm3.h、 core_cm3.c、 system_LPC17xx.h、 system_LPC17xx.c、 LPC17xx.h、startup_LPC17xx.s 和 main.c。

1.2.2 跟蹤啟動代碼開始調試之前,須将工程設置的 DEBUG 欄中取消掉 Run to main()的勾選,否則代碼會直接運行到 main()函數,我們也就無法看到芯片的啟動過程了。然後啟動調試,最先進入 Reset_Handler,如下圖所示。

cortex-m3定義的内核圖是怎樣的(Cortex-M3内核芯片進階之啟動過程詳解)2

圖 1.2 Reset_Handler

繼續單步運行,程序跳入__main(C Library 的代碼,并非用戶代碼),如下圖所示。

cortex-m3定義的内核圖是怎樣的(Cortex-M3内核芯片進階之啟動過程詳解)3

圖 1.3 __main

繼續單步運行,經過一系列的代碼(主要是 scatter load 過程)後,程序進入__rt_entry(同樣是由 C Library 管理,并非用戶代碼)。

cortex-m3定義的内核圖是怎樣的(Cortex-M3内核芯片進階之啟動過程詳解)4

圖 1.4 __rt_entry

繼續單步運行,再次經過一系列的代碼之後(主要是堆棧和堆的初始化以及 C Library 的初始化),程序進入 main()。如下圖所示。

cortex-m3定義的内核圖是怎樣的(Cortex-M3内核芯片進階之啟動過程詳解)5

圖 1.5 進入 main 函數

以上是整個測試代碼啟動過程的跟蹤調試的大緻過程,這個過程對 ARM 系列的芯片來說都是相同的,不同的是裡面具體的細節。

1.2.3 詳細的啟動過程使用的依然是上面的測試代碼,詳細啟動過程如下圖所示。

cortex-m3定義的内核圖是怎樣的(Cortex-M3内核芯片進階之啟動過程詳解)6

圖 1.6 芯片啟動的詳細過程

上圖顯示的就是測試代碼在 LPC17xx 上啟動運行的全過程(詳細過程),由此圖可見LPC17xx 的啟動過程與圖 1.1 所示的啟動過程是基本一緻的,但是還有差别,可以說是圖 1.1所示啟動過程的簡化版。注意:并非所有代碼的啟動過程全部相同,此啟動過程與所使用的鍊接器、用戶代碼以及其所集成的 C Library 密切相關。此圖為由“armlink”的鍊接器為程序清單 1.2 所示的測試代碼産生的啟動過程,其他情況可能會有一些差異(比如有的代碼的啟動過程就會運行一段 RW段的解壓代碼等,而本例程中則沒有)。

1.2.4 __main

若程序使用的是 C 或 C 語言編寫的代碼,那麼 C/C 程序的入口是在 C Library 中的__main。庫代碼在此處執行以下操作。

  • 1 将非根(RO 和 RW)執行區從其加載地址複制到執行地址(這裡的根區指的就是__main 和__rt_entry)。另外,如果壓縮了任何數據節,則會将它們從加載地址解壓縮到執行地址(此數據壓縮及解壓縮過程并不是對所有代碼都會執行);
  • 将 ZI 區清零;
  • 跳轉到__rt_entry。

1.2.5 __rt_entry

__rt_entry 符号是使用 ARM C 庫的程序的起點。将所有分散加載區重定位到其執行地址後,會将控制權傳遞給__rt_entry。其有如下缺省實現:

  • 設置堆和堆棧。
  • 調用__rt_lib_init 以初始化 C Library。
  • 調用 main()。
  • 調用__rt_lib_shutdown 以關閉 C Library。
  • 退出。

注意:最後兩步是在程序退出 main()函數時才會執行,而嵌入式程序一般都是死循環,所以基本不會執行這兩個過程。還有,以上過程是對标準 C Library 而言,不包括使用 microlib的情況。

1.2.6 __rt_lib_init

這是庫初始化函數,它與__rt_lib_shutdown()配合使用。

這是庫初始化函數。它是緊靠__rt_stackheap_init()後面調用的,并傳遞一個要用作堆的初始内存塊。此函數是标準 ARM 庫初始化函數,不能重新實現此函數。

1.3 關于 microlib

microlib 是缺省 C 庫的備選庫。它旨在與需要裝入到極少量内存中的深層嵌入式應用程序配合使用。這些應用程序不在操作系統中運行。 microlib 進行了高度優化以使代碼變得很小。它的功能比缺省 C 庫少,并且根本不具備某些 ISO C 特性。某些庫函數的運行速度也比較慢,例如, memcpy()。microlib 與缺省 C 庫之間的主要差異是:

  • microlib 不符合 ISO C 庫标準。不支持某些 ISO 特性,并且其他特性具有的功能也較少;
  • microlib 不符合 IEEE 754 二進制浮點算法标準;
  • microlib 進行了高度優化以使代碼變得很小;
  • 無法對區域設置進行配置。缺省 C 區域設置是唯一可用的區域設置;
  • 不能将 main()聲明為使用參數,并且不能返回内容;
  • 不支持 stdio,但未緩沖的 stdin、 stdout 和 stderr 除外;
  • microlib 對 C99 函數提供有限的支持;
  • microlib 不支持操作系統函數;
  • microlib 不支持與位置無關的代碼;
  • microlib 不提供互斥鎖來防止非線程安全的代碼;
  • microlib 不支持寬字符或多字節字符串;
  • 與 stdlib 不同, microlib 不支持可選擇的單或雙區内存模型。 microlib 隻提供雙區内存模型,即單獨的堆棧和堆區。

1.4 x.map

想要更好的了解啟動代碼的運行機制,我們就有必要了解一下由 Keil 的鍊接器“armlink”生成的描述文件即 x.map 文件。

1.4.1 關于鍊接器

cortex-m3定義的内核圖是怎樣的(Cortex-M3内核芯片進階之啟動過程詳解)7

圖 1.7 目标文件的組成

上圖即是 armlink 的鍊接器為程序清單 1.1 所示的測試代碼生成的.map 文件中的一部分,其描述了鏡像文件的組成信息,其中可以明顯的看到其由兩部分構成:

  • 由 User Code 生成的目标文件;
  • 由 C Library 生成的目标文件。

可見我們在上文中所描述的啟動過程中看到的__main、 __rt_entry、 __scatterload 以及__rt_lib_init 等,就是 C Library 中的代碼。同理,我們每次燒錄的可執行的 ARM 的 bin 文件中不僅有開發者編寫的代碼,還有C Library 的代碼。

1.4.2 RW 段在 RAM 的存放

cortex-m3定義的内核圖是怎樣的(Cortex-M3内核芯片進階之啟動過程詳解)8

圖 1.8 由 armlink 生成的 RW 段在 RAM 中存放的描述

1.5 關于 ARM 程序的 Memory 管理

1.5.1 ARM 鏡像文件的組成(image

所謂 ARM 映像文件就是指燒錄到 ROM 中的 bin 文件,也成為 image 文件。以下用 Image文件來稱呼它。 Image 文件包含了 RO 和 RW 數據(注意:不包含 ZI 數據)。之所以 Image 文件不包含 ZI 數據,是因為 ZI 數據都是 0,沒必要包含,隻要程序運行之前将 ZI 數據所在的區域(執行區域)一律清零即可。包含進去反而浪費存儲空間。

1.5.2 關于 image 文件(鏡像文件)

從以上兩點可以知道,燒錄到 ROM 中的 image 文件與實際運行時的 ARM 程序之間并不是完全一樣的。因此就有必要了解 ARM 程序是如何從 ROM 中的 image 到達實際運行狀态的。實際上, RO 中的指令(啟動程序)至少應該有這樣的功能:

  • 将 RW 從 ROM 中搬到 RAM 中,因為 RW 是變量,變量不能存在 ROM 中。
  • 将 ZI 所在的 RAM 區域全部清零,因為 ZI 區域并不在 Image 中,所以需要程序根據編譯器給出的 ZI 地址及大小來将相應得 RAM 區域清零。 ZI 中也是變量,同理:變量不能存在 ROM 中。在程序運行的最初階段, RO 中的指令(啟動程序)完成了這兩項工作後(也就是分散加載的過程) C 程序才能正常訪問變量。否則隻能運行不含變量的代碼。說了這麼多可能還是有些迷糊, RO, RW 和 ZI 到底是什麼,下面我将給出幾個例子,最直觀的來說明 RO, RW, ZI在 C 語言中的含義。

1.5.3 RO

看下面兩段程序,它們之間差了一條語句,這條語句就是聲明一個字符常量。因此按照之前的内容,它們之間應該隻會在 RO 數據中相差一個字節(字符常量為 1 字節)。

Prog1:

#include <stdio.h>

void main(void)

{

;

}

Prog2:

#include <stdio.h>

const char a = 5;

void main(void){

;

}

Prog1 編譯出來後的信息如下(來自.map 文件):

cortex-m3定義的内核圖是怎樣的(Cortex-M3内核芯片進階之啟動過程詳解)9

Prog2 編譯出來後的信息如下(來自.map 文件):

cortex-m3定義的内核圖是怎樣的(Cortex-M3内核芯片進階之啟動過程詳解)10

以上兩個程序編譯出來後的信息可以看出: Prog1 和 Prog2 的 RO 包含了 Code 和 RO Data兩類數據。他們的唯一區别就是 Prog2 的 RO Data 比 Prog1 多了 1 個字節。這正和之前的推測一緻。如果增加的是一條指令而不是一個常量,則結果應該是 Code 數據大小有差别。

1.5.4 RW

同樣再看兩個程序,它們之間隻相差一個“已初始化的變量”,按照之前所講的,已初始化的變量應該是算在 RW 中的,所以兩個程序之間應該是 RW 大小有區别。Prog3:

#include <stdio.h>

void main(void)

{;

}

Prog4:

#include <stdio.h>

char a = 5;

void main(void)

{

;

}

Prog3 編譯出來後的信息如下(來自.map 文件):

cortex-m3定義的内核圖是怎樣的(Cortex-M3内核芯片進階之啟動過程詳解)11

Prog4 編譯出來後的信息如下(來自.map 文件):

cortex-m3定義的内核圖是怎樣的(Cortex-M3内核芯片進階之啟動過程詳解)12

以上兩個程序編譯出來後的信息可以看出: Prog1 和 Prog2 的 RO 包含了 Code 和 RO Data兩類數據。他們的唯一區别就是 Prog2 的 RO Data 比 Prog1 多了 1 個字節。這正和之前的推測一緻。如果增加的是一條指令而不是一個常量,則結果應該是 Code 數據大小有差别。

1.5.5 ZI 段(初始化為 0 或未初始化的變量)

再看兩個程序,他們之間的差别是一個未初始化的變量“a”,從之前的了解中,應該可以推測,這兩個程序之間應該隻有 ZI 大小有差别。

Prog3:

#include <stdio.h>

void main(void)

{

;

}

Prog4:

#include <stdio.h>

char a;

void main(void)

{

;

}

Prog3 編譯出來後的信息如下(來自.map 文件):

cortex-m3定義的内核圖是怎樣的(Cortex-M3内核芯片進階之啟動過程詳解)13

Prog4 編譯出來後的信息如下(來自.map 文件):

cortex-m3定義的内核圖是怎樣的(Cortex-M3内核芯片進階之啟動過程詳解)14

編譯的結果完全符合推測,隻有 ZI 數據相差了 1 個字節。這個字節正是未初始化的一個字符型變量“a”所引起的。

注意: 如果一個變量被初始化為 0,則該變量的處理方法與未初始化華變量一樣放在 ZI區域。即: ARM C 程序中,所有的未初始化變量都會被自動初始化為 0。以上代碼是再 ADS 下編譯的, keil 環境下與之不同,比如在 keil 下生成 ZI 數據段就必須定義一個大于 8 字節的未初始化或初始化位 0 的變量,且必須在源代碼中引用此變量才會在鍊接的描述文件中看到其生成的 ZI 文件。

1.6 缺省内存映射

對于沒有描述内存映射的映像,鍊接器根據缺省内存映射放置代碼和數據。如下圖所示。

cortex-m3定義的内核圖是怎樣的(Cortex-M3内核芯片進階之啟動過程詳解)15

圖 1.9 缺省内存映射

  • 創建一個可以在其中執行 C 或 C 程序的環境。這包括:
  • 創建一個堆棧;
  • 創建一個堆(如果需要);
  • 初始化程序所用的庫的部分組成内容;
  • 調用 main()以開始執行程序;
  • 支持程序使用 ISO 定義的函數;
  • 捕獲運行時錯誤和信号,如果需要,還可以在出現錯誤或程序退出時終止執行。

注意:這個内存映射并非對所有芯片都有效,不同的芯片的内存映射是不同的。在

Cortex-M3 中的内存映射與上圖是一緻的。

1.7 内存模型

在 Keil for ARM 下你可以選擇以下任意内存模型:

1. 單内存區

堆棧從内存區頂部向下增長。堆從内存區底部向上增長。這是缺省設置。由堆管理的内存從來不會縮減。不能将通過調用 free()釋放的堆内存再次用于其他用途。

2. 雙内存區

一個内存區用于堆棧,另一個内存區用于堆。堆區大小可以是零。堆棧區可以位于分配的内存中,也可以從執行環境中繼承。要使用雙區模型而不是缺省的單區模型,請使用以下任一方法:

  • 彙編語言中的 IMPORT __use_two_region_memory;
  • C 中的#pragma import(__use_two_region_memory)。

例如下圖所示,此代碼來自 startup_LPC17xx.s。

cortex-m3定義的内核圖是怎樣的(Cortex-M3内核芯片進階之啟動過程詳解)16

如果使用雙區内存模型,并且未提供任何堆内存,則無法調用 malloc()、使用 stdio 或獲取main()的命令行參數。如果将堆區大小設置為 0,并且将__user_heap_extend()定義為可擴展堆的函數,則會在需要時創建堆。

,

更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!

查看全部

相关圖文资讯推荐

热门圖文资讯推荐

网友关注

Copyright 2023-2024 - www.tftnews.com All Rights Reserved