堆栈作用的就是用来保存局部变量,从本质上讲也就是将CPU寄存器的值保存到RAM中。在uCOS中,每一个任务都有一个独立的任务堆栈。为了深入理解任务堆栈的作用,不妨分析任务从“出生”到“消亡”的整个过程,具体就是分析任务的建立,运行,挂起几种状态中任务堆栈的变化情况。
现在假设系统运行着一个由用户创建的用以完成打印工作的任务TPrint。TPrint最初通过OSTaskCreate()函数创建,在该函数中与任务堆栈有关的第一段代码是大家比较熟悉的函数OSTaskStkInit(),这个函数是在uCOS移植过程中必须实现的,其作用是“初始化堆栈”,其实就是预先在RAM中的一块区域中把任务将来运行开始时CPU寄存器应处的状态(正确值)准备好,之后,任务第一次被内核调度器调度运行时,将这些准备好的数据(寄存器的值)推到CPU的寄存器中,如果数据设计的合理,CPU便会按照我们预先设计好的思路运行。所以,“初始化堆栈”实际上是做了一个“未雨绸缪”的工作。这个过程中有两点是必须慎重考虑的,一是PC该如何定位,二是CPU的其它寄存器(除PC之外)该怎么处理。先说第一点,因为任务是第一次运行,而任务从本质上将就是一段代码,所以PC指针应该定位到这段代码的第一行处,即所谓的入口地址(Entry Point)处,这个地址由任务指针保存着,所以把该指针值赋给PC即可。第二,这段代码还未被执行过,所以代码中的变量与CPU的其它寄存器一点关系也没有,因此R0-R12,R14可随便给值,或者不赋值也可,让这些寄存器保持原来的值,显然后者更为简单。最后再给CPSR赋值,用户可以根据实际需要使系统运行于系统模式或管理模式。经过入栈和出栈,此时SP指向任务堆栈的最底端(就是已经定义好的任务堆栈数组的最后一个元素)。
之后任务代码开始正式运行,因为CPU的寄存器是有限的,所以在运行时不可避免地要把一些临时变量暂时保存到堆栈中。具体应保存到哪个地址呢,不用担心,SP知道(任务第一次运行时,这个地址就是任务堆栈数组的最后一个元素的地址)。任务堆栈的大小和任务代码中临时变量的数目有关,如果这段代码临变量特别多,堆栈就应设计的大一些。
然后,TPrint任务由于某种原因将要被挂起,所以应把任务的运行现场放到堆栈里保护起来,TPrint任务再次运行时再把这个现场还原,任务就能从上次断点处紧接着运行。那么,这个现场是什么呢?从本质上讲,TPrint任务的运行过程就是CPU在执行一段特定的代码,所以这个现场就是CPU的现场,也就是寄存器的值。这些寄存器的值包含了代码执行时的所有信息,包括当前运行到了这段代码的哪个位置处(由PC值指明)。因此,把CPU的寄存器的值推入堆栈,然后记住栈顶指针的位置(SP由OSTCBCur->OSTCBStkPtr保存),当任务再次将要运行前,从SP指向的地址处依次把先前保存的CPU寄存器的值放到CPU的寄存器中,任务就可以从上次中断的地方准确无误地执行。这个过程就像突然把任务冻结了,与任务有关的任何东西都不能动了,一段时间之后又把任务解冻,与它有关的东西又变得可用,于是任务又可以活蹦乱跳地跑起来了。
从以上分析可以看出,任务堆栈至始至终伴随着任务,与之生死与共,它的作用可以概括为两点:第一,当任务运行时,它用来保存一些局部变量;第二,当任务挂起时,它负责保存任务的运行现场,也就是CPU寄存器的值。有些朋友正是忽视了第一点,产生了“任务堆栈大小应是固定值的疑问”。我感觉,这可能与对函数OSTaskStkInit()的理解有关,我们都称之为堆栈初始化函数,但此处的“初始化”与我们理解的初始化不太一样,平时讲的(变量的)初始化似乎指的是将变量的所有成员都一一初始化。而此处的堆栈的初始化仅仅是初始化了很大一个堆栈的一小部分,因为当前只有这部分是有用的,而剩余的大部分用不到,所以不用初始化,就像有些变量不用初始化一样(有默认值或随机值)。更深入一点考虑,当任务挂起时,任务堆栈中保存任务挂起前CPU寄存器的这一连续的区域肯定在整个堆栈的最上面;当任务重新开始运行时,SP弹出寄存器的值,这段区域变成空白的区域。而且,任务每次挂起前用来保存当前CPU寄存器这一连续区域在整个任务堆栈空间中是浮动的。
讨论一下,如果堆栈初始化太小了,程序占用超过初始化值时会发生什么 样的情况呢
堆栈相对于来说每一个任务来说就像是他的一个仓库,而任务运行的关键因素都保存在这个仓库中。每一个任务都有这样一个仓库。每一次任务决定放弃CPU时都会把自己的重要信息先保存到自己的仓库中。以便于下次再次获得CPU使用权时使用。如果堆栈较小,也就是仓库容量不足,就会出现两种可能,一个是自己重要的信息无法全部保存,另外一种情况下占用了别的任务的仓库。第一种情况会造成任务再次运行时得不到全部的资源而无法正常运行。第二种情况会修改别的任务的重要信息而造成别的任务无法正常运行。(在这里解释一下,由于UCOS不想Linux一样使用虚拟地址,UCOS使用的是实地址模式,无法进行地址保护)当任务无法正常运行时就会造成任务之间的相互干扰。最终导致系统的崩溃。所以堆栈的分配就至关重要。这就好比你当老板结果给员工的待遇却很低又想让你的员工好好干活。结果会怎样。轻则员工罢工,重则扰乱军心公司倒闭。呵呵!
在使用系统时,任务本身的程序没有问题而当运行到某一点占用堆栈很大时发生了地址异常,那么可能是你的任务堆栈溢出。
关键字:STM ucos-ii 堆栈
引用地址:
STM之ucos-ii堆栈
推荐阅读最新更新时间:2024-11-08 18:11
STM32F4之USART【库函数操作】
STM32F407xx内嵌四个通用同步/异步接收器(USART1,USART2,USART3 和USART6)和两个通用异步收发器(UART4和UART5)。这6个接口提供异步通信的IrDASIR ENDEC支持,多机通信模式,单线半双工通信模式LIN主/从功能。 USART1和USART6接口能够速度高达10.5 Mbit / s的通信其他可用的接口通信高达5.25bit/s。USART1,USART2,USART3和USART6还提供硬件管理的CTS,RTS信号,智能卡的模式(ISO7816兼容)和类似的SPI通信能力。所有接口都可以通过DMA控制器。 这里只使用了两根线的最简单串口设置。 硬件环境:STM32
[单片机]
stm32的唯一ID编码
产品唯一的身份标识非常适合: ● 用来作为序列号(例如USB字符序列号或者其他的终端应用) ● 用来作为密码,在编写闪存时,将此唯一标识与软件加解密算法结合使用,提高代码在闪存存储器内的安全性。 ● 用来激活带安全机制的自举过程 96位的产品唯一身份标识所提供的参考号码对任意一个STM32微控制器,在任何情况下都是唯一的。用户在何种情况下,都不能修改这个身份标识。 这个96位的产品唯一身份标识,按照用户不同的用法,可以以字节(8位)为单位读取,也可以以半字(16位)或者全字(32位)读取。 基地址:0x1FFF F7E8 每个CPU 出厂的时候都 配置的一个ID,96 位的.这个唯一码可以利用作软件加密....... stati
[单片机]
STM32 AD双通道DMA模式
//通过脚PA1,PA2采集AD。每路AD采集10次。 view plain copy print ? #include ad_driver.h //全局变量 //AD采样存放空间 __IO uint16_t ADCConvertedValue ; //函数 //初始化AD void init_ad(void) { ADC_InitTypeDef ADC_InitStructure; DMA_InitTypeDef DMA_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; //---------
[单片机]
stm32f7 HAL库 串口重定向 使用printf
首先将串口初始化,然后添加重定向代码 下面使用的是串口三,因此以串口三为例: UART_HandleTypeDef huart3; #ifdef __GNUC__ /* With GCC/RAISONANCE, small printf (option LD Linker- Libraries- Small printf set to 'Yes') calls __io_putchar() */ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int
[单片机]
使用HAL库对STM32F030系列芯片做RTC低功耗唤醒
该例程采用STM32F030C8T6的开发板,例程中5s低功耗和5s单片机正常交替工作。首先在CumeMX软件界面中配置RTC、中断、时钟等(具体请参考例程)。在调试过程中我也是参考了该大神的例程,大家也可以参考一下,https://blog.csdn.net/xiaoeleis/article/details/72529090。最重要的一点要提醒一下初始化过程中MX_GPIO_Init函数中要将所有端口都初始化,功耗还是很大在几个mA附近,没办法达到uA级别(如下图编写)。三种低功耗模式都可以,正常情况下建议使用stop模式,如果 三种模式不清楚,可以百度自行搜索一下,纸上得来终觉浅,绝知此事要躬行!!!
[单片机]
玩转STM32(15)确定栈的位置和大小
前面学习了怎么样确定CPU加载运行第一行代码,在那里发现需要加载栈指针,那么你也许会问为什么要首先加载栈指针呢?难道栈就是这么重要?在这里,我们就来探讨一下栈的问题,比如栈的位置和大小。 在现代的CPU技术里,往往有中断系统,这就决定了CPU必须有栈的结构,因为中断出现时,需要把当时CPU运行的数据进行保存,以便中断处理之后再恢复回来。如下图这样处理: 如果没有栈,就没有办法保存当前的数据,必然被中断程序里运行的代码把当前的数据修改了,这样就没有办法恢复到原始状态了。从上图可以看到中断调用时,有中断栈,因此在CPU运行之后,时刻有可能被中断,这样需要栈来保存相应的数据。另外,我们来看一下C语言的运行,当一个函数被调用时,它
[单片机]
STM32有几个时钟源 STM32系统时钟专题讲解
在数字电路中时钟是整个电路的心脏,电路的的一举一动都是根据时钟节拍下进行的,随着信息量逐渐提高,对硬件信息处理能力提出了更大的需求,时钟作为数字硬件的关键成员,其性能需要我们关注,尤其在高速电路设计中对模拟转换芯片对时钟性能有很高的需求,因此正确选择时钟是很关键的一步,前提是我们要了解时钟的关键参数咯。在数字电路中最常见的时钟元件有晶振和锁相环、时钟缓冲器等,本节对系统时钟进行重点讲解。 STM32 系统时钟专题讲解 时钟对于整个硬件系统来说是十分重要的,每一个外设包括CPU,如果没有外部时钟的驱动就无法工作,时钟就相当于硬件的脉搏,在时钟驱动下完成指令执行。CPU和外设工作的快慢和工作效率常用时钟周期,主频来进行评定。为了让
[单片机]
stm32用什么语言编程 STM32单片机原理
STM32可以使用多种语言进行编程,包括: 1. C语言:C语言是最常用的STM32编程语言。STMicroelectronics提供了专门的C编译器和开发工具链,使开发者可以使用C语言进行STM32的软件开发。 2. C++语言:除了C语言,STM32也可以使用C++语言进行编程。C++是C的扩展,提供了面向对象的编程能力,可以使STM32的软件更加模块化和可重用。 3. 基于HAL库的C语言:STMicroelectronics还提供了一套称为HAL(Hardware Abstraction Layer)的库,可以方便地进行硬件抽象和驱动开发。使用HAL库,开发者可以使用C语言编写高层次的代码,而不需要直接操作寄存器。 4.
[单片机]