ucos在s3c2410上运行过程整体剖析之基础知识-c语言和堆栈

发布者:RadiantJourney最新更新时间:2017-01-19 来源: eefocus关键字:ucos  s3c2410  c语言  堆栈 手机看文章 扫描二维码
随时随地手机看文章

我们知道C语言是一种高级语言,所谓高级语言就是要经过翻译才能在具体平台上运行的程序。而编译程序是一种比较繁琐的程序,它要把高级语言编译和链接后,成为能够在具体平台运行的程序。这其中有很多知识是和操作系统和具体硬件平台相关的,如果你想弄清楚编译程序请学习编译原理,有一本书可以参考《linkers_and_loaders》。

 

我们这里只是说明一下C语言运行的环境以及和栈的关系。让我们从汇编语言和底层硬件来了解C语言的一些概念和C语言是如何利用栈来进控制过程调用的。

先讲一下栈:

栈是这样一种结构:本事是一段连续的内存空间,怎么使用这样一种内存空间才算是起到了栈的实际作用那,首先要规定这一段连续空间的基地址,然后就从这个地址开始依次放东西。取东西时也是从最上面的开始取。按照上面的方案管理这一段存储空间,就是发挥了栈的作用。因为栈使用的频率实在是太高了,所以在计算机汇编层次就有专门操作栈的指令。包括push(入栈)、pop(出栈)等。

其实栈又有一些逻辑上的分类:

根据先腾出空间再用还是先用再腾空间分为:

1,满堆栈:即入栈后堆栈指针sp指向最后一个入栈的元素。也就是sp先减一(加一)再入栈。

2,空堆栈:即入栈后堆栈指针指向最后一个入栈元素的下一个元素。也就是先入栈sp再减一(或加一)。

根据从高地址开始用还是从低地址开始用分为:

1,递增堆栈:即堆栈一开始的地址是低地址,向高地址开始递增。就如同一个水杯(假设上面地址大)开口的是大地址,从杯底开始装水。自己画一画图就清楚了。我就偷懒一下不画了。

2,递减堆栈:即堆栈一开始的地址是高地址,向低地址开始递增。就如同还是刚才说的那个水杯,现在开口的是小地址,从大地址开始用,往下走,相当于杯子口朝下。我们用的时候是把水往上一点点压上去。呵呵呵,不过这样的杯子就失去了用途。但在内存上还是可以的。

那么根据这两种分类方法,我们就可以得到四种栈的类型,而ARM920T中使用的是递减满堆栈。

下面重点说明c语言运行时是怎么用栈来控制函数调用过程的。

大家想一想,我们写c语言时用到函数调用,有时候还嵌套调用很多函数。还有有些函数还需要参数和返回值。怎么处理各个函数的参数和返回值,以及当每一个函数完成工作时该返回到那个地方。这些都是要解决的问题。当然最容易想到的也是必须做的是在进行调用跳转之前,把我这个函数现有的状态保存起来,保存什么那,调用函数返回后的下一条指令,还有我这个函数需要的哪些数据。还有就是保存这些信息到哪些地方哪?这些都是我们要解决的问题。还有就是你不光要保存这些信息,还要保存这些信息的顺序。因为函数调用本身有顺序,你像a调用b,b又接着调用c。在c执行完后要返回到b,b执行完再返回a。呵呵,有顺序。

我们一一想办法来解决,当然别人已经用栈的策略解决的很完美了,我们只是想一些更简洁的最容易想起来的但是不完善的方法,也正说明了人家的策略是多么的优秀。

关于调用函数的问题,我们可以把返回地址保存到一些地方,当然程序员知道在那?还知道顺序,再根据顺序返回就好了,但做这样的工作太累了,除了写程序还要记这些东西。哎肯定不好也不这样做。关于传参,有这样可以考虑的,用专门规定好的寄存器来做传参。行,但有缺陷,如果传的参数很多或者是变化的,就不好用寄存器传参了。而且我们有操作系统时往往要求编译器产生的代码具有可重入性,也就是保证代码和数据的相对独立性。一个函数被调用两次,都有两次的参数环境。到底现在我们是怎么做的那。答案是用栈。

怎么用,嘻嘻,下面一一道来:

 

函数在执行一个函数调用调用时,用栈不仅保存函数的返回地址,并且一起把函数所需要的参数和返回值都保存在堆栈中。

 

也就是每一个函数都有一个这样的栈,保存着一些信息。先说一下栈帧的概念,在函数调用过程中要保存的整个参数集合,包括返回地址称作一个栈帧。

 

