目录
1 虚拟地址
我们在程序中看到的或使用的地址,不是真实的物理内存的地址,而是经由系统内核的内存管理系统管理后所看到的虚拟地址。
虚拟地址和物理地址之间存在映射对应关系。
内存管理系统一方面保证物理内存的安全,避免物理内存被直接操作。
同时降低了程序员的编码难度(不用手算物理内存地址了)。
其他概念:
磁盘上用来缓存物理内存数据的部分,称为交换分区,windows叫虚拟内存。
2 虚拟地址空间
对于32位操作系统而言,每个进程所得到的虚拟地址都在一个固定的范围内,该范围称为虚拟地址空间。
虚拟地址空间本质是一个地址范围,表示程序的寻址能力,和物理内存大小无关(32位系统有4G虚拟地址空间,可用在512M内存条)。我们所看到的虚拟地址,都在这个范围内。
32位操作系统,虚拟地址空间范围从0x0000 0000到0xFFFF FFFF,共4G:
其中,0~3G-1范围归用户使用,称为用户地址空间。
其中,3G~4G-1范围归内核使用,称为内核地址空间。
64位操作系统,目前应用程序的内存需求不大,所以不支持完全的64位虚拟地址:
用户地址空间范围0x0000 0000 0000 0000~0x0000 FFFF FFFF FFFF(256T?128T?)
内核地址空间范围0xFFFF 0000 0000 0000~0xFFFF FFFF FFFF FFFF(256T?128T?)
内核地址空间和用户地址空间之间是不规范地址空间,不允许使用。
用户地址空间的代码不能直接防范内核空间的代码和数据,但可以通过系统调用进入内核态,间接与系统内核交互。
3 虚拟地址空间布局
程序中不同性质的数据,加载到内存后,其虚拟地址会被映射到虚拟地址空间中不同区域。
用户地址空间,从低地址到高地址布局如图:(数据区和BSS区可统称为数据区)
//map.c 虚拟地址空间布局
#include<stdio.h>
#include<stdlib.h> // malloc()
const int const_global = 10;//常全局变量
int init_global = 20;//初始化全局变量
int uninit_global;//未初始化全局变量
int main(int argc,char* argv[],char* envp[]){
static const int const_static = 30;//常静态变量
static int init_static = 40;//初始化静态变量
static int uninit_static;//未初始化静态变量
const int const_local = 50;//常局部变量
int local = 60;//局部变量
char* string = "hello";//字面值常量
int* heap = malloc(sizeof(int));//堆变量
printf("---------参数和环境区---------\n");
printf(" 命令行参数:%p\n",argv);
printf(" 环境变量:%p\n",envp);
printf("-------------栈区-------------\n");
printf(" 常局部变量:%p\n",&const_local);
printf(" 局部变量:%p\n",&local);
printf("-------------堆区-------------\n");
printf(" 堆变量:%p\n",heap);
printf("------------BSS区-------------\n");
printf(" 未初始化全局变量:%p\n",&uninit_global);
printf(" 未初始化静态变量:%p\n",&uninit_static);
printf("------------数据区------------\n");
printf(" 初始化全局变量:%p\n",&init_global);
printf(" 初始化静态变量:%p\n",&init_static);
printf("------------代码区------------\n");
printf(" 常全局变量:%p\n",&const_global);
printf(" 常静态变量:%p\n",&const_static);
printf(" 函数:%p\n",main);
printf(" 字面值常量:%p\n",string);
printf("------------------------------\n");
return 0;
}
编译执行,结果如下:
uc_03$gcc map.c -o map
uc_03$./map
---------参数和环境区---------
命令行参数:0x7fff2fa44938
环境变量:0x7fff2fa44948
-------------栈区-------------
常局部变量:0x7fff2fa44830
局部变量:0x7fff2fa44834
-------------堆区-------------
堆变量:0x871010
------------BSS区-------------
未初始化全局变量:0x601060
未初始化静态变量:0x60105c
------------数据区------------
初始化全局变量:0x601050
初始化静态变量:0x601054
------------代码区------------
常全局变量:0x400868
常静态变量:0x400ae0
函数:0x400626
字面值常量:0x40086c
------------------------------
uc_03$
3.* const int a = 5;
该变量在函数外,就是常全局变量,地址被映射到虚拟地址空间-用户地址空间的代码区。
该变量在函数内,就是非静态局部变量,地址被映射到虚拟地址空间-用户地址空间的栈区;
3.* 递归函数栈溢出?;
问:函数地址在代码区,为什么递归函数不当,却显示栈溢出?
答:函数的本质是代码块。
调用函数 == 执行代码块,此时是通过函数地址(代码区)调用函数没错。
但函数中局部变量的地址是在栈区中的,栈帧可下移,过量时栈溢出。
4 内存映射的建立与解除
4.1 mmap() 内存映射的建立
malloc()底层调用的就是mmap()。
没有与物理地址建立映射关系的虚拟地址,无法直接使用,我们可以通过系统调用mmap函数,来手动建立虚拟地址和物理地址之间的映射关系。
#include <sys/mman.h>
void* mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset);
-功能:建立虚拟内存 到 物理内存或磁盘文件的映射
-参数:start 映射区虚拟内存的起始地址,NULL则系统自动选定后返回,推荐。
length 映射区字节数,自动按页圆4096取整。多页在虚拟内存空间中连续,
在物理内存空间中不一定连续。(同一页圆内,一定连续)
prot 映射区操作权限,可取以下值:
PROT_READ 映射区可读
PROT_WRITE 映射区可写
PROT_EXEC 映射区可执行
PROT_NONE 映射区不可访问
flags 映射标志,可取以下值:
MAP_ANONYMOUS 匿名映射,将虚拟内存映射到物理内存而非文件,
忽略fd和offset函数
MAP_PRIVATE 对映射区的写操作只反映到缓冲区,并不真正写入文件
MAP_SHARED 对映射区的写操作直接反映到文杰中
MAP_DENYWRITE 拒绝其它对文件的写操作
MAP_FIXED 若在start上无法创建映射,则失败(无此标志系统会自动调整)
fd 文件描述符
offset 文件偏移量,自动按页(4K)对齐
-返回值 成功返回映射区虚拟内存的起始地址,失败返回MAP_FAILED(-1)
//mmap.c 内存映射
#include<stdio.h>
#include<sys/mman.h>// mmap() munmap()
#include<string.h>
int main(void){
//建立内存映射
char* start = mmap(NULL,8192,PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS,0,0);
if(start == MAP_FAILED){
perror("mmap");
return -1;
}
//使用映射得到的虚拟地址访问内存
strcpy(start,"铁锅炖大鹅");
printf("%s\n",start);
//解除第一个页映射
if(munmap(start,4096) == -1){
perror("munmap");
return -1;
}
//printf("%s\n",start); //段错误
//第二个页的起始虚拟地址
char* start2 = start + 4096;
strcpy(start2,"小鸡炖蘑菇");
printf("%s\n",start2);
//解除第二个页的映射
if(munmap(start2,4096) == -1){
perror("munmap");
return -1;
}
//printf("%s\n",start2); //段错误
return 0;
}
4.2 munmap() 内存映射的解除
#include<sys/mman.h>
int munmap(void* start, size_t length);
-功能:解除虚拟内存到物理内存或磁盘文件的映射
-参数:start 映射区虚拟内存的起始地址。
length 映射区字节数,自动按页圆取整。
-返回值:成功返0,失败返-1
munmap()允许对映射区的一部分解除映射,但必须按页处理