1. ARM体系结构
1.1 ARM处理器简介
ARM
(
Advanced RISC Machines
)是一个
32
位
RISC
(精简指令集)处理器架构,
ARM
处理器则是 ARM架构下的微处理器。
广泛应用。
ARM
处理器的特点有指令长度固定,执行效率高,低成本等。
1.2 RISC设计主要特点
1
、指令集
——RISC
减少了指令集的种类,通常一个周期一条指令,采用固定长度的指令格式,编
译器或程序员通过几条指令完成一个复杂的操作。而
CISC
指令集的指令长度通常不固定。
2
、流水线
——RISC
采用单周期指令,且指令长度固定,便于流水线操作执行。
3
、寄存器
——RISC
的处理器拥有更多的通用寄存器,寄存器操作较多。例如
ARM
处理器具有
37
个。
寄存器。
4
、
Load/Store
结构
——
使用加载
/
存储指令批量从内存中读写数据,提高数据的传输效率。
5
、寻址方式简化,指令长度固定,指令格式和寻址方式种类减少。
1.3 ARM指令集特点
ARM
处理器是基于
RISC
的,但不是纯粹的
RISC
体系结构。为了使
ARM
处理器能够更好的满足嵌入式 系统的需要,ARM
指令集和单纯的
RISC
指令集有以下几点不同:
1、一些特定的指令周期数可变。
2、内嵌的桶形移位寄存器产生了更复杂的指令。
3、可以执行Thumb16位指令集。
4
、可以使用条件执行,减少指令数目。
5
、增强指令(例如可以使用DSP指令)。
1.4 ARM处理器特点
1
、
ARM
指令都是
32
位定长的
2
、寄存器数量丰富(
37
个寄存器)
3
、普通的
Load/Store
指令
4
、多寄存器的
Load/Store
指令(拓展功能,特点的指令周期数可变)
5
、指令的条件执行
6
、单时钟周期中的单条指令完成数据移位操作和
ALU(逻辑运算)
操作
7
、通过变种和协处理器来扩展
ARM
处理器的功能
8
、扩展了
16
位的
Thumb
指令来提高代码密度
2. ARM编程模型
2.1 ARM数据类型
1
、字(
Word
):在
ARM
体系结构中,字的长度为
32
位。
2
、半字(
Half-Word
):在
ARM
体系结构中,半字的长度为
16
位。
3
、字节(
Byte
):在
ARM
体系结构中,字节的长度为
8
位。
2.2 ARM处理器存储格式
ARM
体系结构将存储器看作是从
0
地址开始的字节的线性组合。作为
32
位的微处理器,
ARM
体系结
构所支持的最大寻址空间为
4GB
。
ARM
体系结构可以用两种方法存储字数据,分别为大端模式和小端模式。
大端模式(高低高低):字的高字节存储在低地址字节单元中,字的低字节存储在高地址字节单元
中。
小端模式(高高低低):字的高字节存储在高地址字节单元中,字的低字节存储在低地址字节单元
中。
2.3 ARM处理器工作状态
从编程的角度来看,
ARM
微处理器的工作状态一般
ARM
和
Thumb
有两种,并可在两种状态之间切
换。
1
、
ARM
状态:此时处理器执行
32
位的字对齐
ARM
指令,绝大部分工作在此状态。
2、Thumb状态:此时处理器执行16位的半字对齐的Thumb指令。
2.4 ARM处理器工作模式
1
、用户模式(
usr
,
User Mode
):
ARM
处理器正常的程序执行状态。
2
、快速中断模式(
fiq
,
Fast Interrupt Request Mode
):用于高速数据传输或通道处理。当触发
快速中断时进入此模式。
3
、外部中断模式(
irq
,
Interrupt Request Mode
):用于通用的中断处理。当触发外部中断时进
入此模式。
4
、管理模式(
svc
,
Supervisor Mode
):操作系统使用的保护模式。在系统复位或执行软件中断
指令
SWI
时进入。
5
、数据访问中止模式(
abt
,
Abort Mode
):当数据或指令预取中止时进入该模式,可用于虚拟
存储及存储保护。
6
、系统模式(
sys
,
System Mode
):运行具有特权的操作系统任务。
7
、未定义指令中止模式(
und
,
Undefined Mode
):当未定义的指令执行时进入该模式,可用于
支持硬件协处理器的软件仿真。
除了用户模式之外,其余六种模式都是特权模式。除了用户模式和系统模式之外,其余五种模式都 是异常模式。 在特权模式下程序可以访问所有的系统资源。
用户模式如果需要访问硬件,必须切换到特权模式下,才允许访问硬件。
2.5 ARM处理器的寄存器
通用寄存器(R0-R15):
1
、未分组寄存器
R0-R7
在所有运行模式下,未分组寄存器都指向同一个物理寄存器,他们未被系统用作特殊的用途。因此 在中断或异常处理进行异常模式转换时,由于不同的处理器运行模式均使用相同的物理寄存器,所以可能造成寄存器中数据的破坏。
-
R0至R3 - 这四个寄存器通常用于传递函数参数。
-
R4至R7 - 这些寄存器用作局部变量,它们保存在被调用的函数中,并且在函数返回之前不需要保存其原始值
2
、分组寄存器
R8-R14
对于分组寄存器,他们每次所访问的物理寄存器都与当前的处理器运行模式相关。具体如上图。
-
R8 - 在某些体系结构中,R8也可以用作局部变量,但在其他体系结构中,它可能有不同的用途。
-
R9至R12 - 这些寄存器通常用于保存上下文,或者作为寄存器变量,它们的值在子程序调用之间保留。
-
R13 - 通常用作堆栈指针(Stack Pointer, SP)。它指向当前栈顶的地址。
-
R14 - 称为链接寄存器(Link Register, LR)。当执行子程序时,R14可得到R15(PC)的备份,执行完子 程序后,又将R14的值复制回PC,即使用R14保存返回地址。
3
、程序计数器
PC
(
R15
)
程序计数器(Program Counter, PC)。它存储下一条将要执行的指令的地址。
由于ARM体系结构采用了多级流水线技术,对于
ARM
指令集而言,
PC
总是指向当前指令的下两条指令的地址,即PC
的值为当前指令的地址值加
8
个字节。
2.6 程序状态寄存器CPSR和SPSR
CPSR(
Current Program Status Register
,当前程序状态寄存器),
CPSR
可在任何运行模式下被访 问,它包括条件标志位、中断禁止位、当前处理器模式标志位以及其他一些相关的控制和状态位。
每一种运行模式下都有一个专用的物理状态寄存器,称为SPSR(
Saved Program Status Register
, 备份的程序状态寄存器),当异常发生时,SPSR
用于保存当前
CPSR
的值,从异常退出时则可由
SPSR
来 恢复CPSR
。 由于用户模式和系统模式不属于异常模式,这两种状态下没有SPSR
,因此在这两种状态下访问 SPSR,结果是未知的。
2.7 工作模式的切换
(
1
)执行软中断(
SWI
)或复位命令(
Reset
)指令。如果在用户模式下执行
SWI
指令,
CPU
就进入 管理(Supervisor
)模式。
(
2
)有外部中断发生。如果发生了外部中断,
CPU
就会进入
IRQ
或
FIQ
模式。
(
3
)
CPU
执行过程中产生异常。最典型的异常是由于
MMU
保护所引起的内存访问异常,此时
CPU 会切换到Abort
模式。如果是无效指令,则会进入
Undefined
模式。
(
4
)有一种模式是
CPU
无法自动进入的,这种模式就是
System
模式,要进入
System
模式必须由程 序员编写指令来实现。要进入System
模式只需改变
CPSR
的模式位为
System
模式对应的模式位即可。
(
5
)在任何特权模式下,都可以通过修改
CPSR
的
MODE
域来进入其他模式。不过需要注意的是由 于修改的CPSR
是该模式下的影子
CPSR
,即
SPSR
,因此并不是实际的
CPSR
,所以一般的做法是修改影子 CPSR,然后执行一个
MOVS
指令来恢复执行某个断点并切换到新模式。
3. bootloader和根文件系统
3.1 bootloader简介
Linux系统要启动就必须需要一个
bootloader
程序,也就说芯片上电以后先运行一段
bootloader
程序。这段 bootloader
程序会先
初始化时钟,看门狗,中断,
SDRAM
,
等外设,然后将
Linux
内核从 flash(
NAND, NOR FLASH,SD,MMC
等)
拷贝
到
SDRAM
中,最后
启动
Linux
内核。
最主要的工作就是启动 Linux
内核,
bootloader
和
Linux
内核的关系就 PC上的
BIOS
和
Windows
的关系一样,
bootloader
就相当于
BIOS
。总得来说,
Bootloader
就是一小段程序,它在系统上电时开始执行,初始化硬件设各、准备好软件环境,最后调用操作系统内核
3.2 根文件系统简介
根文件系统首先是一种文件系统,用于存储数据文件,相对于普通的文件系统,它的特殊之处在于,它是内核启动时所挂载(mount)的
第一个文件系统
,内核代码的映像文件保存在根文件系统中,系统引导启动程序会在根文件系统挂载之后从中把一些初始化
脚本
如
rcS,inittab
)和
服务
加载到内存中去运行,里面包含了
Linux
系统能够运行
所必需的应用程序、
库等
,比如可以给用户提供操作
Linux
的控制界面的
shell
程序、动态连接的程序运行时需要的
glibc
库 等。我们要明白文件系统和内核是完全独立的两个部分。在嵌入式中移植的内核下载到开发板上,是没 有办法真正的启动Linux
操作系统的,会出现无法加载文件系统的错误。
-
启动时挂载:根文件系统通常是在系统启动时第一个被挂载的文件系统,它包含了操作系统的核心组件和启动脚本。
-
包含系统软件:根文件系统包含了操作系统的内核、设备驱动程序、系统库、shell和其他基本的系统工具。
-
应用程序和配置文件:除了系统软件,根文件系统还包含了运行在系统上的应用程序及其配置文件。
-
目录结构:根文件系统遵循一定的目录结构标准,如/bin
存放二进制可执行文件,/etc
存放配置文件,/lib
存放库文件,/usr
存放用户相关的应用程序和文件等。
-
存储介质:根文件系统可以存储在各种介质上,包括ROM、NOR Flash、NAND Flash、eMMC或硬盘等。
根文件系统是嵌入式系统中不可或缺的一部分,它为系统提供了运行环境和必要的资源。开发者需要仔细设计和优化根文件系统,以满足特定应用的性能、存储和安全性需求。
3.3 根文件系统的作用
(1)init
进程的应用程序必须运行在根文件系统上
(2)
根文件系统提供了根目录
“/”
(3)linux
挂载分区时所依赖的信息存放于根文件系统
/etc/fstab
这个文件中
(4)shell
命令程序必须运行在根文件系统上,譬如
ls
、
cd
等命令
总之:一套linux
体系,只有内核本身是不能工作的,必须要
rootfs
(上的
etc
目录下的配置文 件、/bin /sbin
等目录下的
shell
命令,还有
/lib
目录下的库文件等
···
)相配合才能工作。
在 Linux
中将一个文件系统与一个存储设备关联起来的过程称为挂载(
mount
)。使用 mount 命令将一个文件系统附着到当前文件系统层次结构中(根)。
4. Bootloader(Uboot)启动流程分析
从固态存储设备上启动的
Bootloader
大多都是两阶段的启动过程。第一阶段使用汇编来实现,它完成一些依赖于CPU
体系结构的初始化,并调用第二阶段的代码;第二阶段则通 常使用C
语言来现,这样可以实现更复杂的功能,而且代码会有更好的可读性和可移植性。
4.1 Bootloader第一阶段的功能
4.1.1 硬件设备初始化
首先需要设置时钟,设置
MPLL
(具体参见下面的
FCLK HCLK PCLK
部分)。接着设置
CLKDIVN
地址为0x4C000014
,写入
0x05,
表示设置分频系数为
FCLK:HCLK:PCLK=1:4:8
。接着,关闭看门狗,关中 断,启动ICACHE
,关闭DCACHE和TLB
,关闭
MMU
(
ICACHE
为指令缓存,可以不关闭,指令直接操作 的硬件,实际的物理地址。但是DCACHE
就必须要关闭,此时
MMU
没有使能,虚拟地址映射不成功, sdram无法访问,
DCACHE
无数据)。
这里具体讲下是如何设置
FCLK HCLK PCLK
。
FCLK
又称为
内核时钟
,是提供给
ARM920T
的时钟。
HCLK
又称为
总线时钟
,是提供给用于存储器控制器,中断控制器,
LCD
控制器,
DMA
和
USB
主机 模块的 AHB
总线(advanced high-performance bus)
的时钟。(高时钟频率)
PCLK
又称为
I/O
接口时钟
,是提供给用于外设如
I2C
,
PWM
定时器,
MMC/SD
接口,
ADC
,
UART
,
GPIO
,
SPI
的
APB (advanced peripherals bus)
总线的时钟。(外设)
当设置
PLL 后,CPU
并不是马上就使用设置好的高频时钟,而是有一段锁定时间,在这段时间里,
CPU
停止运行, 等12MHz
变成高频时钟稳定以后,整个系统再重新运行。
开启
MPLL
的过程:
1
、设置
LOCKTIME
变频锁定时间
2
、设置
FCLK
与晶振输入频率(
Fin
)的倍数
3
、设置
FCLK
,
HCLK
,
PCLK
三者之间的比例
(为设备设置时钟频率)
当设置完
MPLL
之后,就会自动进入
LockTime
变频锁定期间,
LOCKTIME
之后,
MPLL
输出稳定时钟频率。
4.1.2 为加载 Bootloader的第二阶段代码准备RAM空间(初始化内存空间)
lowlevel_init中设置相应
BANK地址,主要用来设置SDRAM
。内存是被映射在了
0x30000000- 0x40000000的位置,即
bank6
与
bank7
。
bank0
也是需要关注的是启动启动程序存放的位置。但是
bank0
是由 OM[1:0],即板子上的那几个小开关中的两个来控制的,所以这里程序上是不用管它的。
初始化NANDFLASH
,其中包括设置时序
NFCONF
,
(
参考芯片手册和
2440
手册设置
nandflsh
的启动时序)
。
TACLS
表示的建立所用的时间,
TWRPH0
表示
nWE
写控制信号的持续时间,
TWRPH1
表数据生 效所用的时间,什么时候可以读数据。 最后就是使能NFCONT NAND Flash
控制器,初始化
ECC,
禁止片 选。到这里,NANDFLASH
的初始化就完成了。下面就可以进行重定位了。
4.1.3 复制 Bootloader的第二阶段代码到SDRAM空间中(重定位)
首先判断是NOR
启动还是
NAND
启动,如果是
NAND
启动就直接拷贝数据。拷贝代码之前,要传递 给拷贝函数三个参数,源,目的,长度。读取NAND:
选中NAND,发出读命令,发出地址,发出读命令,判断状态,读取数据,取消选中等。
最后要清除bss,
不清零的话未初始化的变量可能会存在未知的数值。
4.1.4 设置好栈
设置栈跳转到SDRAM执行。
4.1.5 跳转到第二阶段代码的C入口点
跳转到
SDRAM
执行剩下的程序。
4.2 Bootloader第二阶段的功能
4.2.1 初始化本阶段要使用到的硬件设备
为了方便开发,至少要初始化一个串口以便程序员与
Bootloader
进行交互。
4.2.2 检测系统内存映射( memory map)
所谓检测内存映射,就是确定板上使用了多少内存、它们的地址空间是什么。由于嵌入式开发中 Bootloader多是针对某类板子进行编写,所以可以根据板子的情况直接设置,不需要考虑可以适用于各 类情况的复杂算法。
4.2.3 将内核镜像和根文件系统镜像从 Flash上读到SDRAM空间中
Flash上的内核映象有可能是经过压缩的,在读到SDRAM之后,还需要进行解压。当然,对于有自解压功能的内核,不需要Bootloader来解压。将根文件系统映象复制到SDRAM中,这不是必需的。这取决于是什么类型的根文件系统以及内核访问它的方法。
将内核存放在适当的位置后,直接跳到它的入口点即可调用内核。调用内核之前,下列条件要满足︰
1.设置寄存器,R0=规定,R1=机器类型ID(板子的型号)
2.CPU工作模式,禁止中断,设置寄存器(CPSR)为SVC模式(管理模式)
3.关闭MMU,关闭数据Cache,打开或关闭指令Cache
4.2.4 为内核设置启动参数
Bootloader与内核的交互是单向的,
Bootloader
将各类参数传给内核。由于它们不能同时行,传 递办法只有一个:Bootloader
将参数放在某个约定的地方之后,再启动内核,内核启动后从这个地方获
得参数
。
Linu2.4x以后的内核都期望以
标记列表
(tagged_list)的形式来传递启动参数。标记,就是
一种数据结构
;
4.2.5 调用内核
调用内核就是uboot启动的最后一步了。到这里就uboot就完成了他的使命。
4.3 uboot启动内核详解
4.3.1 uboot与Linux内核之间的参数传递
uboot启动后已经完成了基本的硬件初始化(如:内存、串口等),接下来它的主要任
务就是加载
Linux
内核到开发板的内存,然后跳转到
Linux
内核所在的地址运行。
跳转的做法直接修改
PC
寄存器的值为
Linux
内核所在的地址
,这样
CPU
就会 从Linux
内核所在的地址去取指令,从而执行内核代码。
4.3.2 为什么要给内核传递参数呢?
在此之前,uboot
已经完成了硬件的初始化,可以说已经
”
适应了“这块开发板。然而,内核并不是对于所有的开发板都能完美适配的。
告诉内核开发板的信息,启动Linux内核,uboot
必须要给内核传递一些必要的信息来告诉内核当前所处的环境
。
4.3.3 如何给内核传递参数
uboot就把
机器
ID
通过
R1
传递给内核
,
Linux
内核运行的时候首先就从
R1
中读取机器
ID
来判
断是否支持当前机器。
R2存放的是块内存的基地址
,这块内存中存放的是
uboot
给
Linux
内核的其他参数。这些参数有内存 的起始地址、内存大小、
Linux
内核启动后挂载文件系统的方式等信息。按照双方规定的格式存放参数,除了约定好参数存放的地址外,还要规定参数的结构
。
具体举例说明参数传递:
(1)
设置开始标记
ATAG_CORE
(2)
设置内存标记
(3)
设置命令行参数标记
(4)
设置结束标记
4.3.4 uboot跳转到Linux内核
在uboot中可以使用go和bootm来跳转到内核:
(1)go命令仅仅修改pc的值到指定地址
(2)bootm命令是uboot专门用来启动uImage格式的Linux内核,它在修改pc的值到指定地址之前, 会设置传递给Linux内核的参数
bootm命令的功能
(
1
)读取
uImage
头部,把内核拷贝到合适的地方。
(
2
)把参数给内核准备好。
(
3
)引导内核。
4.4 内核镜像格式vmlinuz和zImage和uImag
(
1
)
uboot
经过编译直接生成的
elf
格式的可执行程序是
u-boot
,这个程序类似于
windows
下的
exe
格式,在操作系统下是
可以直接执行的
。但是这种格式
不能用来烧录下载
。我们用来烧录下载的是
uboot.bin,这个东西是由
u-boot
使用
arm-linux-objcopy
工具进行加工(主要目的是去掉一些无用的)得 到的。这个u-boot.bin
就叫镜像(
image
),镜像就是用来烧录到
iNand
中执行的。
(
2
)
linux
内核经过编译后也会生成一个
elf
格式的可执行程序,叫
vmlinux
或
vmlinuz
,这个就是
原
始的未经任何处理加工的原版内核
elf
文件
;嵌入式系统部署时烧录的一般不是这
vmlinuz/vmlinux
, 而是要用objcopy
工具去制作成烧录镜像格式(就是
u-boot.bin
这种,但是内核没有
.bin
后缀),经过制 作加工成烧录镜像的文件就叫Image
(制作把
78M
大的精简成了
7.5M
,因此这个制作烧录镜像主要目的 就是缩减大小,节省磁盘
)。
(
3
)原则上
Image
就可以直接被烧录到
Flash
上进行启动执行(类似于
u-boot.bin
),但是实际上
并不是这么简单。实际上
linux
的作者们觉得
Image
还是太大了所以对
Image
进行了压缩,并且在
image 压缩后的文件的前端附加了一部分解压缩代码
。构成了一个压缩格式的镜像就叫
zImage
(因为当年 Image大小刚好比一张软盘(软盘有
2
种,
1.2M
的和
1.44MB
两种)大,为了节省
1
张软盘的钱于是乎设 计了这种压缩Image
成
zImage
的技术)。
(
4
)
uboot
为了启动
linux
内核,还发明了一种内核格式叫
uImage
。
uImage
是由
zImage
加工得到
的,
uboot
中有一个工具,可以将
zImage
加工生成
uImage
。注意:
uImage
不关
linux
内核的事,
linux 内核只管生成zImage
即可,然后
uboot
中的
mkimage
工具再去由
zImage
加工生成
uImage
来给
uboot
启 动。这个加工过程其实就是在
zImage
前面加上
64
字节的
uImage
的头信息即可
。
(
5
)原则上
uboot
启动时应该给他
uImage
格式的内核镜像,但是实际上
uboot
中也可以支持
zImage
,是否支持就看
x210_sd.h
中是否定义了
LINUX_ZIMAGE_MAGIC这个宏。但是
所有的
uboot
肯定都支持
uImage
启动
。
通过上面的介绍我们了解了内核镜像的各种格式,如果通过uboot
启动内核,
Linux
必须为
uImage
格式
。
5.内存结构分析
5.1 代码段,数据段,bss段,堆,栈等
代码段 :代码段通常用来存放程序
执行代码
的一块区域。这部分区域的大小在程序运行前就已确
定了,通常这块内存区域属于
只读
,有些架构也允许可写,在代码段中也有可能包含以下只读的常数量,例如字符串常量等。程序段为程序代码在内存中映射一个程序可以在内存中有多个副本。
数据段 :数据段通常用来存放程序中
已初始化的全局变量和已初始化为非
0
的静态变量
的一块内存
区域,属于静态内存分配。直观理解就是
C
语言程序中的全局变量(注意:
全局变量才算是程序的数
据,局部变量不算程序的数据,只能算是函数的数据
)
BSS段
:
bss
段通常是指用来存放程序中
未初始化的全局变量和未初始化的静态变量或者初始化为
0
的静态变量
一块区域。
bss
英文
Block started by symbol
,
bss
属于静态内存分配。
bss
段的
特点就是被
初始化为
0
,
bss
段
本质上也是属于数据段
,
bss
段就是被初始化为
0
的数据段。
数据段(.data
)和
bss
段的区别和联系
:二者本来没有本质区别,都是用来存放
C
程序中的全局变量的。区别在于把显示初始化为非零的全局变量存在.data
段中,而把
显式初始化为
0
或者并未显式初始
化(
C
语言规定未显式初始化的全局变量值默认为
0
)的全局变量存在
bss
段
。
rodata段
存放的是只读数据,一般是程序里面的
只读变量
(如
const
修饰的变量
)和
字符串常量
。 单独设立".rodata"
段有很多好处,不光是在语义上支持了
C/C++
的
const
关键字,而且操作系统在加载的 时候可以将".rodata"
段的属性映射成只读,这样对于这个段的任何修改操作都会作为非法操作处理,保 证了程序的安全性。
comment段
存放程序的注释。
堆(
heap
)
:堆是用来存放进程中被动态分配的内存段,它的
大小并不固定,可动态扩张或减
。 当进程调用malloc
等函数分配内存时,新分配的内存就被动态分配到堆上,当利用
free
等函数释放内存时,被释放的内存从堆中被剔除。
堆存放new
出来的对象、栈里面所有对象都是在堆里面有指向的、假如栈里指向堆的指针被删除、 堆里的对象也要释放(C++
需要手动释放)、当然我们现在好面向对象程序都有
'
垃圾回收机制
'
、会定期 的把堆里没用的对象清除出去。
栈(stack
)
:栈又称堆栈,是用户存放
程序临时创建的变量
,也就是我们
函数
{}
中定义的变量
,
但
不包括
static
声明的变量
,
static
意味着在
数据段中存放变量
。除此之外,在函数被调用时,其
参数也会
被压入发起调用的进程栈中
,并且待到调用结束后,函数
的返回值也会被存放回栈中
,由于栈的先进后 出特点,所以栈特别方便用来保存、恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存, 交换临时数据的内存区。 栈存放局部变量、临时变量、声明、返回值、指向堆对象的地址(指针)
、总之存放一些
小的东
西。
5.2 为什么堆的空间是不连续的
堆分配的内存空间是不连续的
问题的引出:
堆包含一个
链表来维护已用和空闲的内存块
。在堆上新分配(用
new
或者
malloc
)内存是从空闲
的内存块中找到一些满足要求的合适块
答案:
申请和释放许多小的块可能会产生如下状态:在已用块之间存在很多小的空闲块。进而申请大块内 存失败,虽然空闲块的总和足够,但是空闲的小块是零散的
,
不连续的
,不能满足申请的大小,这叫做 “堆碎片
”
。 当旁边有空闲块的已用块被释放时,新的空闲块会与相连的空闲块合并成一个大的空闲块,这样就 可以有效的减少"
堆碎片
"
的产生。 堆分配的空间在逻辑地址(虚拟地址)上是连续的
,但在
物理地址上是不连续的
(因为采用了页式 内存管理,windows
下有段机制、分页机制),如果逻辑地址空间上已经没有一段连续且足够大的空间, 则分配内存失败。
5.3 哈佛结构和冯诺依曼结构
区别
冯诺依曼结构釆用指令和数据统一编址
,使用
同条总线
传输,
CPU
读取指令和数据的操作无法重 叠。
哈佛结构釆用指令和数据独立编址
,使用
两条独立的总线
传输,
CPU
读取指令和数据的操作可以重叠。
利弊
冯诺依曼结构主要用于通用计算机领域,需要对存储器中的代码和数据频繁的进行修改,统一编址 有利于节约资源
。 哈佛结构主要用于嵌入式计算机,程序固化在硬件中,有较高的可靠性、运算速度和较大的吞吐。 程序和数据都放在内存中,且不彼此分离的结构称为冯诺依曼结构。譬如Intel的
CPU
均采用冯诺依曼结构。
冯诺依曼结构中程序和数据不区分的放在一起,因此安全和稳定性是个问题,好处是处理起来简 单。 哈佛结构中程序(一般放在ROM、flash
中)和数据(一般放在
RAM
中)独立分开存放,因此好处 是安全和稳定性高,缺点是软件处理复杂一些(需要统一规划链接地址等)
5.4 ARM流水线技术
流水线技术通 过多个功能部件并行工作来缩短程序执行时间,提高处理器核的效率和吞吐率,从而成为微处理器设计中最为重要的技术之一。ARM7处理器核使用了典型
三级流水线的冯
·
诺伊曼结构
,
ARM9
系列则采用了基于
五级流水线的哈佛结构
。通过增加流水线级数简化了流水线各级的逻辑,进一 步提高了处理器的性能。
PC代表程序计数器,流水线使用三个阶段,因此指令分为三个阶段执行:
1.
取指(从存储器装载一 条指令);2.
译码(识别将要被执行的指令);
3.
执行(处理指令并将结果写回寄存器)。
R15(
PC
)总是指向
“
正在取指
”
的指令,而不是指向
“
正在执行
”
的指令或正在
“
译码
”
的指令。一般来说, 人们习惯性约定将“
正在执行的指令作为参考点
”
,称之为当前第一条指令,因此
PC
总是指向第三条指 令。当ARM
状态时,每条指令为
4
字节长,所以
PC
始终指向该指令地址加
8
字节的地址,即:
PC
值
=
当前
程序执行位置
+8
;
当突然发生中断的时候,保存的是
PC
的地址(
PC-8+4 = PC-4
下一条指令的地址)
5.5 中断与轮询
中断向量表其实是处理器内部的概念,因为处理器除了会被外部设备中断外,其内部也可能产生异 常等事件
。当这些事件发生时,
CPU
必须暂停手头上的工作,转 去处理中断或异常,因此处理器需要知道到哪里去获得这些中断或异常的处理函数的目标地址
中断向量表就是用来解决这个问题,其中每一项都是一个中断或异常处理函数的入口地址
,具体来说
4
个字节的函数指针将指向一段汇编微码(intConnectCode
)执行跳转。
外部设备的中断常常对应向量表中的某一项,这是通用框架的外部中断处理函数入口,因此在进入 通用的中断处理函数之后,系统必须知道正在处理的中断是哪一个设备产生的,而这正是由软件中断号
irq
定的决定
。中断向量表的内容是由操作系统在初始化阶段来填写,对于外部中断,操作系统负责实现 一个通用的外部中断处理函数,然后把这个函数的入口地址放到中断向量表中的对应位置。用户注册设备驱动ISR
,实际上就是挂接到中断向量表中,覆盖某一项的默认处理实现特化。
中断 本质上是轮询,中断信号触发中断请求,
cpu
查询到中断请求之后根据当前中断寄存器配置决
定是否进行中断响应。
本质上仍然是轮询,只不过是把轮询操作放在了硬件层,提高了轮询的效率。中断是 如何实现的?通过IRQ(Interrup ReQuest)
,
IRQ
其实就是一个硬件的轮询系统,它连接了所有的硬件,
每个硬件分配一个独立的
IRQ
编号。硬件有消息就会放在它自己的
IRQ
里。然后
CPU
会在每个指令周期结 束时查看IRQ
里是否有消息,如果有就触发中断给内核去处理。
5.6 快中断和一般中断
为何就FIQ速度快?
1:
ARM
的
FIQ
模式提供了
更多的
banked
寄存器
,
r8
到
r14
还有
SPSR
,IRQ没有这些寄存器意味着在
ARM
的
IRQ
模式下,中断处理程序
自
己要保存
R8
到
R12
这几个寄存器
,然后
退出中断处理时程序要恢复这几个寄存器
,而
FIQ
模式由于这几个寄存器都有banked
寄存器,模式
切换时
CPU
自动保存这些值到
banked
寄存器
,退出
FIQ
模式时自动恢复
。
2.FIQ
比
IRQ
有
更高优先级
,如果
FIQ
和
IRQ
同时产生,那么
FIQ
先处理。
3.当
CPU
处于
FIQ
模式处理
FIQ
中断的过程中,预取指令异常,未定义指令异
常,软件中断全被禁止,所有的中断被屏蔽。FIQ会被优先执行,而IRQ执行过程中可能被打断。
4.、 FIQ可以把程序放在0x1c处,地址末尾。减少程序跳转,速度更快。
5.7 Linux操作系统与ARM工作模式
ARM开发板在刚上电或复位后都会首先进入SVC即管理模式。
接着,bootloader引导Linux内核,此时、Linux内核一样运行在ARM的SVC即管理模式下;当内核 启动完毕、准备进入用户态init进程时,内核将ARM的当前程序状态CPSR寄存器M[4:0]设置为10000,进而用户态程序只能运行在ARM的用户模式。
Linux内核从ARM的SVC模式下启动,但内核态不仅仅指ARM的SVC模式(还包括可以访问内核空间的所有ARM模式);Linux用户程序从ARM的用户模式启动,但用户态不仅仅指ARM的用户模式。
5.8 什么是PLL(锁相环)
简单来说,输入时钟的存在是作为“参考源”。锁相环不是为了单纯产生同频同相信号,而是一般集 进某种“频率综合电路”,产生一个不同频,但锁相(同源)的信号。在很多现实应用中有要求同源时钟的场合,所以锁相环被广泛应用。
用PLL
是为了生产一系列跟输入时钟有一定关联的时钟,不同的输出供给系统不同的模块,但每个 模块的时钟我们都是可以预知的。
5.9 NAND FLASH 和NOR FLASH异同
NOR:随机访问,读快写慢擦除慢,可靠性高,容量小贵,常用来保存关键数据
NAND:严格时序访问,读快写快擦除快,可靠性低,比NOR便宜,用来保存数据
5.10 怎么接收、发送串口数据的
发送:查询->中断->空闲中断(硬中断)
接收:空闲中断+DMA。
空闲中断可以接受不定长数据,不需要复制的帧格式,一个数据帧错误不会影响下一个。
DMA:减少CPU干预,提高效率,接受一个数据帧只需要一次CPU。
有的时候,数据量很大,CPU
来不及处理,那么你可以通过以下方式解决:
增加消息队列(非常好的解决方式)。
增加两帧之间的发送时间(对于实时性要求很高的可能不合适)。
前面两种方式叠加。
6. SPI协议和I2C协议
6.1 SPI协议简介
高速全双工协议。用于和CPU和串行外设通信
SPI(Serial Peripheral Interface--串行外设接口)
总线系统是一种同步串行 外设接口,它可以使MCU
与各种外围设备以串行方式进行通信以交换信息。
SPI
总线可直接与各个厂家 生产的多种标准外围器件相连,包括FLASHRAM
、网络控制器、
LCD
显示驱动器、
A/D
转换器和
MCU 等。
该接口一般使用4条线:串行时钟线(
SCLK
)、主机输入
/
从机输出数据线
MISO
、主机输出
/
从机输 入数据线MOSI
和低电平有效的从机选择线
NSS
。
6.2 SPI通讯详解
在点对点的通信中,SPI
接口不需要进行寻址操作,且为全双工通信。
(
1
)
MOSI –
主器件数据输出,从器件数据输入
(
2
)
MISO –
主器件数据输入,从器件数据输出
(
3
)
SCLK –
时钟信号,由主器件产生
(
4
)
NSS –
从器件使能信号,由主器件控制
NSS、
SCK
、
MOSI
信号都由主机控制产生,而
MISO
的信号由从机产生, 主机通过该信号线读取从机的数据。 MOSI
与
MISO
的信号只在
NSS
为低电平的时候才有效,在
SCK 的每个时钟周期 MOSI
和
MISO
传输一位数据。
起始和结束信号看NSS电平变化。
SPI 一共有四种通讯模式,它们的主要区 别是总线空闲时 SCK
的时钟状态以及数据采样时刻。
6.3 IIC协议
它是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据,是一个多主机的半双工通信方式 。
每个挂接在总线上的器件都有个唯一的地址。
I2C
总线系统结构
,
如下所示
1)
空闲状态
当总线上的SDA
和
SCL
两条信号线同时处于高电平
,
便是
空闲状态
,
如上面的硬件图所示
,
当我们不传输 数据时, SDA
和
SCL
被上拉电阻拉高
,
即进入空闲状态 。
2)
起始信号
当SCL
为高
期间,
SDA
由高到低的跳变
;便是总线的
启动信号
,
只能由主机发起
,
且在空闲状态下才能 启动该信号,
如下图所示
:
3)
停止信号
当SCL
为高期间
,
SDA
由低到高的跳变
;便是总线的
停止信号
,
表示数据已传输完成
,
如下图所示
:
4)
传输数据格式
当发了起始信号后
,
就开始传输数据
,
传输的数据格式如下图所示
: 当SCL
为高电平时
,
便会获取
SDA
数据值
,
其中
SDA
数据必须是稳定的
(
若
SDA
不稳定就会变成起始
/
停 止信号)
当
SCL
为低电平时
,
便是
SDA
的电平变化状态 若主从机在传输数据期间,
需要完成其它功能
(
例如一个中断
),
可以主动
拉低
SCL
,
使
I2C
进入
等待状态
, 直到处理结束再释放
SCL,
数据传输会继续
I2C总线上的数据都是以
8
位数据
(
字节
)
进行的
,
当发送了
8
个数据后
,
发送方
会在
第
9
个时钟脉冲期间释
放
SDA
数据
,
当接收方接收该字节成功
,
便会输出一个
ACK
应答信号
,
当
SDA
为高电平
,
表示为非应答信号 NACK,当
SDA
为低电平
,
表示为
有效应答信号
ACK
PS:
当主机为接收方时
,
收到最后一个字节后
,
主机可以不发送
ACK,
直接发送停止信号来结束传输
。
6)
完整的数据传输
如下图所示,
发送起始信号
后
,
便发送一个
8
位的设备地址
,
其中
第
8
位是对设备的读写标志
,
后面紧跟着的就是数据
了
,
直到发送停止信号
终止
6.4 IIC传输数据的格式
1.写操作:
2.
读操作:
半双工通信:
主芯片通过一根
SDA
线既可以把数据发给从设备,也可以从
SDA
上读取数据,连接
SDA
线的引
脚里面
必然有两个引脚
(发送引脚
/
接受引脚)。
6.5 I2C控制器
在嵌入式系统里面的主控芯片一般都会有I2C
控制器,要是没有可以根据
I2C
协议用GPIO管脚模拟, 但是非常麻烦,我们要发送数据时,可以把数据放到某个寄存器,它就会自动的发出时钟,并且把数据 发送给从设备,同时会等待从设备会返回回应信号。
根据上图,我们首先设置
IICCON(
来设置时钟
)
,时钟源是
PCLK(
是
50MHZ)
太快了我们需要设置这个 分频系数,把时钟降低,降低到我们想要的SCL,
然后我们要发出
start
信号,我们需要设置寄存器发出 start信号,之后我们需要发出数据啊,我们的程序可以把
数据写入到
IICDS
寄存器
,一写入就会
自动的
发出时钟
,并且把这
8
位数据从
SDA
发送给从设备
,数据发送之后,在第九个时钟会收到
回应信号
,可以
查询
IICSTAT
是否有
ACK
(
有
ACK
表示数据发送成功了
)
,可以继续发送数据,等发完数据之后,再来设置
IICSAT
让它发出
P信号。
IICCON:设置时钟,检测ACK,设置时钟,开启中断,标识中断是否发生
IICDS:存储要发送或者已接收的数据
IICADD:存储从机地址
IICSTAT:,控制发送P信号,发送S信号,选择工作模式,使能发送/接收,标识各种状态(是否收到ACK等)
处理中断: