学生空间建设网站,网站如何规范的排版编辑,教学资源库 网站建设,班级文化墙创意设计图片程序地址空间回顾
在C语言学习的时#xff0c;对程序的函数、变量、代码等数据的存储有一个大致的轮廓。在语言层面上存储的地方叫做程序地址空间#xff0c;不同类型的数据有着不同的存储地址。
下图为程序地址空间的存储分布和和特性#xff1a; 使用以下代码来验证一下… 程序地址空间回顾
在C语言学习的时对程序的函数、变量、代码等数据的存储有一个大致的轮廓。在语言层面上存储的地方叫做程序地址空间不同类型的数据有着不同的存储地址。
下图为程序地址空间的存储分布和和特性 使用以下代码来验证一下各个类型的是数据存储是否如图所示
#include stdio.h
#include unistd.h
#include stdlib.hint g_unval;
int g_val 100; int main(int argc, char *argv[], char *env[])
{const char *str helloworld;printf(code addr: %p\n, main);printf(init global addr: %p\n, g_val);printf(uninit global addr: %p\n, g_unval);static int test 10;char *heap_mem (char*)malloc(10);char *heap_mem1 (char*)malloc(10);char *heap_mem2 (char*)malloc(10);char *heap_mem3 (char*)malloc(10);printf(heap addr: %p\n, heap_mem);printf(heap addr: %p\n, heap_mem1);printf(heap addr: %p\n, heap_mem2);printf(heap addr: %p\n, heap_mem3);printf(test static addr: %p\n, test);printf(stack addr: %p\n, heap_mem);printf(stack addr: %p\n, heap_mem1);printf(stack addr: %p\n, heap_mem2);printf(stack addr: %p\n, heap_mem3);printf(read only string addr: %p\n, str);for(int i 0; i argc; i){printf(argv[%d]: %p\n, i, argv[i]);}for(int i 0; env[i]; i){printf(env[%d]: %p\n, i, env[i]);}return 0;
}结果如下
$ ./a.out
code addr: 0x40055d // 正文代码 main()
init global addr: 0x601034 // 未初始化全局变量
uninit global addr: 0x601040 // 初始化的全局变量
heap addr: 0x1791010 // 堆向上增长 ↑
heap addr: 0x1791030
heap addr: 0x1791050
heap addr: 0x1791070
test static addr: 0x601038 // static int 类型
stack addr: 0x7ffd0f9a4368 // 栈向下增长 ↓
stack addr: 0x7ffd0f9a4360
stack addr: 0x7ffd0f9a4358
stack addr: 0x7ffd0f9a4350
read only string addr: 0x400800 // const char *str
argv[0]: 0x7ffd0f9a4811
env[0]: 0x7ffd0f9a4819
env[1]: 0x7ffd0f9a482
env[2]: 0x7ffd0f9a4845
env[3]: 0x7ffd0f9a4850
env[4]: 0x7ffd0f9a4860
env[5]: 0x7ffd0f9a486e通过地址结果的验证可以明确 堆向上增长随着创建申请空间空间地址逐渐变大。栈向下增长随着创建变量变量空间地址逐渐变小。const char*的最字符串常量地址与正文代码的地址相近说明在编译的时候会将该类型硬编到正文代码中所以形成了代码只可读。函数内部的static类型的变量地址与初始化数据中全局变量地址相近因为static类型的变量在编译时就会在初始化数据区域所以就会作为全局变量。则static是全局属性。 虚拟地址 实际上程序的地址空间是内存吗 地址空间不是内存地址而是虚拟地址
在语言层上我们会叫做程序地址空间。但是在系统层面上会将其称为进程地址空间或者虚拟地址空间。
可以通过以下代码来验证
#includestdio.h
#include unistd.hint gval 100;int main()
{pid_t id fork();if (id 0){while (1){printf(子: gval: %d, gval: %p, pid: %d, ppid: %d\n, gval, gval, getpid(), getppid());sleep(1);gval;}}else{while (1){printf(父: gval: %d, gval: %p, pid: %d, ppid: %d\n, gval, gval, getpid(), getppid());sleep(1);}}return 0;
}结果如图 父子进程按照代码逻辑进行运行随着每次睡眠过后子进程的全局变量就会1。但是通过结果可以发现父进程和子进程各自的全局变量地址都是0x601054。明明是同一个地址空间为什么全局变量gval的值不同呢
这就证明了进程的地址空间一定不是内存地址不是物理上的地址而是虚拟地址我们在程序中使用指针指向的地址以及取变量地址等操作实际上都是在访问虚拟地址。
物理地址一般不会向用户展示操作系统会将虚拟地址转化成物理地址虚拟地址由操作系统统一管理。 进程地址空间
基础概念 每个进程都有其虚拟地址空间mm_struct和页表存在于task_struct中每个地址空间1字节。所以对于32位的机器在虚拟地址空间中共有2^32个地址空间64位机器则有2^32地址空间。
页表中存储的是虚拟地址和物理地址的映射关系。
程序在运行时实际上管理的是虚拟地址空间中的地址当程序需要进行管理一个地址的时候操作系统会将该地址在页表中进行查找就可以得到与其对应映射的物理内存地址。然后操作系统会对物理内存地址的数据进行访问管理。
子进程会继承父进程的虚拟地址空间和页表。
如何通过一个字节地址访问多个字节大小的数据 通过地址和类型偏移量确定整个数据。 假设存在一个int变量a当我们通过虚拟地址空间的映射找到物理地址后会通过int类型在结构体中的偏移量进行确定整个数据内容因为所有的数据都是通过先描述后组织进行管理通过对应的数据结构就可以确定数据的位置。
进程如何独立 子进程的虚拟地址空间和页表会继承父进程那么进程之间是怎么独立的呢
假设父进程存在一全局变量int g_val在当前父进程虚拟地址在页表中已经与物理地址映射。然后创建子进程当子进程中尝试对g_val修改时操作系统会进行以下操作
在物理地址空间中会重新开辟一块int大小的空间在此空间内存储修改后的地址。在页表中查询子进程虚拟地址空间中g_val虚拟地址然后将新开辟的物理地址与虚拟地址重新建立映射关系。此时因为继承的关系父进程与子进程中的g_val使用的是同一个虚拟地址但由于子进程对g_val进行修改所以同一个变量的虚拟地址映射的是不同的物理地址。
这就是写时拷贝的机制
所以发生写时拷贝后子进程对于修改的数据会重新构建映射关系而其他的数据、代码、变量等都是共享的物理资源这也避免了重复拷贝的内存的浪费减少创建时间。
**通过这种机制就形成了进程的独立 ** 虚拟地址与进程地址空间关系
通过上文可知对于32位的机器来说每个进程的虚拟地址空间有2^32字节的大小也就是4个G。但如果整个内存只有4G的话那么一个进程就要把所有的内存空间全部占满吗显然不可能。
如其名虚拟地址空间并不是真正的内存空间。操作系统会让每个进程都认为他们可以独占物理内存但是在实际使用的时候会根据真实的需求通过映射关系开辟内存空间。
虚拟地址空间如何从物理内存划分
由于进程不会独占物理内存那么肯定有相对应的划分管理方法。 虚拟地址的本质结构体对象数据结构 mm_struct 中存储的起始地址和结束地址用 int 表示。每个区域的范围是 [start_address, end_address]这些地址用 int 记录下来。例如
struct mm_struct {int code_start; // 代码段起始地址int code_end; // 代码段结束地址int heap_start; // 堆起始地址int heap_end; // 堆结束地址int stack_start; // 栈起始地址int stack_end; // 栈结束地址
};虚拟地址通常是用 int4字节32位 类型存储的而每个 int 值就直接对应一个地址。虚拟地址空间中的地址可以用一个 int 值表示因为 int 的取值范围足够覆盖整个虚拟地址空间的范围0 ~ 232−12^{32} - 1232−1即 4GB。
在32为机器中虚拟地址由2^32字节空间每个区域栈堆…都有自己确定的区域然后堆所有的区域进行编址。虚拟地址空间就是结构体mm_struct里面存放的就是每个区域的起始地址和结束地址对应的int值。
区域调整
既然每个区域的大小是用int值进行确定那么当需要对这个区域大小进行调整的时候区域调整就是对起始和结束的整数范围进行调整。
根据各个区域的特性例如堆向上增长栈向下增长将其对应的start和end进行或者—以此来进行区域的调整。
小结虚拟地址空间是什么
操作系统需要对进程中的虚拟地址空间进行管理虚拟地址空间是内核中的一种数据结构mm_struct大部分属性都是各个区域的开始和结束地址的int值。
先描述在组织。作为数据结构操作系统不仅会对进程进行管理也会对mm_struct进行管理用链表进行管理。但是实际上通过PCB也可以直接访问到mm_struct。