ARM Linux中的非对齐内存访问

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

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

对于ARMv6及以上的CPU,CPU本身就要求支持对非对齐地址访问的处理,因此基本不用关心/proc/cpu/alignment里设置的值,不过对于像LDM, STM, LDRD和STRD这些复合指令进行的非对齐地址访问,仍需要软件协助处理,在这些CPU中,/proc/cpu/alignment默认设置为2(fixup)。

具体的处理方式详见内核代码arch/arm/mm/alignment.c:alignment_init(), hook_fault_code(), do_alignment()。

在不支持非对齐内存访问的芯片上,进行强制类型转换就要注意了,例如下面的例子:

    #include <stdio.h>
    #include <stdlib.h>
    #include <signal.h>
     
    void sigbus_handler(int sno)
    {
     printf("signal %d captured\n", sno);
     exit(1);
    }
     
    int main(int argc, char *argv[])
    {
     char intarray[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
     
     signal(SIGBUS, sigbus_handler);
     
     printf("int1 = 0x%08x, int2 = 0x%08x, int3 = 0x%08x, int4 = 0x%08x\n",
       *((int *)(intarray + 1)), *((int *)(intarray + 2)), *((int *)(intarray + 3)), *((int *)(intarray + 4)));
     
     return 0;
    }

在支持非对齐访问系统上的结果如下(小端系统):

root@NVR:~# ./testalign
int1 = 0x55443322, int2 = 0x66554433, int3 = 0x77665544, int4 = 0x88776655

而在不支持非对齐访问系统上,对/proc/cpu/alignment设置不同的值表现也不相同:

~ # echo 0 > /proc/cpu/alignment
~ # ./testalign
int1 = 0x11443322, int2 = 0x22114433, int3 = 0x33221144, int4 = 0x88776655
~ #
~ # echo 2 > /proc/cpu/alignment
~ # ./testalign
int1 = 0x55443322, int2 = 0x66554433, int3 = 0x77665544, int4 = 0x88776655
~ #
~ # echo 1 > /proc/cpu/alignment
~ # ./testalign
Alignment trap: testalign (979) PC=0x0000860c Instr=0xe5931000 Address=0xbef6fc99 FSR 0x001
Alignment trap: testalign (979) PC=0x0000860c Instr=0xe5931000 Address=0xbef6fc99 FSR 0x001
int1 = 0x11443322, int2 = 0x22114433, int3 = 0x33221144, int4 = 0x88776655
~ #
~ # echo 4 > /proc/cpu/alignment
~ # ./testalign
signal 7 captured
~ #

当然也可以设置为3,在正确处理非对齐地址访问的同时给出警告。

我们可以使用char类型来对非对齐的内存进行读写来避免非对齐地址的处理,或使用memcpy这类函数来避免赋值或强制类型转换带来的问题。
看下面的例子:

    #include <stdio.h>
    #include <stdlib.h>
    #include <signal.h>
    #include <string.h> /* memcpy */
     
    struct pack_info {
     unsigned char sno;
     unsigned int len;
    } __attribute__((packed));
     
    void sigbus_handler(int sno)
    {
     printf("signal %d captured\n", sno);
     exit(1);
    }
     
    int main(int argc, char *argv[])
    {
     signal(SIGBUS, sigbus_handler);
     
     struct pack_info * mem_cos = (struct pack_info *)malloc(sizeof(struct pack_info));
     if (!mem_cos)
      return 1;
     
     unsigned int * cy_len = &mem_cos->len;
     mem_cos->sno = 0x12;
     mem_cos->len = 0x55667788;
     
     /* 1. 给成员变量赋值 */
     mem_cos->len = 0xaabbccdd;
     
     /* 2. 给指针cy_len的值赋值 */
     *cy_len = 0xaabbccdd;
     
     /* 3. 通过memcpy赋值 */
     unsigned int cy_len_i = 0xaabbccdd;
     memcpy(cy_len, &cy_len_i, 4);
     
     printf("sno = %#x, len = %#x\n", mem_cos->sno, mem_cos->len);
     
     free(mem_cos);
     
     return 0;
    }

由于给struct pack_info加上了__attribute__((packed))属性,所以它的len成员的地址不是4字节对齐的。
上面的代码用三种方式给len成员赋值,在不支持非对齐访问系统上,第二种方式就会有问题,不能得到预期的结果。

--------------------- 
原文:https://blog.csdn.net/jasonchen_gbd/article/details/51535018  
 

猜你喜欢

转载自blog.csdn.net/CLP_CSDNID/article/details/84977751