arm嵌入式系统开发笔记
[toc]
基于ARM的嵌入式系统
RISC 设计思想
对比起传统的复杂指令集,RISC设计重点在降低硬件执行指令的复杂度,将要求放在了编译器,降低处理器的复杂度
设计思想主要有几个准则来实现
- 指令集——RISC减少了指令种类,一个周期就能执行一条指令,通过几条简单指令实现复杂操作。每一条指令长度固定,允许流水线在当前指令译码阶段去取下一条指令。而CISC指令长度不固定,执行也需要多个周期
- 流水线——指令的处理过程被拆分成几个更小的、能够被流水线并行执行的单元在理想情况下,流水线每周期前进一步,可获得最高的吞吐率;而 CISC 指令的执行需要调用微代码的一个微程序。
- 寄存器——RISC处理器拥有更多的通用寄存器。每个寄存器都可存放数据或地址。寄存器可为所有的数据操作提供快速的局部存储访问;而 CISC处理都是用于特定目的的专用寄存器。
- load-store结构——处理器只处理寄存器中的数据。独立的 load 和 store 指令用来完成数据在寄存器和外部存储器之间的传送。因为访问存储器很耗时,所以把存储器访问和数据处理分开。这样有一个好处,那就是可反复地使用保存在寄存器中的数据,而避免多次访问存储器。相反在 CISC 结构中,处器能够直接处理存储器中的数据。
面向嵌入式的指令集
为了使 ARM 指令集能够更好地满足嵌人式应用的需要,ARM 指令集和单的 RISC定义有以下几方面的不同。
- 一些特定指令的==周期数可变==——并不是所有的ARM 指都是单周期的,例如:多寄存器装载/存储的 load/store 指令的执行周期就是不确定的,须根据被传送的寄存器个数而定。如果是访问连续的存储器地址,就可以改善性能,因为连续的内存访问通常比随机访问要快,同时,代码密度也得到了提高,因为在函数的起始和结尾,多个寄存器的传输是很常用的操作。
- ==内嵌桶形移位器产生了更为复杂指令==——内嵌桶形移位器是一个硬件部件,在个输入寄存器被一条指令使用之前,内嵌桶形移位器可以处理该寄存器中的数据。它扩展了许多指令的功能,以此改善了内核的性能,提高了代码密度。
- ==Thumb 16 位指令集==——ARM 内核增加了一套称之为 Thumb 指令的16 位指令集,使得内核既能够执行 16 位指令,也能够执行32 位指令,从而增强了 ARM 内核的功能。16 位指令与 32 位的定长指令相比较,代码密度可以提高约 30%。
- 条件执行——只有当某个特定条件满足时指令才会被执行。这个特性可以减少分支指令的数目,从而改善了性能,提高了代码密度。
- 增强指令——一些功能强大的数字信号处理器(DSP)指令加入到标准的 ARM 指令之中,以支持快速的 16X16 位乘法操作及饱和运算。在某些应用中,传统的方法需要微处理器加上 DSP 才能实现。ARM 的这些增强指令,使得 ARM处理器也能够满足这些应用的需要。
嵌入式系统的硬件
ARM总线技术
有两种不同类型的逻辑设备连接到总线:ARM 处理器是总线主设备(master)——拥有对总线的仲裁权,通过同一总线,该逻辑设备可主动发起数据传输请求;外围器件是总线从设备(slave)——在总线上是==被动==的,逻辑设备只能对主设备发出的一个传输请求作出反应
总线可分为两个结构层:第一层是物理层,定义一些电气特征和总线宽度(16,32 或 64位);第二层是协议层(protocol),定义了处理器和外围设备之间进行数据通信的逻辑规则。
ARM 主要是一个芯片设计公司,它几乎不实现具体的总线电气特性,但它详细地定义了总线协议。
AMBA 总线协议
高级微控制总线结构(AMBA)
ARM 系统总线(ASB)、ARM 外设总线(APB)、 ARM 高性能总线(AHB)
AHB能够提供比 ASB 更高的数据吞率,因为它不同于 ASB 的双向总线设计
AHB是基于集中多总线机制(centralized multiplexed bus scheme)的。这种改变使得 AHB总线能够在更高的时钟速度下运行,并成为第一个支持 64 位和128 位宽度的ARM 总线。
ARM 的 AHB 总线结构还引人了两个变体多层 AHB 和 AHB Lite。原来的 AHB 在任时候都只允许==一个主设备活动==在总线上,而==多层 AHB 允许多个活动的总线主设备==。
AHB Lite 是标准 AHB 总线的一个子集,也只允许一个总线主设备。
多层 AHB 新的互联==有利于多处理器系统==,它==允许并发操作和更高的吞吐率==。
存储器
存储器数据宽度
存储器的数据宽度是指每次访问所返回的数据位数——典型的有 8,16,32 和 64 位,存储器宽度对系统整体的性价比会==有直接的影响。==
如果一个没有 cache 的系统使用 32 位 ARM 指和16位宽度的存储器芯片则处理器每次取指就需要 2个 16 位的存储器访问,这显然会降低系统的性能, 16 位宽度的存储器价格会相对便宜。
与之相比,如果内核执行 16 位的 Thumb 指令则对于16 位宽度的存储器将获得更好的性能,因为处理器获取每条指令只需一次存储器访问。因此,对于 16 位宽度的存储器,使用Thumb 指令可获得性能和成本两方面的优势
外设
所有的 ARM 外设都是存储器映射的——编程接口是一组对应于某些存储器地址的寄存器。这些寄存器的地址是从某个特定外设的基地址开始的偏移量。
控制器是特殊的外设 , 可在一个嵌人式系统中实现更高层的功能。两个重要类型的控制器就是存储器控制器和中断控制器
存储器控制器
各种不同类型的存储器通过存储器控制器连接到处理器总线上。上电时,存储器控制器由硬件配置(初始化),使得某些存储器处于工作状态。这些存储器允许执行初始化代码。有些存储器必须通过软件设置才能使用,比如在使用 DRAM 之前, 必须首先设置存储器定时和刷新率。
中断控制器
当一个外设或器件需要服务时,它就向处理器提出一个中断请求。中断控制器提供一套可编程的管理机制 , 使软件通过设置中断控制寄存器中的相应位,来决定在任何特定时刻,哪一个外设或器件可以中断处理器。
ARM处理器有两种中断控制器 : 标准的中断控制器和向量中断寄存器(VIC)。(也许是NVIC?我也忘了
标准中断控制器在一个外部设备需要服务时,发送一个中断请求信号给处理器核。控制器可以通过编程设置来忽略或屏蔽某个或某些设备的中断请求。中断处理程序读取在中
初始化代码
初始化代码在把控制权交给操作系统之前,须处理许多管理任务。我们可以把这些不同的任务划分为 3 个阶段:初始化硬件配置、诊断和引导。
引导过程包括了装载一个映像文件并将控制权交给它。如果系统必须引导不同的操作系统或者同一个操作系统的不同版本,那么引导过程本身就可能很复杂
启动一个映像文件是最后一个阶段,但首先必须装载这个映像文件。装载一个映像文件的过程可以是拷贝包括代码和数据的整个程序到 RAM 中,也可以只拷贝包含易变(volaile)变量的数据区到 RAM 中。一旦启动,系统通过更改程序计数器(pc)指向映像文件的起始地址,将控制权交出。
图 1.5对比了存储器在重新组织前/后的情况。对于基于 ARM 的入式系统来说,通常都会提供存储器重映射,因为它允许系统上电后立刻从 ROM 中开始运行初始化代码然后,初始化代码会重新定义或重构存储器映射,把 RAM 空间放在地址 0x00000000。这步很重要,因为这样异常向量表就在 RAM 中,并可以被程序改写了。我们将在 2.4 节中羊细讨论向量表。
ARM处理器基础
指令译码器在指令执行前先将它们翻译。每一条可执行指令都属于一个特定的指令集。
与所有的 RISC处理器一样,ARM 处理器采用 load-store 体系结构。这就意味着它只有两种类型的指令用于把数据移入/移出处理器;load 指令从存储器复制数据到内核的寄存器;反过来,store 指令从寄存器里复制数据到存储器。没有直接操作存储器数据的数据处理指令,因此,数据处理只能在寄存器里进行。
数据项存储在寄存器文件里——一组 32 位的存器存体(storage bank)由于ARM 内核是32 位处理器,大部分指令认为寄存器中保存的是 32 位有符号或无符号数当从存储器读取数据至一个寄存器时,符号扩展硬件会把8 位 16 位的有符号数转换成32位。
典型的 ARM 指令通常有2个源寄存器 Rn 和 Rm1个结果或目的寄存器 Rd。源操作数分别通过内部总线 A 和 B从寄存器文件中读出。(看图)
ALU(算术逻辑单元)或 MAC(乘累加单元)通过总线A 和 B得到寄存器值 Rn 和 Rm 并计算出一个结果,数据处理指令直接把 Rd 中的计算结果写到寄存器文件。load-store指令使用==ALU==来产生一个地址,这个地址将被保存到地址寄存器==并发送到地址总线上。==
ARM 的一个重要特征是,寄存器 Rm 可以选择在进入 ALU 前是否先经过桶形移位器预处理。桶形移位器和 ALU 协作可以计算较大范围的表达式和地址。
在经过有关功能单元后,Rd 寄存器里的结果值通过结果总线(result bus)写回到寄存器文件。对于load-store 指令 , 在内核从下一个连续的存储器单元装载数据到下一个寄存器,或写下一个寄存器的值到下一个连续的存储器单元之前,地址加法器会自动更新地址寄存器。处理器连续执行指令,直到==发生异常或中断而改变了正常的执行流==。
寄存器
状态和指令集
内核的状态决定了处理器将执行哪种指令集。有3种指集:ARM、Thumb 和 Ja-zelle。只有当处理器处于 ARM 状态时,ARM 指令集才有效;
一旦处于 Thumb状态处理就纯粹执行 16位的Thumb 指令。
在 cpsr 中的J(Jazelle)和 T(Thumb)位反映了处理器的状态。当T位和J位都为0时,处理器处于 ARM 状态,执行 ARM 指令,这是处理器上电时的默认状态。若T位置位则处理器处于 Thumb状态,为了改变状态,内核要执行专门的分支指令。
ARM 设计者引进了第 3种指令集,被称为 Jazelle。Jazelle 执行 8 位指令它是一个软件与硬件的混合体能,够加速 Java字节码(bytecodes)的执行。
为了执行Java 字节码,需要 Jazelie 技术外加一个Java 虚拟机的特殊修订版。特别要注意的是,Jazelle 的硬件部分只支持 Java 字节码的一个子集其余的由==软件仿真==
中断屏蔽
中断屏蔽位用来禁止某些中断请求来中断处理器。
ARM 处理器内核有 2个级别的中断请求——中断请求 IRQ 和快速中断请求 FIQ。
cpsr 有 2个中断屏位 , 位7和位6(或I和 F)它们分别控制 IRQ FIQ的中断屏蔽。
I位设置为1时,屏蔽IQ;同样,F位设置为1时,屏蔽 FIQ
流水线
图显示了一个3级流水线: 取指(fetch)-从存储器装载一条指令; 译码(decode)——==识别将被执行的指令;== 执行(excute)——==处理指令并把结果写回寄存器==
没懂
另外,还有 3个值得注意的流水线特征:
- 执行一条分支指令或直接修改 pc 而发生跳转时,会使 ARM 内核==清空流水线==;
- ARM10 使用分支预测技术 , 通过==预测==可能的分支并在指令执行前装载新的分支地址,从而减小了清空流水线的影响;
- 即使产生了一个中断,==一条处于“执行”阶段的指令也将会完成==。流水线里其它指令将会被放弃,而处理器将从向量表的适当入口开始填充流水线。
异常、中断和向量表
当一个异常或中断发生时,处理器会把 pc 设置为一个特定的存储器地址。
这一地址放在一个被称为==向量表(vector table)==的特定的地址范围内。
向量表的入口是一些跳转指令,跳转到专门处理某个异常或中断的子程序。
存储器映射地址 0x00000000 是为向量表(一组 32 位字)保留的。在有些处理器中,向量表可以选择定位在存储空间的更高地址(从偏移量 0xfff0000 开始)。操作系统,如Linux和Microsoft 的嵌入式操作系统就可以利用这一特性。
当一个异常或中断发生时,处理器挂起正常的执行转而从向量表装载指令,每一个向量表入口包含一条指向一个特定子程序的跳转指令。
- 复位向量是处理器上电后执行的第一条指令的位置。这条指令使处理器跳转到初始化代码处。
- 未定义指今向量 是在处理器==不能==对一条指今译码时使用的
- 软件中断向量是执行 SWI指今时被调用的。SWI 指令经常被用作调用一个操作系统例的机制。
- 预取指中止向量 发生在处理器试图从一个==未获得正确访问权限==的地址去取指时,实际上中止发生在==“译码”==阶段。
- 数据中止向量与预取指中止类似,发生在一条指令==试图访问未获得正确访问权限的数据存储器==时。
- 中断请求向量是用于外部硬件(外设)中断处理器的正常执行流。只有当 cpsr 中的IRQ 位未被屏蔽时才能发生。
- 快速中断请求向量类似于中断请求,是为要求更短的中断响应时间的硬件保留的只有当 cpsr 中的 FIQ 位未被屏蔽时才能发生。
内核扩展
cache 和紧耦合存储器
ARM 有两种形式的 cache。第一种形式是针对冯·诺伊曼结构的内核。它把==数据和指令==放在一个统一的 cache 里,如图所示。
为了简单起见,我们把==存储器与AMBA的连接部分统称为逻辑与控制==
第二种形式是针对哈佛结构的内核,有独立的指令 cache 和数据 cache。
cache 改善了系统的整体性能,但也使程序的==执行时间变得不可预测==。
对于实时系统来说,代码执行的确定性——装载和存储指令或数据的时间必须是可预测的,这一点至关重要。
使用称为紧耦合存储器 TCM 的存储器就可以实现。
TCM 是一种快速 SRAM,它紧挨内核,并且保证取指或数据操作的时钟周期数——这对于一些要求确定行为的实时算法是很重要的。TCM 位于存储器地址映射中,可作为快速存储器来访问。一个带 TCM 的处理器内核的例子如图所示 把上述两项技术结合,ARM 处理器既能够改善性能,又能够获得可预测的实时响应,图显示了一个结合了 cache 和TCM 的内核例子
存储管理
嵌入式系统通常使用多个存储设备,因此有必要实施某种策略来组织管理这些设备,并保护系统,避免一些应用非法访问硬件。可以使用存储器管理硬件来实现这些功能
ARM 内核的存储器管理硬件有 3 种不同类型——没有提供扩展,没有硬件保护(无保护);提供有限保护的存储器保护单元(MPU);提供全面保护的存储器管理单元(MMU)。
- 无保护存储器是固定的,只能提供非常有限的灵活性。它通常用于小的、简单的嵌人式系统,这种系统由于其应用特点而不要求存储器保护。
- MPU 使用一个只用到==少量存储区域==的简单系统。这些区域由一组特殊的协处理器寄存器控制,每一个区域定义了专门的访问权限。这种类型的存储器管理,适用于==要求有存储器保护但没有复杂存储器映射的系统==。关于 MPU 将在第 13 中作进一步介绍。
- MMU 是ARM 上广泛的存储器管理硬件。MMU使用一组转化表,以提供精细的存储器控制。这些表保存在主存里,并且提供虚拟地址与物理地址的映射和访问。
协处理器
协处理器可以附属于 ARM 处理器。一个协处理器通过扩展指令集或提供配置寄存器来扩展内核处理功能。一个或多个协处理器可以==通过协处理器接口与 ARM 内核相连==。
协处理器可以通过一组专门的、提供 load-store 类型接口的 ARM 指令来访问。例如协处理器 15(CP15),ARM 处理器使用协处理器 15 的寄存器来控制 cache TCM 和存储器管理
协处理器也能通过提供一组专门的新指令来扩展指令集。例如,有一组专门的指令可以添加到标准 ARM 指令集中,以处理向量浮点(VFP)运算。
==这些新指令是在 ARM 流水线的译码阶段被处理的。==如果在译码阶段发现是一条协处理器指令,则把它送给相应的协处理器。如果该协处理器不存在,或不认识这条指令,则ARM认为发生了未定义指今异常。这也使得编程者可以用软件来仿真协处理器的行为(使用未定义指常服务子序)。
体系结构的不同版本
每个 ARM 处理器都有一个特定的==指令集架构 ISA==,而一个 ISA 版本又可以有多种处理器实现。 ISA 随着嵌人式市场的需求而发展。ARM 公司精心规划该发展过程,使得在较早的架构版本上编写的代码也可以在后继版本上执行。 在解释 ISA 的发展过程前,我们先来介绍 ARM处器的命名规则。命名规则确定了处理器具有的相关功能特性
总结、
在本章中,我们把注意力集中在实际 ARM 处理器的硬件基础上。
ARM 处理器可抽象成8个部件——ALU、桶形移位器、MAC、寄存器文件、指令译码器、地址寄存器、增量加法器和符号扩展。
ARM有3个指令集:ARM,Thumh 和Jazelle。寄存器文件包含 37 个寄存器,但是在任意时刻只有 17 或 18 个寄存器可以被访问;其余的根据处理器模式被保护。当前的处理器模式保存在 cpsr 中,同时它还保存了处理器内核的当前状况中断屏蔽位、条件标志和状态,该状态决定了哪个指今集正在被执行。
一个 ARM 处理器由一个内核及周围的组件并通过总线连接起来。内核扩展包括
- cache 可改善系统的总体性能;
- TCM 可改进具有确定性的实时响应
- 存储管理用于组织存储器和保护系统资源
- 协处理器用于扩展指令集和功能,协处器 15控制 cache、TCM 和存储器管理
一个 ARM 处理器是一个特定指今集架构 ISA 的具体实现。自从第一个 ARM 处理器问世以来,ISA 就一直在不断改进、完善。各种处理器根据相似的特性,可以被划分在各个系列(ARM7,ARM9,ARM10 和ARM11)中。
ARM指令集
基本指令集
ISA 指在哪个修订版本加入的
ARM 指令只对存放在寄存器的数据进行处理,对于存储器数据,只能使用 load 和store 指令进行存取。
ARM 指令可以划分为以下几类:数据处理指令、分支指令、load-store 指令、软件中断指令和程序状态寄存器指令。后续章节将分类介绍 ARM 指令的语法和功能
数据处理指令
数据处理指令对于存放在寄存器中的数据进行操作,包括 MOVE(传送)、算术指令、逻辑指令、比较指令和乘法指令。大多数数据处理指令可以使用桶形移位器对其中的一个操作数进行预处理。
如果在数据处理指令前使用 S 前缀,指令的执行将会更新==cpsr==中的标志。MOVE 指令和逻辑指令会对==进位标志 C、负数标志 N 及零标志==之产生影响。在最后一位移出时,桶形移位的结果将更新==进位标志 C:N 标志根据操作结果的第 31 位进行设置==;如果结果为零,那么2标志就会被设置。
Move指令
可以将N 寄存器或者是立即数送到目标寄存器rd
桶形移位器
能够在Move的时候先用桶形移位器将其移位
大体的意思是移位之后会将32-y的位移入C标志位,比如移1位,会将31的移入C标志位,应该就是nzcv,cpsr的C标志位被更新
可以看到,在r1被复制到r0之前左移了一位,同时cpsr的C位被更新成nvCv条件域(就你妈逆天,c位就是C变大写了)
N | 语法 |
---|---|
立即数 | #immediate |
寄存器 | Rm |
立即数逻辑左移 | Rm,LSL # shift_imm |
寄存器逻辑左移 | Rm , LSL Rs |
立即数逻辑右移 | Rm , LSR # shift_imm |
寄存器逻辑右移 | Rm , LSR Rs |
立即数算术逻辑右移 | Rm,ASR #shift_imm |
寄存器算术逻辑右移 | Rm,ASR Rs |
立即数循环右移 | Rm,ROR # shift_imm |
寄存器循环右移 | Rm, ROR Rs |
扩展循环右移 | Rm,RRX |
算术指令
注意逆向减法,看上去也只是反过来了
可以利用这个RSB,N=0,用0-Rn做一个取反操作
RSB r0,r1,#0
r0=-r1
SUBS 指令可以实现循环计数器递减
SUBS r1,r1 ,# 1
算术指令使用桶形移位器
我们用的移位功能一般都用在第二操作数上,
比如我们将存储在r1的值乘与3,将最后的结果扔到r0
ADD r0 , r1 , r1 ,LSL #1
逻辑指令
Example,将r1r2做异或,存到r0
ORR r0,r1,r2
注意,BIC 这个逻辑位清零还挺好用,也经常用于改变 cpsr 中的中断屏蔽位。
比如我们想清除r1中的部分位,我们将要清除的部分位设置在r2
r1=0b1111
r2=0b0101
BIC r0,r1,r2
结果就是r0=0b1010
r2中置一的位会对应清楚在r1的位
只有当逻辑指令有 S后缀时,指令才会更新 cpsr。这些指令可以像算术指令一样使用桶形移位第 2 操作数
比较指令
比较指令通常用于把一个寄存器与一个 32 位的值进行比较或测试。比较指令根据结果更新 cpsr 的标志位,但不影响其它的寄存器。在设置标志位后,其它指令可通过条件执行来改变程序的执行流程。
对于比较指令,不需要使用 S 后缀就可以改变标志位
CMP指令
cpsr=....z...
r0=4
r9=4
CMP r0,r9
结果是cpsr=...Z...
执行前,z标志是0,小写字母表示,执行后变1,说明结果相等
其实 CMP的本质是不返回运算结果的减法指令,同样,TST指令是一个没有保存结果的逻辑与操作,TEQ为异或
乘法指令
注意,长整型的“乘累加”要用代表64位的一对寄存器
我们执行一个乘法指令需要的周期数取决于处理器的具体表现,对于某些处理器的实现,需要的周期数还依赖于Rs的值
具体例子:
MUL r0,r1,r2 ;r0=r1*r2
长整型乘法
UMULL r0,r1,r2,r3 ;[r1,r0]=r2*r3
r0是RdLo低位
r1是RdHi高位,
分支指令
这种执行流程的改变迫使程序计数器pc指向一个新的地址
这个感觉没怎么懂,像是子程序把要跳转的程序地址从lr 整到pc去了,但是lr指哪?pc又转了吗?
load-store指令
用于存储器和处理器寄存器之间的传输数据 有三种
- 单寄存器传输指令
- 多寄存器传输指令
- 交换指令
单寄存器ls的寻址方式
ARM指令提供了几种存储器寻址的不同方式
- 回写前变址
- 前变址
- 后变址
回写型前变址和前变址的区别是
回写型前变址在计算出新的地址后要用新的地址更新基址寄存器的内容,然后再利用新的基址寄存器进行寻址
而前变址方式虽然也利用对基址寄存器的改变值进行寻址,但==基址寄存器在操作之后仍然保持原值==。
后变址和回写型前变址类似,也要更新基址寄存器的内容,但后变址方式==先利用基址寄存器的原值进行寻址操作,然后再更新基址寄存器==。这两种方式在遍历数组时是很有用的。
感觉例程写错了,r1的是90000,但是存储器的地址是9000
寻址方式中的==立即数== 说明==地址==是通过==基址寄存器==和一个编码在指令中的==12 位偏移量==计算而得(什么玩意儿?
寄存器 说明地址是通过基址寄存器和另一个特定的寄存器中的内容计算而得
第一个是直接在r1上加一个立即数
第二个应该是直接在r1上加寄存器的数
第三个是用桶形移位器直接让r2位移,复习一下移位器
但是,不应该移两位吗,移一位是乘2
给我整蒙了,而且这个是右移,除2了哥们
是我不会用吗,译者说,
由于使用桶形移位器来计算比例,比例因于只能是 2 的倍数。一一译者注
难道LSR移位的是比例因子?
而前变址
还是没能理解这几种的区别,遍历又如何?
而和前变址的区别就是他会不会改变这个r1,顺便复习一下上面的回写和不回写的区别
后变址更像是回写前变址,会先利用基址寄存器的原值进行寻址操作,然后再更新基址寄存器
Addressing2方式和变址方法
不明
多寄存器传送指令
省流:
- 多寄存器的ls指令会延迟中断,因为流水线的性质会让她们执行完当前指令再响应中断。
- 在数据块操作、上下文切换、堆栈操作上比单寄存器传送指令有更高的执行效率
- 编译器(如armcc)会提供一个开关来控制一句ls指令所可以传送的最大寄存器数目来限制最大的中断延迟
注意,可以通过选择使用Rn后缀字符!来确定Rn的值是否随着传送而改变
堆栈操作
堆栈也只是用ls的指令实现的,具体是ascending还是descending,我不到啊。
交换指令
交换指令是 load-store 指令的一种特例
它把一个存储器的内容与寄存器内容相交换。
交换指令是一个原子操作(atomic operation)——在连续的总线操作中 读/写一个存储单元,在操作期间阻止其它任何指令对该存储单元的读/写
这个交换大多数用于操作系统中的信号量和互斥操作
不看了,看了好多都是忘了也不知道有什么用
高效的C 编程
基本的C数据类型
因为ARM处理器内部为32位寄存器和32位的数据处理操作,其体系架构是RISC ls结构(也就是说需要提前将数据load出来到寄存器后才能让ALU处理?
不同版本架构支持的指令不同,不知道armcc编译器对不同架构的会不会有特别优化?
局部变量类型
因为大部分ARM数据处理操作都是32位的,因此建议局部变量尽量用32位的int 或者long,即是处理8或者16位也要避免用char或者short作为局部变量
总结:
- 尽量用32位的去计算,能够避免一些隐式数据宽度窄化警告(需要让其进行多次的位移操作实现符号扩展)、
- 访问连续的内存时,尽量避免用数组下标data[i]来访问数组,直接*(data++)就好,只需一条ARM指令装载数据
有无符号数?
如果程序里只有加减乘,没有除,有无符号都差不多
但是有除法就不一样了。用了除法无符号快。(我不到啊
总结篇:
通过读取数组或者全局变量并赋给不同类型的局部变量时,或者把局部变量写入不同类型的数组或者全局变量时,要进行显式(explicit)数据类型转换。
这种转换使编译器可以明确、快速地处理,把存储器中数据宽度比较窄的数据类型扩展,并赋给寄存器中较宽的类型。通过打开编译器的隐式数据宽度窄化警告(implicit narrowingcast warning)选项,可以观察到隐式数据类型转换。
由于隐式或者显式的数据类型转换通常会有额外的指令周期开销,所以在表达式中应尽量避免使用。load store 指令一般不会产生额外的转换开销,因为 load 和store 指令是自动完成数据类型转换的。
对于函数参数和返回值应尽量避免使用 char 和 short 类型。即使参数范围比较小也应该使用int举型,以防止编译器做不必要的类型转换