对ARM处理器的内存对齐问题(译)


介绍

可以对齐不对齐的内存访问。对齐的内存访问发生时的数据都位于其自然大小边界。例如,如果该数据类型的大小是4个字节,那么它属于被4整除的内存地址是位于其自然大小边界。未对齐的内存访问发生在所有其他情况下(在上面的例子中,内存地址时,是不能被4整除)。 ARM处理器的设计有效地访问对齐的数据。在ARM处理器上试图访问未对齐的数据会导致不正确的数据或显着的性能损失(这些不同的症状会在稍后讨论)。与此相反,大多数CISC型处理器(即x86)的访问未对齐的数据是无害的。 这份文件将讨论一些比较常见的方式,一个应用程序可能会执行未对齐的内存访问,并提供一些建议的解决方案,以避免这些问题, 。 



症状

上述问题,适用于所有ARM架构。然而,根据MMU(内存管理单元)和操作系统支持的可用性,应用程序可能会看到不同的行为在不同的平台上。默认情况下,未对齐的内存访问不会被困住了,会导致不正确的数据。与功能的MMU的平台上,但是,OS捕获非对齐访问,它在运行时进行纠正。其结果将是正确的数据,但在10-20 CPU周期的成本。

常见原因


上述问题的类型转换适用于所有ARM架构。然而,根据MMU(内存管理单元)和操作系统支持的可用性,应用程序可能会看到不同的行为在不同的平台上。默认情况下,未对齐的内存访问不会被困住了,会导致不正确的数据。与功能的MMU的平台上,但是,OS捕获非对齐访问,它在运行时进行纠正。其结果将是正确的数据,但在10-20 CPU周期的成本。

代码:

void my_func(char *a) {
  int *b = (int *)a;
  DBGPRINTF("%d", *b);
}

这个简单的例子,可能会导致未对齐的内存访问,因为我们不能保证的char * a是一个4字节的边界上对齐。只要有可能,应避免这种类型的施放。

使用数据缓冲区
未对齐的内存访问的最常见的原因源于不正确地处理数据缓冲区。这些数据缓冲区可能包含任何数据从USB端口读取,通过网络,或从一个文件中。这个数据是很常见的包装,有没有插入填充,以确保数据在缓冲区内位于其自然大小边界。在这个例子中,我们会考虑的情况下,从文件加载的Windows BMP和解析的头。 的Windows BMP文件包含一个头的像素数据。的标头是由两个结构: 

代码:

typedef PACKED struct {
   unsigned short int type;         /* Magic identifier            */
   unsigned int size;               /* File size in bytes          */
   unsigned short int reserved1, reserved2;
   unsigned int offset;             /* Offset to image data, bytes */
} HEADER;

typedef PACKED struct {
   unsigned int size;               /* Header size in bytes      */
   int width,height;                /* Width and height of image */
   unsigned short int planes;       /* Number of colour planes   */
   unsigned short int bits;         /* Bits per pixel            */
   unsigned int compression;        /* Compression type          */
   unsigned int imagesize;          /* Image size in bytes       */
   int xresolution,yresolution;     /* Pixels per meter          */
   unsigned int ncolours;           /* Number of colours         */
   unsigned int importantcolours;   /* Important colours         */
} INFOHEADER;


请注意,在的HEADER和INFOHEADER结构的大小,分别为14和40字节。 让我们假设我们要确定在运行时的图像的宽度和高度。的代码来访问这些数据可能看起来像这样: 

代码:

#define INFOHEADER_OFFSET (sizeof(HEADER))
#define WIDTH_OFFSET (INFOHEADER_OFFSET + offsetof(INFOHEADER, width))
#define HEIGHT_OFFSET (INFOHEADER_OFFSET + offsetof(INFOHEADER, height))

int imageWidth, imageHeight;
void * fileBuf;

pMe->mFile = IFILEMGR_OpenFile(pMe->mFileMgr, "test.bmp", _OFM_READ);

if (pMe->mFile) {
   IFILE_GetInfo(pMe->mFile, &fileInfo);
   fileBuf = MALLOC(fileInfo.dwSize);

   if (fileBuf) {
      result = IFILE_Read(pMe->mFile, fileBuf, fileInfo.dwSize);

      if (result == fileInfo.dwSize) {
         imageWidth = *((uint32*)(((byte*)fileBuf) + WIDTH_OFFSET));
         imageHeight = *((uint32*)(((byte*)fileBuf) + HEIGHT_OFFSET));
      }
   }
}


