1. 微处理器与微控制器
嵌入式系统的处理器大多是微控制器(microcontroller),不同于微处理器(microprocessor),microcontroller指在同一块芯片内除了中央处理单元(CPU)之外还集成了部分内存和外设。集成于微控制器内的内存和外设分别称之为“片内内存”和“片内外设”,否则称之为“片外内存”和“片外外设”。嵌入式系统大多是微控制器的原因,是为了节约成本和节省功耗。另外,由于微控制器内集成了大量的外设,使得嵌入式系统的硬件设计得到了极大的简化。
台式机和笔记本电脑中的处理芯片属于微处理器。
从编程的角度来看,微处理器与微控制器其实没有区别。
2. 寄存器
处理器(这里指CPU)是通过寄存器来运行和加工数据的。不同的处理器所包含的寄存器数量和名称有所不同,但功能却大同小异。可以说寄存器内的值(的变化)决定了处理器的行为。
寄存器可以分为两大类——通用寄存器(General Purpose Register,GPR)和浮点寄存器(Floating Point Register,FPR)。通用寄存器的作用包括执行指令、进行整型数据和逻辑运算,而浮点寄存器则用于运算具有小数点的数据。
实际上,不光是中央处理单元存在寄存器,集成在微控制器没的外设也有寄存器。通过配置这些寄存器,可以控制外设的行为和工作方式。此外,这些寄存器在处理器的地址空间中占有相应的位置,配置这些寄存器就是对这些空间根据芯片手册进行读写操作。
3. 处理器是如何启动的
像C语言的入口函数main()一样,具有导读意义。
每块处理器在出厂时已固化好其寄存器的默认值,这些值决定了处理器上电时刻的行为。也就是说,处理器的第一条执行指令的地址是通过硬件设计来实现的,这是硬件工程师的工作内容。在大多数嵌入式系统中,处理器所取得的第一条指令应当属于引导加载器程序的一部分,而第一条指令的获取标志着引导加载器程序开始运行。
4. 输入与输出
除了CPU和内存外,另一大部件是外设(peripheral)。CPU与外设的通信被称为输入与输出(Input/Output),简称I/O。
外设也像内存的存储单元那样通过地址进行区分,但是它们的地址被称为I/O端口(I/O Port)。处理器除了通过I/O端口进行通信外,另一个重要的手段是中断。
I/O端口所在的空间被称为I/O空间,各种架构的处理器存在不同的I/O空间设计形式。其中一种I/O空间设计是将之设计成独立于内存所在的空间。在这种设计下,读写I/O端口需要使用与存取内存不一样的指令。从编程角度来看,对I/O端口操作不能像操作内存那样直接使用C语言中的指针完成,而是需要调用相应的函数,这些函数封闭了I/O端口的操作指令。另一种I/O空间设计是将之设计成与内存在同一个地址空间中,它被称为内存映射I/O空间。从编程角度来看,内存映射I/O空间的端口操作与访问内存是完全一样的。x86处理器既有独立的I/O地址空间(大小为64KB),也有内存映射I/O空间。但像ARM、PowerPC这样的处理器完全采用内存映射I/O空间。
对外设的I/O端口写什么、如何读是在外设的芯片手册中指定的。因此,要正确配置外设并与之交互,离不开熟读外设的芯片手册,这是嵌入式软件驱动开发的一项基本功。
5. 字节序(参见 网络:字节序)
6.边界对其
边界对其(boundary alignment)是处理器为了提高处理性能而对存取数据的其实地址所提出的一种要求。要对数据结构进行高效的操作,从处理器的角度来看,需要尽可能减少对内存的访问次数,尽管处理器包含了缓存,但处理器在处理数据时还得读取缓存中的数据,显然,读取缓存的次数也越少越好。对于32位处理器,每一次读取操作都是32位的,即4个字节(即一次存取可以获得4个字节)。
例如:
struct{
char a;
int b;
}
采用边界对其时: 从内存地址0x0000开始依次存放a,pad,pad,pad,b0,b1,b2,b3;(pad表示填充的意思)这时,当处理器需要分别访问a和b变量时只需进行一次存取。
不采用边界对其时:从内存地址0x0000开始依次存放a,b0,b1,b2,b3;这时,a变量无论如何只要进行一次存取,而b变量却要进行两次。更麻烦的是,对于b还得合成一个4字节,这需要更多的指令来完成,降低了程序的执行效率。
7. 内存管理单元
内存管理单元(Memory Management Unit,MMU)实现以下功能:
(1)虚拟内存。有了虚拟内存,可以在处理器上运行比实际物理内存大的应用程序。为了使用虚拟内存,操作系统通常要设置一个交换分区(通常是硬盘),通过将内存中不活跃的数据放入交换分区以腾出物理内存来为其他的程序服务。
(2)内存保护。通过这一功能,可以将特定的内存块设置为读、写和可执行属性。
在嵌入式系统中通常不会使用虚拟内存这一功能,因为它会使操作系统的实时性更具不确定性。还有另一个原因是,嵌入式系统的外部存储空间通常很小,且没有硬盘空间做交换分区。
内存管理单元在嵌入式系统中主要用于实现内存保护。程序中的text段和data段通常放在相邻的连续内存空间中,并通过内存管理单元实现只读和可执行保护。对于设置成只读的内存区当被意外地改写时,处理器会产生一个异常中断,操作系统利用这一中断可以记录下出错时的函数调用栈,以帮助我们定位问题。
内存管理单元中存在页的概念,对于所有与内存保护相关的操作都是以页的大小来进行的。比如在常用的32位x86处理器上,页的大小是4KB。在设置内存块的读写属性时,内存块的开始地址必须是页的整数倍,或者说地址必须是以页大小作为边界对齐的。由于页的存在,在运用内存管理单元对text段进行保护时必须保证text段的起始地址是页对齐的,这可以通过连接器的脚本来达到目的。由于内存管理单元是以页大小保护单位的,因此得保证text段的大小也是页的整数倍,否则可能出现data段(它是可写的)有一部分与text段的最后部分放入同一个页中。为了避免这一问题,将data段的起始地址也通过链接脚本设置成页对齐就行了。
这样看来,使用内存管理单元就会浪费一点内存空间,因为当text段的大小不是页的整数倍时,text段与data段之间会存在空隙。
8. 缓存
缓存存在于芯片内存中。
当处理器需要从外部内存取数据时,先从缓存中查找,找到了,则称为“缓存命中”,可将这一数据直接拿去使用。反之,如果没有找到,则需要从外部内存中进行存取。在这种情况下,不是要一个字节就读一个字节,而是一次性地采用突发方式(burst mode)从内存读入一行缓存行。一行缓存行可能是64个字节,视不同处理器而不同。
当处理器需要向内存写数据时,需要根据我们对缓存的配置进行不同的操作。其中一种模式是数据先写入缓存,然后由处理器在合适的时间将缓存中的数据写回到外部内存中;另一种模式是数据直接写回到外部内存中。
在处理器中我们可以配置各地址空间是否需要时能缓存功能,或者说缓存并非只针对内存,还可以针对其他的存储器,如闪存。如果对于的空间是闪存,那么对闪存进行编程(烧写)时一定要禁用缓存功能。这时因为在对闪存芯片进行编程时需要一定的写命令序列,如果闪存所在的空间使能了缓存功能,那么编程写命令序列有肯能不会立即发送给闪存芯片,而是存放在缓冲中,结果就是不能正常地进行闪存编程操作了。
在一些高端处理器上还提供了缓存锁定功能,使得某块缓存区中的数据不会因为腾出空间的需要(对缓存里面的内容更新,即淘汰)而变成无效。这一功能对于那些需要频繁地查找数据表的程序很有用处。例如,可能有这样一张数据表,程序需要以高负荷的方式频繁地查找该表,将其锁定在缓存中能显著地提高处理器的执行效率。