arm(linux)下非对齐地址访问的问题

背景

就在这周,我在写串口命令的时候,遇到了一个很奇怪的现象,百思不得其解,刚开始以为是自己粗心,对于地址的偏移计算错误,但是经过自己长时间排查,甚至向同事请教,让他们帮我看一下代码逻辑,都没有发现什么异常。一度让我怀疑人生。后面经过我仔细的摸索,终于找出了其中的原因,有种恍然大明白的感觉。所以在这里总结一下,避免其他人也入坑。

demo

我代码的逻辑就不贴图了,知识点其实如下(操作系统为小端):

int main ()
{
    char *ptr = "12345678";
    int *len =(int*) (ptr + 2);
    printf("*len = 0x%08x\n",*len);
}
请问输出多少?

正常情况下,我们的分析思路应该如下:

假设字符串常量"12345678"的首地址是0X1000,那么它的内存地址分布应该是这样的

addr value value value value
0X1004 0X35 0x36 0x37 0x38
0X1000 0X31 0X32 0X33 0X34

len指向0x33这个地址,因为len是int型指针,占四个字节,所以输出0x36353433。的确,正常情况下应该是这个结果,但是在我这个环境中就不是这样的结果,这就需要引入我们今天的知识点非地址对齐访问

内存字节对齐

在进入主题前,我想介绍一下内存字节对齐。相信大家对内存对齐应该都知道,比如我们定义结构体时,会考究成员的顺序,否则造成内存空洞。例如:

struct A{
    char  a;
    int   b;
    short c;
}

struct B{
    char  a;
    short b;
    int   c;
}

我们应该都知道A结构体占用的字节应该是12,而B结构体应该是8.至于原因,我想大家应该都是知道的。至于编译器为什么还会进行这种处理,其实的目的有两个:

  1. 就是在可接受的空间浪费下,提高CPU运行效率。
  2. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常

我们的这个情况就是属于第二种了,并没有得到正确的值。

原理

计算机主要的架构就分为两类,复杂指令集计算机(CISC)和精简指令集计算机(RISC)。CISC最有代表性的架构就是x86,RISC最有代表性的架构就是ARM。不管是什么架构,对要访问的一定长度的数据的地址是有要求的,比如要访问一个32位的整数,那么这个数据必须(最好)存储在以4字节(32/8=4)对齐的地方。一般来说,RISC对对齐要求的更严格些,非对齐访问可能会带来性能上的损失。这对程序在不同架构间移植非常重要,因为它极有可能导致你的程序崩溃。

其中X86平台一般是支持非对齐地址访问,ARM是不支持的。而我们的H10平台就是cpu就是ARM。

ARM内存访问的对齐问题:
按照ARM文档上的描述,其访问规则如下:

  1. 一次访问4字节内容,该内容的起始地址必须是4字节对齐的位置上;
  2. 一次访问2字节内容,该内容的起始地址必须是2字节对齐的位置上;

因此在访问short型和int型数据是需要注意地址对齐问题。

进阶

知道了ARM平台的问题,我们该怎么处理呢?经过查找资料,如下:
ARMv5指令集的CPU(一般是arm9架构)默认不支持非对齐内存访问,ARMv6及以上的CPU默认支持处理大部分的非对齐内存地址访问。对齐指的是起始地址是一个word长度的整数倍,通常是4字节对齐。

通过设置/proc/cpu/alignment文件内容可修改内核中对非对齐地址访问的处理。

root@(none):~# cat /proc/cpu/alignment
User:           3905290
System:         0
Skipped:        0
Half:           0
Word:           0
DWord:          2672136
Multi:          1233154
User faults:    2 (fixup)

这个文件中最后一行"User faults"即是内核中如何处理非对齐的内存地址访问,这个值是一个位图:

#define UM_WARN    (1 << 0)
#define UM_FIXUP   (1 << 1)
#define UM_SIGNAL  (1 << 2)

UM_WARN:只给出“Alignment trap”警告。
UM_FIXUP:尝试正确处理非对齐的内存地址。
UM_SIGNAL:发生非对齐地址访问时,发送SIGBUS信号通知相应进程。
几种处理方式可以进行组合,例如设置为(UM_WARN | UM_FIXUP),就是在fixup的同时给出警告信息。如果设置为0则为ignore。

对于ARMv5的CPU,User faults的值默认是0,即忽略非对齐的地址访问,这时如果进程访问了非对齐的地址,就会导致程序执行异常。如果程序中不得不存在非对齐的地址访问,可以设置为fixup:

echo 2 > /proc/cpu/alignment

这样的话,内核就会做额外的工作以使访问非对齐内存地址可以得到正确的结果

参考博客:

https://blog.csdn.net/jasonchen_gbd/article/details/51535018
https://blog.csdn.net/zyboy2000/article/details/4293450

你可能感兴趣的:(日常记录,非对齐地址访问,跨平台移植,内存对齐)