文章目录
C1 抽象:地址空间
1.1 早期系统
早期的机器并没有提供多少抽象给用户,基本上,机器的物理内存很简单:

操作系统曾经是一组函数,在物理内存的开始处,然后正在运行的程序,使用物理内存中剩余的内存
这里几乎没有抽象,用户对操作系统的要求也不多
1.2 多道程序和时分共享
过了一时间,由于机器昂贵,每台机器的成本是数十万甚至百万美元,人们开始更有效地共享机器,提高机器的利用率,因此多道程序系统时代开启,其中多个进程在给定的时间准备运行,比如当有一个进程在等待I/O时,操作系统会切换到别的进程,这增加了CPU的效率
但人们对机器的要求更多了,分时系统诞生,许多人认识到了批量计算的局限性,交互性也变得很重要,因为很多用户可能同时使用机器,每个人都在等待他们执行的任务及时响应
一种实现分时共享的方法是,让一个进程单独占用全部内存运行一小段时间,然后停止它,将它的所有状态信息和所有的物理内存存储到磁盘中,再加载其它进程的状态信息并执行,以此往复,但是太慢了,特别是内存增长的时候,因此在进程切换时,仍将进程信息存储在内存中,以此来提高效率

三个进程A,B,C,每个进程拥有物理内存的一小部分,假设只有一个CPU,操作系统选择运行其中一个进程,其它进程在队列中等待
但是上述的方法没有保护进程,多个程序同时驻留在内存中时,我们不希望一个进程可以读取,修改其它进程的信息
1.3 为每个进程抽象一个地址空间
为了满足这些需求,操作系统需要提供一个一个易用的物理内存的抽象,这个抽象叫地址空间,是运行的程序看到的系统中的内存,但不是真实的物理内存
一个进程的地址空间包含运行的程序的所有内存状态,如程序的代码,栈(利用栈来保存当前函数的调用信息,分配空间给局部变量,传递参数和函数返回值)和堆(管理动态的,用户分配的内存)

代码是静态的,可以将它放在地址空间的顶部,接下来当程序运行时,地址空间的堆或栈可能增长或收缩
地址空间是操作系统提供给运行程序的抽象,程序不在物理地址0-16kb的内存中,而是被加载到任意合法的物理地址 ,真实的物理地址用户并不能得到,当进程A在尝试在地址0执行加载操作时,然而操作系统在硬件的支持下,处于某种原因,必须确保不是加载到物理地址0,而是别的地址
为每个进程抽象一个地址空间,是为了隔离这些进程,防止互相干扰,甚至影响到底层操作系统的操作
(1) 看到的地址都不是真实的物理地址
在C中可以打印指针,看到的值是一串地址,但是这些能看到的地址并不是真实的物理地址,而是虚拟地址
这些能看到的地址将由操作系统和硬件翻译成物理地址,以便从真实的物理位置获取该地址
1.4 虚拟化内存的目标
虚拟内存的第一个主要目标:透明,操作系统实现虚拟内存的方式,应该让运行的程序看不到,因此程序感知不到内存被虚拟化的事实,操作系统和硬件完成这些工作,实现了地址空间这个假象
虚拟内存的第二个主要目标:效率,操作系统应该使得虚拟化在时间上和空间上尽可能地高效
虚拟内存地第三个主要目标:保护,操作系统应该确保进程受到保护,不被其它进程影响,每个进程在自己独立的环境中运行,避免其它出错或恶意进程的干扰
1.5 小结
虚拟内存系统负责为程序提供一个巨大的,稀疏的,私有的地址空间的假象,其中保存了程序的所有指令和数据,操作系统在硬件的帮助下,通过每一个虚拟内存的索引,将其转换成物理地址,物理内存根据获得获得的物理地址去获取所需的信息,操作系统会同时对许多进程执行此操作,并且确保程序之间不会受到影响,也不会干扰操作系统
整个过程需要大量的机制(硬件和操作系统的支持)和一些关键的策略(如何管理可用空间,空间不足时释放哪些页面等)
C2 插叙:内存操作API
2.1 内存类型
在C中,会分配两种类型的内存
第一种称为栈内存,它的申请和释放由编译器来隐式管理
void fun() {
int x;
....
}
在调用该函数时,编译器在栈上开辟x的空间,当从该函数退出时,编译器释放内存,这种内存类型不可长期保存
第二种称为堆内存,其中所有的申请和释放操作都需要程序员显示地完成,必须小心地使用操作堆内存地接口,否则会有很多麻烦
void fun() {
int *x = (int *)malloc(sizeof(int));
...
}
当执行这段程序时,它会在堆上请求int类型变量的空间,函数返回一个指向该变量的指针x,然后将其存储到栈中以供程序使用,堆中的数据依然存在,需要恰当的时刻显示释放
2.2 malloc(),free()调用
(1) malloc()
传入要申请的堆空间的大小,它成功就返回一个指向新空间的指针,失败返回NULL
double *d = (double *)malloc(sizeof(double));
malloc()只需要一个size_t的参数,该参数表示你需要多少个字节,一般不直接传入数字,采用各种函数和宏
(2) free()
int *x = malloc(10 * sizeof(int));
...
free(x);
free()很简单,只接收一个由malloc()返回的指针,难的是知道何时,如何以及是否释放内存
2.3 常见错误
忘记分配内存,没有分配足够的内存,忘记初始化分配的内存,忘记释放内存,在用完前释放内存,反复释放内存,错误调用free()
2.4 底层操作系统支持
malloc()与calloc()不是系统调用,而是库调用,因此,malloc()库管理地址空间内的空间,但它们是建立在一些系统调用之上的,这些系统调用会进入操作系统,来请求更多的内存或将这些内容释放回系统
一个这样的系统调用叫是brk(),它用来改变程序分断的位置(堆结束的位置),它需要一个参数(新分断的地址),从而根据新分断是大于还是小于当前分断,来增加堆的大小
还可以通过mmap()从操作系统获取内存,通过传入正确的参数,mmap()可以在程序中创建一个匿名内存区域,这个区域不与任何特定文件相关联,而是与交换空间相关联
2.5 其它调用
如calloc(),比malloc多一个参数,通过参数类型*个数,正确地申请空间
realloc(),创建一个新的更大的内存区域,将旧区域复制到其中,并返回新区域的指针
本文介绍了虚拟内存的概念和发展历程,包括早期系统、多道程序、时分共享等背景,以及如何为每个进程抽象一个地址空间。同时,文章还探讨了内存操作API,如malloc()和free()的使用方法及常见错误。
1703

被折叠的 条评论
为什么被折叠?



