ARM Linux中的非对齐内存访问(Alignment trap警告的原因)
2016-06-04 22:12
134 查看
ARMv5指令集的CPU(一般是arm9架构)默认不支持非对齐内存访问,ARMv6及以上的CPU默认支持处理大部分的非对齐内存地址访问。对齐指的是起始地址是一个word长度的整数倍,通常是4字节对齐。
通过设置/proc/cpu/alignment文件内容可修改内核中对非对齐地址访问的处理。
这个文件中最后一行"User faults"即是内核中如何处理非对齐的内存地址访问,这个值是一个位图:
UM_WARN:只给出“Alignment trap”警告。
UM_FIXUP:尝试正确处理非对齐的内存地址。
UM_SIGNAL:发生非对齐地址访问时,发送SIGBUS信号通知相应进程。
几种处理方式可以进行组合,例如设置为(UM_WARN | UM_FIXUP),就是在fixup的同时给出警告信息。如果设置为0则为ignore。
对于ARMv5的CPU,User faults的值默认是0,即忽略非对齐的地址访问,这时如果进程访问了非对齐的地址,就会导致程序执行异常。如果程序中不得不存在非对齐的地址访问,可以设置为fixup:
这样的话,内核就会做额外的工作以使访问非对齐内存地址可以得到正确的结果。
对于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()。
在不支持非对齐内存访问的芯片上,进行强制类型转换就要注意了,例如下面的例子:
在支持非对齐访问系统上的结果如下(小端系统):
而在不支持非对齐访问系统上,对/proc/cpu/alignment设置不同的值表现也不相同:
当然也可以设置为3,在正确处理非对齐地址访问的同时给出警告。
我们可以使用char类型来对非对齐的内存进行读写来避免非对齐地址的处理,或使用memcpy这类函数来避免赋值或强制类型转换带来的问题。
看下面的例子:
由于给struct pack_info加上了__attribute__((packed))属性,所以它的len成员的地址不是4字节对齐的。
上面的代码用三种方式给len成员赋值,在不支持非对齐访问系统上,第二种方式就会有问题,不能得到预期的结果。
通过设置/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成员赋值,在不支持非对齐访问系统上,第二种方式就会有问题,不能得到预期的结果。
相关文章推荐
- [装载]ARM Linux内核启动过程
- linux 防火墙开放特定端口与指定ip谨防
- LINUX日志系统之WEB日志(一)
- Linux内核:简介
- CentOS 6.x RHCS Cluster 配置
- Linux 用Makefile编译C代码
- CentOS 7安装LAMP环境及Laravel框架
- Linux执行PHP脚本(简单实例)
- Linux+PHP设置北京时间
- Linux进程管理---task_struct
- [platform]linux platform device/driver(三)--Platform Device和Platform_driver注册过程之代码对比
- 深入理解Linux内核 chap 3 进程
- Linux与JVM的内存关系分析
- [platform]linux platform device/driver(二)--Platform Device和Platform_driver注册过程之详细代码
- linux(centos)搭建SVN服务器
- Linux学习
- 嵌入式Linux裸机开发(一)——点亮Led
- CentOS 6.6 下源码编译安装MySQL 5.7.5
- 在Linux下用C语言写一个小程序实现一个进度条编写Makefile来进行运行
- Linux内核源码结构