[讨论] ucosii V2.92.11版本堆栈8字节对齐

seanwaye   2017-4-13 11:16 楼主
各位朋友好 最近发现个现象,移植V2.92.11版本到M3核的时候已经在任务堆栈初始化函数里面用如下代码让堆栈8字节对齐了: p_stk = ( OS_STK * ) ( ( OS_STK ) ( p_stk ) & 0xFFFFFFF8u ); 但是调用printf函数打印浮点的时候却出错,如果把这个8字节的语句屏蔽掉反而能正常输出浮点数,比较纳闷,printf函数按照介绍应该是需要8字节对齐的,为何我测试的现象正好是相反的呢? 看启动代码里面有align=3,但是我修改这个地方也没看出对于printf函数输出浮点有何影响,不知道CM3的堆栈8字节对齐是如何实现的,为何手动8字节对齐会出错,希望了解细节的朋友可以指点一二。 本帖最后由 seanwaye 于 2017-4-13 11:17 编辑

回复评论 (14)

之前我做一个类似UCOS操作系统的时候也遇到过这个问题,下面是我的小总结,希望对你有帮助。 基于ARM架构的处理器的C语言程序设计遵循ATPCS。ATPCS规定数据栈为FD(满递减Full Decrease)类型,并且对数据栈的操作是8字节对齐的。在我自己的轻量级的嵌入式操作系统tqOS中没有考虑到线程工作栈的8字节对齐的问题,这样从内存池中分配到的栈的起始地址可能是4字节对齐的也可能是8字节对齐的,如果运气好每一个线程的栈式8字节对齐的则不会有什么问题出现,如果运气差线程的栈式4字节对齐的,那么就会导致种种错误......例如,最要命的是在线程函数中进行浮点数运算的时候,两个浮点数初始化之后打印出来都是错误的数据,进行算术运算之后也是错误的结果。因为浮点数double是8字节的,可能非8字节对齐的栈会导致运算出错。为了解决这个问题,我在tqOS的任务创建函数中对任务栈空间的分配中做了调整,将栈的起始地址始终设置为8字节对齐,如果不为8字节对齐则将栈指针下移至8字节对齐处。另外,APTCS要求了对栈的操作必须是8字节对齐的,所以对任务栈的初始化也是有要求的,也就是说一次要入栈两个数据,或者说一次要入栈偶数个数据,因为一次只入栈一个数据(4字节长度)的话就会导致栈的地址变成非8字节对齐了,这是不允许的! 本帖最后由 汤权 于 2017-4-13 11:40 编辑
亚里士缺德
点赞  2017-4-13 11:38
引用: 汤权 发表于 2017-4-13 11:38
之前我做一个类似UCOS操作系统的时候也遇到过这个问题,下面是我的小总结,希望对你有帮助。
        ...

谢谢版主的热心回复,可是我还是比较纳闷为何我设置了8字节对齐打印浮点会出错,不手动设置8字节对齐反而能正常打印浮点数,以下是ucos2.92.11版本里面对堆栈初始化的操作:

OS_STK *OSTaskStkInit ( void ( *task ) ( void *p_arg ), void *p_arg, OS_STK *ptos, INT16U opt )
{
    OS_STK *p_stk;


    ( void ) opt;                                /* 'opt' is not used, prevent warning                 */
    p_stk       = ptos;                            /* Load stack pointer                                 */
    /* Align the stack to 8-bytes.                        */
    p_stk      = ( OS_STK * ) ( ( OS_STK ) ( p_stk ) & 0xFFFFFFF8u );
    /* Registers stacked as if auto-saved on exception    */
    * (p_stk )     = ( INT32U ) 0x01000000uL;      /* xPSR                                               */
    * ( --p_stk )  = ( INT32U ) task;              /* Entry Point                                        */
    * ( --p_stk )  = ( INT32U ) OS_TaskReturn;     /* R14 (LR)                                           */
    * ( --p_stk )  = ( INT32U ) 0x12121212uL;      /* R12                                                */
    * ( --p_stk )  = ( INT32U ) 0x03030303uL;      /* R3                                                 */
    * ( --p_stk )  = ( INT32U ) 0x02020202uL;      /* R2                                                 */
    * ( --p_stk )  = ( INT32U ) 0x01010101uL;      /* R1                                                 */
    * ( --p_stk )  = ( INT32U ) p_arg;             /* R0 : argument                                      */

    /* Remaining registers saved on process stack         */
    * ( --p_stk )  = ( INT32U ) 0x11111111uL;      /* R11                                                */
    * ( --p_stk )  = ( INT32U ) 0x10101010uL;      /* R10                                                */
    * ( --p_stk )  = ( INT32U ) 0x09090909uL;      /* R9                                                 */
    * ( --p_stk )  = ( INT32U ) 0x08080808uL;      /* R8                                                 */
    * ( --p_stk )  = ( INT32U ) 0x07070707uL;      /* R7                                                 */
    * ( --p_stk )  = ( INT32U ) 0x06060606uL;      /* R6                                                 */
    * ( --p_stk )  = ( INT32U ) 0x05050505uL;      /* R5                                                 */
    * ( --p_stk )  = ( INT32U ) 0x04040404uL;      /* R4                                                 */

    return ( p_stk );
}


