1. 不读入内存就无法运行
计算机中主要的存储部件是内存和磁盘。
磁盘中存储的程序,必须要加载到内存后才能运行。
在磁盘中保存的原始程序是无法直接运行的。这是因为,负责解析和运行程序内容的CPU,需要通过内部程序计数器来指定内存地址, 然后才能读出程序。
![](https://i-blog.csdnimg.cn/blog_migrate/60815d6f4d35d1d83bc8460ad9744f5d.png)
2. 磁盘缓存加快了磁盘访问速度
磁盘缓存(dik cache)指的是把从磁盘中读出的数据存储到内存空间中的方式。
这样,当接下来需要读取同一数据时,就不用通过实际的磁盘,而是从磁盘缓存中把内容读出。
使用磁盘缓存可以大大改善磁盘数据的访问速度(图5-2)。
![](https://i-blog.csdnimg.cn/blog_migrate/913dcd93e747cf7bd23c620938a53755.png)
3. 虚拟内存把磁盘作为部分内存来使用
虚拟内存(virtual memory)是指把磁盘的一部分作为假想的内存来使用。这与磁盘缓存是假想的磁盘(实际上是内存)相对,虚拟内存是假想的内存(实际上是磁盘)。
通过借助虚拟内存,在内存不足时也可以运行程序。
不过,虚拟内存虽说是把磁盘作为内存的一部分来使用,但实际上正在运行的程序部分,在这个时间点上是必须存在在内存中的。
也就是说,为了实现虚拟内存,就必须把实际内存(也可称为物理内存)的内容,和磁盘上的虚拟内存的内容进行部分置换(swap),并同时运行程序。
虚拟内存的方法有分页式和分段式两种。
Windows采用的是分页式。该方式是指,在不考虑程序构造的情况下,把运行的程序按照一定大小的页(page)进行分割,并以页为单位在内存和磁盘间进行置换。
在分页式中,把磁盘的内容读出到内存称为Page In, 把内存的内容写入磁盘称为Page Out。
![](https://i-blog.csdnimg.cn/blog_migrate/ce6811298d92624ee59847fc94d47e2c.png)
4. 节约内存的编程方法
许多人可能会认为,通过借助磁盘虚拟内存就可以解决内存不足的问题。而虚拟内存也确实能避免因内存不足导致的应用无法启动。
不过,由于使用虚拟内存时发生的Page In和Page Out往往伴随着低速的磁盘访同,因此在这个过程中应用的运行会变得迟钝起来。
为了从根本上解决内存不足的问题,需要增加内存的容量,或者尽量把运行的应用文件变小。
4.1 通过DLL文件实现函数共有
DLL(Dynamic Link Library),是在程序运行时可以动态加载Library(函数和数据的集合)的文件。
多个应用可以共有同一个DLL文件,从而可以达到节约内存的效果。
例如, 假设我们编写了一个具有某些处理功能的函数MyFunc()。应用A和应用B都会使用这个函数。
在各个应用的运行文件中内置函数MyFunc()(这个称为Static Link,静态链接)后同时运行这两个应用,内存中就存在了具有同一函数的两个程序。
但这会导致内存的利用效率降低。所以,有两个同样的函数,还是有点浪费(图5-5)。
![](https://i-blog.csdnimg.cn/blog_migrate/e95084228532e993541780772bc3d3ac.png)
那么,如果函数MyFunc()是独立的DLL文件而不是应用的执行文件(EXE文件)。
由于同一个DLL文件的内容在运行时可以被多个应用共有,因此内存中存在的函数MyFunc()的程序就只有1个。
这样一来,内存的利用效率也就提高了。
![](https://i-blog.csdnimg.cn/blog_migrate/e4a9fac3712fcdcc1d76eafa8fe897ee.png)
DLL文件还有一个优点是,在不变更EXE文件的情况下,只通过升级DLL文件就可以更新。
4.2 通过调用_stdcall来减小程序文件的大小
通过调用_stdcall来减小程序文件的方法,是用C语言编写应用时可以利用的高级技巧。
C语言中,在调用函数后,需要执行栈清理处理指令,是指把不需要的数据从接收和传递函数的参数时使用的内存上的栈区域中清理出去。
该命令不是程序记述的,而是在程序编译时由编译器自动附加到程序中的。编译器默认将该处理附加在函数调用方。
例如, 在代码清单5-1中,从函数main中调用了函数MyFunc()。
按照默认设定, 栈的清理处理会附加在函数main() 这一方。
在同一个程序中,同样的函数可能会被多次反复调用。而如果是同样的函数,栈清理处理的内容也是一样的。
由于该处理是在调用函数一方,因此就会导致同一处理披反复进行。这就造成了内存的浪费。
将代码清单5-1中调用函数MyFunc()的部分用汇编语言来表示,就如代码清单5-2所示。最后1行的处理就是清理处理。
![](https://i-blog.csdnimg.cn/blog_migrate/b30df39d8ee91bdc125d668cd8ddbe60.png)
C语言通过栈来传递函数的参数。
push是往栈中存入数据的指令。32位CPU中,1次push指令可以存储4个字节的数据。
代码清单5-2中,由于使用了两次push指令把两个参数(456和123) 存入到了栈中,因此总的来说就是存储了8字节的数据。
通过call指令调用函数MyFunc()后,栈中存储的数据就不再需要了。
于是这时就通过add esp, 8这个指令,使存储着栈数据的esp寄存器前进8位(设定为指向高8位字节地址),来进行数据清理。
由于栈是在各种情况下都可以再利用的内存领域,因此使用完毕后有必要将其恢复到原状态。
上述这些操作就是栈的清理处理。另外,在C语言中,函数的返回值,是通过寄存器而非栈来返回的。
栈清理处理,比起在函数调用方进行,在反复被调用的函数一方进行时,程序整体要小一些。这时所使用的就是_stdcall。
在函数前加上_stdcall,就可以把栈清理处理变为在被调用函数一方进行。
把代码清单5-1中的int MyFunc(int a, int b) 部分转成int_stdcall MyFunc(int a, int b)进行再编译后,和代码清单5-2中add esp,8同样的处理就会在函数MyFunc()一方执行。
![](https://i-blog.csdnimg.cn/blog_migrate/0b7c240b47f2e0d588e6f3d2920406d9.png)
# 5. 磁盘的物理结构 磁盘是通过把其物理表面划分成多个空间来使用的。
划分的方式有扇区方式和可变长方式两种,前者是指将磁盘划分为固定长度的空间,后者则是指把磁盘划分为长度可变的空间。
扇区方式中,把磁盘表面分成若千个同心圆的空间就是磁道,把磁道按照固定大小(能存储的数据长度相同)划分面成的空间就是扇区(图5-8)。
![](https://i-blog.csdnimg.cn/blog_migrate/6338b71df9c74e1cab485b40c99cf3b3.png)
扇区是对磁盘进行物理读写的最小单位。
Windows中使用的盘,一般1个扇区是512字节。不过,Windows在逻辑方面(软件方面)对磁盘进行读写的单位是扇区整数倍簇。
根据磁盘容量的不同,1簇可以是512字节(1簇=1扇区)、1KB(1簇=2扇区),2KB、4KB、8KB、16KB、32KB(1簇=64扇区)。
磁盘的容量越大,簇的容量也越大。
不过,在软盘中,1簇=512字节=1扇区,簇和扇区的大小是相等的。
不管是硬盘还是软盘,不同的文件是不能存储在同一个簇中的,否则就会导致只有一方的文件不能被删除。
因此,不管是多么小的文件,都会占用1簇的空间。这样一来,所有的文件都会占用1簇的整数倍的磁盘空间。## 标题
参考
《程序是怎样跑起来的》 —— 5. 内存和磁盘的亲密关系