第7章 对内存的思考
Intel 80x86处理器存在着许多架构上的不规整性,在保持芯片向后兼容的同时,不得不限制一些革新。
在Intel 80x86内存模型中,段是内存模型设计的结果,由于要保持兼容性,各种处理器的地址空间并不一致,但都被分割成以64kB为单位的区域,每个这样的区域便称为段。
Intel 80x86处理器的内存地址是通过组合16位的段地址和16位的偏移地址形成的。在加上偏移地址之前,段地址要左移4位。这就意味着许多不同段地址/偏移地址组合可能指向同一个内存地址。
在microsoft C中,far关键字表示指针存储了段寄存器的内容和偏移地址。near关键字表示指针只存储16位的偏移地址,它的段地址使用当前数据段或堆栈段寄存器中的值。
今天计算机系统结构的真正挑战不在于内存的容量,而是内存的速度。
虚拟存储的基本思路是使用廉价的磁盘来扩充快速却昂贵的内存。在任意给定时刻,程序实际需要使用的虚拟内存区段的内容就被载入物理内存中。当物理内存中的数据有一段时间未被使用,就把它们转移到硬盘中,以便节省物理内存空间用于载入需要使用的其它数据。
操作系统采用虚拟存储实现,所有进程共享机器的物理内存,当内存用完时就用磁盘保存数据。在进程运行时,数据在磁盘和内存之间来回移动。内存管理硬件负责把虚拟地址翻译为物理地址,并让一个进程始终运行于系统的真正内存中。
虚拟内存通过页的形式组织。页就是操作系统在磁盘和内存之间移来移去或进行保护的单位。
在磁盘中有一个特殊的交换区,用于保存从内存中换出暂时不运行的进程。在一台机器中,交换区的大小一般是物理内存的几倍。
虚拟内存已经成为一项操作系统中不可或缺的技术,它允许多个进程运行于较小的物理内存中。
Cache存储器是多层存储概念的更深扩展。它的特点是容量小、价格高、速度快。Cache位于CPU和内存之间,是一种极快的存储缓冲区。
就像堆栈段能够根据需要自动增长一样,数据段也包含了一个对象,用于完成这项工作,这就是堆。堆区域用于动态分配的存储,也就是通过malloc(内存分配函数)获得的内存,并通过指针访问。
用于内存管理的调用是:
malloc和free—从堆中获得内存以及把内存返回给堆。
brk和sbrk—调整数据段的大小至一个绝对值(通过某个增量)。
由于C语言并不自动确认并回收不再使用的内存块,堆经常会出现两种类型的问题:
释放或改写仍在使用的内存(称为“内存损坏”);
未释放不再使用的内存(称为“内存泄露”)。
这是最难被调试发现的问题之一。如果每次分配的内存块不再使用而程序员并不释放它们,进程就会一边分配越来越多的内存,一边却并不释放不再使用的那部分内存。如果出现内存泄露,最终可能导致可以分配的内存无法满足内核的需要,结果每个内核程序都无限制地等待—于是机器便被挂起。
总线错误几乎都是由于未对齐的读和写引起的,出现未对齐的内存访问请求时,地址总线组件被阻塞。对齐就是数据项只能存储在地址是数据项大小的整数倍的内存位置上。编译器通过自动分配和填充数据(在内存中)来进行对齐。
段错误是由于内存管理单元(MMU)的异常所致,而该异常通常是由于解除引用一个未初始化或非法的指针引起的。
通常导致段错误的几个直接原因:
解除引用一个包含非法值的指针。
解除引用一个空指针(常常由于从系统程序中返回空指针,并未检查就使用)。
在未取得正确的权限时进行访问。
用完了堆栈或堆空间(虚拟内存虽然巨大但绝非无限)。
在绝大多数架构的绝大多数情况下,总线错误意味着CPU对进程引用内存的一些做法不满,而段错误则是MMU对进程引用内存的一些情况发出抱怨。
可能导致段错误的常见编程错误是:
1、坏指针错误:在指针赋值之前就用它来引用内存,或者向库函数传送一个坏指针,以及对指针释放之后再访问它的内容;
2、改写错误:越过数组边界写入数据,在动态分配的内存两端之外写入数据,或改写一些堆管理数据结构。
3、指针释放引起的错误:释放同一内存块两次,或释放一块未曾使用malloc分配的内存,或释放仍在使用中的内存,或释放一个无效的指针。
文章评论(0条评论)
登录后参与讨论