虚拟内存
系统中的每一个进程运行的时候都会认为自己有连续和完整的的地址空间,这些地址空间被称作虚拟地址。数据和程序被存储在固态介质中,当系统系统需要某一段数据或程序时就会被搬运到内存当中,而不是将整个程序搬运到内存当中再运行起来,这样大大提高了程序的运行效率,和节约了内存空间。
如上图所示,每个程序都认为自己有连续和完整的地址,实际上只占了内存的一小部分。内存会生成每块为4K大小的页框,存在固态介质中的程序也会分成每块4k的页,页框用来存放页的内容,页框和页分别有页框码和页码,MMU将会通过这两个码建立页表。
当处理器试图访问一个虚拟内存页面时,先去页表中去查询该页是否已映射到页框中。如果在,则MMU会把页码转换成页框码,并加上虚拟地址提供的页内偏移量形成物理地址后去访问物理内存,再根据物理内存地址去访问虚拟内存;如果不在,则意味着该虚存页面还没有被载入内存,这时MMU就会通知操作系统:发生了一个页面访问错误(页面错误),接下来系统会启动所谓的“请页”机制,即调用相应的系统操作函数,判断该虚拟地址是否为有效地址。
虚拟内存的布局
注意:
图中有两段空间是不可访问的。
栈内存
栈内存属于自动存储区,内存空间的分配,释放由系统自动完成,不需要用户参与,栈内存中存了,环境变量,命令行参数,函数调用产生的局部变量。地址是从高地址向地址值增长的。
栈内存的特点:
1.空间有限,尤其嵌入式的环境中。尽量不要去使用过多的栈空间;
2.每当一个函数被调用的时候就会产生一个新的栈空间(栈帧),用来存放该函数的局部变量;
3.每当一个函数退出的时候他的栈帧将会被释放。释放后内存的内容还在,只不过内存已经归还给系统进行下一次分配。
ulimit -a //查看栈的大小
ulimit -s 1024 //临时将栈设置成1024K的大小
数据段
数据段分为三种数据,分别是未初始化的静态变量(.bss段),已初始化的静态数据(.data)和常量(.rodata)。
静态数据有全局变量(定义在函数体外面的变量)和被关键字static修饰的局部变量。
int kl ; // 全局变量 , 未初始化的静态变量
int func(int arg , char const * argv[])
{
int a ; // 未初始化的局部变量
static char b ; // 使用static 修饰的局部变量,未初始化的静态变量
static int j = 0 ;// 使用static 修饰的局部变量,并初始化为0
return 0 ;
}
注意:
1.未初始化的静态变量被存放到.bss段中,并且它的值全部零(被初始化为0;
2.初始化语句只会被执行一次;
3.静态数据从程序开始运行就已经存在,直到程序退出才会被释放;
4.static 修饰的局部变量:会把原本应该存放在栈空间的数据改为存放到数据段中;
5.static 修饰的全局变量:使得原本是所有文件可见变为本文件可见(缩小了可见范围)。
代码段
代码段分为.text(用户代码)和.init(系统初始化代码)
堆空间
堆空间(heap)又称为堆内存、自由内存。由于该区域的内存分配以及释放都由用户自己决定。
特征:
相对与栈内存来说他的内存空间非常大,堆空间的大小只受限于物理内存,系统不会对堆内存有过多的干预;
堆内存从低地址往高地址增长;
堆内存的名字都是匿名,只能通过指针来访问;
堆内存的空间只有用户手动释放,不然他永远都存在,直到程序退出;
堆内存空间操作的API接口:
申请: malloc / calloc / realloc
释放: free
清空: bzero / memset
malloc
头 文 件:#include <stdlib.h>
定义函数:void * malloc(size_t size);
参数分析:size --> 需要申请内存空间字节大小(堆内存)
返 回 值:成功 返回一个指针指向该空间的起始地址 失败 返回NULL
free
头 文 件:#include <stdlib.h>
定义函数:void free(void *ptr);
参数分析:ptr --> 需要释放的原先配置内存的起始地址
返 回 值:无
示例代码:
int *p_int = NULL ;
p_int=(int *)malloc(4);//在堆内存中申请一篇4字节的空间
*p_int = 1900 ;
printf("*p_int:%d
" , *p_int);
printf("p_int:%p
" , p_int);
printf("&p_int:%p
" , &p_int);
free(p_int); // 释放掉 p_int所指向的内存空间
p_int = NULL ;
calloc
头 文 件:#include <stdlib.h>
定义函数:void *calloc(size_t nmemb, size_t size); //申请成功后还会将申请的内存清零
参数分析:nmemb --> 需要申请的内存块数(多少个房间;size --> 每一块内存的大小 (房间的大小)
返 回 值:成功 返回内存的入口地址 失败 返回NULL
int * p = calloc(10 , sizeof(int));
for (int i = 0; i < 10 ; i++)
{
printf("p +_ %d : %d
" , i , *(p + i) ); //可以发现打印出来的值都是0
}
realloc
头 文 件:#include <stdlib.h>
定义函数:void *realloc(void *ptr, size_t size);
参数分析:ptr --> 需要重新分配的内存的首地址 (老房子) size --> 新内存的大小
返 回 值:成功 返回新的入口地址 失败 NULL
bzero
头 文 件:#include <string.h>
定义函数:void bzero(void *s, int n);
参数分析:s --> 需要清空内存的起始地址 n --> 需要清空数据的大小
返 回 值:无
memset
头 文 件: #include <string.h>
定义函数: void * memset(void *s, int c, size_t n);
参数分析:s --> 需要清空内存的起始地址 c --> 需要填入的内容(字符型) n --> 需要填入的字节数
返 回 值:成功 返回指向 s 的指针 失败
申请时注意:
1.memset 是按一字节来填充内存的,unsigned char, 所以范围在 0 到 255 之间;
2.malloc申请内存,他的初始值是随机值,一般需要使用bzero/memset来清空;
3.calloc 申请内存,默认初始化为0;
4.不可以修改动态分配内存的指针,不然会发生段错误。
释放时注意:
1.释放意味着这篇内存交换给系统;
2.释放内存是不会改变指针的指向的,需要手动指向NULL;
3.释放内存只是单纯的交还给系统,并没有对内存的内容有任何的修改;
4.free 只能释放堆空间 ,不可以释放其他内存区的内容。
“全家福”
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//函数声明
void func (void );
// 全局变量
int d ;// 数据段 .bss 未初始化的静态数据 int e = 100 ; // 数据段 .data 已初始化的静态数据
int main()
{
int a = 100 ; // 栈空间
static int b ; //数据段 .bss 未初始化的静态数据
static int c = 100 ; // 数据段 .data 已初始化的静态数据
char * p = "Hello " ; // 数据段 .rodata 常量区
char *p1 = malloc(4); // p1 在栈空间,指向堆空间
printf("&a:%p
" , &a );
printf("p1:%p
" , p1 );
printf("&b:%p
" , &b );
printf("&c:%p
" , &c );
printf("&d:%p
" , &d );
printf("&e:%p
" , &e );
printf("p:%p
" , p );
printf("&func:%p
" , &func );
return 0;
}
void func (void )
{
//代码段
return ;
}