1. 什么是操作系统?
操作系统其实就是一种软件,这种软件能够协调好计算机相关硬件的协同工作,并为我们提供服务,使我们更为简单的使用计算机。
换句话说OS就相当于乙方,而用户相当于甲方,甲方只需要通过某种媒介告诉乙方我的需求,具体怎样实现由乙方来做甲方不管。而这种媒介呢,就相当于操作系统留给用户的接口,可以是命令形式、系统调用、图标操作等等。
那么乙方需要做的具体工作,对应于OS就是对系统各种资源的管理与调度。比如说对处理机调度、内存管理、IO管理、文件管理等等
综上所述呢OS就是将计算机底层的运作抽象化,转而给用户提供简单的操作,更方便用户的使用。
2. CPU进程调度
2.1 并行并发区别? 进程和程序之间的关系?
并发:两个或多个事件在同一间隔内发生。
并行:两个或多个事件在同一时刻内发生。
就比方说如果系统有双核的CPU,他同时处理两个进程,那么两个进程同时都可以获得CPU资源,同时都可以执行,不存在进程得不到CPU资源而等待的情况。
但是如果系统只有一个单核CPU,但是要他同时处理两个进程,则它根本不可能真正同时执行上述两个进程,它只能把CPU运行时间划分成几个时间段,再将CPU按照时间段分配给各个进程执行,在一个时间段的进程执行的时侯,其它进程处于挂起状态。在外界看来,整个一段时间内,这两个进程是都执行了。但是实际的运行时间还是并发执行的两倍。
综上所述,这就是并发与并行的区别。
2.2 并发带来的缺点?
并发带来很多缺点,比如说程序的执行不再是连续的,反而需要等待所需资源走走停停。其次,执行的结果可能不是确定的,多个程序之间执行顺序的不同可能导致产生不同的运算结果。
2.3 如何解决并发带来的缺点?引入进程
并发带来的问题主要是由多个程序运行过程难以控制引起的,所以就引入了进程来控制程序的运行。所以呢,进程就是程序的执行过程,他是动态的概念。并且引入了进程同步机制、进程通信机制等保证运行结果的再现性。
2.4 进程、程序、PCB的作用 ?
首先我们应该明白为什么我们要引入进程?直接让程序运行不可以吗?我们都知道,为了提高资源利用率,我们让程序并发执行,但是呢并发带来很多缺点,这导致简单的程序根本不能并发执行,这才引入了进程的概念。进程就是保证并发执行的程序在运行过程中能正常运行,也可以理解为进程是程序执行的过程,他是动态的概念。
从定义上来看 程序、数据、PCB构成了进程,从这点看出来程序比进程少个PCB,而PCB是进程的关键,正因为有了PCB才能保证程序的并发执行。
并发执行的缺点:
缺点1间断走走停停:
PCB中可以存储处理机状态信息,保证在间断后被调度重新运行时恢复现场CPU的信息,为保证执行结果再现性提供基础。
PCB中也可以存储进程调度的相关信息,比如说进程所处于的状态,进程获取CPU的优先级参数等,这保证了进程调度的正确执行。
缺点2:不可再现性
PCB中也可存储进程控制信息,就比如程序和数据存储地址、程序执行所需的资源清单、进程同步通信(解决不可再现性)所需信息如信号量等。
正因为有了PCB才能使得程序能正常并发执行。
程序时静态的代码,而进程时动态的执行过程。同时呢,进程的PCB数据结构存储了很多必要信息,保证了程序能正常并发执行。
但进程也有不同角度的定义,比方说,进程是程序的一次执行过程,是程序与数据在CPU上顺序执行所发生的活动,很显然,除了PCB外,进程也是一个过程,其具有动态性,其具有生命周期,但程序只是一段指令,存放于介质上。其还具有并发性,进程可并发执行,但程序无PCB只能顺序执行。其还具有独立性,进程是一个独立运行且独立获得资源和独立接收调度的基本单位,但程序并不能作为一个独立单位运行。其还具有异步性,进程以异步方式运行,以不可预知速度向前推进,故才引入进程同步机制(跟PCB有关,仔细读读PCB的作用)。
2.5 进程同步机制
进程同步的作用就是协调各个进程的执行顺序,保证各个进程按照某种规则共享资源,保证程序可以正常并发执行。
各个进程并发执行过程中有两种制约关系:
- 直接制约关系
往往多个进程需要合作完成一项任务,因此他们必须各司其职,这使得进程执行肯定是要有一定的先后顺序。比方说输入进程与输出进程,只有输入进程输入数据之后,输出进程才能被唤醒执行。这就是他们之间的制约关系。各个进程之间执行必须有先后顺序。 - 间接制约关系
多个进程并发执行时需要共享系统资源,但是多个进程在同一时刻只有一个进程能进入临界区,也就是说针对像打印机、I/O设备这样的临界资源,各个进程必须互斥地对其访问。
并发程序执行时往往程序之间的运行存在两种制约关系,而通过进程同步机制保证进程之间的合作满足他们之间的制约关系,使其合理的共享资源,保证程序执行的可再现性。
常见的 进程同步机制有:?
临界区:
首先对临界资源的访问那段代码被称为临界区,为了互斥的访问临界区,每个进程在进入临界区时,都需要先进行检查,也就是查看临界区是否上锁,若没有上锁则访问临界资源,并上锁。临界资源就像车票,同一时间只能有一个进程来访问。否则会出现问题。
同步与互斥:
同步:往往多个进程需要合作完成一项任务,因此他们必须各司其职,这使得进程执行肯定是要有一定的先后顺序。比方说输入进程与输出进程,只有输入进程输入数据之后,输出进程才能被唤醒执行。这就是他们之间的制约关系。各个进程之间执行必须有先后顺序。
互斥:多个进程并发执行时需要共享系统资源,但是多个进程在同一时刻只有一个进程能进入临界区,也就是说针对像打印机、I/O设备这样的临界资源,各个进程必须互斥地对其访问。
信号量:
有整形信号量、记录形信号量、And形信号量
整形信号量:
信号量是一个整型变量,可以对其执行 P 和 V 操作。
P:如果信号量大于零,就对其进行减 1 操作;如果信号量等于 0,进程进入 waiting 状态,等待信号量大于零。
V:对信号量执行加 1 操作,并唤醒正在 waiting 的进程
如果信号量只能取 0 或者 1,那么就变成了互斥量,其实也可以理解成加锁解锁操作,0 表示已经加锁,1 表示解锁。
记录形信号量:
设置整形变量value作为资源数目,并可以进行P、V操作,每次P操作如果信号量大于零,就对其进行减 1 操作,代表资源数目少一个;如果value数量小于0,表示资源分配完,让进程进入阻塞状态避免忙等。此时value的绝对值表示已阻塞进程个数。
V操作表示进程释放一个临界资源,value+1,如果value值还是负的,那么应该按照某种策略唤醒一个阻塞的进程,因为此时已经有一个资源可用了。
如果信号量只能取 0 或者 1,那么就变成了互斥量,其实也可以理解成加锁解锁操作,0 表示已经加锁,1 表示解锁。
2. 利用信号量实现程序或语句之间的前驱关系。有两个并发执行的进程P1 P2
管程:(不熟悉)
使用信号量机制实现的生产者消费者问题需要客户端代码做很多控制,而管程把控制的代码独立出来,不仅不容易出错,也使得客户端代码调用更容易。
管程有一个重要特性:在一个时刻只能有一个进程使用管程。进程在无法继续执行的时候不能一直占用管程,否则其它进程永远不能使用管程。
管程引入了 条件变量 以及相关的操作:wait() 和 signal() 来实现同步操作。对 条件变量 执行 wait() 操作会导致调用进程阻塞,把管程让出来给另一个进程持有。signal() 操作用于唤醒被阻塞的进程。
2.6 消费者和生产者模型
生产者和消费者模型,期间就需要线程之间进行通信来实现互斥和同步。
需求是这样的:
生产者每生成一个产品,就消耗一个缓冲区,只有当缓冲区不满的时候才能放入;
消费者每消费一个产品,就消耗一个产品,只有当缓冲区不空的时候才能消费。
做法:
因为缓冲区是临界资源,所以需要定义一个互斥信号量,为 mutex 来实现两者对临界资源的互斥访问。
为了同步生产者和消费者的操作,需要记录缓冲区的剩余大小 empty 和 产品的个数 full。当缓冲区大小不为 0 时,生产者才能放入产品;当产品个数不为 0 时,消费者才能拿走产品。
注意!不可对临界区先加锁,设想这样一个情况:当 empty = 0 时,生产者此时先对临界区加锁,然后发现缓冲区的数量为 0,则开始进入阻塞等待消费者消费的状态,而此时一个消费者开始进入消耗一个产品,但发现临界区被加锁,所以生产者在等待消费者消费产品,而消费者在等待生产者释放临界区锁,进入了一个死锁状态。
伪代码如下
mutex = 1;
empty = N;
full = 0;
void Producer() {
P(empty); // 生产者生产一个产品,消耗一个缓冲区
P(mutex);
.... // 临界区
V(mutex);
V(full); // 产品数量加1
}
void Consumer() {
P(full); // 消费者消耗一个产品,释放一个缓冲区
P(mutex); // 临界区上锁
....
V(mutex); // 临界区锁释放
V(empty); // 增加一个缓冲区
}
void P(S){
S--;
if(S < 0) block(); // 如果小于0,代表资源没了
}
void V(S){
S++;
if(S <= 0) wakeUp(); // 如果小于等于0,代表有进程仍然在等待,通知他们ok了
}
2.7 进程通信的方式
-
共享内存
共享内存允许两个或多个进程共享一个给定的存储区,这一段存储区可以被两个或两个以上的进程映射至自身的地址空间中,一个进程写入共享内存的信息,可以被其他使用这个共享内存的进程,通过一个简单的内存读取错做读出,从而实现了进程间的通信。
共享内存的方式就可以解决拷贝耗时很长的问题了,对于像管道和消息队里等通信方式,则需要再内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次:一次从输入文件到共享内存区,另一次从共享内存到输出文件。
因为进程是直接对内存进行存取的,可以多个进程对共享内存同时操作,所以对共享空间的访问必须要求进程对共享内存的访问是互斥的。所以我们经常把信号量和共享内存一起使用来实现进程通信。 -
管道
简单版: 管道我们可以形象的想象出一个管道,也可以看成是一种特殊的文件。写进程把大量的数据输进管道,读进程就从管道出口把数据接收。linux 里的竖线,就是管道的意思 |,管道这种通信的方式是半双工通信的,只能单向交替传输,匿名管道只能在具有亲属关系的进程之间通信使用。命名管道任何进程有相应的权限都可以对它进行访问。
(命名管道有名字匿名管道没名字,知道管道名字访问名字即可访问) -
管道和共享内存优缺点
管道需要在内核和用户空间进行四次的数据拷贝:由用户空间的buf中将数据拷贝到内核中 -> 内核将数据拷贝到内存中 -> 内存到内核 -> 内核到用户空间的buf。而共享内存则只拷贝两次数据:用户空间到内存 -> 内存到用户空间。
管道用循环队列实现,连续传送数据可以不限大小。共享内存每次传递数据大小是固定的;
共享内存可以随机访问被映射文件的任意位置,管道只能顺序读写;
管道可以独立完成数据的传递和通知机制,共享内存需要借助其他通讯方式进行消息传递。
也就是说,两者之间最大的区别就是: 共享内存区是最快的可用IPC形式,一旦这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传递,就不再通过执行任何进入内核的系统调用来传递彼此的数据,节省了时间。 -
信号量
共享内存最大的问题就是多进程竞争内存的问题,就像平时所说的线程安全的问题,那么就需要靠信号量来保证进程间的操作的同步与互斥。
信号量其实就是个计数器,例如信号量的初始值是 1,然后 a 进程访问临界资源的时候,把信号量设置为 0,然后进程 b 也要访问临界资源的时候,发现信号量是 0,就知道已有进程在访问临界资源了,这时进程 b 就访问不了了,所以说信号量也是进程间的一种通信方式。 -
消息队列
消息队列,就是一个消息的链表,是一系列保存在内核中消息的列表。用户进程可以向消息队列添加消息,也可以向消息队列读取消息。比如 a 进程要给 b 进程发消息,只需要把消息挂在消息队列(可以是中介邮局,也可以是进程自己的信箱)里就行了,b 进程需要的时候再去取消息队列里的消息。 -
套接字
套接字可以实现两个不同的机器之间的进程通信,比如 socket 使用。
进程间通信详解 包括管道详解
2.8 进程与线程的区别?(不熟悉)
进程是资源调度与分配的独立单位,但是在多道程序并发系统中,频繁的进程切换会造成很大的时空开销,故而引入了线程的概念,使线程作为处理机调度的对象,其本身拥有少量资源,多个线程之间共享进程所有的资源,因此线程之间的调度大大减少了回收分配资源等带来的开销,因此大大提高了并发量,减少了系统开销。
但是由于进程拥有独立的地址空间,线程共享进程资源,假设线程1坏掉了,其他线程也无法访问共享资源,导致其他线程也会挂掉,进而整个进程就挂掉了,但如果进程挂掉,因为其具有独立性,却不会对其他进程造成严重影响。故对安全性与稳定性要求较高的系统中使用进程较好。
2.9 进程的几种状态
有创建状态、就绪状态、运行状态、阻塞状态、结束状态。分别对应于进程的出生一直到死亡的过程。
创建状态就是为进程分配PCB,然后初始化PCB中相关的控制和管理进程的信息。然后把进程插入就绪队列,等待调度。
插入就绪队列的进程都处于就绪状态,故名思意就是其他资源都以准备完毕,只需要等待CPU分配时间片即可。就绪状态的进程获取到CPU之后就可以运行了此时就处于运行状态。运行状态在使用完 CPU 时间片后,又重回就绪状态。
阻塞状态是进程在运行状态时,需要等待某个资源。比如打印机资源,而进入一个挂起的状态,等资源拿到后会回到就绪状态,等待 CPU 时间片。
挂起状态就是让进程进入静止状态,虚拟机中有常见的挂机操作,一般如果发现一时半会儿解决不了的问题,就暂时挂机等回头再解决。
2.10 进程调度算法
2. # 总结 # 2
要明白进程是什么?首先我们要知道为什么要有进程,多个程序并发执行存在很多问题,如不可再现性等,而进程就是为了控制程序并发执行的过程而引入的。从这一点上来看,进程是动态的概念,就相当于程序的执行过程。
其次,既然进程是程序的执行过程,我们通过管理进程不久就能控制程序的执行吗,那么进程他肯定必须要有控制的相关信息吧,当然,这些信息都存在PCB中,而PCB是进程的关键,包含着进程调度、管理相关的种种信息,PCB保证程序的并发执行的基础,也是进程的灵魂。但是只有单纯的进程还远远不够,所以还引入了进程同步机制、进程通信机制保证进程正确的并发执行。
综上所述,进程就是程序的执行过程,进程中包含相关管理控制以及调度的相关信息,并通过进程同步机制等来管控进程,保证程序的并发执行。其实我们发现,很多计算机相关知识的发展都是,发现问题、提出问题、然后解决问题。我觉得研究生研究最重要的也是这一点。
进程就是为了解决程序并发执行而引入的,单纯的多个程序并发执行存在很多问题,为了解决这些问题引入了进程,进程的PCB中包含着进程调度、管理相关的种种信息,但是只有单纯的进程还远远不够,所以还引入了进程同步机制、进程通信机制保证进程正确的并发执行。
3. 程序的运行过程?
- 编译 链接 装入 p132
- 绝对装入方式、可重定位装入(根据内存分配情况将逻辑地址转为物理地址)、动态运行时装入方式(解决程序在内存中移动的问题,将地址转换推迟到运行时再进行)
- 静态链接方式、装入时动态链接(装入一部分模块,等发生过程调用再装入其他模块,共享且便于修改)、运行时动态链接(运行时用到再链接,比方说错误处理模块,不发生错误就不用装入)
4. 死锁
何为死锁 ?要明白什么是死锁我们应该先知道为什么会产生死锁?众所周知多个进程在并发执行的时候,不可避免的会相互争夺资源,假如有进程1和2,1需要的资源2占有,2需要的资源1占有,两者都不让步,这就会导致两者相互等待的现象,这种现象就叫做死锁。所以,综上所述的话,死锁就是每一个发生死锁的进程,都在等待另一个死锁进程所占有的资源,这导致每个进程都无法运行,每个进程都处于无限等待的死锁状态。当然计算机中死锁的现象也经常发生。
4.1 死锁发生的必要条件?
互斥条件:是资源分配是互斥的,资源要么处于被分配给一个进程的状态,要么就是可用状态。
等待和占有条件:进程在请求资源得不到满足的时候,进入阻塞等待状态,且不释放已占有的资源。
不剥夺条件:已经分配给一个进程的资源不能强制性地被抢占,只能等待占有他的进程释放。
环路等待:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程释放所占有的资源。
4.1 常见的死锁现象 ?
比如生产者消费者模型中的死锁;写错代码就会发生死锁。
数据库中的死锁问题以及解决方案(数据库复习的时候要温习)
4.2 如何解决死锁 ?( 银行家算法不熟悉)
预防策略:从形成死锁的条件入手,基本思想就是打破形成死锁的四个条件中的一个或多个,保证系统不会进入死锁状态。
破坏互斥条件:同一资源在同一时间可以被多个进程访问,比如只读文件、磁盘等软硬件资源可采用这种办法处理。
破坏占有和等待条件:在进程开始执行之前,就把其要申请的所有资源全部分配给他,直到所有资源都满足,才开始执行。会导致资源浪费。
破坏不剥夺条件:允许进程强行从资源占有者那里夺取某些资源。
破坏环路等待条件:给系统的所有资源编号,规定进程请求所需资源的顺序必须按照资源的编号依次执行。如果已经占据高号资源想请求低号资源必须先释放高号资源,事实上总有一个进程占据着最高号资源,这保证了它此后申请的资源是空闲的。因为他已经是最高号了,比他高的资源必定是空闲的呀。
银行家算法:
4.3 如果发生了死锁咋办?
死锁检测:发现死锁之前总归需要先检测到死锁,可以将各个线程以及请求资源的情况,模拟成有向图,通过检测有向图中是否存在环来判断是否有死锁,从一个节点出发进行 dfs,对访问过的节点进行标记,如果访问到了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。具体检查有向图中是否有环的操作可以看 Java相关知识
启动四个线程t1,t2,t3,t4,各自锁上资源A,B,C,D,然后t1锁B,t2锁C,t3锁D,t4锁A。
死锁恢复:
- 撤销进程法:
1 .撤消陷于死锁的全部进程;
2 .逐个撤消陷于死锁的进程,直到死锁不存在; - 资源剥夺法:
1 从陷于死锁的进程中逐个强迫放弃所占用的资源,直至死锁消失;
2 从另外的进程那里强行剥夺足够数量的资源分配给死锁进程,以解除死锁状态。 - 鸵鸟算法,直接不管!重启
5. 存储器管理
5.1 程序的运行过程 ?
程序要想运行首先应该把它装入内存,然后将其变成一个可执行文件。包含以下几个步骤:
- 编译,对源程序进行编译,形成若干个模块。
- 链接,将编译后形成的各个模块与对应的库函数连接起来,形成一个完整的装入模块。
- 装入,装入程序将装入模块装入内存。
5.2 装入方式 ?链接方式 ?
- 绝对装入方式:程序中的逻辑地址和程序装入内存中的物理地址是一样的。
- 可重定位装入:根据内存分配情况将程序放入内存,并将逻辑地址重新定位成物理地址。
- 动态运行时装入方式:解决程序在内存中移动的问题,将地址转换推迟到运行时再进行。
- 静态链接方式:把编译之后的各个模块与其库函数链接起来成为新的模块,以后就不再拆开。
- 装入时动态链接:采取边装入边链接的方式,先装入一部分模块,等发生过程调用再装入其他模块,便于共享且便于修改。
- 运行时动态链接:运行时用到哪个模块再链接,比方说错误处理模块,不发生错误就不用装入。
为什么需要链接?什么情况下不需要进行链接?
在我们的实际开发中,不可能将所有代码放在一个源文件中,所以会出现多个源文件,而且多个源文件之间不是独立的,如一个源文件可能要调用另一个源文件中定义的函数,但是每个源文件都是独立编译的,即每个.c文件会形成一个.o文件,为了满足前面说的依赖关系,则需要将这些源文件产生的目标文件进行链接,从而形成一个可以执行的程序。这个过程就是链接。
假如说,只有一个.c文件,什么库函数,头文件都不用,那么就只有一个目标模块,不需要进行链接。
5.3 内存管理相关概念
连续分配存储器管理:为用户程序分配一段连续的内存空间,代码和数据的逻辑地址相邻,物理地址也相邻。容易产生大量碎片。
离散式存储区管理: 将进程分散的装入不同的且不相邻的分区中,便可以充分利用内存空间。
动态重定位:将逻辑地址转换成物理地址的过程推迟到程序执行的时候,它设置了一个重定位寄存器存放程序在内存中的起始地址,程序和数据存放的真正内存地址由起始地址加上相对地址即可。
对换: 换入换出操作,将内存中暂时运行不了的进程或者说暂时不用的数据换出到外存上,以便能腾出足够内存空间,再将已具备运行条件的进程和数据换入到内存中。
5.4 分页存储管理
将用户程序地址空间划分成固定大小的一个个区域,这些区域就叫做页面,同时将内存空间也分成一个个的区域,这样就可以把用户程序的任一页放在内存的任一块中。
内存碎片:只可能再最后一页中出现一点碎片。页面大小一般为1KB
快表:快表就是为了提高找页号地址的速度,访问页表需要多一次访问内存,而如果快表中有要找的页号,那就提高检索速度了。
5.5 分段存储管理
把用户地址空间分成一个一个段,各个段由用户编写,每个段都由用户来决定不同的作用,比如说数据段和代码段等等,在内存分配中以段为单位,段与段大小可以不同,也可以不相邻。
优点:
- 便于用户编程,用户根据编程需求自行设定需要的各个段。
- 便于共享资源,可以设置共享段。
- 便于信息保护,可以对特定的段设置可读或者可修改的权限。
- 便于动态链接,可以根据需要将不同的代码段链接到模块中。
5.6 段式存储VS页式存储
- 页与段存储信息意义不同:
页只是系统管理的需要,只是一个固定大小的存储区域,里面存的啥,没啥特别意义,但是段是根据用户需要自己设置的,它通常包含有逻辑意义的数据,比方说代码段、数据段等。 - 页与段大小不同
页是固定大小的由系统决定,而段的大小不确定,是由具体程序和数据决定。 - 段式存储逻辑上是二维的,先把内存分成多段,然后再给每一段编号,
1.================
2.================
3.================
向上面那样,每一排是一段,然后有N段,你看看是不是二维的。这样我可以使用 [段号][段内位置]来定位。
页式存储,是 页码*页大小+偏移量,逻辑上我们是这样使用的,而不是 [页码][页内位置],所以逻辑上页式是一维的。
5.7 段页式存储
将用户程序分成若干个段,再为每个段分成若干个页。这样即有利于用户编程又可以减少内存碎片。本质上来说就是用分段方法管理用户地址空间,用分页方法管理物理存储空间。结合了两者优点。
现在普遍采用的段页式内存分配方式就是将进程的内存区域分为不同的段,然后将每一段由多个固定大小的页组成。通过页表机制,使段内的页可以不必连续处于同一内存区域,从而减少了外部碎片,然而同一页内仍然可能存在少量的内部碎片,只是一页的内存空间本就较小,从而使可能存在的内部碎片也较少。
https://blog.youkuaiyun.com/zwl1584671413/article/details/123552593
5.8 虚拟地址与物理地址 ?
what?
什么是虚拟地址呢?
我们在写程序的时候打交道的都是虚拟地址,比如 C 语言的指针,这个虚拟地址由操作系统决定,而物理地址指的是真实内存地址寄存器的地址。处理器通常使用虚拟寻址,用 MMU 把虚拟地址翻译成物理地址才能访问到真正的物理地址。
why?
为什么要有虚拟地址这种东西呢?
如果直接操作物理地址,用户程序可以直接访问底层物理地址,很容易破坏操作系统,造成系统崩溃。
想要同时运行多个程序很难,多个程序可能对同一个物理地址进行操作,发生崩溃。
5.9 虚拟内存 ?
why?
为什么要有虚拟内存这种技术呢?
因为传统的内存管理有这么两个特点:
一次性:程序运行时把所有的作业都 load 到内存里
驻留:这些程序和数据会一直驻留到运行结束
这样也会带来两个问题:
一些比较大的作业是没有办法一次性装入内存的,比如一个 GTA5 就几十个G,不可能一次性装内存里的。
装入内存的数据和程序就算没有被使用,也会一直占有内存,直到被释放
how?
虚拟内存的实现思路是什么?
在一段时间内只需要少部分数据就可以保证程序的正常运行。
所以在程序运行的开始,只把少部分很快就要用到的程序和数据 load 到内存里,暂时用不到的部分放在外存里。
如果在运行过程中发生缺页,就从外存调入要用到的页面。
如果调页的时候发现内存满了,就根据一些策略把不用的页调出去。
而在 os 的管理下,让程序认为自己拥有连续可用的内存,产生独享主存的错觉,这就是虚拟内存。