标签归档:虚拟存储器

虚拟存储器--小结

虚拟存储器是对主存的一个抽象。
支持虚拟存储器的处理器通过使用一种叫做虚拟寻址的间接形式来引用主存。处理器产生一个虚拟地址,在被发送至主存之前,这个地址被翻译成一个物理地址。从虚拟地址空间到物理地址空间翻译要求硬件和软件紧急合作。专门的硬件通过使用页表来翻译虚拟地址,而页表的内容是由操作系统提供。
虚拟存储器提供三个重要的功能。
第一:它在主存中自动缓存最近使用的存放磁盘上的虚拟地址空间的内容。虚拟存储器缓存中的块叫做页。对磁盘上页的引用会触发缺页处理程序,缺页将控制转移到操作系统的一个缺页处理程序。缺页处理程序将页面从磁盘拷贝到主存缓存中。
第二:虚拟存储器简化了存储器管理,简化了链接 进程间共享数据 进程间存储器分配 以及程序加载
第三:虚拟存储器通过在每条页条目中加入保护位,简化存储器的保护。
地址翻译的过程必须和系统中任意硬件缓存的操作集成在一起。大多数页表条目位于L1高速缓存中,但是一个称为TLB条目在芯片上的高速缓存,通常会消除访问在L1页表条目的开销。
现代系统通过将虚拟存储器块和磁盘上的文件组块关联起来,来初始化虚拟存储器组块,这个过程称为存储器映射。存储器映射为共享数据、创建新的进程以及程序加载,提供一种高效的机制,
1 应用mmap函数手工创建和删除虚拟地址空间的区域
2 通过malloc free动态创建和管理虚拟地址空间区域

相关阅读:
虚拟存储器之–垃圾收集
常见的虚拟存储器错误
malloc free函数的用法
malloc的使用方法及注意事项
为什么要使用动态存储器分配(malloc)

linux c 常见的错误之存储器

对c语言程序猿来说: 管理和使用虚拟存储器可能是一个容易出错和苦难的任务。与存储器有关的错误属于那些令人费解的错误,因为它经常在时间和空间上,都在距离源一段距离之后,才表现出来。将错误的数据编写到错误的位置上,你的程序可能在最终失败之前运行好几个小时或好几天,且使程序中止的位置同错误的位置已经很远,下面将举例说明一些常见的错误:

间接引用坏指针

在进程的虚拟地址空间中存在较大的未被分配空间,没有映射到任何有意义的数据,如果我们试图间接引用这些信息的指针,那么操作系统就会以段异常中止我们的程序(内存越界访问),而且虚拟存储器的有些区域为只读区域,试图写这些区域也会发生内存保护性错误。
如 常见的间接引用坏指针 scanf, scanf从标准输入中读取一个整数到一个变量中,常见的方法为:
scanf(“%d”,&a);

如果我们写成这样 scanf(“%d”,a) 那么此时系统就会把整型a的内容解析为一个地址传送给变量
此时我们获取变量通常会发生两种错误:
1 程序直接报段错误
2 a整型变量所指向的值为当前进程合法的地址,此时将导致获取变量的值发生错误,可能会产生令人费解的答案。


读未初始化的存储器

通常情况下.bss存储器位置(未初始化的c变量),在加载时会被初始化零,但是对于堆存储器有时候却不一样,
例:一个堆存储器未初始化零
int *m(int **a,int *b,int n){
int i,j;
int *y = (int *)malloc(n*sizeof(int));
for(i=0;i < n ; i++ ) for(j=0;j < n ; j++ ) y[i] +=a[i][j]*b[j];
return y;
}
以上的例子,正确的操作方式应该赋值 y[i] =0; 不然的话 在y[i] +..时,系统会报数据未定义的错误。

允许栈缓冲区溢出

例:一个输入字符串不检查字符串的大小时就写入栈中的目标缓冲区,那么就会发生缓冲区溢出错误:
void a(){
char buf[64];
gets(buf); //输入超过64个字符将产生错误
return ;
}

指针和它们指向的对象是相同大小的

1 int **m(int m,int n){
2 int i;
3 int **a =(int **)malloc(n*sizeof(int));

4 for(i=0;i
造成错位错误

错位错误是一种常见的覆盖错误:
1 int **m(int m,int n){
2 int i;
3 int **a =(int **)malloc(n*sizeof(int *));

4 for(i=0;i
引用指针,被错误的操作对象,或操作错误的对象



误解指针运算


指针的算术操作是以它们指向的对象的大小为单位来进行的。
而这种大小并不一定是字节,如下所示:
函数的目的是扫描一个int数组,并返回一个指针,指向val的首次出现:
int *s(int *a,int val){
while(*a && *a !=val)
a +=sizeof(int);
return a;
}
这个遍历的错误之处在于,每循环一次就跳过了4条记录。


引用不存在的变量

int *t(){
int b;
return &b;
}
这个函数返回一个指针,指向栈里的一个局部变量,尽管b也指向一个合法的存储器地址,但是它已经不再指向一个合法的变量,当以后程序中有其它函数这个地址上的值时,可给程序带来某明奇妙的错误。

引用空闲堆块中的数据

malloc free 后再引用刚刚分配空间的变量,将会产生错误。


存储器泄漏

malloc分配内存后未释放

相关阅读:
malloc free函数的用法
malloc的使用方法及注意事项
为什么要使用动态存储器分配(malloc)

空闲块合并




额外堆存储器

如果分配器不能为请求块找到合适的空闲块。一个选择就是通过合并哪些在存储器中物理上相邻的空闲块来创建一些更大的空闲块。如果这样还是不能生成一个足够大的块,且空闲块已经是最大程度的合并,此时分配器就会向内核请求额外的堆存储器,此时要么调用mmap函数,或sbrk函数,此时分配器会将额外的存储器转换成一个大的空闲块,将这个块插入到空闲链表中,然后再将请求的块放入这个新的空闲块中。



合并空闲块

当分配器释放一个已分配块时,可能有其它空闲块与这个新释放的块相邻,这些相邻的块可能会形成一个假碎片现象,对付这种假碎片现象,任何实际的分配器都必须合并相邻的空闲块,这个过程称为合并操作。
常见的合并方式有:立即合并 推迟合并两种策略。



边界标记合并

边界标记的方法就是在每个块的脚步定义个同头部一样的标记,可以记载块的状态,当后一个块释放时,直接检测前一个块的状态,可以快速的进行相邻的块合并。
边界标记策略是Knuth提出的一个聪明通用的技术,允许在常数时间内进行对前面块的合并,这种思想是在每个块的结尾处添加一个脚部,其中脚步就是头部的一个副本。如果每个块包括这样一个脚部,那么分配器就可以通过检查前面一个块的脚步,来判断前面一个块的起始位置和状态。



为了使空闲块可以进行快速合并,分配器采用了新的策略:边界标记合并来快速合并空闲块。

相关阅读:
mmap函数
free函数用法
碎片
隐式空闲链表