网站建设钅金手指排名,平面设计专业的大专院校,做旅游视频网站,h5网站后台管理模板目录
一#xff1a;引入
1#xff1a;基本概念
2#xff1a;NAND启动
3#xff1a;NOR启动
4:变量
5#xff1a;实验证明
A:代码makefile
B:NOR启动
C:NAND启动
D:内存空间
二#xff1a;链接脚本
1:NOR
2:NAND
3:解决方法
A:尝试解决
B:方法一解决
A:简…目录
一引入
1基本概念
2NAND启动
3NOR启动
4:变量
5实验证明
A:代码makefile
B:NOR启动
C:NAND启动
D:内存空间
二链接脚本
1:NOR
2:NAND
3:解决方法
A:尝试解决
B:方法一解决
A:简单实现
B:通用方法
C:方法二解决
D:链接脚本解释
三:bbs文件解决
四:拷贝代码和链接脚本的改进
五:代码重定位与位置无关码
A:代码重定义
B:位置无关码
六:C语言实现 一引入
1基本概念 CPU发出的指令通过内存控制器可以直接到达内存类设备SRAM, SDARM,NOR FLASH。但是不能直接到达NAND FLASH 2440给我们配置了4K的SRAM和基地址为0x3000 0000的SDRAM。还有其他的许多外设。 具体的我们参考我04:2440---内存控制器所写的内容
2NAND启动 由于CPU不能直接访问我们的NAND FLASH, 所以在NAND FLASH启动的时候2440的硬件会把NAND前4K的内容复制到SRAM中去。 CPU直接访问SRAMCPU从SRAM0地址取出命令执行这样做达到了NAND FLASH启动读取写入数据的目的。 NAND启动的时候NOR不可访问。SRAM的基地址为0。 当bin文件超过4K的时候》因为我们配置的2440SRAM只有4K所以这个时候我们不能在复制到SRAM中去了SRAM内存不够了。我们使用代码重定义把他复制到SDRAM中去CPU也可以直接访问内存类设备SDARM。
3NOR启动 NOR的基地址为0但是SRAM的基地址为0x4000 0000(NAND FLASH复制到SRAM的内存) CPU读取出NOR第一个指令(前4个字节)执行CPU继续在读取出其他的指令在执行;一边读取一边执行 NOR可以向内存一样读但是不能向内存一样写。如果非要写必须发出一定的格式
4:变量 通常来说我们经常说的写入是指写入数据段而不是代码段。 局部变量放在栈中------指向SRAM可读可写 全局变量放在bin文件里面写在NOR上面直接写入无效NOR不能写
为什么全局变量无法直接写入NOR FLASH中 全局变量不能直接写入NOR FLASH中的原因主要有以下几点 存储机制不同全局变量通常存储在RAM随机存取存储器中而NOR FLASH是按照块或页进行存储的其读取和写入操作需要按照特定的时序和命令进行。写入命令不同NOR FLASH使用特殊的命令集进行擦除和编程操作而全局变量的写入操作不符合这些命令规范。数据格式不同全局变量的数据格式可能与NOR FLASH的存储格式不兼容导致无法正确写入。存储空间限制NOR FLASH的存储空间通常是有限的直接写入全局变量可能会超出其容量限制。 为了将全局变量写入NOR FLASH中您需要使用特定的FLASH API或库函数这些函数封装了正确的NOR FLASH擦除和编程命令并确保数据格式正确。此外还需要遵循特定的编程时序和数据传输方式。 NOR FLASH不能在全局变量所处的数据段data中直接写入 NOR FLASH不能直接写的意思是它不支持直接在已有的数据上进行修改。NOR FLASH是一种非易失性存储器类似于常规的NOR门电路具有直接读取和执行代码的能力。然而它不支持随机写入即不能直接在已有的数据上写入新的数据。如果要写入新的数据必须先擦除整个块或页然后再进行写入。因此在使用NOR FLASH时需要考虑到它的这种特性并正确地处理数据的写入和擦除操作 我们经常说的写入是写入数据段还是代码段 通常来说我们经常说的写入是指写入数据段而不是代码段。 数据段是用来存储程序中使用的变量、数组、结构和其他数据结构的内存区域在程序运行期间需要频繁地被写入和读取。因此当我们说“写入”时通常是指向数据段的写入操作。 而代码段是用来存储程序代码的内存区域通常是只读的不需要频繁地被写入。在程序运行期间代码段只需要被读取不需要进行写入操作。 当然具体的情况可能会因不同的系统和程序而有所不同。例如在某些嵌入式系统中代码段也可能需要进行修改和更新这时候就需要进行写入操作。因此具体情况还需要根据具体的系统和程序来确定。 5实验证明
A:代码makefile
#include uart.h
#include sc2440_so.h
#include NOR.h
#include led.hchar g_Char A;
const char g_Char2 B;
int g_A 0;
int g_B;void delay(volatile int d)
{while (d--);
}
int main(void)
{UART_init();puts(The UART function is normal:\n\r);while (1){putchar(g_Char);g_Char;delay(1000000);}return 0;
} 发现问题---bin文件出奇的大我们查看反汇编文件 代码段的位置0 发现数据段位置有些不太好 我们修改makefile文件改变他data段的地址
all:arm-linux-gcc -c -o uart.o uart.carm-linux-gcc -c -o main.o main.carm-linux-gcc -c -o start.o start.Sarm-linux-gcc -c -o led.o led.carm-linux-gcc -c -o NOR.o NOR.carm-linux-gcc -c -o uart.o uart.carm-linux-ld -Ttext 0 -Tdata 0x800 start.o led.o uart.o NOR.o main.o -o uart.elfarm-linux-objcopy -O binary -S uart.elf uart.binarm-linux-objdump -D uart.elf uart.dis
clean:rm *.bin *.o *.elf *.dis
修改代码段起始的地址使他起始地址为0x800---这样不会时bin文件太大 bin文件的大小为 0x81
B:NOR启动 从这里我们可以看到全局变量在NOR无法写入只能读
C:NAND启动 我们可以看到些在nand上面的数据可以正常的写入。
D:内存空间 可以看到初始值为0的全局变量无初始值的全局变量和注释的地址为0x804和0x808而我们的bin文件的大小只有0x801。所以这两个并不在我们的bin文件里面 二链接脚本
1:NOR 一上电我们的CPU从零开始取代码来执行它要操作到gchar g_Char的时候读完全没问题可以读到这个a加加的时候他要把这个值加加后的值写到。800这里去他写不进去所以说再次读出来的时候仍然是原来的所以说如果我们的程序从诺启动的话它会一直输出 为了解决我们全局变量无法直接写入NOR中去。我们可以将g-char代码重定义到SDARM中去。提供2种解决方法
解决方法 2:NAND 当我们实验NAND FLASH启动的时候NOR不可见。NAND中的4K内存被复制到SARM中去CPU直接访问我们的SARM。
3:解决方法
A:尝试解决
makefile
all:arm-linux-gcc -c -o uart.o uart.carm-linux-gcc -c -o main.o main.carm-linux-gcc -c -o start.o start.Sarm-linux-gcc -c -o led.o led.carm-linux-gcc -c -o NOR.o NOR.carm-linux-gcc -c -o uart.o uart.carm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o NOR.o main.o -o uart.elfarm-linux-objcopy -O binary -S uart.elf uart.binarm-linux-objdump -D uart.elf uart.dis
clean:rm *.bin *.o *.elf *.dis 我们修改makefile文件使我们的数据段开始地址为-----0x3000 0000。直接代码重定义指向我们的SDARM,这样我们就可以修改全局变量 发现我们的bin文件巨大数据段也确实是从0x3000 0000开始的
问题的原因 代码段和数据段中间出现的巨大的黑洞。我们解决的方法在上面以及写了。现在我们来实施 使用代码重定义----解决NOR数据段data)无法写入的问题。
B:方法一解决 A:简单实现
makefile文件
all:arm-linux-gcc -c -o led.o led.carm-linux-gcc -c -o uart.o uart.carm-linux-gcc -c -o NOR.o NOR.carm-linux-gcc -c -o main.o main.carm-linux-gcc -c -o start.o start.S#arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elfarm-linux-ld -T sdram.lds start.o led.o uart.o NOR.o main.o -o sdram.elfarm-linux-objcopy -O binary -S sdram.elf sdram.binarm-linux-objdump -D sdram.elf sdram.dis
clean:rm *.bin *.o *.elf *.dis 使用链接脚本设置我们代码段和数据段的起始地址 * -T sdram.lds: 指定链接脚本为sdram.lds。链接脚本定义了如何组织和布局各个段section在输出文件中的位置。 SDRAM.lds
SECTIONS {.text 0 : { *(.text) }.rodata : { *(.rodata) } .data 0x30000000 : AT(0x700) { *(.data) } .bss : { *(.bss) *(.COMMON) }
}.text 0 : { *(.text) } 这一行告诉链接器将所有的 .text section 放置在地址 0。这是非常不常见的因为通常程序会从更高的地址开始执行。这个地址可能适用于某些特殊的硬件或者模拟器。.rodata : { *(.rodata) } 这一行将所有的 .rodata section 放置在同一地址。这是只读数据段通常包含不会在程序执行期间改变的数据。.data 0x30000000 : AT(0x700) { *(.data) } 这一行稍微复杂一些。它将所有的 .data section 放置在地址 0x30000000但是在链接过程中这些 section 的虚拟地址将被设置为 0x700。这意味着程序在运行时将使用虚拟地址 0x700 来访问这些数据但是在内部它们仍将在物理地址 0x30000000。这种做法通常用于实现内存保护和隔离。.bss : { *(.bss) *(.COMMON) } 这一行将所有的 .bss section 和 .COMMON section 放置在同一地址。.bss section 通常包含程序运行期间会被初始化为零的变量。.COMMON 是一个不太常见的 section 类型通常用于在多个目标文件之间共享未初始化数据。 请补充缺失的 END 标签并确保这个链接脚本与你的目标文件和编译器兼容。如果你有任何其他问题或需要进一步的解释请随时提问。 strat.S bl sdram_init/* 重定位data段 */mov r1, #0x700 ldr r0, [r1]mov r1, #0x30000000str r0, [r1]bl main 上面的这种方法只能复制0x700处的一位数据不太通用下面写一个更加通用的复制方法在b中我们使用同用的方法来解决
B:通用方法
makefile文件不改变
SDRAM.lds
SECTIONS {.text 0 : { *(.text) }.rodata : { *(.rodata) }.data 0x30000000 : AT(0x800) { data_load_addr LOADADDR(.data);data_start . ;*(.data) data_end . ;}.bss : { *(.bss) *(.COMMON) }
} 这段代码看起来像是链接脚本的一部分通常在嵌入式系统或操作系统内核的编译过程中使用。这个脚本定义了几个段sections在内存中的位置。让我们逐行地解析它 SECTIONS {: 开始定义链接脚本的sections部分。.text 0 : { *(.text) }: 这一行定义了.text段该段通常包含程序的代码。0是该段在内存中的初始地址{ *(.text) }表示链接器应将所有的.text段链接到该地址。.rodata : { *(.rodata) }: 这一行定义了.rodata段该段通常包含程序中不可修改的数据如字符串常量。链接器会将所有的.rodata段链接到该地址。.data 0x30000000 : AT(0x800) { ... }: 这一行定义了.data段该段通常包含初始化的全局变量。0x30000000是该段在内存中的初始地址AT(0x800)指定了该段在内存中的最终地址重定位地址然后的大括号内部定义了数据加载的地址和起始/结束标记。data_load_addr LOADADDR(.data);: 这行代码将.data段的加载地址存储在变量data_load_addr中。data_start .;: 这行代码将当前位置标记为.data段的开始。*(.data);: 这行代码告诉链接器将所有的.data段链接到之前定义的地址.data段的开始和结束已经在之前定义。data_end .;: 这行代码将当前位置标记为.data段的结束。.bss : { *(.bss) *(.COMMON) }: 这一行定义了.bss段该段通常包含未初始化的全局变量。大括号内的内容告诉链接器将所有的.bss段和.COMMON段链接到该地址。}: 结束定义sections部分。 这个链接脚本的主要目的是告诉链接器如何组织和定位代码和数据在内存中。 strat.S bl SDARMA_init /* 重定位data段 */ldr r1, data_load_addr /* data段在bin文件中的地址, 加载地址 */ldr r2, data_start /* data段在重定位地址, 运行时的地址 */ldr r3, data_end /* data段结束地址 */cpy:ldrb r4, [r1]strb r4, [r2]add r1, r1, #1add r2, r2, #1cmp r2, r3bne cpy
C:方法二解决
见下面的代码重定位与无关码
D:链接脚本解释
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr ){ contents } region :phdr fill
...
}secname 段名start 起始地址运行时的地址(runtime addr)重定位地址(relocate addr)AT ( ldadr ) 可有可无(load addr:加载地址) 不写时LoadAddr runtime addr{ contents } 的内容 start.o //内容为start.o文件*.text所有的代码段文件start.o *(.text)文件① 运行地址—链接地址他们两个是等价的只是两种不同的说法。 运行地址程序在SRAM、SDRAM中执行时的地址。就是执行这条指令时PC应该等于这个地址换句话说PC等于这个地址时这条指令应该保存在这个地址内。 ② 加载地址—存储地址他们两个是等价的也是两种不同的说法。 加载地址程序保存在Nand flash中的地址。 三:bbs文件解决 我们发现了一个问题在SDRAM.lds文件中 bbs初始化为0的全局变量或者没有给值得全局变量如果太多了怎么办我们把bbs在得区域直接设置为0就ok了。 bbs不在bin文件中。
makefile
all:arm-linux-gcc -c -o led.o led.carm-linux-gcc -c -o uart.o uart.carm-linux-gcc -c -o NOR.o NOR.carm-linux-gcc -c -o main.o main.carm-linux-gcc -c -o start.o start.S#arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elfarm-linux-ld -T sdram.lds start.o led.o uart.o NOR.o main.o -o sdram.elfarm-linux-objcopy -O binary -S sdram.elf sdram.binarm-linux-objdump -D sdram.elf sdram.dis
clean:rm *.bin *.o *.elf *.dis
sdram.bin
SECTIONS {.text 0 : { *(.text) }.rodata : { *(.rodata) }.data 0x30000000 : AT(0x800) { data_load_addr LOADADDR(.data);data_start . ;*(.data) data_end . ;}bss_start .;.bss : { *(.bss) *(.COMMON) }bss_end .;
}
start.S bl SDARMA_init /* 重定位data段 */ldr r1, data_load_addr /* data段在bin文件中的地址, 加载地址 */ldr r2, data_start /* data段在重定位地址, 运行时的地址 */ldr r3, data_end /* data段结束地址 */cpy:ldrb r4, [r1]strb r4, [r2]add r1, r1, #1add r2, r2, #1cmp r2, r3bne cpy/* 清除BSS段 */ldr r1, bss_startldr r2, bss_endmov r3, #0
clean:strb r3, [r1]add r1, r1, #1cmp r1, r2bne cleanbl main
四:拷贝代码和链接脚本的改进 我们在汇编文件start.S 中使用了下面的代码导硬件的访问效率低下一次只能访问一个字节。 ldrb r4, [r1]strb r4, [r2] 读 我们知道这SDRAM是32位的那么你读SDRAM写SDRAM的时候一次性操作只能够以32位作为最小单位来访问。这个CPU想去ldrb读一个字节它会把这个命令发给这内存控制器。这个内存控制器从里面读到四个字节的数据然后挑出CPU。 写CPU把这个地址和数据发给内存控制器。这个内存控制器把32位的数据发给SDARM。同时这个内存控制器还会发出数据屏蔽信号dqm DQ mdq mdq mdq m比如说我这个CPU只想写一个字节那么它会发出三条dqm数据屏蔽信号。屏蔽掉不需要写的其他三个字节这样最终只会写SDRAM里面的一个字节。 改进方法
使用ldr 和 str一次读取4个字节 读取假设读取16字节的数据需要我们执行4次1byte8bit一次读取4个字节4*416)NOR为16位每访问一次读取2个字节。访问硬件次数16byte/2byte8次 写入原理一样 所以在这个里面我们只需要访问硬件12次节约的效率 makefile
all:arm-linux-gcc -c -o led.o led.carm-linux-gcc -c -o uart.o uart.carm-linux-gcc -c -o NOR.o NOR.carm-linux-gcc -c -o main.o main.carm-linux-gcc -c -o start.o start.S#arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elfarm-linux-ld -T sdram.lds start.o led.o uart.o NOR.o main.o -o sdram.elfarm-linux-objcopy -O binary -S sdram.elf sdram.binarm-linux-objdump -D sdram.elf sdram.dis
clean:rm *.bin *.o *.elf *.dis sdram.lds
SECTIONS {.text 0 : { *(.text) }.rodata : { *(.rodata) }.data 0x30000000 : AT(0x800) { data_load_addr LOADADDR(.data);. ALIGN(4);data_start . ;*(.data) data_end . ;}. ALIGN(4);bss_start .;.bss : { *(.bss) *(.COMMON) }bss_end .;
} 注意 使其按4字节边界对齐---- . ALIGN(4); 这个代码要写否则可能在汇编中把bbs段清理位0的时候也会把data段设置为0。
strat.S bl SDARMA_init/* 重定位data段 */ldr r1, data_load_addr /* data段在bin文件中的地址, 加载地址 */ldr r2, data_start /* data段在重定位地址, 运行时的地址 */ldr r3, data_end /* data段结束地址 */cpy:ldr r4, [r1]str r4, [r2]add r1, r1, #4add r2, r2, #4cmp r2, r3ble cpy/* 清除BSS段 */ldr r1, bss_startldr r2, bss_endmov r3, #0
clean:str r3, [r1]add r1, r1, #4cmp r1, r2ble cleanbl main
五:代码重定位与位置无关码
A:代码重定义
makefile文件不变
这个也就是方法2 一个程序由代码段、只读数据段、数据段、bss段等组成。 程序一开始可以烧在Nor Flash上面运行时代码段仍可以在Nor Flash运行但对于数据段就必须把数据段移到SDRAM中因为只要在SDRAM里面数据段的变量才能被写操作CPU无法直接访问NOR FLASH,NOR 不是内存类设备把程序从一个位置移动到另一个位置把这个过程就称为重定位。 前面的例子我们只是重定位了数据段这里我们再尝试重定位整个代码。
先梳理下把整个程序复制到SDRAM需要哪些技术细节 1. 把程序从Flash复制到运行地址链接脚本中就要指定运行地址为SDRAM地址 2. 编译链接生成的bin文件需要在SDRAM地址上运行但上电后却必须先在0地址运行这就要求重定位之前的代码与位置无关(是位置无关码) SDRAM.lds
SECTIONS
{. 0x30000000;. ALIGN(4);.text :{*(.text)}. ALIGN(4);.rodata : { *(.rodata) }. ALIGN(4);.data : { *(.data) }. ALIGN(4);__bss_start .;.bss : { *(.bss) *(.COMMON) }_end .;
}SECTIONS: 这是在告诉链接器接下来的部分定义了各个段的位置。段的定义是链接器脚本中的基本构造它们决定了程序在内存中的布局。{: 开始定义段。. 0x30000000;: 这行设置了当前地址或者说“dot”为0x30000000。这是内存地址的一个例子你的程序将从这个地址开始存放数据。. ALIGN(4);: 这会使地址以4字节为单位对齐。也就是说如果你的地址不是4的倍数这行代码会移动地址使其成为4的倍数。.text :定义一个名为.text的段这个段主要用于存放程序的代码部分。{: 开始定义.text段的内容。*(.text): 这会包含所有.text段的内容也就是说所有的代码段都会被合并在一起。}: 结束定义.text段的内容。. ALIGN(4);: 同上这会使地址以4字节为单位对齐。.rodata : { *(.rodata) }: 定义一个名为.rodata的只读数据段用于存放程序中不可修改的数据。. ALIGN(4);: 同上这会使地址以4字节为单位对齐。.data : { *(.data) }: 定义一个名为.data的可读写数据段用于存放程序中可修改的数据。. ALIGN(4);: 同上这会使地址以4字节为单位对齐。__bss_start .;: 定义一个名为__bss_start的符号并将其设置为当前的地址。这个符号通常用于初始化未初始化的全局变量。.bss : { *(.bss) *(.COMMON) }: 定义一个名为.bss的未初始化数据段用于存放程序中未初始化的全局变量。_end .;: 定义一个名为_end的符号并将其设置为当前的地址。这个符号通常用于表示程序的结束地址。}: 结束前述指令和整个段的定义。 我们写的这个链接脚本称为一体式链接脚本对比前面的分体式链接脚本区别在于代码段和数据段的存放位置是否是分开的。 例如现在的一体式链接脚本的代码段后面依次就是只读数据段、数据段、bss段都是连续在一起的。 分体式链接脚本则是代码段、只读数据段中间相关很远之后才是数据段、bss段。
我们以后的代码更多的采用一体式链接脚本原因如下 1. 分体式链接脚本适合单片机单片机自带有flash不需要再将代码复制到内存占用空间。而我们的嵌入式系统内存非常大没必要节省这点空间并且有些嵌入式系统没有Nor Flash等可以直接运行代码的Flash就需要从Nand Flash或者SD卡复制整个代码到内存 2. JTAG等调试器一般只支持一体式链接脚本
start.S
/* 重定位text, rodata, data段整个程序 */mov r1, #0ldr r2, _start /* 第1条指令运行时的地址 */ldr r3, __bss_start /* bss段的起始地址 */cpy:ldr r4, [r1]str r4, [r2]add r1, r1, #4add r2, r2, #4cmp r2, r3ble cpy/* 清除BSS段 */ldr r1, __bss_startldr r2, _endmov r3, #0
clean:str r3, [r1]add r1, r1, #4cmp r1, r2ble cleanbl main halt:b halt梳理下把整个程序复制到SDRAM需要哪些技术细节 1. 把程序从Flash复制到运行地址链接脚本中就要指定运行地址为SDRAM地址 2. 编译链接生成的bin文件需要在SDRAM地址上运行但上电后却必须先在0地址运行这就要求重定位之前的代码与位置无关(是位置无关码)----------在NOR FLASH的test段的代码负责把整个程序放在SDARM里面。所以必须先从0地址开始也必须写的是位置无关码 在生成的bin文件里代码保存的位置是0x30000000。随后烧写到NOR Flash的0地址但代码的结构没有变化。之后再重定位到SDRAM。 B:位置无关码
概念的补充 位置无关码Position Independent CodePIC是一种在计算机程序中实现不依赖于特定内存地址的指令代码的技术。它通过使用相对寻址和间接寻址的方式使得程序可以在内存的任意位置运行而不需要改变代码本身。 相对寻址是指使用相对于当前指令的地址偏移量来访问内存中的数据或指令。间接寻址是指使用指针或引用来访问内存中的数据或指令。 位置无关码的主要优点是提高了代码的可移植性和灵活性。由于程序可以在任意内存位置运行因此可以将代码加载到不同的地址空间中而无需修改代码。这使得程序可以更容易地在不同的操作系统和硬件平台上运行同时也方便了代码的共享和重用。此外位置无关码还能提供一定程度的安全性因为它使得恶意代码更难以利用特定的内存布局进行攻击。 在现代计算机体系结构中位置无关码已经成为编程的标准实践并得到了广泛应用。许多编程语言和编译器都提供了对位置无关码的支持例如C语言中的函数指针就可以用于实现位置无关码。另外操作系统和动态链接器也提供了加载和执行位置无关码的功能。 反汇编
3000005c: eb000106 bl 30000478 sdram_init 30000060: e3a01000 mov r1, #0 ; 0x0
30000064: e59f204c ldr r2, [pc, #76] ; 300000b8 .text0xb8
30000068: e59f304c ldr r3, [pc, #76] ; 300000bc .text0xbc这里的bl 30000478不是跳转到30000478这个时候sdram并未初始化; 为了验证我们做另一个实验修改连接脚本sdram.lds, 链接地址改为0x32000478编译查看反汇编
3000005c: eb000106 bl 30000478 sdram_init 30000060: e3a01000 mov r1, #0 ; 0x0
30000064: e59f204c ldr r2, [pc, #76] ; 300000b8 .text0xb8
30000068: e59f304c ldr r3, [pc, #76] ; 300000bc .text0xbc可以看到现在变成了bl 30000478,但两个的机器码eb000106都是一样的机器码一样执行的内容肯定都是一样的。 因此这里并不是跳转到显示的地址而是跳转到: pc offset这个由链接器决定。(PCProgram Counter是计算机的指令计数器它记录了当前要执行的指令的地址。) 假设程序从0x30000000执行当前指令地址0x3000005c ,那么就是跳到0x30000478如果程序从0运行当前指令地址:0x5c 调到0x00000478 跳转到某个地址并不是由bl指令所决定而是由当前pc值决定。反汇编显示这个值只是为了方便读代码。
重点 反汇编文件里 B或BL 某个值只是起到方便查看的作用并不是真的跳转。 写位置无关码 //bl main /*bl相对跳转程序仍在NOR/sram执行*/ ldr pc, main /*绝对跳转跳到SDRAM*/ 相对跳转相对跳转指令使用的是相对地址它基于当前位置进行跳转。例如“bl main”就是一种相对跳转指令它会将程序计数器pc的值增加main函数在内存中的地址与当前位置的相对偏移量从而实现跳转。这种跳转方式在程序仍在NOR/SRAM执行时使用。绝对跳转绝对跳转指令使用的是绝对地址它直接将程序计数器pc设置为目标地址从而使程序跳转到该地址处执行。例如“ldr pc, main”就是一种绝对跳转指令它会将pc设置为main函数在SDRAM中的地址从而实现跳转。这种跳转方式在需要跳转到SDRAM执行时使用。 六:C语言实现
我们使用c语言来实现代码重定位和清除bbs段
bl sdram_init//bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 *//* 重定位text, rodata, data段整个程序 */bl copy2sdram/* 清除BSS段 */bl clean_bss//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */ldr pc, main /* 绝对跳转, 跳到SDRAM */注意这三个函数要在main函数前调用。所以只能在汇编语言里面调用不能在c语言中的main函数调用否则会导致程序出现无法预计的错误。
sdarm.lds
SECTIONS
{. 0x30000000;__code_start .;. ALIGN(4);.text :{*(.text)}. ALIGN(4);.rodata : { *(.rodata) }. ALIGN(4);.data : { *(.data) }. ALIGN(4);__bss_start .;.bss : { *(.bss) *(.COMMON) }_end .;
}c语言实现 /* /* 重定位text, rodata, data段整个程序 */
void copy2sdram(void)
{ /*__code_start:代码段的开始__bss_start代码段的结束即bbs段的开始功能把0地址的数据复制到 __code_start:代码段的开始*/extern int __code_start, __bss_start;/*声明外部变量---sdram.lds文件中的*/volatile unsigned int* dest (volatile unsigned int*)__code_start;volatile unsigned int* end (volatile unsigned int*)__bss_start;volatile unsigned int* src (volatile unsigned int*)0;while (dest end){*dest *src;src;dest;}
}
void clean_bss(void)
{ /*功能把bbs段的数据清零没有被定义的全局变量和为0的全局变量*/extern int _end, __bss_start;volatile unsigned int* dest (volatile unsigned int*)__bss_start;volatile unsigned int* end (volatile unsigned int*)_end;while (dest end){*dest 0;}}