可以看出,这里面

p_stk      = ( OS_STK * ) ( ( OS_STK ) ( p_stk ) & 0xFFFFFFF8u );

是对堆栈首地址8字节对齐的,但是实际调试打印浮点却出错,如果把这个语句屏蔽了或者改成4字节对齐反而能正常打印浮点数。
点赞  2017-4-13 13:34
引用: 汤权 发表于 2017-4-13 11:38
之前我做一个类似UCOS操作系统的时候也遇到过这个问题,下面是我的小总结,希望对你有帮助。
        ...

是所有的ARM架构的,包括M0、M3、M4内核的MCU也是遵循ATPCS的吗?如果都设置为8字节对齐,那对32位的MCU来说是不是太浪费栈空间?
点赞  2017-4-13 13:54
引用: seanwaye 发表于 2017-4-13 13:34
谢谢版主的热心回复,可是我还是比较纳闷为何我设置了8字节对齐打印浮点会出错,不手动设置8字节对齐反而 ...

这行代码在我的系统里面也有一样功能的代码,的确是要8字节对齐才行,但是不知道为什么你这里8字节对齐就不能打印浮点数,不知道是不是编译器设置上有问题,还是UCOS配置上有问题,我之前也用过UCOS也没遇到过这个问题,UCOS是一个很成熟的系统,应该不会出现这种低级错误,应该不是内核源码的问题,你可以想想其他方向。
亚里士缺德
点赞  2017-4-13 13:58
引用: bobde163 发表于 2017-4-13 13:54
是所有的ARM架构的,包括M0、M3、M4内核的MCU也是遵循ATPCS的吗?如果都设置为8字节对齐,那对32位的MCU ...

是的,它定义了统一的调用标准,可以参见ARM的程序调用标准AAPCS(ARM Application Procedure Call Standard)。
亚里士缺德
点赞  2017-4-13 14:03
引用: 汤权 发表于 2017-4-13 13:58
这行代码在我的系统里面也有一样功能的代码,的确是要8字节对齐才行,但是不知道为什么你这里8字节对齐就 ...

我发现这行代码在v2.86等老版本上并没有这个语句,需要手动用_align(8)关键词去手动规定任务栈的首地址8字节对齐,但是我换到v2.92版本后发现ucos的任务堆栈初始化函数就多了这条语句:

p_stk      = ( OS_STK * ) ( ( OS_STK ) ( p_stk ) & 0xFFFFFFF8u );

然后就出现了帖子说的那些情况。

版主要是感兴趣可以把这个语句加到v2.86版本的任务栈初始化函数里面去,然后打印浮点数(不用_align(8)的情况下),浮点打印是会出错的,把这个语句去了反而能正常打印。
点赞  2017-4-13 14:09
引用: bobde163 发表于 2017-4-13 13:54
是所有的ARM架构的,包括M0、M3、M4内核的MCU也是遵循ATPCS的吗?如果都设置为8字节对齐,那对32位的MCU ...

调用部分标准库函数不8字节会出错,8字节对齐确实浪费内存,个人感觉如果不调用标准库函数的话4字节对齐也是能正常跑的,做浮点运算也不会影响结果,我做过一部分测试,没有8字节对齐的时候浮点数运算结果是正确的,调用标准库函数的时候会出错。
点赞  2017-4-13 14:12
按规定,堆栈必须8字节对齐,也就是进入任务函数的第一条语句时必须8字节对齐,这是OS必须要遵守的,如果你的源码是没修改过的,应该不会出现这个错误,你可以仿真,在汇编窗口找到任务的入口,在此断点,查看此时PSP的值是否8字节对齐

