当前课程知识点:智能车制作:嵌入式系统 >  第三章 MCU基础 >  3.2.1 堆栈的概念 >  Video

返回《智能车制作:嵌入式系统》慕课在线视频课程列表

Video在线视频

Video

下一节:Video

返回《智能车制作:嵌入式系统》慕课在线视频列表

Video课程教案、知识点、字幕

各位同学大家好

我是清华大学工程物理系的曾鸣老师

欢迎大家继续回到我们

ARM微控制器与嵌入式系统的学习课堂

那么我们现在进入

第三章的第二个单元的学习

在第一个单元

我们初步认识了

ARM的微控制器里头CPU运行的机制

知道了这个程序是怎么运行的

在一个存储有地址的空间这种概念里头

我们在PC指针寄存器

或者叫做程序计数器寄存器的指引下

根据地址一条条的指令取进来

放到我们CPU里运行

那么我们CPU在这些指令的解析下

从我们的存储器

或者从我们当前的CPU内部的寄存器

拿到的数交给逻辑运算单元

进行一步一步的运算

那么指令告诉了这些数 从哪来

以及这些数拿进来了

分别应该做什么运算

我们称为指令的解析和数据的流向

那么上一个单元

最后遗留了一个问题是什么呢

就是我们的PC程序计数器

或者叫PC指针寄存器

它所指引的程序的运行的流程

可能很多时候不是简单的

逐一递增这样一种自动的操作

很多时候它会出现

具有一定偏移量的跳转比如If Else

但是我们刚才提到的最后一个遗留问题是

当我们发生类似于函数调用

这样的使用的时候

在一个程序执行的流程当中

调用了一个子函数

完成一个特定的功能

那么我们以怎样的机制

能够让计算机

让CPU自动的保存一个返回的地址

知道这个CPU能够回到哪去

那么很多同学会说

如果是我来设计这个计算机

我来设计这个CPU

那很简单啊

你不是有一个寄存器

保存这个正在算的数吗

你不是有一种寄存器

保存这个程序执行到哪个地址

下一条指令是哪个地址了吗

那在CPU设计的时候

再多设计几个寄存器不就完了

比如设计一个寄存器

专门叫做返回地址寄存器

那么这个时候我当发生函数调用的时候

我发生调用之前

要回的那个地址我在CPU里头存一下

那么一旦这个函数执行完

我用一条指令说函数要返回

我就读一下这个寄存器

把这个值赋值还给PC寄存器

我程序不就回去了嘛

PC指针寄存器接着刚才程序往下走

那么这些同学是聪明的

在早期的CPU的设计里

一开始就是这么设计的

有一个叫做返回地址寄存器

之后会发现不够用了

为什么呢

因为我们的程序

有可能会发生函数的嵌套调用对不对

我们经常会在写程序

包括我们自己写程序里头

我们会写一个函数

它在里头调用了一个函数

由这个函数再调用一个子函数

那么当我们出现了函数嵌套调用的时候

我前一次调用的返回地址不能丢

我后一次调用的函数地址还得存

那是不是这个能存几次返回地址

就决定了我能嵌套多少个函数调用啊

那么这对于C语言是非常不舒服的

因为C语言它是一个非常standard的语言

我们可能会使用它的库函数

你无法知道库函数里有没有发生函数调用

所以这样一种机制在1974年我们上次讲计算机

八卦计算机史的时候

4004 8008这样的CPU里头

它从最初的片内

有一个返回地址寄存器

到有两个 三个 四个

这样在逐渐的增加

增加到最后

大家觉得那不能没完没了的加下去啊

我们一定要想一种机制

让它变的更加的聪明和更加的自动

那么这种机制是什么呢

就是我这一个单元我们要讲的栈的概念

栈在英文里就是stack

但是用我们中国人的语言

非常不喜欢用一个单字来表达词汇

这是近代汉语的一个特点

所以我们往往会在

计算机交流的时候会说堆栈 堆栈

绝大多数我们专业

或者准专业人士说堆栈的时候

心里想的可能就只是栈

因为堆是堆 栈是栈

我们待会会讲堆是heap

栈是stack

那么栈究竟是一个什么样的东西呢

