BAD_BOY 发表于 2011-8-15 10:10:04

[交流]浅析AVR内部SRAM,望傻孩子多多指点我这个坏小子!!!

首先本人简单自我介绍一下,我不是什么高手,仍然还是菜鸟一只,而且用户也是刚刚注_册的,发表本帖,主要是以一个学习探索者身份来写的,帮助那些和我一样的菜鸟,高手们就多多指导了!!!看到大家都这么慷慨解囊,所以最终下定决心写下帖子-(郁闷的是,前天写这帖子一半是电脑突然死机,不得不重头再来)。
先回顾下傻孩子[古董贴][交流]ICC动态内存分配ABC http://www.ourdev.cn/bbs/bbs_content.jsp?bbs_sn=637781&bbs_page_no=1&bbs_id=1038 里面提到了如需使用void *realloc(void *ptr, size_t size),void *malloc(size_t size) 动态内存函数前必须先要用extern char _bss_end;
NewHeap(&_bss_end+1, &_bss_end + 201); // 200 bytes heap,开辟一段堆,否则返回的全为空指针(正是这绝世好贴让我认识了傻孩子)。于是疑问马上就来了,为什么一定要先用NewHeap(&_bss_end+1, &_bss_end + 201)呢,而变量 _bss_end又表示什么呢,ICCAVR在动态内存分配时是如何操作的呢?至少我当时就是这样想的,这里随便废话一句,作为一只菜鸟,要发挥拿来主义精神,但更重要的在于消化。于是马上打开ICCAVR帮助菜单一栏,顶住头皮把E问看完,终于有点点思路了,于是乎自己在编写几个简单的程序一仿真,大概问题就解决了,现在正是分享给大家。
   首先从SRAM内部结构开始!在没有使用NewHeap(&_bss_end+1, &_bss_end + 201) 函数前,内部的SRAM分为三个部分,如下图所示:
http://cache.amobbs.com/bbs_upload782111/files_44/ourdev_667736YKEKOD.jpg
(原文件名:001.jpg)
硬堆栈,软堆栈,数据存储区(又分初始化的全局变量,静态变量,和字符串,未初始化的全局变量,静态变量,默认为0),是没有堆区的,而我们的动态内存分配函数申请分配空间都是在堆区-注意堆和栈是用区别的,至于有何区别,大家百度一下吧!这里不作详解。而我们调用void _NewHeap(void *start, void *end)后,内部SRAM结构重新分配如下图所示:
http://cache.amobbs.com/bbs_upload782111/files_44/ourdev_667737SAH7E3.jpg
(原文件名:未命名.jpg)
这时在软堆栈和数据存储区就插入了一个堆区。那各部分的大小是如何分配的呢?首先硬堆栈位于SRAM顶部,区域从顶部第一个字节到硬堆栈的警界线的上一个字节-警界线这个词语大家先可以莫关他,明白这是指一个范围就好了,后面会有详细说明。属于向下生长型,一般默认为16,硬件堆栈是可以设定的,iccavr中可以设定。一般的应用没有多重嵌套,没有使用浮点数据类型可以使用默认的0x10就可以了,使用浮点和多重嵌套一般设为0x30,再向上到0x40一般足够,设置如下 project->options->Target里的Return Stack Size.
那软件堆栈怎么分配呢?不是说硬堆栈和软堆栈都可能会溢出吗?那软堆栈怎么设定呢?于是乎马上百度,但这次没那么幸运,好像没有那个帖子或文章提到软堆栈大小分配?带着怀疑和猜想只能自己编写一段代码测试,具体怎么测试先就不说了,直接给出答案!原来软堆栈的大小是系统首先根据全局变量,静态变量和字符串的大小自动分配一定大小的区域,而这区域的边界线就是以_bss_end变量为上界,其实_bss_end也是一个警界线变量,后面再慢慢分析,再没有调用void _NewHeap(void *start, void *end) 前,在_bss_end的上一个字节到硬件堆栈的警界线的下一个字节都属于软堆栈。而调用void _NewHeap(void *start, void *end) ,堆区的大小是由堆分配函数决定,大小=end-start,注意end,start都是表示地址。空间是从_bss_end的后一个字节开始,同时软堆栈大小减小end-start,范围缩小到从紧接堆区的上一个字节到硬堆栈警界线的下一个字节。
   上面不是提到了警界线吗?那警界线究竟有何作用?下节继续!!!我先吃个早点!!!

BAD_BOY 发表于 2011-8-15 10:54:53

先了解一下所谓的硬堆栈和软堆栈。ICCAVR 使用两个堆栈:一个用于子程序调用和中断操作的硬件堆栈,一个用于传递参数、临时变量和局部变量的软件堆栈。他们都属于向

下生长型,如果函数的调用层次太深,有可能会发生硬件堆栈溢出到软件堆栈中,改变了软件堆栈中数据的内容,同样,当定义了太多的局部变量或一个局部集合变量太多也有可

能出现软件堆栈溢出到数据区或者堆区(如果使用了void _NewHeap(void *start, void *end)分配了堆区的话,就是软堆栈生长到了堆区,注意堆是

向上生长,但是堆区在向上的过程中是不会超过堆区申请分配的空间大小,至少我在AVRSTDIO里仿真是这样,如果超过了堆的容限,将返回空指针),两个堆栈都有可能溢出,如果堆栈溢出,会引起不可预测的错误。那我们怎么才知道是否溢出了呢?这时我们就得调用堆栈检查函数,大家先看看堆栈检查函数源代码吧!
void _StackCheck(void)
        {
        extern char *_HWStackBottom(void);    //声明一个返回指向char型指针的函数,该函数的功能就是返回硬堆栈底部的地址
        extern void _StackOverflowed(char);   //声明一个根据堆栈溢出类型-0表示软堆栈溢出,1表示硬堆栈溢出,的处理函数

        extern char _bss_end;               //声明_bss_end变量,该变量就是警界字节

        char *hwstk = _HWStackBottom();      

        if (hwstk != 0xAA)                //检验硬堆栈是否溢出
                _StackOverflowed(1);
        if (_bss_end != 0xAA)
                _StackOverflowed(0);         //检验软堆栈是否溢出
        }