注意的宽度和高度的偏移量。因为他们属于一个半字边界上,以上述方式访问这些值会导致未对齐的内存访问。下面列出的一些推荐的方法来避免这个问题。

推荐的解决方案


使用memcpy

我们的第一个选项是,只需执行MEMCPY从缓冲区中的数据到本地变量:

代码:

if (result == fileInfo.dwSize) {
   MEMCPY(&imageWidth,
          (((byte*)fileBuf)+WIDTH_OFFSET),
          sizeof(uint32));

   MEMCPY(&imageHeight,
          (((byte*)fileBuf)+HEIGHT_OFFSET), 
          sizeof(uint32));
}


其结果是,存储器被复制字节逐字节,避免任何疑问对准。 


包装的编译器指令

或者,我们可以使用压缩的编译器指令允许使用指针,直接将我们需要的数据,同时迫使编译器来处理对齐问题。在BREW环境中,PACKED被定义如下:

代码:

#ifdef __ARMCC_VERSION
  #define PACKED __packed
#else
  #define PACKED
#endif


包装形式,通过指定一个指针,ARM编译器将生成相应的说明来正确地访问内存,无论对齐。修改后的版本,上面的例子中,使用PACKED指针,如下: 

代码:

#define INFOHEADER_OFFSET (sizeof(HEADER))
#define WIDTH_OFFSET (INFOHEADER_OFFSET + offsetof(INFOHEADER, width))
#define HEIGHT_OFFSET (INFOHEADER_OFFSET + offsetof(INFOHEADER, height))

PACKED uint32 * pImageWidth;
PACKED uint32 * pImageHeight;
uint32 imageWidth, imageHeight;
void * fileBuf;

pMe->mFile = IFILEMGR_OpenFile(pMe->mFileMgr, "test.bmp", _OFM_READ);

if (pMe->mFile) {
   IFILE_GetInfo(pMe->mFile, &fileInfo);
   fileBuf = MALLOC(fileInfo.dwSize);

   if (fileBuf) {
      result = IFILE_Read(pMe->mFile, fileBuf, fileInfo.dwSize);

      if (result == fileInfo.dwSize) {
         pImageWidth = (uint32*)(((byte*)fileBuf) + WIDTH_OFFSET);
         pImageHeight = (uint32*)(((byte*)fileBuf) + HEIGHT_OFFSET);
         imageWidth = *pImageWidth;
         imageHeight = *pImageHeight;
      }
   }
}




虽然程序员通常会无法控制标准化的数据格式,如BMP头在上面的例子中,当你定义自己的数据结构应确保奠定了良好的对齐方式中的数据定义对齐的数据结构。下面的基本示例演示了这样的原则:

代码:

#ifdef __ARMCC_VERSION
typedef PACKED struct {
   short a;		// offsetof(a) = 0
   int   b;		// offsetof(b) = 2 � misalignment problem!
   short c;		// offsetof(c) = 6
} BAD_STRUCT;
 
typedef struct {
   int   b;		// offsetof(b) = 0 � no problem!
   short a;		// offsetof(a) = 4
   short c;		// offsetof(c) = 6
} GOOD_STRUCT; 


通过简单地重新排列中,我们声明的结构成员,我们可以解决一些对齐的问题。另外请注意,如果未声明为包装,BAD_STRUCT,编译器通常会插入填充,每个字段对齐。然而,这通常是不希望的,因为它浪费内存和避免几乎总是可以简单地通过声明为了减小尺寸的字段。 

BREW模拟器测试


BREW模拟器3.1.2及以上版本提供了能够使数据对齐检查。BREW模拟器启用此功能时,将显示一个对话框,通知您的每一个未对齐的内存访问,并为您提供的选项对这一问题视而不见,或闯入的代码 ,请参阅BREW SDK用户文档一节揗isaligned数据异常支持更多信息,此功能。 注: 由于x86架构的访问未对齐的数据不会有任何问题,你可以不编译模拟器的DLL使用__packed指令(PACKED这就是为什么在WIN32环境下的空白被定义为)。这意味着,通过使用PACKED指针的非对齐访问,解决依旧会触发在模拟器的对齐检查。 


https://brewx.qualcomm.com/bws/content/gi/common/appseng/en/knowledgebase/docs/kb95.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值