它就是为了应对

我们刚才所说的这个问题的一种机制

首先看看栈

堆栈 这个栈是一段连续的存储空间

那么我们可以把它就想成一个内存

之前讲过这个概念有很多格子

每个都有个地址

里头会存数

但是栈约定了对这段存储空间

我们按一种特殊的方法来加以使用

为什么呢

我们对它采用后入先出的方式来进行工作

也就是Last In First Out

那么也就是说

我们因为后入先出更形象的一个比喻

就是我们把它看成一个堆叠的存储器

它是一段可以使用的存储空间

有很多很多格子

这个时候

我们不再按地址直接对它进行访问

而是只能从上面顶部

来放一个数据或者拿走一个数据

这件事情大家的感觉如果还不够鲜明的话

对于初学同学我给你们一个更形象的比喻

就是有一个口很小的桶

里头有很多饼干

然后有一只小猴子来吃饼干

它每次只能把手伸进去拿最上面那个饼干

假如这个小猴子很善于储蓄

有了富余的饼干

它也只能拿放进去

放最上面那个位置

所以这样一种筒状的这种后入先出的结构

就决定了你每次拿到的

总是最后放进去的东西

那么这个东西在我们栈的概念里头就是数据

那么大家想想这样一种结构

这样一种方式使用内存

为什么我们要设计这样一种机制

或者叫我们的前辈

为什么要设置这样一种机制

它一定有它的特点

它的特点是什么

这样一种约定的使用方式的特点

就是它能够保持

我们数据进入这个存储器的顺序

而不用额外的方式

来加以保存这种顺序

大家理解这件事吗

如果我们要存储一个数据

并且把这个数据的顺序加以记录

我们是不是至少要两列数据啊

一列是序号

一列是数据的值

而如果我们对于这个数据

约定了以这样一种方式进行存取

其实我们不需要额外的空间

来存储那个序号

他们摆放的位置

他们的地址顺序本身就是顺序

就是数据进入存储器的顺序

所以这样一种结构

导致我们对于存储器的使用

只有两种基本的操作

不再像原来那样

根据那个地址就直接加以访问

我们只有两种操作

一种称为推入

就是push

把一个数据放到了这个栈的顶部

还有一种呢叫做取出

或者我们叫弹出 pull

把一个数据弹出来拿走

只有这两种操作

那么这两种操作的结果就是

我们对于这样一种存储空间

我们会依次把数据放上去

而我们要拿走的时候

一定是倒序把它拿走

当然了这里要补充一句

我们刚才举的例子

都是这个存储器是自下而上的使用

所谓的顶部就是指

这个最后放进数据的位置

少部分CPU是反过来的

或者是可以配置的

那我们在这里不展开讲

那么有了这样一种机制

我们想想它可以用来做什么

最最简单的一个用途

就是我们刚才所讲的

在程序之间的跳转 调用的时候

它可以用来保存

我们所说的嵌套调用时候的返回地址

比如说我们函数发生了一次调用

我约定好了一块存储空间可以这样使用

是不是我把当前PC指针的地址

放在这个存储空间里

然后我们这个函数

如果只有一级调用

调用完了我是不是到

这存储空间取一个值出来

一定是我刚才放进去的

那么我赋回给我的PC指针寄存器

我的程序自动就指向了

下一条指令 继续往下执行

大家注意计算机并没有那么神秘

只是在PC指针寄存器

所指向地址去取指令来顺序执行

只要程序写的正确

我们的CPU就能完成我们要的功能

那么如果我们的函数发生了嵌套调用呢

或者说我们的子程序发生了嵌套调用呢

是不是在第一次调用

第一级的函数的时候保存了

一个地址在这个堆栈里

在这个栈里

在这个函数没有返回的时候

这个返回地址还在这个栈里

又发生一次调用

于是把当前第二次调用时候的PC指针的值

再放到栈里压在了它的上头

然后我们第二级调用的函数返回的时候

要返回值是不是只用

直接从这个栈的顶部

把最后一次放上去值取出来

就是第二次调用之前的返回值

然后第二级的函数返回以后

第一级函数执行完再返回的时候

我再从栈取一个

