P141
Intel 80x86 内存模型以及它的工作原理
在Intel 80x86 内存模型中,段是内存模型设计的结果,各处理器的地址空间并不一致,但它们都被分割成以64KB为单位的区域,每个这样的区域便称为段。
内存地址的形成经过是:取得段寄存器的值,左移4位(相当于乘上16),或者换一种思路,把段寄存器的值看成20位的,也就是在值的右边扩充4个0。然后就是16位的偏移地址,它表示段内的地址。如果把段寄存器的值(经过移位)加上偏移地址,就得到最终的地址。
P143
在讨论数字概念时,需要注意所有的磁盘制造商都是使用十进制数而不是二进制来表示磁盘的容量。所以2GB的磁盘可以存储2000000000个字节的数据而不是2147483648个字节。
P145
虚拟内存
它的基本思路是利用廉价但是缓慢的磁盘来扩充快速却昂贵的内存。 在一给定时刻,程序实际需要使用的虚拟内存区域的内容被载入物理内存中。当物理内存中的数据有一段时间未被使用,他们就可能被转移到硬盘中,节省下来的物理内存空间用于载入需要使用的其他数据。
下图显示了有关虚拟内存的一些基础知识。
虚拟内存通过“页”的形式组织。页就是操作系统在磁盘和内存之间移来移去或进行保护的单位,一般为几K字节。
如果进程可能不会马上运行(可能他的优先级低,也可能是它处于睡眠状态),操作系统可以暂时取回所有分配给它的物理内存资源,将该进程的所有相关信息都备份到磁盘上。在磁盘中有一个特殊的“交换区”,用于保存从内存中换出的进程。在一台机器中,交换区的大小一般是物理内存的几倍。
P148
Cache存储器
Cache存储器是多层存储概念的更深扩展。它的特点是容量小、价格高、速度快。Cache位于CPU和内存之间,是一种极快的存储缓冲区。
下图显示了Cache存储器的基本知识。当数据从内存读入时,整“行”(一般为16或者32字节)的数据被装入Cache。如果程序具有良好的地址引用局部性(如:它顺序浏览一个字符串),那么CPU以后对邻近数据的引用就可以从快速的Cache读取,从而不用从缓慢的内存中读取。Cache操作的速度与系统的周期时间一样,所以一个50MHZ的处理器,其Cache的存储周期为ns。在典型情况下,主存的存取速度可能只有它的四分之一!与常规的内存相比,Cache要贵得多。所以,在系统中我们把它作为存储系统的附加部分,而不是把它作为唯一的存储形式。
P152
数据段和堆
在本书的上一章讲过关于堆栈段的描述,而数据段和堆却出现在这一章,我也没有搞懂作者为何要这样分配。
堆在内存中的位置如下图所示。
堆区域用于动态分配的存储,也就是通过
malloc(内存分配)函数获得的内存,并通过指针访问。堆中的所有东西都是匿名的——不能按名字直接访问,只能通过指针间接访问。从堆中获取数据的唯一办法就是通过调用malloc(以及同类的calloc、realloc等)库函数。
calloc()函数在返回指针之前先把分配好的内存的内容都清空为零。realloc()函数改变一个指针所指向的内存块的大小,既可以扩大也可以缩小。
内存泄漏: 未释放不再使用的内存
如何检测内存泄漏
- 第一步,使用
swap命令观察还有多少可用的交换空间:/usr/sbin/swap -s。在一分钟内键入该命令三到四次,看看可用的交换区是否在减少。 - 第二步就是确定可以的进程。
P158
总线错误: 总线错误几乎都是由于未对齐的读或者写引起的。
一个会引起总线错误的小程序:
union {
char a[10];
int i;
}u;
int *p = (int *)&(u.a[1]);
*p = 17;
这将导致一个总线错误,因为数组和
int的联合确保数组a是按照int的4字节对齐的,所以a+1的地址肯定未按照int对齐。然后我们试图往这个地址存储4个字节的数据,但这个访问只是按照单字节的char对齐,这就违反了规则。
段错误
最终可能导致段错误的常见编程错误是:
- 坏指针值错误: 在指针赋值之前就用它来引用内存,或者想库函数传送一个坏指针。第三种可能是对指针进行释放之后再访问他的内容。可以修改free语句,在指针释放后再将它置为空值。
- 改写(overerite)错误: 越过数组的边界写入数据,在动态分配的内存两端之外写入数据,或改写一些堆管理数据结构。
- 指针释放引起的错误: 释放同一个内存块两次,或释放一块未曾使用
malloc分配的内存,或释放仍在使用中的内存,或释放一个无效的指针。