从内存来看,早期的机器并无提供多少抽象给用户。基本上,机器的物理内存看起来如图所示。程序员
操做系统曾经是一组函数(其实是一个库),在内存中(在本例中,从物理地址0开始),而后有一个正在运行的程序(进程),目前在物理内存中(在本例中,从物理地址64KB开始),并使用剩余的内存。函数
过了一段时间,因为机器昂贵,人们开始更有效地共享机器。所以,多道程序系统和分时系统分别开启了。oop
在下图中,有3个进程(A、B、C),每一个进程拥有从512KB物理内存中切出来给它们的一小部份内存。假定只有一个CPU,操做系统选择运行其中一个进程(好比A),同时其余进程(B和C)则在队列中等待运行。spa
随着时分共享变得流行,人们对操做系统又有了新的要求。特别是多个程序同时驻留在内存中,使保护(protection)成为重要问题。操作系统
为了解决这些问题,操做系统须要提供一个易用(easy to use)的物理内存抽象。这个抽象叫做地址空间(address space),是运行的程序看到的系统中的内存。指针
一个进程的地址空间包含运行的程序的全部内存状态。好比:程序的代码(code,指令)必须在内存中,所以它们在地址空间里。当程序在运行的时候,利用栈(stack)来保存当前的函数调用信息,分配空间给局部变量,传递参数和函数返回值。最后,堆(heap)用于管理动态分配的、用户管理的内存。固然,还有其余的东西(例如,静态初始化的变量),但如今假设只有这3个部分:代码、栈和堆。code
在下图的例子中,咱们有一个很小的地址空间(只有16KB)。程序代码位于地址空间的顶部(在本例中从0开始,而且装入到地址空间的前1KB)。代码是静态的(所以很容易放在内存中),因此能够将它放在地址空间的顶部,咱们知道程序运行时再也不须要新的空间。队列
当咱们描述地址空间时,所描述的是操做系统提供给运行程序的抽象。程序不在物理地址0~16KB的内存中,而是加载在任意的物理地址。可是运行的程序意识不到这点,它认为本身被加载到特定地址(例如0)的内存中,而且具备很是大的地址空间。这就是虚拟内存系统须要作的事情。进程
虚拟内存(VM)系统的一个主要目标是透明(transparency)。操做系统实现虚拟内存的方式,应该让运行的程序看不见。所以,程序不该该感知到内存被虚拟化的事实,相反,程序的行为就好像它拥有本身的私有物理内存。内存
虚拟内存的另外一个目标是效率(efficiency)。操做系统应该追求虚拟化尽量高效(efficient),包括时间上(即不会使程序运行得更慢)和空间上(即不须要太多额外的内存来支持虚拟化)。在实现高效率虚拟化时,操做系统将不得不依靠硬件支持,包括TLB这样的硬件功能。
最后,虚拟内存第三个目标是保护(protection)。操做系统应确保进程受到保护(protect),不会受其余进程影响,操做系统自己也不会受进程影响。当一个进程执行加载、存储或指令提取时,它不该该以任何方式访问或影响任何其余进程或操做系统自己的内存内容(即在它的地址空间以外的任何内容)。
在运行一个C程序的时候,会分配两种类型的内存。第一种称为栈内存,它的申请和释放操做是编译器来隐式管理的,因此有时也称为自动(automatic)内存。第二种类型的内存,即所谓的堆(heap)内存,其中全部的申请和释放操做都由程序员显式地完成。
malloc函数很是简单:传入要申请的堆空间的大小,它成功就返回一个指向新申请空间的指针,失败就返回NULL。
#include <stdlib.h> ... void *malloc(size_t size);
要释放再也不使用的堆内存,程序员只需调用free():
int *x = malloc(10 * sizeof(int)); ... free(x);
该函数接受一个参数,即一个由malloc()返回的指针。分配区域的大小不会被用户传入,必须由内存分配库自己记录追踪。
在使用malloc()和free()时会出现一些常见的错误。
许多例程在调用以前,都但愿你为它们分配内存。例如,例程strcpy(dst, src)将源字符串中的字符串复制到目标指针。可是,若是不当心,你可能会这样作:
char *src = "hello"; char *dst; // oops! unallocated strcpy(dst, src); // segfault and die
另外一个相关的错误是没有分配足够的内存,有时称为缓冲区溢出(buffer overflow)。一个常见的错误是为目标缓冲区留出“几乎”足够的空间。
char *src = "hello"; char *dst = (char *) malloc(strlen(src)); // too small! strcpy(dst, src); // work properly
在这个错误中,程序员正确地调用malloc(),但忘记在新分配的数据类型中填写一些值。这样的话程序最终会遇到未初始化的读取(uninitialized read),它从堆中读取了一些未知值的数据。
另外一个常见错误称为内存泄露(memory leak),若是忘记释放内存,就会发生。在长时间运行的应用程序或系统(如操做系统自己)中,这是一个巨大的问题,由于缓慢泄露的内存会致使内存不足,此时须要从新启动。
有时候程序会在用完以前释放内存,这种错误称为悬挂指针(dangling pointer)。随后的使用可能会致使程序崩溃或覆盖有效的内存(例如,你调用了free(),但随后再次调用malloc()来分配其余内容,这从新利用了错误释放的内存)。
程序有时还会不止一次地释放内存,这被称为重复释放(double free)。这样作的结果是未定义的。
注:系统中实际存在两级内存管理。第一级是由操做系统执行的内存管理,操做系统在进程运行时将内存交给进程,并在进程退出(或以其余方式结束)时将其回收。第二级管理在每一个进程中,例如在调用malloc()和free()时,在堆内管理。即便你没有调用free(),操做系统也会在程序结束运行时,收回进程的全部内存。