是不是它保证了函数是顺次调用的

顺次保存返回地址 函数退出的时候

是按顺序逆序返回的

自动的逆序取回返回值

后入先出非常完美的契合在了一起

那么实际上

在我们的汇编语言和C语言里头

栈并不仅仅只发挥这样一个作用

我们很多的时候

大家如果深入的学习C语言的原理会知道

我们的C语言的函数的参数

和函数的返回值的传递是使用栈的

然后我们C语言在一个函数内部的

局部变量也会根据编译器帮你在栈上使用

占用一到两个存储空间来给局部变量用

那么这件事情因为涉及到

C语言很多内部的东西

我们不展开在这门课程里

但是大家会更加隐性深入的理解到一件事是什么呢

一个是这个堆栈会根据

我们C语言程序的执行来慢慢的加以使用消耗对吧

第二呢

就是说为什么我们C语言

说局部变量是有生命周期的

当一个函数返回的时候

这个变量就不再能够使用

因为函数一旦返回

这些存储空间就加以释放弹出就不再使用了

日后我们还会学习对于计算机的硬件

对于CPU的硬件

如果我们要使用一个难点 中断的功能的时候

栈是一个标准的

用来存储上下文关系的寄存器值的一个存储区域

那么栈有很多很多用处

那么讲清了这个概念以后

大家就会慢慢有一种感觉是什么呢

就是刚才讲的那个CPU那个构架是不完整的

我们还缺一个东西

我们缺一段片外的存储器

我们可以说是内存空间

用来给堆栈用

那么我们需要有一个

类似于指针的东西

指向这个存储空间的某一个地址

从这个地址开始把这个内存当堆栈用

刚才那个机制有个非常大的好处就是

我不需要存储很多地址

我只要存储一个地址

每次从这个地址上加1的往上放数据

或者每次从这个地址上减1的往外取数据

就可以完成一个堆栈的功能

那么我们需要一个CPU内部的寄存器

就是一个新的东西

我们称为堆栈指针寄存器

Stack Pointer

来指向我们内存用于堆栈

这样一种方式使用的内存空间的栈底

或者还没有使用的地址

所以呢 有了这么一个概念

我们对于堆栈的使用一定记住一件事情

就是如果你自己要用堆栈进行操作

用汇编语言编程的时候

你Once push must pull

就是你一旦放了东西你一定记得拿走

然后Last in First Out

你最后放进去的东西

一定是你待会取的时候第一个拿到的东西

一定不要把这个次序弄错

那么所幸如果我们学习的是C语言的时候

我们对于堆栈的绝大多数使用

大家是感觉不到的

很多同学都学过C语言

你其实从来没有接触过堆栈

那么为什么呢

因为C语言的函数调用

函数的返回值

参数的传递

局部变量的开销

都是由编译器帮你隐性的使用了堆栈

所以没有堆栈就没有办法使用C语言

那我们在进行开发日后会学习的时候

我们的开发工具

会帮我们写出了代码初始化堆栈

我们的C语言编译器

会帮我们隐性的使用堆栈

那么直到有朝一日大家用汇编

要做中断的编程的时候

你们会接触到这个概念

那么学了这个概念

我们就对CPU的基本构架

有了一个更加完整的认识

那么这个完整的认识呢

就是现在这张图

我们在一个完整的这个CPU构架里

有一个逻辑运算单元可以负责运算

它什么都不管 它只管算

但是它的数可以从寄存器来

也可以从片外的存储器来

那么它数从哪来

做什么运算

是由我们指令解析产生的

逻辑控制单元来控制它有序的产生

而我们的逻辑控制单元的指令

又是在PC指针寄存器的指引下

从程序的那个地址空间一条一条指令

一个一个0和1的序列

拿进来解析控制数据的流向和数据的来源

以及要做的运算的内容

那么这些运算的过程当中

如果发生PC指针寄存器不是逐一累加的时候

我们需要在堆栈指针寄存器的指引下

把当前PC指针寄存器的值

我们要返回的值想办法存到一个特定的内存空间

用堆栈 用压栈 出栈的方式

来使用这段内存

有序的保存这个值来应付程序之间的函数调用