如上图所示,我们以这个图为例,分析一下栈在函数调用中的应用。函数p有两个参数 x1、x2,函数p 调用函数q ,且有两个参数。储存在栈帧的第一帧是上一个栈帧的地址,当前栈帧的地址就是正在使用的栈帧的基值,使用这个指针能方便的找到函数所需的变量和参数等。那为什么每一针的第一个地址要保存上一个栈帧的地址那,和保存返回地址一个初衷,当你当前用的栈帧用完时,把在当前栈帧保存的上一个栈帧的地址取出就还原了上一个栈帧。

接着是返回地址,如果函数有返回值的话,返回值放在返回地址的下面。接着为函数所需要的变量申请空间。

下面说说函数p调用函数q时的具体情况。当执行call q(y1)时,会为函数q创建一个新的栈帧,具体过程是:先保存丄一帧的地址,如果有返回值的话为返回值分配存储空间,然后保存返回地址。然后为y1分配空间并把它初始化为调用q时给的参数。接着分配另一个参数的空间y2,这个参数用于在函数内部计算。

在任何状态下,都有一个当前栈帧的指针fp,这个指针用来保存当前栈帧的地址,那这个值怎么保证是当前的那,先说q函数的吧,是把栈的指针sp先保存下来,然后接着保存fp。然后把fp的值改为sp-4 ,因为我们知道每个栈帧的第一个要保存的是fp。

其实整个过程是动态的,所谓我说是动态的是因为sp指针一直是快速移动的。所以要在每一帧开始的时候先把这个sp保存住。然后往减4的地址处放fp。当这个栈帧全弹出时,就把你保存的fp又恢复到原来你保存的fp了。就一直有fp代表当前栈帧底部。也是唯一一个不变的基地址,用它来找其他的变量。

好了,下面我们看一个在ARM下C语言写的程序然后编译成汇编语言分析其栈的应用。(在linux2.4内核下写的c语言程序,用arm-linux-gcc3.4.1编译器编译)

C语言源程序如下:

#include

int max(int,int);

int main(int argc,char *argv[])

{

    int a=3,b=5;

    max(a,b);

    return 0;

}

int max(int x,int y)

{

    if(x>y)

       return x;

    else

       return y

}

函数很简单,在主函数里调用一个外部函数max用于两个数中数值较大的那个数并返回。

下面看ARM的汇编是怎么实现的这些功能,以及这中间栈的使用情况。

       .file  "max.c"

       .text

       .align       2

       .global     main

       .type       main, %function

