嵌入式 > 技术百科 > 详情

51堆栈的安全(精确)设置

发布时间:2020-09-15 发布时间:
|
几个问题:
1、编译器、连接器把堆栈段定位在IDATA内所有段的最后面,也即内存IDATA高端;
2、中断堆栈被定位在堆栈段内的最后面,即IDATA最顶端;所以堆栈段的安全余量设置,实际上是中断堆栈深度的配置。
3、如果不考虑系统堆栈的安全余量设置,一个没有二级中断嵌套的一级中断堆栈深度应该是13字节。——为什么?
4、系统中断的安全余量配置应该是……字节。——为什么?

下面详细说明:

1、整个51内存256字节。C51首先分配全局静态变量在IDATA低端,接着分配的是项目中所有函数的参数和局部变量共享覆盖区——也属于全局静态变量区(段)。(有没有动态数组区段冷漠不知道也不关心,)然后就是系统堆栈段,其栈底指针?STACK——由C51自动生成,栈顶应该是IDATA顶端——0xFF。系统堆栈深度=0xFF-(?STACK)。
     如果项目中含有汇编模块, 那么系统堆栈?STACK中还包含汇编模块私有堆栈STACK(指针指向)如果项目中包含有reentrant 重入函数 foo,那么系统堆栈中还包含有每个foo所属的私有模拟堆栈simulated  stack;它也是C51为每个reentrant 函数自动分配的。对于interrupt 属性函数,C51为其分配中断函数私有堆栈……还有硬件堆栈hardware  stack 的概念(函数调用CALL或者中断发生时由硬件自动压入PC的堆栈。)
         所以,一个系统堆栈段内分配了很多私有堆栈,所谓私有,应该是操作系统概念,用在这里是为了与系统堆栈?STACK 区分概念,例如,当一个中断事件发生时,它所打断的后台程序 F1 所正在使用的堆栈,是不可能与中断函数堆栈共享的。——2者堆栈都是私有的。
      由于中断函数具有最高优先权,且堆栈独立,所以我说中断函数堆栈必然处在所有其它私有STACK之上,是IDATA最顶端的堆栈;——安全余量必是在中断堆栈之上分配。

……Cx51编译器会(自动——冷漠注释)产生一个?STACK的堆栈段,该段将被自动定位到IDATA空间的顶部。……一般不需要特别指定?STACK 的位置,对于具有几个堆栈的汇编程序才需要采用STACK命令。需要注意的是,重新定位?STACK 段必须非常小心,因为可能会破坏 DATA 或 IDATA 空间的变量而导致程序无**常运行。

   ——摘自《Keil Cx51 V7.0 单片机高级语言编程与uVision2 应用实践》 徐爱钧编著 P640

冷漠同学啊, 引用就引用吧, 干么断章取义,还夹杂私货, 这样不好嘛。

1)中断堆栈最大可能深度是15(2+5+8)字节, 不是 13。原因已经给出, 不再重复。

2)8051 运行时有只有一个统一stack, 不存在 中断堆栈 以及 非中断堆栈, os 或程序人为操纵stack 另当别论。

3)堆栈段定位在IDATA所有段的最后面, 把空白区全部作为stack 这非常自然, 而且 8051 stack grow *UP*, 用脚后跟想想就会明白。c51:

While the 8051 architecture restricts the stack to internal memory, it may be located at any point therein. The stack typically starts following the last individual variable allocation in internal memory and is free to grow *up* through whatever memory remains.

4)私有堆栈是****编译时*****的中间产物, 用于最后连接计算, 连接时linker 统一定位。目标代码*****运行时*******,只有一个stack. 所有的函数调用, 中断断点, 以及中断register保护都在一个 stack 里。这是最基本的****常识***问题!!!! os 的人为分割切换 stack 完全是另一个概念。

请教 冷漠大师:

您所言的“后台堆栈”,实际上是C语言中的软堆栈,负责分配在全局静态变量空间共享覆盖区,其长度为每个独立的后台函数的私有堆栈之和!
您所言的“前台堆栈”,实际是51单片机中真正的硬件堆栈,负责中断响应需要保护的变量,子函数调用等硬性的压栈出栈操作!

C语言的运行是依靠这一软一硬两个堆栈协调工作,您所言的:一高(端)一低(端),一小一大,一前(台)一后(台)。就是指这一软一硬两个堆栈。

不知俺理解的对不对?

提一个问题:什么是堆栈?

如果,堆栈是指硬件堆栈,那个由堆栈指针SP所控制的,那当然只有一个堆栈,没有什么私有的堆栈,包括中断等,都是往这个堆栈上放,

如果自己在函数里(操作系统也不过是一些函数,一些公用的函数集而已)定义一个堆栈,那个堆栈就不好说,那叫软堆栈。

一个处理系统有且只有一个当前堆栈,就是有SP寄存器控制的那个。不管是中断发生还是函数调用,都是用这个SP定位。其余什么私有堆栈不过是程序修改SP而实现的。
堆栈的深度是不宜精确设置的,除非你的程序很简单,或根本就没有中断嵌套,这样你可以很容易计算出系统可能最大堆栈。不然没什么好说的,将必要的内存设置好,其余的统统留给堆栈(反正闲着也是闲着)。

特地说明一点:
“reentrant"的 "simulated stack" 只是为了模拟通过 stack 传递参数(这是大多数c 编译器的做法)以实现重入,实际实现上有点复杂,是Rn寄存器再加上一个“simulated stack" 数据区(具体情况可以看看反汇编)。  这个“simulated stack" 自顶向下, 参数通过 r0, r1或 dptr 存取, 是一个特别数据区, 不是一个真正的 stack,  与真正的硬件自动 stack (通过 sp push/pop) 无关. 

具体没看过编译手册,不过对于这句有些疑问:
其栈底指针?STACK——由C51自动生成,栈顶应该是IDATA顶端——0xFF

51堆栈是向上生长型,刚开始栈顶应该在栈底那里,每次PUSH,往上加,直到最大0XFF.但是你这里说栈顶应该在IDATA的顶端--OXFF,应该不对。

负责分配在全局静态变量空间共享覆盖区,其长度为每个独立的后台函数的私有堆栈之和!

“软堆栈”的提法非常赞同。绝不是一般人所能理解到如此深刻的。其它先不多说,——我还没讲到。但是上面红线部分我有异议:就像后台所有非重入函数的参数传递和局部变量被分配在共享覆盖区一样,其长度应该是占用内存字节数最多的那个函数所占有的区域,其它函数分时共享这个区域。——而不是所有之和。这才是共享、覆盖的操作系统内存管理的方法和意义吧?
    还有,私有堆栈之和可能忽略了一件事,所有私有堆栈都是动态意义的,当这个函数未被调用时,它是不活动的,所以它的私有堆栈也不存在(长度等于零),这也是覆盖与共享的前提吧?
    我表达的不好,感谢老许是真正懂的、真正讨论问题的人。

同意12楼,说的太好了。这里确实不好得出唯一结论;记得ayb_ice详细论证过,堆栈长度开始为什么是1字节。建议去看看他的帖子。我不能确定唯一结论的理由:
1、?STACK是由编译器自动生成的系统堆栈段,——所有含有PUSH / POP指令操作的函数,都在这个系统堆栈段里享有自己的连续堆栈空间(注意是动态的。)?STACK所指向的系统堆栈段被分配在系统所有其它RAM段的最后面。?STACK肯定是始终指向系统堆栈栈底,连接定位之后就不可移动的;

2、“最后面的”后面还能安排有东西么?冷漠认为编译器不可能出尔反尔,所以认为没有了,所以冷漠说栈顶是IDATA的顶。12楼可以说栈顶是移动的,移动上限是IDATA顶端。冷漠坚决同意。
        当然通过设置可以让IDATA的顶不等于0xFF;例如等于0xEE什么的。这是冷漠说话不严格。

?STACK的定位是连接器确定的,和编译器无关。LZ已经举了书上的Keil说明书译文, 连接器是否自动把?STACK定位在IDATA顶端?还是需要人工执行STACK连接命令之后才行?冷漠也有疑问?不太清楚。

S
请你仔细看我的原话,没有理解就不要乱引用
我是说KEIL默认的堆栈长度是1字节,但其实所有没有被编译器使用的IDATA空间都是堆栈,当然中间的间隙不算

对自己在12楼的话解释一下
关于栈顶的理解其实和LZ是一样的,只是说法稍微不同。
堆栈指针SP的范围,最小叫栈底,最大叫栈顶。SP在栈底和栈顶之间活动。所以本例中,栈顶是IDATA顶端0XFF,这么理解没错。

我12楼这么说,我是这么理解的
因为堆栈指针SP的值是栈顶的地址,所以SP活动,那么栈顶也跟着活动的。(SP指向栈顶)
栈顶的范围:最小值即栈底,最大值即 最大栈顶值。

其实和LZ的意思是一样的。

冷漠有另一观点:

C51对用户来说没有SP的概念,你是C51用户你不是编译器作者,要么你说自己只用汇编,那我们讨论不在一个层次,一定注意不要以汇编的概念来理解C编 译器,?STACK 就是编译器生成的堆栈段,而且我认为这个?STACK 以上编译器不可能分配其它数据段。所以从?STACK以上都是堆栈段,即使有很多不用的空间,也不可能被谁占用,编译器没有让谁用。——这不是汇编语言编 程,程序员无法控制SP的。  冷漠才不管它SP在哪。愿意在哪在哪,编译器一定比我做的高明。是吧。小问题,无所谓对错。都不影响使用C51。[page]
    关键还是主题。

哈哈, 冷漠同学高深莫测,意识流的运用堪比大师. 好吧, 为了清晰起见, 我替冷漠同学总结一下:

1) "假定项目中有3个汇编程序模块A.a51,B.a51,C.a51,它们当然每个模块都有自己的私有堆栈"
"BL51 A.OBJ,B.OBJ,C.OBJ  STACK(?DT?A(50H),B(60H),C(70H))
C51下的公有堆栈指针是?STACK ,而每个模块的私有堆栈指针是STACK,一个公有?STACK里包含一个或者多个私有STACK,?STACK指针由编译器确定分配在 idata 内所有段的最后面"

2) "只有PUSH / POP指令才能操作STACK,硬件自动压入的属于不可控的系统控制栈,STACK根本不指向!2字节压入PC根本不影响STACK指针"

3) "C程序中硬件自动压入的PC在私有STACK指向下面就完成了,用户程序根本看不见的。好像称为系统控制栈内容"

4) " 私有堆栈(每个后台函数的私有STACK,和C编译器中的?STACK是两回事)被编译器分配在RAM低端,从全局静态变量区(包括共享覆盖区)后面开始,即初始SP所指向区域,直到?STACK所指向为结束。

?STACK所指向的是前台堆栈,被编译器默认自动分配在所有段(包括所有私有STACK段)的最后面(RAM高端)——栈顶部分!关键的是从这里开始,直到栈顶,才存在溢出危险。——它是一个独立ISR函数(不是多个后台函数)的堆栈"

5)"对于interrupt 属性函数,C51为其分配中断函数私有堆栈……还有硬件堆栈hardware  stack 的概念"

引用结束.
假设:
有A.obj, B.obj, C.obj 三个模块构成的一个8051单片系统, 使用了一个定时中断, 简单起见, 没有使用 reentrant。其中a调用了b中的函数, b调用了c, c调用了a, 期间有中断,而且没有重新设置过sp。  

请问冷漠同学:

1) 按照冷漠同学的解释, 此系统有: 中断函数私有堆栈, abc模块私有堆栈, 还有硬件堆栈hardware  stack. 一共 1+3+1 = 5 个栈   
2) 如果不是 5个栈, 这个系统总共有多少个栈? 请给出明确的数字。
3)每个栈是如何操作的? 请给出说明。


冷漠修正一些错误:

1) "假定项目中有3个汇编程序模块A.a51,B.a51,C.a51,它们当然每个模块都有自己的私有堆栈"
"BL51 A.OBJ,B.OBJ,C.OBJ  STACK(?DT?A(50H),B(60H),C(70H))
C51下的公有堆栈指针是?STACK ,而每个模块的私有堆栈指针是STACK,一个公有?STACK里包含一个或者多个私有STACK,?STACK指针由编译器确定分配在 idata 内所有段的最后面"
——呵呵,这种概念所长还不如老许理解透彻:一会给你抄抄操作系统的书。highgear是精通555时基的,怎 么可能理解这么深刻的机制?所长从裸奔和汇编的概念出发当然不可能理解。不妨跟老许学学:什么叫软堆栈?如若谁再提出个“软中断”,所长该不会大呼小叫 吧。要不要冷漠给你注明哪本书上写的?你不可能比书作者还高明。
2) "只有PUSH / POP指令才能操作STACK,硬件自动压入的属于不可控的系统控制栈,STACK根本不指向!2字节压入PC根本不影响STACK指针"
3) "C程序中硬件自动压入的PC在私有STACK指向下面就完成了,用户程序根本看不见的。好像称为系统控制栈内容"

——呵呵,硬件自动压入堆栈的PC影响的是?STACK,(幸亏冷漠前面没有写问号,也即SP指针。这称为“Hardware  stack”,别混为一谈,装明白人了。要不要冷漠注明摘自那本书?——你不可能比书作者高明 !!


4) " 私有堆栈(每个后台函数的私有STACK,和C编译器中的?STACK是两回事)被编译器分配在RAM低端,从全局静态变量区(包括共享覆盖区)后面开始,即初始SP所指向区域,直到?STACK所指向为结束。

——冷漠的错误,先说对不起了:“即初始SP所指向区 域,”这句话应该是……“从共享覆盖区后面开始,直到?STACK所指向为结束。”这正是老许说的后台软堆栈区,它是人们汇编语言概念上的堆栈么?别以为 一个STACK命令分配区就认为是堆栈了。看书一点联想里都没有。难怪学不会操作系统。要不要我贴张图讲得更清楚一点,文章出处当然就是P640啦,再回 家好好看看这一段。你不可能比书作者还高明。

待续……
TACK所指向的是前台堆栈,被编译器默认自动分配在所有段(包括所有私有STACK段)的最后面(RAM高端)——栈顶部分!关键的是从这里开始,直到栈顶,才存在溢出危险。——它是一个独立ISR函数(不是多个后台函数)的堆栈"

5)"对于interrupt 属性函数,C51为其分配中断函数私有堆栈……还有硬件堆栈hardware  stack 的概念"

引用结束.

哈哈, 冷漠同学概念混乱, 从头到尾含糊其辞。冷漠同学既不敢明确回答有多少个栈, 也不敢***具体详细*** 的解释stack的原作过程。

我先把这里 stack 的具体化一些,  以免误解引申:
*) stack 操作是指影响 8051 sp 的操作, 如函数调用, push/pop.
*) sp 的活动区域为 stack 区。
这样, 计算机软件算法的 stack 就不在此例。(呵呵, 在我面前卖弄 软件stack和软中断, 如同卖弄bios 一样可笑)。

我下面会把 ?stack 和 stack, 什么“私有模块栈“ 讲解清楚。 我不会比书作者还高, 但 keil 会。
书作者的问题是没有讲清楚, 而冷漠的问题是不清楚却胡说八道。

我先给出keil 关于 bl51 stack 的说明, 对照 p640, 认真的看看, 就会知道原委.

http://www.keil.com/support/man/docs/bl51/bl51_stack.htm

■Use of the STACK directive to locate the ?STACK segment is typically not required.
■The STACK directive is typically used with assembly programs that have several stack segments.
■Use extreme caution when using the STACK directive. Improper use may result in a target program that crashes or that corrupts DATA and IDATA variables.

keil 从头到尾没有提出 "私有堆栈" (private stack) 的概念, 冷漠同学硬生生造出了很多的“术语“。

在讲解 "私有堆栈" 来龙去脉前, 还是请冷漠明确的说明有多少个涉及硬件栈指针的stack ? 2个, 3个还是 5个?

“模块私有栈“:
这个术语在徐爱钧编著P640里以及 keil 的网站里都没有, 显然是冷漠杜撰的, 如同"递归可重入“

Bl51 stack 命令参数看似可以设定一个”模块私有栈”, 其实这个stack 命令参数的作用是为了汇编模块,而且是需要重新设置 sp 的汇编模块。 C 程序不需要关心 sp, 更不鼓励用户设置 sp. 只有汇编不得不这么做。汇编不得不自己设置 sp,不得不自己控制push/pop.

假设某个第三方的汇编模块里改写 sp (由于特殊的应用),此人为了通用,没有直接设置 sp 为一个固定值,而是提供了一个sp地址的命名名称, 如?ID?MEASURE,以便可以让最终用户在连接时由 linker 为?ID?MEASURE确定地址 。模块结构大致如下:
        ?ID?MEASURE segment idata
       rseg  ?ID?MEASURE
       ds  1
        
        mov SP, #?ID?MEASURE

Linker 在连接时,可以自动也可以手工用 stack参数设定?ID?MEASURE的具体位置, 并可以保留若干字节(8086 汇编里经常有这种需求,为了不污染原来的stack). 这种 sp 被更改所产生的数据区域, 徐爱钧的书以及 keil 称为栈段(stack segment), 没有称为 "模块私有栈".

因此, 只要模块中没有设置 sp, 那么就不会有stack segment, 也就是冷漠所说的“模块私有栈".

而 ?stack 仅仅是一个命名名称,作用是在startup 初始化sp,即:
   mov sp, #?stack - 1
?stack 的值在连接时确定, 此外,别无特殊意义。当在某个模块里重设sp 后,其后所有的栈活动(call, interrupt, push, pop,ret,reti)都会从新栈点开始。
.
.
.

结论:
a) 如果程序中, 用户没有重新设置 sp, 而且排除另一个概念---计算机软件算法中的栈, 那么
  *) 不存在所谓的“模块私有栈“, 不论有多少模块
  *)只有一个涉及sp 的栈,没有其他的乱七八糟的栈

b) 如果程序员在程序中多次设置 sp, 这些sp的活动数据区被称为 stack segment, 而不是一个独立的“stack"  

 

『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
用于MAX7456随屏显示器SPI