它们的嵌套调用以及跳转

那么有了这个完整的概念

我们多讲一个高阶的对于堆栈的概念

这就是大家会产生一种非常清晰的对于堆栈的思路

它像一个什么呢

我们刚才打了个比喻

像一个筒子里有很多饼干

小猴子拿饼干

这个小猴子如果嘴巴很馋它会不停的拿饼干

拿到没有为止

如果这个小猴子是一个像松鼠一样的动物

它很喜欢存储

它会把饼干不停的往里攒 越攒越高

什么时候冬天来了肚子饿了

拿一块拿一块它往下消耗

那么我们可以想象

这个内存的使用是一个什么东西啊

有点像那个开水瓶里的水你倒走了水位下降了

你填进去水位上升了

我们叫做这个是floating的

它是涨落的

那么这样一个过程当中意味着

对于内存的开销在动态的变化

它是以一个运行时的动态变化

也就是说它是在你的程序运行的过程当中

根据你程序所撰写的

那个方式 函数的调用的形式在动态的使用

那我们会想一件事情是什么

假设我们的内存终究还是有限的

我们假设把堆栈从内存的最底部

能够使用的最下面开始用

它随着使用会慢慢的往上涨

虽然它会动态的涨落

它总会有一个最大值的时候

那么我们另外还有一个非常重要的概念是什么

就是刚才说到堆的概念

那么堆实际上是从系统的内存的顶部

一般从顶部开始使用的一段存储空间

那么这个空间一般是全局的

我们能够使用堆的一个最基本的东西

就是在我们C语言

我们经常会在main函数之外顶部声明一些全局变量

那么这个是在堆的

如果我们有操作系统

我们可能还会用操作系统的内存分配函数

从堆上分配一些静态的内存来使用

我们做微控制器

做一些没有操作性的嵌入式系统的时候

因为没有操作系统存在

我们往往不会去这样子使用堆

所以我们可以认为堆可能相对小

但是仍然因为某些编程的原因

我们很多同学会声明一些全局变量

它在堆上

那么堆在上面使用内存

栈在下面动态涨落

这个时候我们就会产生一个

很有意思的概念或者现象

就是总会有一种风险

就是函数或者程序的调用

使栈的使用逐渐接近堆

在某个时间点如果他们碰上了会出现什么问题

就是格子只有一个

作为堆它认为我是个变量

这个内存属于我

我想要存数的时候我就对它写个值

而对于栈

它认为我已经涨到这儿了

这个格子属于我

我一旦函数调用

我函数在返回值存在这儿

待会函数退出我要把这个值拿出来作为我的返回值

这是很可能发生的对吧

这种错误在计算机里是一种非常容易碰到的

或者非常严重的错误

我们称为堆栈溢出

那么很多同学说这是高级的东西

老师你现在给我讲好像还很遥远

不是的

我们生活中很多时候会碰到这样的错误

包括大家调写单片机程序的时候

你们很可能会遇到

为什么呢

我们后面会讲会遇到我们的编程习惯的影响

比如很多同学声明变量上来就用int 四个字节

建个数组我不知道用多少

我上来这个数组就一千个元

4000个字节就没有了

那么到使用的时候我们就会发现有问题

或者有同学我做个应用做个智能车

我要抓一个图像

这个图像不大

100乘以50个像素

那么就是5000个单元

每个单元存一个字节

5000个字节就没有了

这个时候栈还在底下往上用

那么我们有两个地方

会非常容易的遇到堆栈的溢出

哪两个呢

一个就是我们做

用windows的时候

都会遇到蓝屏

如果你仔细看看蓝屏的时候那个提示信息

有相当一部分是memory overflow

其实就是溢出错误

这在操作上是不允许所以它就蓝屏了

我们还有很大的概率

遇到另外一种蓝屏的错误是什么呢

就是越界访问

这种错误的发生也是刚才这个机制

一旦溢出

堆栈弹出的时候

它认为我应该把这段存储器里头的值

作为我函数的返回值

但是因为堆栈已经溢出了

这段存储器可能被某一个

别的全局变量加以使用

于是这个值被错误的取出来

当成函数的返回值