main:

       @ args = 0, pretend = 0, frame = 16

       @ frame_needed = 1, uses_anonymous_args = 0

       mov ip, sp

       stmfd      sp!, {fp, ip, lr, pc}

       sub  fp, ip, #4

       sub  sp, sp, #16

       str   r0, [fp, #-16]

       str   r1, [fp, #-20]

       mov r3, #3

       str   r3, [fp, #-24]

       mov r3, #5

       str   r3, [fp, #-28]

       ldr   r0, [fp, #-24]

       ldr   r1, [fp, #-28]

       bl     max //开始调用max函数

       mov r3, #0

       mov r0, r3

       sub  sp, fp, #12

       ldmfd      sp, {fp, sp, pc}

       .size main, .-main

       .align       2

       .global     max

       .type       max, %function

max:

       @ args = 0, pretend = 0, frame = 12

       @ frame_needed = 1, uses_anonymous_args = 0

       mov ip, sp

       stmfd      sp!, {fp, ip, lr, pc}

       sub  fp, ip, #4

       sub  sp, sp, #12

       str   r0, [fp, #-16]

       str   r1, [fp, #-20]

       ldr   r2, [fp, #-16]

       ldr   r3, [fp, #-20]

       cmp r2, r3

       ble   .L3

       ldr   r3, [fp, #-16]

       str   r3, [fp, #-24]

       b     .L2

.L3:

       ldr   r3, [fp, #-20]

       str   r3, [fp, #-24]

.L2:

       ldr   r0, [fp, #-24]

       sub  sp, fp, #12

       ldmfd      sp, {fp, sp, pc}

       .size max, .-max

       .ident      "GCC: (GNU) 3.4.1"

以上代码比较长,告诉大家一个密码,其实原来说的main()函数其实也是一个普通的函数,只是由更高级的东东调用而已,你写的普通函数不能调用而已。我们就先不管main()函数的事,重点从main()函数调用max()函数开始。用红色标出来的是函数的调用开始。我们观察一下在调用max()之前有两句代码,这是把调用max()时的参数a,b传到了r0和r1中,在max()函数中再从r0和r1中来取,这就实现了主函数main()和max()函数传参的机制。当然,这里的传参是利用的寄存器传参,哎,这里怎么不是用栈传参那,这和ARM编程时的过程调用规范有关,在ARM编程时有一个叫ATPCS的调用规范规定,我们编程时要按这个规范来编写。关于这方面的知识可以参考ATPCS.pdf(英文官方资料)和ATPCS概述(中文版简要介绍)。其中简单的一个规则就是少于4个参数的都用r0~r3这四个寄存器传参,当参数多于4个时,将剩余的字数据传送到数据栈中,入栈的顺序与参数顺序相反,即最后一个字数据先入栈。这里参数只有两个,因此只用了寄存器传参。下面分析跳转到max后汇编语言做到工作。

 

下面看看arm的汇编语言的栈帧的使用。

max:

       @ args = 0, pretend = 0, frame = 12

       @ frame_needed = 1, uses_anonymous_args = 0

mov ip, sp //先把开始创建栈帧的sp的值保存

       stmfd      sp!, {fp, ip, lr, pc} //接着保存相关信息

       sub  fp, ip, #4//重新设置fp使其指向当前栈帧

       下面就是申请空间和有关计算了

       sub  sp, sp, #12//申请了三个整形变量,其中两个局部变量a,b,另一个是用于存放返回值。

       str   r0, [fp, #-16]

       str   r1, [fp, #-20]// 把通过寄存器传过来的参数保存到栈空间中。

       ldr   r2, [fp, #-16]// 下面就是比较两个数谁大谁小的代码实现了,想知道怎么回事那就自己分析一下吧。

       ldr   r3, [fp, #-20]

       cmp r2, r3

       ble   .L3

       ldr   r3, [fp, #-16]

       str   r3, [fp, #-24]

       b     .L2

.L3:

       ldr   r3, [fp, #-20]

       str   r3, [fp, #-24]

.L2:

       ldr   r0, [fp, #-24] //把返回值放到R0中,在ATPCS中规定了函数的返回值放到R0中。

       sub  sp, fp, #12// 清除你分配局部变量和返回值的存储空间

       ldmfd      sp, {fp, sp, pc}//恢复上一栈帧的相关信息,并实现了函数返回。

       .size max, .-max

       .ident      "GCC: (GNU) 3.4.1"

上面说的有点乱,请你根据我前面说的用栈实现函数过程调用规范和具体事例自己好好分析一下。至于用栈传参的情况你可以把参数的数量多于4个,然后编译看看是怎么实现的哦。

你现在是不是觉着栈这个东东很有魅力,用栈实现的过程调用控制是多么的完美。呵呵。

看完了这个知识点,里面包括了c语言是怎么实现的函数调用以及参数的传递的具体实现。

那你对学习c语言时遇到的一些概念,是不是更自然的接受了。比如局部变量,变量的生命期,变量的作用域等概念。你现在知道什么是局部变量了吧,这种变量空间是随着程序的执行动态的分配以及消亡的。而全局变量是编译、链接时预留的特定的内存区域,是不会消亡的,如果程序在操作系统之上运行,除非它的真个程序结束。这方面的知识将在编译器章节中详细介绍。

其实,在计算机上有两种分配空间的方法,一:把一段空间预留出来,或者初始化或者不初始化。这段空间就算是分配出来了,可以被程序使用。二:在程序运行期间用指令在栈的空间上分配。

课外话:我们学习新事物时,总会有一些疑问,这是好现象。你有疑问就说明你想弄明白这究竟是怎么回事。你真正弄明白了才可能会使用这种新事物去解决类似的问题。如果你对新事物没有任何疑问,而是一味的信任和记忆,那么我说你不可能用它来解决任何问题,因为那只是你一厢情愿,那个新事物根本就不认识你,也不想搭理你。嘻嘻。从学习刚才的知识可以看出,如果一个概念你在他所讲的层次上去思考,你怎么也理解不了时,别灰心,别放弃这中疑问和好奇心。你只要有这颗心,你以后一定能对你的疑问做出一个完美的解答,当然一定不会是从原来的角度来理解的。就像C语言,如果从C语言的角度给你阐述概念,你就怎么也理解不了,而从汇编和CPU的角度讲就比较自然了。那么,我们就知道了学习是一个逐渐的深入过程,但前提是你有好奇心想把这个东东弄明白,而且还从不想放弃。

做一个比喻;学习就像在地上挖井,你不可能一次就很容易的挖出甘泉,而可能会需要很多次努力,最终也不一定得到甘泉,但起码这口井会越来越接近甘泉。我们不要放弃下面有甘泉的信心就好。还有,我们人本来就有享受学习过程的天性,你想想你自己学走路的情形,你可能想不起来了,你可以到网上搜一下小孩学走路的视频,学习走路是很辛苦的,因为那要摔倒很多次,但他们在学习的过程中都是很开心的。只是我们长大了,就连这个最基本的乐趣也丢失了。原因有很多,我们长大了,考虑的事情多了,对外界的评价太过于重视,其实我们真的应该享受学习的过程,而不是最后的成绩。在学习的过程中享受那种科学的严谨和美、巧妙的方法。哎,说多了~

好了,c语言函数的执行和栈的有关情况就说到这。


关键字:ucos  s3c2410  c语言  堆栈 引用地址:ucos在s3c2410上运行过程整体剖析之基础知识-c语言和堆栈

上一篇:ucos在s3c2410上运行过程整体剖析之基础知识--ADS编译、链接器
下一篇:ARM汇编 C语言 C++ 相互调用

推荐阅读最新更新时间:2024-03-16 15:31

DS18B20数字温度计C语言源程序
DS18B20数字温度计使用 1.DS18B20基本知识 DS18B20数字温度计是DALLAS公司生产的1-Wire,即单总线器件,具有线路简单,体积小的特点。因此用它来组成一个测温系统,具有线路简单,在一根通信线,可以挂很多这样的数字温度计,十分方便。 1、DS18B20产品的特点   (1)、只要求一个端口即可实现通信。   (2)、在DS18B20中的每个器件上都有独一无二的序列号。   (3)、实际应用中不需要外部任何元器件即可实现测温。   (4)、测量温度范围在-55。C到+125。C之间。   (5)、数字温度计的分辨率用户可以从9位到12位选择。   (6)、内部有温度上、下限告警设置。 2、DS18
[单片机]
DS18B20数字温度计<font color='red'>C语言</font>源程序
四位数码管显示2012的汇编语言与C语言
其对应的C应用如下: #include reg51.h sbit p20=P2^0; sbit p21=P2^1; sbit p22=P2^2; sbit p23=P2^3; void delay(unsigned int z) { while(z--); } void main() { P2=0x00; while(1) { p20=1; P0=0XA4; delay(500); p20=0; p21=1; P0=0XC0; delay(500); p21=0; p22=1; P0=0XF9; delay(500); p22=0; p23=1; P0=0XA4; delay(500);
[单片机]
四位数码管显示2012的汇编语言与<font color='red'>C语言</font>
如何使用C语言来编写MSP430的高质量代码
微处理器一般用于特定环境和特定用途,出于成本、功耗和体积的考虑,一般都要求尽量节省使用资源,并且,由于微处理器硬件一般都不支持有符号数、浮点数的运算,且运算位有限,因此,分配变量时必须仔细。另外要说明的是,速度和存储器的消耗经常是2个不可兼顾的目标,在多数情况下,编程者必须根据实际情况作出权衡和取舍。 需要注意的事项如下: 1) 通常在满足运算需求的前提下,尽量选择为变量定义字节少的数据类型。 比如最常用的int和char,int是16位的,char是8位的,如果没有必要,不要使用int,而且使用char也最好使用unsigned char。运行时,可以在变量窗口看到,使用类型为unsigned char的变量是16进制的格式
[单片机]
查找嵌入式C语言程序/软件中的缺陷的多种技术
基于模式的静态代码分析、运行时内存监测、单元测试以及数据流分析等软件验证技术是查找嵌入式C语言程序/软件缺陷行之有效的方法。上述技术中的每一种都能查找出某一类特定的错误。即便如此,如果用户仅采用上述技术中的一种或者几种来进行验证,这样的验证方法很有可能会漏过对程序中的一些缺陷的检查。解决此类问题的一种安全和有效的策略就是同时使用上述软件验证中的所有互补技术。这样就能建立起一个牢固的框架来帮助用户检查出可能会避开某种特定技术的缺陷。与此同时,用户也自然地建立起一个能检测出关键并且难以查找的功能性错误的环境。 本文将详尽阐述基于模式的静态代码分析、运行时内存错误检测、单元测试以及数据流分析等自动化技术共同使用时是如何查找出嵌入式C语言
[单片机]
查找嵌入式<font color='red'>C语言</font>程序/软件中的缺陷的多种技术
STM32中较为常见的C语言基础知识
C语言是单片机开发中的必备基础知识,本文列举了部分 STM32 学习中比较常见的一些C语言基础知识。 1位操作 下面我们先讲解几种位操作符,然后讲解位操作使用技巧。C语言支持以下六种位操作: 下面,重点讲解一下位操作在单片机开发中的一些实用技巧。 在不改变其他位的值的状况下,对某几个位进行设值 这个场景在单片机开发中经常使用,方法就是我们先对需要设置的位用&操作符进行清零操作,然后用 | 操作符设值。 比如,我要改变 GPIOA 的状态,可以先对寄存器的值进行&清零操作: GPIOA- CRL&=0xFFFFFF0F;/*将第4~7位清零*/ 然后再与需要设置的值进行 |
[单片机]
STM32中较为常见的<font color='red'>C语言</font>基础知识
s3c2410开发板的VGA调试
光买开发板没有LCD,穷啊,只能用VGA的显示器代替。带的资料也没说怎么就支持VGA,只好上网查,还好解决了,虽然不知道linux驱动的原理,但改改寄存器还是会的。下面说说整个过程。 VGA的接口定义如图所示: 板子上的AD芯片是TI的TL5632,该芯片的三个输出引脚与VGA的RED GREEN BLUE相连,它的24个输入引脚与2410的vd 相连,2410的VSYNC和HSYNC直接连到VGA的对应引脚,其他的该接地接地, MONITOR ID这三个编码引脚用不上。 上面是分辨率为640×480、刷新频率为60 Hz、16位彩色显示模式的VGA接口时序图,LCD寄存器中的一些值就由它们决定: *LCDCON1寄存
[单片机]
<font color='red'>s3c2410</font>开发板的VGA调试
第9章 STM32F429重要知识点数据类型,变量和堆栈
9.1 初学者重要提示 1、如果对C语言不熟练的话,可以阅读C语言书:C Primer Plus(第五版)中文版.pdf 论坛下载:http://www.armbbs.cn/forum.php?mod=viewthread&tid=91219。 2、为了更好的学习本章知识点,可以看之前做的视频教程第10章,针对H7也将在今年发布视频教程: http://www.armbbs.cn/forum.php?mod=viewthread&tid=15408 。 9.2 数据类型 了解数据类型之前要对ANSI C和ISO C的发展史有个了解,特别是C89,C99和C11的由来。 9.2.1 ANSI C和ISO C历史
[单片机]
第9章 STM32F429重要知识点数据类型,变量和<font color='red'>堆栈</font>
秉火429笔记之四启动文件分析
名为“startup_stm32f429_439xx.s”的文件,它里边使用汇编语言写好了基本程序,当STM32芯片上电启动的时候,首先会执行这里的汇编程序,从而建立起C语言的运行环境,所以我们把这个文件称为启动文件。该文件使用的汇编指令是Cortex-M4内核支持的指令,可从《Cortex-M4 Technical Reference Manual》查到,也可参考《Cortex-M3权威指南中文》,M3跟M4大部分汇编指令相同。 startup_stm32f429_439xx.s文件是由官方提供的,一般有需要也是在官方的基础上修改,不会自己完全重写。该文件可以从 KEIL5 安装目录找到,也可以从 ST 库里面找到,找到该文
[单片机]
秉火429笔记之四启动文件分析
热门资源推荐
热门放大器推荐
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
  • 学习ARM开发(16)
    ARM有很多东西要学习,那么中断,就肯定是需要学习的东西。自从CPU引入中断以来,才真正地进入多任务系统工作,并且大大提高了工作效率。采 ...
  • 学习ARM开发(17)
    因为嵌入式系统里全部要使用中断的,那么我的S3C44B0怎么样中断流程呢?那我就需要了解整个流程了。要深入了解,最好的方法,就是去写程序 ...
  • 学习ARM开发(18)
    上一次已经了解ARM的中断处理过程,并且可以设置中断函数,那么它这样就可以工作了吗?答案是否定的。因为S3C44B0还有好几个寄存器是控制中 ...
  • 嵌入式系统调试仿真工具
    嵌入式硬件系统设计出来后就要进行调试,不管是硬件调试还是软件调试或者程序固化,都需要用到调试仿真工具。 随着处理器新品种、新 ...
  • 最近困扰在心中的一个小疑问终于解惑了~~
    最近在驱动方面一直在概念上不能很好的理解 有时候结合别人写的一点usb的例子能有点感觉,但是因为arm体系里面没有像单片机那样直接讲解引脚 ...
  • 学习ARM开发(1)
  • 学习ARM开发(2)
  • 学习ARM开发(4)
  • 学习ARM开发(6)
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved