arm vfp的一些使用心得
近期写了个很奇怪的程序,编译后x86下运行ok的 arm会出现
其中
user faults是2 我的内核是4.14,cpu是imx6q。出现这种情况应该是进入data abort异常后内核没有办法处理了。
程序代码如下:
#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%08f, int2 = 0x%08f, int3 = 0x%08f, int4 = 0x%08f\n",
*((float *)(&intarray[0] )),
*((float *)(&intarray[1] )),
*((float *)(&intarray[2] )),
*((float *)(&intarray[3] )));
return 0;
}
汇编后
发现有vldr和vcvt等vfp协处理指令
对于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)。
因为imx6q是v7的 所以应该是存在什么alignment异常的,至少cpu指令是不应该内核不能解决的。
其中的inf->fn是do_alignment(unsigned long addr, unsigned int fsr, struct pt_regs *regs)函数,进入这个函数发现:
如果是VLDn,VSTn等协处理器指令就会直接进入bad,而不去处理非对齐的异常。
所以得出结论是int,short型的非对齐访问arm v6以上版本cpu是可以处理的,不会产生异常,而v5以下版本的arm,内核中 echo 2 >/proc/cpu/alignment 后是data_abort异常是可以帮助处理非对齐异常的。
short,double类型的非对齐访问,在没有vfp情况下是没有问题的,因为这是的浮点数访问是用其他cpu指令模拟的,如果打开了vfp,并且编译器打开了mfloat-abi=hard会产生vldr指令,
此时非对齐访问是不nice的,会产生非对齐访问,并且内核不能处理,同时会发出SIGBUS信号给进程。
PS:VFP的寄存器在进程context时是如何保存的呢?
进程在切换的时候,内核的函数调用过程是这样的(ARM Arch):
__schedule()->context_switch()->switch_to()->__switch_to()
__switch_to()在Arch/arm/kernel/entry-armv.S中实现,这段代码不长,这里我比较关心的是
...
mov r5, r0
add r4, r2, #TI_CPU_SAVE
ldr r0, =thread_notify_head
mov r1, #THREAD_NOTIFY_SWITCH
bl atomic_notifier_call_chain
mov r0, r5
...
翻译成C就是atomic_notifier_call_chain(thread_notify_head,THREAD_NOTIFY_SWITCH,next->cpu_context);
到我们的VFP代码中去看,
static struct notifier_block vfp_notifier_block = {
.notifier_call = vfp_notifier,
};
vfp_init()
{
...
thread_register_notifier(&vfp_notifier_block);
...
}
很明显进程在切换的时候会执行到vfp_notifier()中去。仔细研究vfp_notifier()的代码,没有发现保存VFP寄存器的代码。倒是这段代码比较可疑:
...
fmxr(FPEXC, fpexc & ~FPEXC_EN);
...
FPEXC的FPEXC_EN是VFP功能的使能位,如果这位未被设置,CPU在执行到VFP指令时会产生“未定义指令”中断。好了,每次一个新进程被切换到CPU的时候FPEXC_EN总是未被设置,此时一个VFP指令就产生了一个“未定义指令”中断。
好了。程序这下跑到__und_usr()->call_fpe()->do_vfp(),这个过程其实还是很复杂的,有兴趣可以仔细阅读代码。
do_vfp:
...
ldr r4, .LCvfp
ldr r11, [r10, #TI_CPU] @ CPU number
add r10, r10, #TI_VFPSTATE @ r10 = workspace
ldr pc, [r4] @ call VFP entry point
...
.LCvfp:
.word vfp_vector
好吗跑到vfp_vector,vfp_vector这个函数指针在vfp_init()里面赋值 vfp_vector = vfp_support_entry;
关键就在vfp_support_entry里面。vfp_support_entry不仅负责保存/恢复进程的VFP寄存器值,还负责处理VFP的异常(例如除0等等)。