所以PC指针寄存器指向了

存储器一个莫名其妙的空间

而且试图把它里头值拿出来进行执行

这个时候我们称为程序跑飞了

对于操作系统

它可能认为这是越界访问

不符合安全规定就蓝屏了

在有些时候嵌入式系统

可能认为是非法指令 程序无法解释

于是程序就复位了

那么这些错误

在我们日后的学习调试中都会遇到

那么我们现在给大家讲这个概念

就是你们一定慢慢的学习一个东西

掌握一些他高阶的知识

最后讲一个有趣味性的东西

我们很多同学都遇到

比如说苹果手机的越狱

一些游戏机的破解

其实很多时候都是用程序里头存在的这样的漏洞

比如很著名的

任天堂的Wii游戏机 我印象非常深刻

他有一代游戏机的越狱或者破解

用的方法就是玩一个《塞尔达传说》的游戏

这个游戏的破解方法就是你要加载一个进度

让这个人走到一个特定的位置

然后这个时候呢

这个存档里头把这个人的身上带的

比如说钱或者道具的数目改成一个特定的值

这个时候你再去执行

你会发现整个系统就找到了漏洞

内部函数就被接管了

那它的机制是什么呢

就是在这个地方有堆栈的溢出

通过改这个道具这个全局变量的值

把它改成了一个函数的入口地址

导致这个函数在进行返回的时候

由于变量的互相之间溢出和覆盖

把这样一个特殊的值跳转

跳出了我们原来的用户程序而进入了内核

获得了破解以后的不应该拥有的权限

所以这也是信息安全当中的一个漏洞

那么学习堆栈我们加深了对于CPU的一个理解

回到我们CPU本身就是这样一张完整的图

那么我们如果现在再来看这张图

我们就会有了一个非常清晰的概念

对于它每一个单元如何运行已经认识的非常清楚

而在这个单元里头

我们会发现其实我们作为一个学习者

老师讲的这些东西可能更多的是概念故事

那么我们深入理解的是什么呢

对于编程者

你其实感受不到当中

它的内部结构的这些运行和电路的设计

因为我们不是CPU设计课程

你能感受到的事情是两件事

一件事情是泛泛层面上如果进行汇编语言的编程

进行C语言编程的调试

你会感觉到这若干个寄存器的存在

比如说你会感觉到堆栈指针寄存器

PC的这个程序计数器

或者PC指针寄存器

你会感觉到若干个通用数据寄存器

你会感受到它的运算值往外送

往寄存器送的这个过程

以及刚才说的这些标志位

所以我们往往对于任何一个通用的CPU

一个complete的一个完整的CPU

不管是谁家的

是ARM还是不是ARM

是Power PC还是0812

都会把它里头的寄存器这些东西提炼出来

我们称为寄存器组

英文叫register file

那么从更大的意义上来讲

我们会把这些东西称为我们的programmer model

也就我们的编程模型

作为一个汇编语言的编程人员

你学习CPU的时候你找到它的手册

比如说上完咱们这门课

日后你要学一个CPU

你非常快的看清楚这个CPU有哪些寄存器是编程的对象

也就它的编程模型

你就对它掌握了一半

那另一半呢

另一半就是这个逻辑运算单元所对应的指令集

它有哪些指令分别代表什么含义

所以如果你充分理解了指令集和寄存器组

这些东西组合起来

你就理解了一个CPU

那么触类旁通

万变不离其宗

大家学习这些知识以后

你可以学习任何一种嵌入式CPU的基本构架

那么我们来看一看几种现实中存在的CPU

非常简短的建立一个概念

而在下一个单元我们会进行一些实际的操作

比如这是08和S08的非常著名的八位的微控制器MCU

它是我们之前所讲过的6800和6502系列CPU的直系后代

架构非常的相似

也就说我们刚才所说任天堂这些游戏机的CPU的后代

是世界上历史上销量第一大的CPU 累计卖了十几亿片

那么这个CPU它的结构非常的简单

它有一个8比特的D data寄存器

然后有一个16比特的地址寄存器

数据寄存器只能存数

地址寄存器只能存对外部存储器访问的地址值

