梁老虎

人无远虑,必有近忧

导航

栈的作用

计算机里面的栈其实有着举足轻重的作用。大学刚学c语言的时候,教的是堆栈,传达的是一种后入先出的算法思想。但其实我们知道,堆和栈是两个截然不同的东西。而这里面说到的栈,则是更融入到计算机系统里面,CPU结构的一部分。

一个函数设计里面,有2个问题:

1.是参数传递的问题。传递参数的目的,是为了代码可以重用,让一种方法可以应用到更多的场合,而不需要为N种情况写N套类似的代码。那用什么方法来做参数的传递,可以选择:

     a.为了速度快,使用cpu的寄存器传递参数。这会碰到一个问题,cpu寄存器的数量是有限的,当函数内再想调用子函数的时候,再使用原有的cpu寄存器就会冲突了。想利用寄存器传参,就必须在调用子函数前吧寄存器存储起来,然后当函数退出的时候再恢复。

     b.利用某些ram的区域来传递参数。这和上面a的情况几乎一样,当函数嵌套调用的时候,还是会出现冲突,依然面临要把原本数据保存到其他地方,再调用嵌套函数。并且保存到什么地方,也面临困难,无论临时存储到哪里,都会有上面传递参数一样的困境。

2.函数里面必然要使用到局部变量,而不能总是用全局变量。则局部变量存储到哪里合适,即不能让函数嵌套的时候有冲突,又要注重效率。

以上问题的解决办法,都可以利用栈的结构体来解决,寄存器传参的冲突,可以把寄存器的值临时压入栈里面,非寄存器传参也可以压入到栈里面,局部变量的使用也可以利用栈里面的内存空间,只需要移动下栈指针,腾出局部变量占用的空间。最后利用栈指针的偏移来完成存取。于是函数的这些参数和变量的存储演变成记住一个栈指针的地址,每次函数被调用的时候,都配套一个栈指针地址,即使循环嵌套调用函数,只要对应函数栈指针是不同的,也不会出现冲突。利用栈,当函数不断调用的时候,不断的有参数局部变量入栈,栈里面会形成一个函数栈帧的结构,一个栈帧结构归属于一次函数的调用。栈的空间也是有限的,如果不限制的使用,就会出现典型的栈溢出的问题。有了栈帧的框架在,我们在分析问题的时候,如果能获取到当时的栈的内容,则有机会调查当时可能出现的问题。

 

      经过上面的简单介绍,应该可以看出栈在程序设计里面的作用,它是每个函数架构的基础,有了它,才可以实现函数的重复利用。而为了更高的提高效率,每个cpu在设计的时候都有自己独立的堆栈指令,例如push pop,有堆栈寄存器存储堆栈指针,如ARM的R13寄存器,来尽可能的加速对栈的操作。

      但这是在汇编机器语言的角度上看到的情况,在c语言的角度上看,明显有意隐藏了栈的存在,这也是高级语言的意义,让我们更关注功能本身,而不是如何被翻译成机器代码。但是了解它也有重要的意义,像上面说道的,问题发生的时候,利用栈来了解问题发生的情况十分必要。

      然而栈存在的意义还不止这点,有了它的存在,才能构建出操作系统的多任务模式。

      让我们看一下下面的一个main函数的调用实例,上面说的栈帧的情况依然,main函数调用A函数,调用B函数,再调用C函数,然后依次返回,试想当单cpu在main函数的框架运行的话,永远都在main函数厄结构里面(假设main函数是个无限循环结构),始终是在一个任务范围内,谈不上多任务。即使有另外一个任务在等待状态,如何在main函数里面跳转到另一个任务。显然在c语言的框架下,这无法实现,因为如果是函数调用关系,则本质上还是属于main函数的任务里面,不能算多任务切换。但需要注意到一个事实,此刻的main函数任务本身其实和它的栈绑定在一起了,无论是如何调用子函数,无论如何入栈退栈,栈指针都在本栈的范围内移动, 属于本任务的局部变量也和任务本身绑定了。

      main()

      ---->A()

              ----->B()

                       ------C()

                   <-----

              <-----B()

       <----A()

       main()

        由此可以看出,一个任务状态可以利用如下信息来表征:1.main函数体代码。2.main的栈指针位置(即存储了局部变量等信息)。3.当前cpu寄存器的信息。假如我们可以保存在这些信息,则完全可以强制让cpu去做别的事情,只是将来想继续执行main任务的时候,把上面的信息恢复就可以。有了这样的先决条件,多任务就有了存在的基础。也可以看出栈存在的意义所在。在多任务模式下,当CPU认为有必要切换到别的任务上运行时,只需要保存好当前任务的状态,即上面说的三个内容。恢复另一个任务的状态,然后跳转到上次运行的位置,就可以恢复继续运行。可见每个任务都有自己的独立的栈空间。正是有了独立的栈空间,为了代码重用,不同的任务甚至可以混用任务的函数体本身,例如可以一个main函数有2个任务实例。有不同的栈空间,这完全可以实现。

         至此之后的操作系统的框架也形成了,譬如任务在调用sleep()等待的时候,可以主动让出CPU给别的任务使用,或者分时操作系统任务在时间片用完是也会被迫的让出cpu。不论是哪种方法,只要想办法切换任务的上下文空间,切换栈即可。切换栈的实现并不能在c语言下完成,之前也说过c语言有意隐藏了栈的使用。所以在关键的上下文切换的地方,操作系统都需要用汇编代码来实现。

 

posted on 2014-06-05 21:28  梁老虎  阅读(6217)  评论(1编辑  收藏  举报