跳转执行 B 指令的格式为:
B{条件} 目标地址
B 指令是最简单的跳转指令。一旦遇到一个B 指令,ARM 处理器将立即跳转到给定的目标地址,从那里继续执行。
注意存储在跳转指令中的实际值是相对当前PC 值的一个偏移量,而不是一个绝对地址,它的值由汇编器来计算(参考寻址方式中的相对寻址)。
它是 24 位有符号数,左移两位后有符号扩展为 32 位,表示的有效偏移为 26 位(前后32MB 的地址空间)。
举例:
B Label ; 程序无条件跳转到标号 Label 处执行
CMP R1 ,# 0 ; 当 CPSR 寄存器中的 Z 条件码置位时,程序跳转到标号 Label 处执行
BEQ Label
带链接和状态切换的跳转。结合了BX与BL功能。BLX 指令的格式为:
BLX 目标地址
BLX 指令从ARM 指令集跳转到指令中所指定的目标地址,并将处理器的工作状态由ARM 状态切换到Thumb 状态,该指令同时将PC 的当前内容保存到寄存器R14 中。
因此,当子程序使用Thumb 指令集,而调用者使用ARM 指令集时,可以通过BLX 指令实现子程序的调用和处理器工作状态的切换。
1、BL 和 BLX 指令可将下一个指令的地址复制到lr(r14,链接寄存器)中。2、BX 和 BLX 指令可将处理器的状态从 ARM 更改为 Thumb,或从 Thumb 更改为ARM。3、BLX label 无论何种情况,始终会更改处理器的状态。4、BX Rm 和 BLX Rm 可从 Rm 的位 [0]推算出目标状态。5、如果 Rm 的位 [0] 为 0,则处理器的状态会更改为(或保持在)ARM 状态。如果 Rm 的位 [0] 为 1,则处理器的状态会更改为(或保持在)Thumb 状态。
中断发生后,ARM核的操作步骤可以总结为4大步3小步。
当前程序的执行状态是保存在CPSR里面的,异常发生时,要保存当前的CPSR里的执行状态到异常模式里的SPSR里,将来异常返回时,恢复回CPSR,恢复执行状态。
硬件自动根据当前的异常类型,将异常码写入CPSR里的M[4:0]模式位,这样CPU就进入了对应异常模式下。不管是在ARM状态下还是在THUMB状态下发生异常,都会自动切换到ARM状态下进行异常的处理,这是由硬件自动完成的,将CPSR[5] 设置为 0。同时,CPU会关闭中断IRQ(设置CPSR 寄存器I位),防止中断进入,如果当前是快速中断FIQ异常,关闭快速中断(设置CPSR寄存器F位)。
当前程序被异常打断,切换到异常处理程序里,异常处理完之后,返回当前被打断模式继续执行,因此必须要保存当前执行指令的下一条指令的地址到LR_excep(异常模式下LR,并不存在LR_excep寄存器,为方便读者理解加上_excep,以下道理相同),由于异常模式不同以及ARM内核采用流水线技术,异常处理程序里要根据异常模式计算返回地址。
该操作是CPU硬件自动完成的,当异常发生时,CPU强制将PC的值修改为一个固定内存地址,这个固定地址叫做异常向量。
BX指令:R0[0]=1,则执行BX R0指令将进入thumb状态
BX指令:R0[0]=0,则执行BX R0指令将进入arm状态
AREA Arm_to_Thumb,CODE, READONLY
ENTRY
CODE32
start
ldr r0,=aaa+1
mov r3,#18
bx r0
CODE16
aaa
mov r1,#12
mov r2,#10
END
要进入异常模式,一定要有异常源,ARM规定有7种异常源:
异常源 | 描述 |
---|---|
Reset | 上电时执行 |
Undef | 当流水线中的某个非法指令到达执行状态时执行 |
SWI | 当一个软中断指令被执行完的时候执行 |
Prefetch | 当一个指令被从内存中预取时,由于某种原因而失败,如果它能到达执行状态这个异常才会产生 |
Data | 如果一个预取指令试图存取一个非法的内存单元,这时异常产生 |
IRQ | 通常的中断 |
FIQ | 快速中断 |
异常向量表是一段特定内存地址空间,每种ARM异常对应一个字长空间(4Bytes),正好是一条32位指令长度,当异常发生时,CPU强制将PC的值设置为当前异常对应的固定内存地址。
跳入异常向量表操作是异常发生时,硬件自动完成的,剩下的异常处理任务完全交给了程序员。由上表可知,异常向量是一个固定的内存地址,我们可以通过向该地址处写一条跳转指令,让它跳向我们自己定义的异常处理程序的入口,就可以完成异常处理了。
正是由于异常向量表的存在,才让硬件异常处理和程序员自定义处理程序有机联系起来。异常向量表里0x00000000地址处是reset复位异常,之所以它为0地址,是因为CPU在上电时自动从0地址处加载指令,由此可见将复位异常安装在此地址处也是前后结合起来设计的,不得不感叹CPU设计师的伟大,其后面分别是其余7种异常向量,每种异常向量都占有四个字节,正好是一条指令的大小,最后一个异常是快速中断异常,将其安装在此也有它的意义,在0x0000001C地址处可以直接存放快速中断的处理程序,不用设置跳转指令,这样可以节省一个时钟周期,加快快速中断处理时间。
存储器映射地址0x00000000是为向量表保留的。在有些处理器中,向量表可以选择定位在高地址0xFFFF0000处【可以通过协处理器指令配置】,当今操作系统为了控制内存访问权限,通常会开启虚拟内存,开启了虚拟内存之后,内存的开始空间通常为内核进程空间,和页表空间,异常向量表不能再安装在0地址处了。
比如Cortex-A8系统中支持通过设置CP15的C12寄存器将异常向量表的首地址放置在任意地址。
本题有点没整明白在问什么,如果分析有误,欢迎给我留言。
Linux下一切皆文件,应用程序访问外设都需要通过驱动来操作外设。
Linux下设备类型分为:字符设备、块设备、网络设备。
架构图参考下图:
此外题目问画出外设寻址的分层模型,我想可能是想问如何访问SDRAM。
如下图所示,CPU要访问SDRAM或者flash需要先通过AHB总线,然后再通过外存接口控制器对外存寻址,然后进行数据的读写。
中断是嵌入式系统中重要的组成部分,很多外设和cpu的交互都是通过中断的方式进行。衡量一个OS的实时性很重要的一个特性就是中断的响应时间,长时间处于中断状态所以中断反应。一旦进入中断状态,cpu是在占用的,所以必须快速的出中断,否则会影响整体性能。
中断服务程序需要满足如下要求:
共享资源也叫临界资源,在进程和线程、内核下都有自己的同步互斥机制, 仅考虑互斥的话,分开来看:
进程:信号量(值设置为1) 线程:互斥体、信号量(也叫信号灯) 内核:原子操作,自旋锁,信号量,互斥锁
Linux进程间通信方式主要有
在实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果有一个函数不幸被设计成为不可重入这样:那么不同任务调用这个函数时可能修改其他任务调用这个函数的数据,从而导致不可预料的后果。这样的函数是不安全的函数,也叫不可重入函数。
满足下列条件的函数多数是不可重入的
可重入函数可以被一个以上的任务调用,而不必担心数据被破坏。可重入函数任何时候都可以被中断,一段时间以后又可以运行,而相应的数据不会丢失。可重入函数或者只使用局部变量,即保存在CPU寄存器中或堆栈中;或者使用全局变量,则要对全局变量予以保护
如何写出可重入函数在函数体内不访问那些全局变量,不使用静态局部变量,坚持只使用缺省态(auto)局部变量,写出的函数就将是可重入的。如果必须访问全局变量,记住利用互斥信号量来保护全局变量。或者调用该函数前关中断,调用后再开中断。Linux常见的可重入函数
A) 可重入函数
void strcpy(char * dest, char * src) {
while(*dest++=*src++);
*dest = 0;
}
B) 不可重入函数1
char temp;//全局变量
void swapchar(char * x, char * y) {
temp = *x;
*x=*y;
*y=temp;//访问了全局变量
}
C) 不可重入函数2
void swapchar(char * x, char * y) {
char temp;//局部变量
temp = *x;
*x=*y;
*y=temp;//使用了全局变量
}
int Exam = 0;
unsigned int example( int para )
{
unsigned int temp;
Exam = para; // (**)
temp = Square_Exam( );
return temp;
}
Exam 是 int 型全局变量,函数 Squre_Exam() 返回 Exam 平方值,所以函数example()不具有可重入性。
此函数若被多个进程调用的话,其结果可能是未知的,因为当 Exam = para语句刚执行完后,另外一个使用本函数的进程可能正好被激活,那么当新激活的进程执行到此函数时,将使 Exam 赋与另一个不同的 para 值,所以当控制重新回到 “temp = Square_Exam( )” 后,计算出的temp很可能不是预想中的结果。
此函数应如下改进:
int Exam = 0;
unsigned int example( int para )
{
unsigned int temp;
[申请信号量操作] //(1) 加锁
Exam = para;
temp = Square_Exam( );
[释放信号量操作] // 解锁
return temp;
}
申请不到“信号量”,说明另外的进程正处于给 Exam 赋值并计算其平方过程中(即正在使用此信号),本进程必须等待其释放信号后,才可继续执行。
若申请到信号,则可继续执行,但其它进程必须等待本进程释放信号量后,才能再使用本信号。
int *fun(void) fun是一个函数名,该函数的参数是void型,返回值是 int *
int (*fun)(void) fun是一个函数指针,该指针用于指向一个函数,函数的参数是void型,返回值是 int 即类型如下:
int function(void)
{
return 0;
}
struct person
{
int a;
unsigned short int m;
char b;
char *q;
char c;
};
char str[]="yikoulinux";
char *p = str;
int n = 10;
则sizeof(str)、sizeof(p)、sizeof(n)、sizeof(int)、sizeof(struct person)的值分别是:
编写测试程序
1 #include <stdio.h>
2
3 int main(int argc, const char *argv[])
4 {
5 struct person
6 {
7 int a;
8 unsigned short int m;
9 char b;
10 char *q;
11 char c;
12 };
13 char str[]="yikoulinux";
14 char *p = str;
15 int n = 10;
16
17 struct person pe;
18
19 printf("sizeof(str):%d sizeof(p):%d sizeof(n):%d sizeof(int):%d sizeof(struct person):%d\n",si
20
21 printf("%p\n %p\n %p\n %p \n %p \n",
22 &pe.a,&pe.m,&pe.b,&pe.q,&pe.c);
23 return 0;
24 }
编译执行:
sizeof(str):11 sizeof(p):4 sizeof(n):4 sizeof(int):4 sizeof(struct person):16
0xbf917d18
0xbf917d1c
0xbf917d1e
0xbf917d20
0xbf917d24
解释:
类型 | 值 | 解释 |
---|---|---|
sizeof(str) | 11 | 这是计算数组str的大小,因为没有设置数组大小,以赋值字符串大小为准,而常量字符串在后一个位置会补一个空字符'\0',所以一共11个字节 |
sizeof(p) | 4 | p是一个指针,32位机的指针都是32位,4个字节 |
sizeof(n) | 4 | n是一个整型变量,所以是4个字节 |
sizeof(int) | 4 | 计算int 类型占用的内存大小,是4个字节 |
sizeof(struct person) | 16 | 默认情况下结构体成员会字节对齐,详细分布见下图 |
推荐阅读
进群,请加一口君个人微信,带你嵌入式入门进阶。
在公众号内回复「1024」,即可免费获取学习资料,期待你的关注~