那么大家可以理解它是个指针对吧

另外还有两个地址寄存器

一个就是我们刚才所说到的PC指针寄存器

存当前指令执行到存储器的哪个地址了

还有一个堆栈指针寄存器

存当前把存储器的哪一段内存空间当做堆栈来使用

我们压数的时候往哪个地址上放

然后最后一个长长的8个比特的条件码寄存器

这里头有得0得1 进位借位等等

那么还有一些中断的开关在里头

那么这样一个简简单单的寄存器组

就是一个8比特MCU

那么它的指令集当然也非常简单

但是我们这么一看我们就知道

哦 八位08的MCU

它的CPU就是这样的

汇编怎么写就对他们进行编程

那么16位呢

它一脉相承的下一代

从68HC11一直到12 S12这样一脉相承

包括我们大学生智能车比赛

曾经长达10年一直到现在

都大家还有人使用的16位的MCU微控制器12系列

它的内部结构就这样的

一个16比特数据寄存器

我们称为D

根据需要呢

它有两个别名

前八个比特后八个比特

可以分为 分别叫A和B

拆开来使用作为两个8比特

然后呢 它有两个寻址寄存器

X和Y

可以保存两个16比特的外部存储地址 指针对吧

同样它有个PC指针寄存器

一个堆栈指针寄存器和一个完整的标志位

所以大家可以明显感到CPU就是这么进化的

从八位到十六位变得

更强大 更复用

数据和地址寄存器变得更多

那么把它加以抽象

我们就会看到这样一个模型

就是我们的逻辑运算单元

可以对两个暂存数据的A和B

或者合起来一个D的数据寄存器拿数据

也可以在X Y这样的寻址寄存器下

对外部的存储器来读取存储器里的数据

而这些数据的通路

我们抽象成了这里的这些箭头

我们日后会说它是一个总线的概念

我们既可以通过地址从存储器拿数据直接给CPU用

也可以通过地址拿数据

放到我们寄存器里来做中间的运算

运算的结果可以放回寄存器

也可以直接写回存储器

还是我们原来那个概念

那么在下一个单元呢我们将跳出这些模型

看看一段真正的程序是如何在里头给运行起来的

那么大家又会觉得豁然开朗

智能车制作:嵌入式系统课程列表:

第一章 概览

-1.1 课程概览

--Video

-1.2 进入嵌入式系统的世界

--Video

-1.3 如何学好嵌入式系统

--Video

第二章 绪论

-2.1 计算机的基本概念、发展历史

--Video

-2.2 从晶体管到CPU

--Video

-2.3 概念CPU、微控制器MCU和嵌入式系统

--Video

-2.4 八卦计算机史

--Video

-2.5 不同领域、不同系列的嵌入式系统

--Video

-2.6 ARM历史与MKL25Z128 MCU

--Video

第三章 MCU基础

-3.1 CPU的基本结构和运行机制

--Video

-3.2.1 堆栈的概念

--Video

-3.2.2 堆栈的概念-头脑体操

--Video

-3.3.1 ARM的体系结构

--Video

-3.3.2 ARM的体系结构-头脑体操

--Video

-3.4 中断的概念和机制

--Video

-3.5 中断子程的概念和编程

--Video

-3.6 复位、时钟、存储器和总线

--Video

-3.7 小结:MCU的总体结构和程序运行机制

--Video

第四章 MCU外设与开发

-4.1 第一种外设:IO

--Video

-4.2 IO外设的编程实操-点亮LED

--Video

-4.3 IO外设的进阶知识

--Video

-4.4 嵌入式开发的基本概念与工具链

--Video

-4.5 嵌入式开发的进阶知识

--Video

-4.6 嵌入式开发中的C语言(上)

--Video

-4.7 嵌入式开发中的C语言(下)

--Video

-E0.1 实验零 开发板的初步认识与工具链的安装

--Video

-E0.2 实验零 体验一个例程的编译与下载

--Video

-E0.3 实验零 编写第一个程序:点亮核心板LED

--Video

-E1 实验一 点灯秘籍

--Video

第五章 MCU与嵌入式系统设计

-5 智能车视角的嵌入式设计

--Video

Video笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。