我在后面加上注释后大家应该很快明白了吗!其实就是在启动代码中在硬件堆栈和软件堆栈的最低字节分别写进一个代码(0xaa),把这个代码称为警戒线。如果硬件堆栈和软件堆栈如果溢出过,则警戒字节的代码(0xaa)就会被改变,堆栈检查函数就是通过检查这两个堆栈的最低字节的代码是否被改变来判断两个堆栈是否溢出。有关这部分的代码如下
; init.s
;
; to be included by the crt*.s files
;
; initialize stacks
;
; set the hardware stack to ram_end, and the software stack some
; bytes below that
ldi R28,<ram_end
ldi R29,>ram_end
out $3D,R28
out $3E,R29
subi R28,<hwstk_size
sbci R29,>hwstk_size

ldi R16,0xAA ; sentenial
std Y+0,R16 ; put sentenial at bottom of HW stack

clr R0
ldi R30,<__bss_start
ldi R31,>__bss_start
ldi R17,>__bss_end
_bss_end 其实就是一个在软堆栈和数据区的一个警界字节或者称之为边界标记字节。
在使用堆栈检查函数时应注意以下几点:

1、在使用堆栈检查函数时,前必须用#i nclude "macros.h"预处理。

2、如果使用自己的启动文件,在ICCAVR6.20以后的版中,如果使用的启动文件中没有警戒线的内容,ICCAVR也会自动添加警戒线。而在ICCAVR6.20以前的版本中,必须自己添加该部分内容,否则生成的代码中堆栈分配将不带警戒线。

3、如果使用动态内存分配,必须跳过警戒线字节_bss_end来分配您的堆(即增加一个字节)。

4、当_StackCheck(void)函数检测到警戒线字节被改变,则会调用一个默认的_StackOverflowed 函数来跳转到程序存储器0的位置(复位向量地址)。可以指定或重新编写一个新的函数来代替它,例如可以用新函数来指示是哪个堆栈溢出等,但这个函数也不可能执行太多的功能或让程序恢复到正常状态。因为堆栈溢出后,会更改掉一些有用的数据,引起不可预测的错误,甚至使程序死机。

似乎将到这里基本都将完了,但是其实好戏还正开始,接下来也正是我写这贴子的重中之重,也望各位高人指点。

BAD_BOY 发表于 2011-8-15 12:09:54

大家请注意前面提到的堆栈检验函数使用第三点-(其实这就是ICCAVR帮助栏中的E文翻译),根据第三点我们理解NewHeap(&_bss_end+1, &_bss_end + 201)函数为何要用&_bss_end+1,而不直接用&_bss_end,就很容易了但似乎在分配堆空间以后这警界字节不起作用了,因为堆是向上生长,数据区分配后是不会生长的,那就是软堆栈还没有生长到数据区之前,即改变警界字节之前,已经于堆区重叠了,这当然也是不允许的,可能也会产生未知的错误,这时我们用堆栈检验函数似乎也检查不出来,当然如果所分配的堆不起作用,那就关系不大,同时在堆区与数据区的警界字节就可以检验软堆栈是否生长到数据区了。这貌似是ICCAVR也没有解决这个问题,应该算作它的一个BUG吧!至少我用软件仿真时,阅读了所用的E文帮助文档,都没有提出解决问题的方法。因为堆栈检验函数检验软堆栈溢出时,是判断_bss_end的值是否改变,即只能检验软堆栈是否生长到数据区,那我们怎么办?其实解决这问题的原理很简单,我们申请分配堆区以后,仿造它在堆区于软堆栈间再加一个警界字节呗!但是我们注意了,堆栈检验函数是仅仅通过检查_bss_end变量的值来判断,那我们必须将_bss_end变量的地址移到堆区和软堆栈之间,(因为堆栈检验函数已经封装成库文件了,如果想通过该变堆栈检验函数,那还得重新改变库文件,这是很麻烦的,而我这种菜鸟是不能及的),如果我们想还继续使用库里面的堆栈检验函数,就只能把_bss_end变量地址重新定义到软堆栈和堆区之间了,通过C语言目前我还没找一个好的方法,不怕大家见笑,我是机械电子专业,软件是我的弱项,在线汇编貌似我也没有成功,所以望各位高人指点,同时各位大虾有什么好的方法不防提出来大家一起分享。还有一种方法就是自己重新写一个堆栈检查函数,小弟我就是用这种方法。肚子饿了,吃饭去了,公司的人都下班了咯!!!

Gorgon_Meducer 发表于 2011-8-17 11:41:02

很精彩!赞一个!

同时推荐你研究一下IAR的存储器管理方式以及GCC的存储器管理方式。

Helloeveryon 发表于 2011-10-15 18:49:36

MARK

maimaige 发表于 2012-5-20 17:18:03

mark 一下,有空研究一下
页: [1]
查看完整版本: [交流]浅析AVR内部SRAM,望傻孩子多多指点我这个坏小子!!!