但你的 OSTaskStkInit 函数貌似有问题,p_stk 指针只减了15次,要偶数才对,
* (p_stk )     = ( INT32U ) 0x01000000uL;  这句应该有问题
* (--p_stk )     = ( INT32U ) 0x01000000uL; 这样才对
所以你去掉了对齐语句就刚好8字节对齐了,但不明白这样也能运行,没用过这个OS,
建议你用RTX.如果你不考虑代码会运行在非ARM内核上的话
点赞  2017-4-13 17:12
如果官方代码就是这么写的,那只能呵呵了! 如果OSTaskStkInit函数之后到任务运行前堆栈指针不变,那这段代码就绝对有BUG. p_stk= ( OS_STK * ) ( ( OS_STK ) ( p_stk ) & 0xFFFFFFF8u ); 这句话的意图明显是对其8字节,但函数退出时又变成没有对齐.除非另外有改变指针的地方. ARM堆栈向下生长,* (p_stk)=0x01000000uL虽然也可以,但结果就是非8字节对齐(也不符合CPU入栈步骤,ARM先将堆栈减4再写入内容,出栈时先读内容再加4),如果这样也能运行,那么OSTaskStkInit函数的ptos参数应该是这样求出的(&buff[size]),不然就会破坏其它内存. 如果你细心也许发现启动代码都是将初始MSP设置为内存的结尾,这个值必须先减4才能写入内容,可想而知 正规写法应该是:ptos的初始值为(栈内存起始地址+栈字节大小), 使用* (--p_stk)=0x01000000uL 包行,且必须这样写,否则也会破坏其它内存. 本帖最后由 samos2011 于 2017-4-13 18:47 编辑
点赞  2017-4-13 18:00
引用: samos2011 发表于 2017-4-13 18:00
如果官方代码就是这么写的,那只能呵呵了!
如果OSTaskStkInit函数之后到任务运行前堆栈指针不变,那这段代码 ...

官方确实是这么写的,这个代码确实是堆栈首地址8字节对齐了,入栈后的栈地址不是8字节对齐了,可能这就是bug的地方了,你有兴趣可以去MDK看看他们提供的源码,源码也是这么写的。非常感谢你解决了我的困扰。
点赞  2017-4-13 22:49
引用: seanwaye 发表于 2017-4-13 22:49
官方确实是这么写的,这个代码确实是堆栈首地址8字节对齐了,入栈后的栈地址不是8字节对齐了,可能这就是 ...

其实我们传递给堆栈初始化函数的堆栈首地址是&tastk_stk[size-1],所以应该就是 * (p_stk )     = ( INT32U ) 0x01000000uL;

因为已经减4了,可以直接入栈,我想这个版本的意图应该是让我们传递&tastk_stk[size]给堆栈初始化函数,然后堆栈初始化函数再首地址对齐,然后 * (--p_stk )     = ( INT32U ) 0x01000000uL;      /* xPSR                                               */
点赞  2017-4-13 23:19
引用: seanwaye 发表于 2017-4-13 23:19
其实我们传递给堆栈初始化函数的堆栈首地址是&tastk_stk,所以应该就是 * (p_stk )     = ( INT32U ) 0x01 ...

对的!,真没想到这么有名气的OS也有这种低级BUG
创建任务的函数应该只需要使用者提供栈首地址与栈字节大小即可,剩下的事情与使用者无关!或者仅提供栈大小,OS自动分配
点赞  2017-4-14 08:51
引用: samos2011 发表于 2017-4-14 08:51
对的!,真没想到这么有名气的OS也有这种低级BUG
创建任务的函数应该只需要使用者提供栈首地址与栈字节大 ...

ucosIII是你说的这个方法,ucosII还是直接给堆栈的首地址,应该直接移植ucosIII就好了,头脑发热想尝试下ucosII的最新版。
点赞  2017-4-14 10:37
引用: seanwaye 发表于 2017-4-14 10:37
ucosIII是你说的这个方法,ucosII还是直接给堆栈的首地址,应该直接移植ucosIII就好了,头脑发热想尝试下 ...

应该新版有改进,
要低耦合高内聚才能易于使用
点赞  2017-4-14 11:22
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复