定义:进程是由内核定义的抽象的实体,并为该实体分配用以执行程序和各项系统资源。从内核角度看,进程由用户内存空间和一系列内核数据结构组成,其中用户内存空间包含了程序代码及代码所使用的变量,而内核数据结构则用于维护进程状态信息。
1、进程内存布局
每个进程所分配的内存由多个部分组成,通常称之为段,主要有以下段:
文本段
文本段包含进程运行的机器语言指令。具有只读属性,防止被意外修改。因为多个进程可以运行同一个程序,因此文本段也是可共享的。初始化数据段
包含显式初始化的全局变量和静态变量。未初始化数据段
包含未进行显式初始化的全局变量和静态变量。程序启动之前会将此段所有变量初始化为0。栈段
栈是一个动态增长和收缩的段,用以支持函数调用,由栈帧组成。系统会为每个当前调用的函数创建一个栈帧,其中存储函数的局部变量,实参和返回值。由高地址向低地址方向增长。堆
堆是可在运行时动态分配的区域。由低地址向高地址方向增长。
栈和栈帧
一个过程调用包括将数据和控制从代码的一部分传递到另一部分。还必须在进入时为局部变量分配空间,并在退出时释放这些空间。在32位机器上,这些是通过操作栈来实现的。
系统用栈来传递过程参数、存储返回信息、保存寄存器用于以后恢复等。为单个函数调用创建的那部分栈称为栈帧。每次调用函数时,会在栈上新分配一帧,函数返回时再从栈上将此帧移去(移除帧时,栈大小并未改变,而会在新分配帧时重用这部分内存)。
假设函数P调用函数Q,则Q的参数放在P的栈帧中。当P调用 Q时,P中的返回地址被压入栈中,形成P的栈帧末尾。返回地址就是当程序从Q返回时应该继续执行的地方。Q的栈帧从保存的帧指针的值开始,后面是保存的其它寄存器的值。
非局部跳转
使用库函数setjmp和longjmp可以执行非局部跳转,即跳转到当前函数之外某个位置。
#include <setjmp.h>
int setjmp(jmp_buf env);初始调用返回0,后续调用返回非0
void longjmp(jmp_buf env, int val);
setjmp的初始调用为后续的longjmp跳转确定了跳转的位置。调用longjmp后,看起来就像第二次调用setjmp返回时完全一样,通过查看setjmp的返回值可以与初始返回区分开。初始调用返回0,后续返回longjmp参数中设置的val值,通过对val参数使用不同的值,能够区分程序中跳转至同一目标的不同起跳位置。
注:如果指定val为0,则longjmp实际会将其替换为1。
参数env将setjmp和longjmp联系起来。setjmp将当前进程环境的各种信息保存到env参数中,调用longjmp时必须指定同一个env变量。因为setjmp和longjmp的调用位于不同函数中,因此通常将env定义为全局变量。
切记longjmp不能跳转到已返回的函数中。
sample code:
#include<iostream>
#include<setjmp.h>
static jmp_buf env;
void func()
{
std::cout<<"Call func."<<std::endl;
longjmp(env, 1);
}
int main()
{
switch(setjmp(env))
{
case 0:
std::cout<<"Initial call of setjmp."<<std::endl;
func();
break;
case 1:
std::cout<<"Jumped back from func."<<std::endl;
break;
default:
break;
}
return 0;
}
setjmp函数的使用限制:
SUSv3和C99规定,对setjmp的调用只能在以下语境中:
- 构成选择或迭代语句中(if、switch、while等)的整个控制表达式。
- 作为一元操作符!的操作对象,其最终表达式构成了选择或迭代语句的整个控制表达式。
- 作为比较操作(==、!=、<等)的一部分,另一操作对象必须是一个整数常量表达式。
- 作为独立的函数调用,且没有嵌入到更大的表达式中。
根据以上限制,类似s = setjmp(env)用法是不符合标准的。