3.7过程
过程是软件中一种很重要的抽象。它提供了一种封装代码的方式,用一组指定的参数和一个可选的返回值实现了某种功能。然后,可以在程序中不同的地方调用这个函数。设计良好的软件用过程作为抽象机制,隐藏某个行为的具体实现,同时又提供清晰简洁的接口定义,说明要计算的是哪些值,过程会对程序状态产生什么样的影响。不同编程语言中,过程的形式多样:函数(function)、方法(method)、子例程(subroutine)、处理函数(handler)等等,但是它们有一些共有的特性。
要提供对过程的机器级支持,必须要处理许多不同的属性。
传递控制。在进入过程Q的时候,程序计数器必须被设置为ρ的代码的起始地址,然后在返回时,要把程序计数器设置为P中调用Q后面那条指令的地址
传递数据。P必须能够向Q提供一个或多个参数,Q必须能够向P返回一个值。
分配和释放内存。在开始时,Q可能需要为局部变量分配空间,而在返回前,又必须释放这些存储空间
人们花了大量的力气来尽量减少过程调用的开销。所以,它遵循了被认为是最低要求策略的方法,只实现上述机制中每个过程所必需的那些。
3.7.1运行时栈
C语言过程调用机制的一个关键特性(大多数其他语言也是如此)在于使用了栈数据结构提供的后进先出的内存管理原则。
程序可以用栈来管理它的过程所需要的存储空间,栈和程序寄存器存放着传递控制和数据、分配内存所需要的信息。
当x86-64过程需要的存储空间超出寄存器能够存放的大小时,就会在栈上分配空间。这个部分称为过程的栈帧(stack fram)
大多数过程的栈帧都是定长的,在过程的开始就分配好了。但是有些过程需要变长的帧,这个问题会在3.10.5讨论
许多过程有6个或者更少的参数,那么所有的参数都可以通过寄存器传递。因此,图3-25中画出的某些栈帧部分可以省略。实际上,许多函数甚至根本不需要栈帧。当所有的局部变量都可以保存在寄存器中,而且该函数不会调用任何其他函数(有时称之为叶子过程,此时把过程调用看做树结构)时,就可以这样处理。
3.7.2转移控制
用指令callQ调用过程Q来记录的。该指令会把地址A压人栈中,并将PC设置为Q的起始地址。压人的地址A被称为返回地址,是紧跟在ca11指令后面的那条指令的地址。对应的指令re会从栈中弹出地址A,并把PC设置为A。
这种把返回地址压入栈的简单的机制能够让函数在稍后返回到程序中正确的点。C语言(以及大多数程序语言)标准的调用/返回机制刚好与栈提供的后进先出的内存管理方法吻合。
3.7.3数据传送
x86-64中,大部分过程间的数据传送是通过寄存器实现的。
x86-64中,可以通过寄存器最多传递6个整型(例如整数和指针)参数。寄存器的使用是有特殊顺序的,寄存器使用的名字取决于要传递的数据类型的大小,如图3-28所示。
会根据参数在参数列表中的顺序为它们分配寄存器。可以通过64位寄存器适当的部分访问小于64位的参数。例如,如果第一个参数是32位的,那么可以用edi来访问它
参数超出6个的部分就要通过栈来传递。参数7位于栈顶
3.7.4栈上的局部存储
有些时候,局部数据必须存放在内存中,常见的情况包括:
寄存器不足够存放所有的本地数据。
对一个局部变量使用地址运算符‘&’,因此必须能够为它产生一个地址。
某些局部变量是数组或结构,因此必须能够通过数组或结构引用被访问到。在描述数组和结构分配时,我们会讨论这个问题
一般来说,过程通过减小栈指针在栈上分配空间。分配的结果作为栈帧的一部分,标号为“局部变量”,如图3-25所示
运行时栈提供了一种简单的、在需要时分配、函数完成时释放局部存储的机制。
3.7.5寄存器中的局部存储空间
寄存器组是唯一被所有过程共享的资源。
虽然在给定时刻只有一个过程是活动的,我们仍然必须确保当一个过程(调用者)调用另一个过程(被调用者)时,被调用者不会覆盖调用者稍后会使用的寄存器值。为此,x86-64采用了一组统一的寄存器使用惯例,所有的过程(包括程序库)都必须遵循
根据惯例,寄存器rbx、rbp和r12~r15被划分为被调用者保存寄存器。当过程P调用过程Q时,Q必须保存这些寄存器的值,保证它们的值在Q返回到P时与Q被调用时是一样的。
所有其他的寄存器,除了栈指针各rsp,都分类为调用者保存寄存器。这就意味着任何函数都能修改它们。可以这样来理解“调用者保存”这个名字:过程P在某个此类寄存器中有局部数据,然后调用过程Q。因为Q可以随意修改这个寄存器,所以在调用之前首先保存好这个数据是P(调用者)的责任
3.7.6递归过程
前面已经描述的寄存器和栈的惯例使得x86-64过程能够递归地调用它们自身。每个过程调用在栈中都有它自己的私有空间,因此多个未完成调用的局部变量不会相互影响此外,栈的原则很自然地就提供了适当的策略,当过程被调用时分配局部存储,当返回时释放存储。
从这个例子我们可以看到,递归调用一个函数本身与调用其他函数是一样的。栈规则提供了一种机制,每次函数调用都有它自己私有的状态信息(保存的返回位置和被调用者保存寄存器的值)存储空间。如果需要,它还可以提供局部变量的存储。栈分配和释放的规则很自然地就与函数调用返回的顺序匹配。这种实现函数调用和返回的方法甚至对更复杂的情况也适用,包括相互递归调用(例如,过程P调用Q,Q再调用P)。
深入理解计算机系统笔记_程序的机器级表示_3.7过程/函数/方法/子例程
最新推荐文章于 2023-06-02 15:54:57 发布