虚拟地址空间
对于32位操作系统而言,每个进程都有4G大小的虚拟地址空间。
地址范围,表示程序的寻址能力。我们所谓的虚拟地址空间本质就是一个地址范围。看到的虚拟地址,都是在这个4G的范围内
对于32位操作系统而言,其虚拟地址空间范围从0x0000 0000到0xFFFF FFFF
其中0~3G-1的范围称为用户地址空间,归用户所使用,3G~4G-1的范围归内核使用,称为内核地址空间。
64位系统上,其用户地址空间范围是:
0x0000 0000 0000 0000~0x0000 FFFF FFFF FFFF
64位系统上,其内核地址空间范围是。
0xFFFF 0000 0000 0000~0xFFFF FFFF FFFF FFFF内核地址空间和用户地址空间之是不规范地址空间,不允许使用。
用户地址空间的代码不能直接访内核空间的代码和数据,但可以通过系统调用进入内核态,间接与系统内核交互
代码实现:
#include<stdio.h>
#include<stdlib.h>
const int const_global = 10;//常局部变量
int init_global = 10;//初始化全局变量
int uninit_global;//未初始化全局变量
int main(int argc, char *argv[], char *envp[]){
const static int const_global = 10;//常静态局部变量
static int init_static = 10;//初始化静态局部变量
static int uninit_static;//未初始化静态局部变量
const int const_local = 10;//常局部变量
int local;//未初始化局部变量
char* str = "hello world";//字符串常量
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_global);
printf(" 字符串常量:%p\n",str);
printf(" main函数:%p\n",&main);
printf("------------------------------------\n");
return 0;
}
代码运行结果(观察地址大小):
段错误
->一切对虚拟内存的越权访问,都会导致段错误
->试图访问没有映射到物理内存的虚拟内存试图以非法方式访问虚拟内存,如对只读内存做写操作等
代码实现:
//段错误演示
#include<stdio.h>
int main(){
/*int *p = (int*)0x12345678;
*p = 1;//访问非法地址,导致段错误*/
const int i = 0;
//i = 1;//试图修改常量,导致段错误
//i-->const int &i-->const int*
*(int*)&i = 2;
printf("i = %d\n",i);
return 0;
}
内存映射的建立与解除
#include <sys/mman.h>
void* mmap(void* start, size t length, int prot, int flagsint fd, off t offset);
->功能:建立虚拟内存到物理内存或磁盘文件的映射:
->参数:start:映射区虚拟内存的起始地址,NULL系统自动选定后返回,length:映射区字节数,自动按页(1页4KB即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)。
#include <sys/mman.h>
int munmap(void* start, size t length);->功能:解除虚拟内存到物理内存或磁盘文件的映射
->参数:start:映射区虚拟内存的起始地址。
length:映射区字节数,自动按页圆整,返回值:成功返回0,失败返回-1。
munmap允许对映射区的一部分解映射,但必须按页处理
代码实现:
//内存映射的建立与解除
#include<stdio.h>
#include<string.h>
#include<sys/mman.h>
int main(){
//申请空间,建立映射
void* start = mmap(NULL,8192,PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,0,0);
start = (char*)start;//强制类型转换
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;
}