mips下非对齐访问问题分析

本文介绍了在RISC架构下,如MIPS和ARM等,地址对齐的重要性及其实现方法。对于非对齐访问,文章通过一个具体的测试程序示例说明了可能引发的异常,并提供了解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 问题

RISC 下使用访存指令读取或写入数据单元时,目标地址必须是所访问之数据单元字节数的整数倍,这个叫做地址对齐。

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

从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。


比 如在 MIPS 平台上,lh 读取一个半字时,存储器的地址必须是 2 的整数倍; lw 读取一个字时,存储器的地址必须是 4的整数倍; sd 写入一个双字时,存储器的地址必须是 8 的整数倍。倘若访存时,目标地址不对齐,则会引起异常,典型的是系统提示“总线错误”后,直接杀死进程。

看一个测试程序(龙芯2E平台):



#include <stdio.h>
#include 
<sys/sysmips.h>

unsigned 
short data[] = {
  
0x10x20x30x4,
  
0x55aa0x66bb0x77cc0x0000,
};

inline 
void unaligned_access(unsigned short * const row)
{
  asm 
volatile
    (
        
".set mips3 "
        
".set noreorder "

        
//"lwr $10, 1(%1) "
        
//"lwl $11, 4(%1) "
        
//"or $10, $11 "
        "ld $10, 2(%1) "     /* %1 is double word aligned, %1+2 is double word unaligned */
        
"sd $10, %0 "

        
".set reorder "
        
".set mips0 "
        : 
"=m"(*(row))
        : 
"r"(row+4)
        : 
"$8""$9""$10"
    );
}

int main()
{
  printf(
"--------------------------------------------------------- ");
  printf(
" Testing Godson2 unaligned access Instruction  ");
  printf(
"--------------------------------------------------------- ");

// sysmips(MIPS_FIXADE, 0);

  unaligned_access(data);

  printf(
"result is: 0x%04x %04x %04x %04x ", data[3], data[2], data[1], data[0]);

}



程序运行后系统提示“非法指令”后退出。


CISC 下(如x86)访存时,如果目标地址不对齐,CPU 不会陷入异常,因为其内部有处理非对齐访问的微程序。


2. 解决

高级语言中一般不会遇到这种问题,编译器常常会处理好数据类型的对齐。但万一遇到、抑或在汇编里遇到,避不开怎么办?

可以使用 MIPS 的指令集里提供的 lwr/lwl, swr/srl, ldr/ldl, sdr/sdl 指令对非对齐地址进行操作。关于他们的原理可以用下图来简单的示意一下(以ldr/ldl 为例,其他类似):






上图解释的是小端模式下的情况,大端模式的情况则相反:首先 ldl t0, 0(t1),然后再 ldr t0, 7(t1)。

可 以看到无论大端模式还是小端模式,非对齐访问的解决都是将原来的一条指令(对齐访问)完成的事分两步完成,即首先取始地址 addr 到下一个对齐地址处的部分数据,置入寄存器右部(小端),(大端置入左部(高位)),然后取从该对齐地址到 addr + len - 1 处的部分数据(len 为数据单元长度,半字为2, 双字为8),置入寄存器左部(小端)。

如小端机器上,始地址为 t1 = 0x1022,则:

ldr t0, 0(t1) 取 0x1022~0x1027 到 t0 的右部
ldl   t0, 7(t1) 取 0x1028~0x1029 到 t0 的左部

注 意上述指令的后缀 r(right), l(left) 都是相对寄存器而言,load 操作是把取到的部分数据,置入寄存器的 left 或者 right, store 操作是将寄存器中数据的 left 或者 right 部分,写入目标地址而已。无论大端和小端寄存器的格式都是固定的,即右端为低位,左端为高位。任意第一条 ldr/ldl/lwr/lwl/sdr/sdl/swr/swl 只能访问内存的始地址到下一个对齐地址处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值