12 之前的汇总
1、一个linux程序系统中,BootLoader和驱动程序都有跟硬件打交道的部分,而这一部分的开发实际上和单片机裸机开发很像。BootLoader实际上就是一个单片机裸机开发的大全。
2、片内SRAM、NOR Flash 、 NAND Flash、片外SDRAM Flash都是非易失存储器,而且可读可写,只不过Flash的每次写入都有轻微的破坏性,寿命有限。NOR Flash采用了类似SDRAM的随机读取技术,因此允许用户直接运行装载在NOR Flash里面的代码,降低了系统中SRAM的需求量。 NAND Flash的存取以“块”的形式来组织,通常一次读取512字节(一块)。 jz2440上 NAND Flash大小为256M,NOR Flash为2M。除此之外jz2440上还有片外的32M大小的SDRAM作为片外内存(也是我们最熟悉的那个内存),S3C2440A片内还有4KB大小的SRAM作为片内的内存。 CPU执行的第一条指令可以从两个地方读取:片外(片指的是S3C2240A)的NOR Flash或者片外的Nand Flash。注意程序不能从内存启动,也就是说我们烧录的程序不是直接烧写到这32M内存中的。 对于 NAND Flash启动(第一条指令从 NAND Flash里取)的程序,硬件在开始时会将 NAND Flash的前4kb内的程序复制到片内SRAM用于程序启动,此时程序相当于就在SRAM里,可以随意读写,此时地址空间的0地址是片内SRAM的基地址。NAND Flash启动模式下,NOR Flash不能访问。 对于 NOR Flash启动,则直接从 NOR Flash的首地址取第一条程序,相当于程序从外存中直接读进来,这时候读可以随意读,但是写需要一些特定格式,此时地址空间的0地址是的NOR Flash基地址,片内SRAM的基地址从0x40000000开始(具体见S3C2440A芯片手册)。 对于C程序,全局变量在编辑好程序,编译成bin文件,烧写到Flash后,其位置就是固定的;而局部变量,是放在栈中,也就是片内4kSRAM中。如果是Nor启动,也就是说全局变量存放在Nor中,Nor是不能通过C语言去写入的,也就是说不能用C对Nor中的全局变量进行操作。
4、如果想看汇编的话,可以将得到的二进制程序进行反汇编,可以查看对应的汇编代码。(09.4)
5、c语言程序—汇编程序—二进制程序—以.bin的文件形式存放
6、int类型的最高位表示符号位,如果要用int * 来存放内存地址,应该用 unsigned int *,如:
unsigned int * pGPFCON=0x56000050;
unsigned int * pGPFDAT=0x56000054;
7、ARM中的BL指令 BL label 指令执行两个操作:(1)跳转到label处执行(函数调用)(2)将返回地址保存到LR寄存器
8、静态链接与动态链接 动态链接使用动态链接库进行链接,生成的程序在执行的时候需要加载所需的动态库才能运行。 动态链接生成的程序体积较小,但是必须依赖所需的动态库,否则无法执行。
静态链接使用静态库进行链接,生成的程序包含程序运行所需要的全部库,可以直接运行, 不过静态链接生成的程序体积较大。
gcc -c -o hello.o hello.c
gcc -o hello_shared hello.o//默认为动态链接
gcc -static -o hello_static hello.o//使用 static 表示静态链接
13 代码重定位
13.1 段的概念以及重定位
主要介绍了为什么需要重定位,以及在裸板运行中“段”的概念。
什么需要代码重定位
对于NAND Flash启动,由于S3C2440A的片内SRAM只有4k,如果程序在Nand中超过4k,则前4k复制到SRAM,前4k的程序要负责将后续的程序复制到片外32M的SDRAM中,这叫做代码重定位。 对于 NOR Flash启动,如果程序中含有需要写入操作的全局变量或者静态变量,则需要将这些变量复制到片外32M的SDRAM中,否则不能正常写入,这叫做代码重定位。
裸板中“段”的概念
裸板不像有OS那样会给每个进程划分进程空间,裸板整个程序的运行就是一个进程,进程空间就是整个地址空间,整个地址空间被划分为代码段、可读可写数据段、只读数据段、初始值为0的数据段、注释段等等。可以在编译的时候,在makefile中指定每个段的起始地址。
13.2 链接脚本的引入与简单测试
注:下面所涉及的所有知识都可以在GNU的官方文档:Using LD, the GNU linker http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html
由于Nor启动时主要问题就是全局变量无法写入,而全局变量存放在数据段中,其他代码存放在代码段中,对Nor启动下的重定位,有三种方案。三种解决方案的手段主要是通过编写链接脚本来实现。
三种重定位方案
第一种是在makefile中将数据段的起始地址设置为SDRAM的起始地址(如果不设置,默认是数据段紧挨着代码段,而代码段一般从0地址开始),S3C2440中SDRAM的起始地址是0x30000000,也就是说在编译形成bin文件后,bin文件的代码段一般是地址0x00000000开始,然后中间空出一大段区域,在0x30000000开始的地方存放数据段,代码如下: arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf 这样做的缺点是会形成一个很大的bin文件,而显然bin文件应该尽可能小才对。注意,不要臆想直接将代码段和数据段的起始地址都设置到SDRAM,Nor启动时第一条代码只能从nor从取得。 第二种是,代码和数据段紧挨着,烧写到Nor。运行时,在代码段的程序中,编写程序将全局变量复制到SDRAM区域。 第三种是,代码和数据段紧挨着,烧写到Nor。运行时,在代码段的程序中,编写程序将整个程序复制到SDRAM区域。
链接脚本的编写(程序005_013_003)
新建一个后缀为.lds的链接脚本文件,将其加入到makefile中,脚本功能是将所有段都紧挨着一起存放,脚本编写如下:
SECTIONS {
.text 0 : { *(.text) }
.rodata : { *(.rodata) }
.data 0x30000000 : AT(0x800) { *(.data) }
.bss : { *(.bss) *(.COMMON) }
}
其中,对于.data 0x30000000 : AT(0x800) { *(.data) }, 0x30000000表示希望数据段存放的地址(程序执行时,也就是main函数执行时,会认为数据段在0x30000000开始的地址里),AT(0x800)表示实际在链接成bin文件时,还是将数据段加在代码段后面(避免bin文件过大),0x800是例子程序的代码段所需的bin文件空间,实际编程时不可能直接写0x800,稍后会有改进。 写完以后放在makefile同一目录下,makefile改写: arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf 如果这样直接去编译链接烧写然后执行的话(不管是nand还是nor启动),会出现乱码,因为我们实际上数据段还是在nor里面,并没有真正在0x30000000里存放数据,执行main函数过程中会取不到数据(main函数试图从0x3000000开始的地方去取)。我们需要在start.s文件里,在跳转到main函数之前,将存放在nor中的数据段复制到0x3000000开始的地址空间中,也就是SDRAM中。start.s的简略修改如下,真实编程不可能直接写0x800。
bl sdram_init
mov r1, #0x800
ldr r0, [r1]
mov r1, #0x30000000
str r0, [r1]
bl main
以上程序很简略,因为我们是事先看了反汇编代码,知道代码段结尾是0x800,而且知道数据段只有一个字节,得写一个更通用得重定位代码,主要解决两个问题,一是自动计算数据段大小,而是自动计算代码段结尾的位置(也就是数据段的bin起始地址)。通用代码如下:
SECTIONS {
.text 0 : { *(.text) }
.rodata : { *(.rodata) }
.data 0x30000000 : AT(0x800)
{
data_load_addr = LOADADDR(.data);
data_start = . ;
*(.data)
data_end = . ;
}
.bss : { *(.bss) *(.COMMON) }
}
以下是start.S:
bl sdram_init
ldr r1, =data_load_addr
ldr r2, =data_start
ldr r3, =data_end
cpy:
ldrb r4, [r1]
strb r4, [r2]
add r1, r1, #1
add r2, r2, #1
cmp r2, r3
bne cpy
bl main
13.3 链接脚本的解析
主要分析上面写的那个链接脚本的语法和内部机理,这里手动加上行号。
SECTIONS {
1、 .text 0 : { *(.text) }
2、 .rodata : { *(.rodata) }
3、 .data 0x30000000 : AT(0x800)
4、 {
5、 data_load_addr = LOADADDR(.data);
6、 data_start = . ;
7、 *(.data)
8、 data_end = . ;
9、 }
10、 .bss : { *(.bss) *(.COMMON) }
}
1、表示所有程序的text段,放在bin文件0地址开始的地方,整个程序包含start.o led.o uart.o init.o main.o等多个子程序,每个子程序都有自己的代码段数据段。这些代码段从0开始存放,谁先存谁后存取决于Makefile语句中的左右顺序,比如这里就是最先是start.o的代码段,其次是 led.o的,最后是main.o,一般第一个最好是start.o,后面就无所谓了,因为start.o里会执行程序的跳转。 2、紧挨着代码段放所有文件的只读数据段 3、7、放所有文件的数据段,数据段bin地址为0x800开始,实际运行时重定位到0x30000000 6、变量data_start等于当前地址 8、 data_end等于当前地址,由于6和8之间执行了存放数据段的指令,data_start和data_end的差值就是数据段的大小。 10、程序运行时,bss段紧挨着数据段存放,其首地址为0x3xxxxxxx,在bin文件中不放bss和common,需要程序运行时把bss段对应的空间清0,否则会声明为0的变量会是乱码。
编写程序让BSS段对应的位置清0(程序006_013_003)
想要实现功能,需要知道bss段存放的具体地址,需要修改链接脚本和start.S,修改后的如下:
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 = .;
}
bl sdram_init
ldr r1, =data_load_addr
ldr r2, =data_start
ldr r3, =data_end
cpy:
ldrb r4, [r1]
strb r4, [r2]
add r1, r1, #1
add r2, r2, #1
cmp r2, r3
bne cpy
ldr r1, =bss_start
ldr r2, =bss_end
mov r3, #0
clean:
strb r3, [r1]
add r1, r1, #1
cmp r1, r2
bne clean
bl main
13.4 拷贝代码和链接脚本的改进(007_013_004)
拷贝代码的改进
start.S中进行数据拷贝的时候,用的是ldrb和strb两个字节访问指令,而SDRAM和Nor分别是32位和16位,以字节访问效率太低,极大增加了硬件操作次数,改进为用ldr和str两个字操作的指令。
链接脚本的改进
因为换成了str和ldr,这些指令按字访问,所操作的地址需要向4对齐(因为是32位机器),在链接脚本里让数据段和bss段向4对齐
13.5 代码重定位和位置无关码(008_013_005)
之前所讲的都是只将数据段重定位,现在考虑将整个程序从Nor上重定位。最终生成的bin文件分为两个部分,一部分是实现真正功能所需的,一部分是完成重定位的。之前我们所写的链接脚本称为分体的链接脚本,即它的代码段和数据段是分开的,数据段经历重定位后与代码段隔很远,一般不用这种链接脚本,也就是一般都是直接将整个程序进行重定位,这需要新的链接脚本。
SECTIONS
{
. = 0x30000000;//表示整个程序最初地址从0x30000000开始运行
. = ALIGN(4);//保证4字节对齐
.text :
{
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(.COMMON) }
_end = .;
}
与位置无关的代码
接下来修改启动文件start.S,将整个程序重定位。start.S实际上其整个bin文件最开始执行的语句,我们在链接脚本中指定CPU从0x30000000取第一条指令,但是程序通过eop最初烧写到Nor,CPU执行的第一条指令也是从Nor读取,而不是从0x30000000,这些最开始的程序需要完成整个实际程序的重定位,这些最开始的程序本来应该位于0x30000000开始,但是实际位于0地址开始,这些代码跟地址无关,称为位置无关码。我们把整个start.S拿出来分析其运行过程:
.text
.global _start
_start:
/* 关闭看门狗 */
ldr r0, =0x53000000 //整个程序中cpu执行的第一条指令
.....中间省略很多............
bl sdram_init
/* 重定位text, rodata, data段整个程序 */
mov r1,
ldr r2, =_start /* 第1条指令运行时的地址 */
ldr r3, =__bss_start /* bss段的起始地址,表示程序结束的地址,bss段不需要复制 */
cpy:
ldr r4, [r1]
str r4, [r2]
add r1, r1,
add r2, r2,
cmp r2, r3
ble cpy
/* 清除BSS段 */
ldr r1, =__bss_start
ldr r2, =_end
mov r3,
clean:
str r3, [r1]
add r1, r1,
cmp r1, r2
ble clean
//在跳转到main以前,都是用b和bl,用的相对跳转,程序还是运行在nor上
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
整个CPU从start标签处开始运行,start对应的第一条指令(ldr r0, =0x53000000 )存放在nor中,地址编号为0地址,程序计数器PC最初始的值就是0地址,PC中始终存放下一条将要执行的指令的地址。程序从start标签依次运行,直到指令ldr pc, =main之前,所有指令都是位置无关码,因为在这些代码里,没有使用绝对地址,都是使用的相对地址。所谓相对地址,CPU在取下一条指令时,都是从PC中去读地址,再发地址信号给内存控制器,最终得到想要的数据。程序从start标签开始顺序取指并运行,直到遇到第一个跳转指令,跳转指令有相对跳转和绝对跳转两种方式,常见的B和BL都是相对跳转,是将PC的值在原来的基础上加上一个偏移量得到新的地址存在PC中,基础PC地址原本在Nor中,加偏移量也还是在Nor中,所以程序可以正确运行。在用位置无关码将整个程序(包括位置无关码本身)全部复制到SDRAM后,就可以直接用绝对地址跳转到main所在位置,不要跳转到0x30000000,因为0x30000000存放的是start标签所对应的ldr r0, =0x53000000,直接用ldr pc, =main,将main函数入口的绝对地址0x3xxxxxxx传给PC,程序接着从main开始运行,之后的程序用相对或绝对跳转都可以,因为PC的基础值已经是0x3开头。
13.6 重定位代码和清除BSS段代码的C语言实现
汇编就ok,暂时没看。注:nand下的重定位还没有,看是不是在里面。
14 异常与中断
14.1 概念引入与处理流程
异常是包含中断的,中断只是异常的一种。ARM处理器包含七种异常,注意这并不等同ARM处理器的7种模式。 对于异常,主要可以分为中断的初始化、异常的产生以及异常的处理三个部分。 中断的初始化主要分为:a、设置中断源使其可以发出中断信号,比如设置定时器以多久的周期发出中断信号;b、设置中断控制器,主要是设置哪些中断源被屏蔽,多个中断同时发生时的中断优先级;c、CPU自身有一个总开关,使能所有中断。
异常的产生,以按键中断为例子。当一个引脚被设置为中断,按键按下时发送中断信号到中断控制器,如果该按键中断没有被屏蔽且当前没有更高优先级的中断,则中断控制器将中断信号发送给CPU,CPU硬件会在每执行完一条指令后检查是否收到中断信号,若收到中断信号则开始处理中断。
异常的处理,主要是保存现场,跳转到异常处理程序,恢复现场。跳转到事先写好的异常处理程序:首先不同的异常(包括中断)会有不同的处理程序,固定的异常类型对应的处理程序的跳转地址存放在固定的内存地址中(异常向量表),CPU在检测到异常信号后,识别出引起该信号的异常类型,接着从中断向量表中取出对应的跳转指令跳转到对应的处理程序(CPU由类型跳到类型对应的异常向量,取出其中的指令然后执行该条指令,这个过程是CPU硬件决定的。我们可以软件决定的是在中断向量表对应的地方放入一条跳转指令,以及决定该跳转指令具体跳转到哪一个地址)。中断向量表的具体位置定义在start.S中。下面是AMR920T的一个start.S
.globl _start
_start: b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq //发生中断时,强制执行这个跳转指令,跳去irq的地址去执行
ldr pc, _fiq
跳转到执行对应的异常处理程序后,在异常处理程序中,会首先进行保存现场,主要是将本来在执行的正常程序的所有寄存器内容保存到堆栈中(包括PC寄存器);随后对异常进行处理,由前面的start.S可以看到,中断向量表里面分类分得很大,并不是具体到按键中断这种尺度,比如我执行了ldr pc, _irq,跳转irq对应的程序,该程序还需要分辨具体是哪个中断源,比如按键或者时钟,然后还要跳转到具体处理按键或者时钟的处理程序;最后恢复现场,主要是将堆栈中的内容从重新赋值给寄存器,此时PC寄存器恢复到中断发生前,随后正常执行程序。
14.2 CPU模式(mode)、状态(state)以及寄存器
讲ARM芯片架构的七种模式,两种状态(ARM和Thumb),以及诸如CPSR之类的寄存器。没看视频
14.3 Thumb指令集程序示例
没看
14.3 und异常模式程序示例
|