过程是程序的一种抽象,以一组参数和返回值实现对一个功能的封装,过程包含的形式有:函数、方法、处理函数等
过程调用必须解决的三大机器级别问题:
控制转移:过程P调用过程Q,进入过程Q时,程序计数器设置为Q的代码起始地址,在Q执行完成返回时,需要把程序计数器设置为P调用Q位置的下一条指令地址。
参数传递:P必须为Q提供若干个参数,Q可以向Q返回一个值
内存分配与释放:被调用过程Q可能会为局部变量分配空间,而在返回前,又需要释放这些局部空间。
【运行时栈】
栈数据结构为后进先出,这与函数调用过程的调用=》进入=》返回 过程是相似的。
当被调过程P被执行时,其调用者P之前的过程被暂时挂起。当Q运行时,它只需要为其内部的局部变量分配空间,或者调用另外一个过程,当Q返回时,又释放这些空间,因此程序采取栈和程序寄存器来存放控制转移和数据,以及内存分配信息。
X86-64架构的栈由高地址向低地址增长,栈顶指针%rsp寄存器始终存放栈顶的地址,而pushq 和popq指令则对应栈的入栈和出栈。分配空间,栈顶指针减小,释放空间,栈顶指针增大。
通常而言,对于过程中的存储空间,优先分配在寄存器组中(速度更快),通常过程6个或更少的参数,均可以通过寄存器存放,当空间不够时,再分配在栈内存中。分配在内存中的部分即为上述的栈帧。过程P调用过程Q 时,会首先把返回地址压人栈中,指明当Q返回后,调用者P从下一条哪个位置继续执行。
【数据传送】
过程调用中,除了要进行控制转移外,还需要进行参数传递和过程返回。在X86-64中,大部分调用过程的数据传输通过寄存器实现。而当被调用过程返回前,其将返回值写入寄存器中(%rax) 调用者通过寄存器获取到这个返回值。
当参数的数量较大以至于寄存器无法存放时,采取栈帧中的参数构造区来进行参数传递。
【局部存储】
参数传递可以采取寄存器组进行实现,但当局部数据更为复杂时,必须在内存中进行空间分配:
- 对于一个局部变量使用取地址运算 & 因此必须在内存中为它分配空间以产生地址。
- 局部变量为数组或结构体类型,必须能够通过数组方式和结构引用访问到,因此必须在内存中分配空间。
可以了解到,参数的传递存放以及局部变量可以采取内存或者寄存器的物理方式进行存储。而由于寄存器组的数量有限,若调用过程很复杂(递归调用等)需要有一个约定,即:
被调用者的活动不会覆盖调用者会使用的寄存器值。
因此在被调过程Q执行过程中,其通过几种方式以确保这些约定:
- 过程Q保证寄存器的值不变,或者不去使用它
- 过程Q使用前先暂时将寄存器的值到内存,在使用完毕后即返回前,将寄存器的值恢复。