近日调试NuttX时被一个奇怪问题所困扰:NuttX任务初始化完成第一次进入上下文切换模块执行上下文保存时只要执行std %l0, [%o0 + L0_OFFSET]指令时立刻弹出错误死掉,多次重复尝试问题稳定出现,通过codeblocks观察问题发生的寄存器结构如下图所示:
折腾一天也未能解决,到最后只能啃SPARC-V8的手册,功夫不负有心人,仔细查阅STD指令部分的说明后得到一点启示,那就是SPARC-V8架构执行STD双字存储指令必须是8字节对齐: 我可以修改代码将一条std指令一次存储两个寄存器到上下文改为两个st指令每次存储一个寄存器至上下文中来解决该问题,但这将延长上下文切换时间,影响NuttX的性能。此外我的上下文切换是参考的RTEMS的代码呀,RTEMS上下文切换程序明确标注了4字节的对齐,我的上下文切换代码也是设置的4字节对齐,为什么RTEMS没问题呢?(左边是RTEMS上下文切换代码,右边是我的上下文切换代码):
仔细思考终于明白了,并不上下文切换代码8字节对齐,而是 STD指令操作的地址需要8字节对齐,即%o0 + L0_OFFSET的值该是8的整数倍,也就是上下文存储结构得8字节对齐,可是上下文数据结构是C语言写的,咋都找不到RTEMS中上下文C语言有设置8字节对其的语句。反复解读RTEMS的代码,终于有所发现,原来RTEMS是在上下文数据结构中用了一个诡异的技巧:在上下文结构体中申明了一个double型的变量l0_and_l1用于存储%l0寄存器和%l1寄存器!当时对它这天外飞仙之笔百思不得姐,现在终于恍然大悟了: 正是结构体中的double类型变量,使得整个结构体按8字节对齐在内存中分配地址,从而使得上下文切换时%o0的值是8的整数倍,进而%o0 + L0_OFFSET的值也是8的整数倍,std %l0, [%o0 + L0_OFFSET]指令当然也就不会出错了。知道问题原因就好办了,由于GNU C 通过 attribute 来声明 aligned 和 packed 属性,指定一个变量或类型的对齐方式。这两个属性用来告诉编译器:在给变量分配存储空间时,要按指定的地址对齐方式给变量分配地址。如果你想定义一个变量,在内存中以8字节地址对齐,就可以这样定义:int a __attribute__((aligned(8));
通过 aligned 属性,我们可以直接显式指定变量 a 在内存中的地址对齐方式。aligned 有一个参数,表示要按几字节对齐,使用时要注意地址对齐的字节数必须是2的幂次方,否则编译就会出错。我通过attribute讲我的上下文寄存器存储数据组设置为8字节对齐:
编译后再次运行,顺利通过,观察%o0的值已经是0x40019B58,已为8的整数倍,问题顺利解决:
|