操作系统基础知识点
1、进程与线程区别
进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位。
线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。
1.进程要分配一大部分的内存,而线程只需要分配一部分栈就可以了.
2.一个程序至少有一个进程,一个进程至少有一个线程.
3.进程是资源分配的最小单位,线程是程序执行的最小单位。
4.一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行.
2、线程同步的方式:互斥锁、自旋锁、读写锁、条件变量
什么是读写锁:
读写锁和互斥量类似,不过读写锁允许更高的并行性。互斥量有两种状态,要么加锁,要么解锁,而且两种状态一次只能有一个线程进行访问。读写锁有3中状态,读模式下加锁,写模式下加锁,不加锁装填。一次只能有一个线程可以占有写模式下的读写锁,但是有多个进程可以同时占有写模式下的读写锁
3、互斥锁与自旋锁的底层区别
Mutex(互斥锁)属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和Core1上。假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞,
Core0会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其它的任务而不必进行忙等待。而Spin lock(自旋锁)则不然,它属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在Core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止。
自旋锁(Spin lock)
自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,“自旋锁”的作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远高于互斥锁。
自旋锁的不足之处:
自旋锁一直占用着CPU,他在未获得锁的情况下,一直运行(自旋),所以占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低。
4、孤儿进程与僵尸进程
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。
5、死锁及避免
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程进入对象的synchronized代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。
当然死锁的产生是必须要满足一些特定条件的:
1.互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放
2.请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
3.不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用
4.循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。
1、破坏互斥条件
方法:
如果允许系统资源都能共享使用,则系统不会进入死锁状态。
缺点:
有些资源根本不能同时访问,如打印机等临界资源只能互斥使用。所以,破坏互斥条件而预防死锁的方法不太可行,而且在有的场合应该保护这种互斥性。
2、破坏请求并保持条件
方法:
釆用预先静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不把它投入运行。一旦投入运行后,这些资源就一直归它所有,也不再提出其他资源请求,这样就可以保证系统不会发生死锁。
缺点:
系统资源被严重浪费,其中有些资源可能仅在运行初期或运行快结束时才使用,甚至根本不使用。而且还会导致“饥饿”现象,当由于个别资源长期被其他进程占用时,将致使等待该资源的进程迟迟不能开始运行。
3、破坏不可剥夺条件
方法:
当一个已保持了某些不可剥夺资源的进程,请求新的资源而得不到满足时,它必须释放已经保持的所有资源,待以后需要时再重新申请。这意味着,一个进程已占有的资源会被暂时释放,或者说是被剥夺了,或从而破坏了不可剥夺条件。
缺点:
该策略实现起来比较复杂,释放已获得的资源可能造成前一阶段工作的失效,反复地申请和释放资源会增加系统开销,降低系统吞吐量。这种方法常用于状态易于保存和恢复的资源,如CPU的寄存器及内存资源,一般不能用于打印机之类的资源。
4、破坏循环等待条件
方法:
为了破坏循环等待条件,可釆用顺序资源分配法。首先给系统中的资源编号,规定每个进程,必须按编号递增的顺序请求资源,同类资源一次申请完。也就是说,只要进程提出申请分配资源Ri,则该进程在以后的资源申请中,只能申请编号大于Ri的资源。
缺点:
这种方法存在的问题是,编号必须相对稳定,这就限制了新类型设备的增加;尽管在为资源编号时已考虑到大多数作业实际使用这些资源的顺序,但也经常会发生作业使用资源的顺序与系统规定顺序不同的情况,造成资源的浪费;此外,这种按规定次序申请资源的方法,也必然会给用户的编程带来麻烦。
四、避免死锁的算法
1、判断“系统安全状态”法
在进行系统资源分配之前,先计算此次资源分配的安全性。若此次分配不会导致系统进入不安全状态,则将资源分配给进程; 否则,让进程等待。
2、银行家算法
1、申请的贷款额度不能超过银行现有的资金总额
2、分批次向银行提款,但是贷款额度不能超过一开始最大需求量的总额
3、暂时不能满足客户申请的资金额度时,在有限时间内给予贷款
4、客户要在规定的时间内还款
6、多线程与多进程比较
线程是可执行代码的可分派单元。这个名称来源于“执行的线索”的概念。在基于线程的多任务的环境中,所有进程有至少一个线程,但是它们可以具有多个任务。这意味着单个程序可以并发执行两个或者多个任务。
简而言之,线程就是把一个进程分为很多片,每一片都可以是一个独立的流程。这已经明显不同于多进程了,进程是一个拷贝的流程,而线程只是把一条河流截成很多条小溪。它没有拷贝这些额外的开销,但是仅仅是现存的一条河流,就被多线程技术几乎无开销地转成很多条小流程,它的伟大就在于它少之又少的系统开销。(当然伟大的后面又引发了重入性等种种问题,这个后面慢慢比较)。
说到子进程只是一个额外的流程,那他跟父进程的联系和区别是什么呢?
我很想建议你看看linux内核的注解(有兴趣可以看看,那里才有本质上的了解),总之,fork后,子进程会复制父进程的task_struct结构,并为子进程的堆栈分配物理页。理论上来说,子进程应该完整地复制父进程的堆,栈以及数据空间,但是2者共享正文段。
7、进程间通信:PIPE、FIFO、消息队列、信号量、共享内存、socket
1. 无名管道pipe:
管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
无名管道用于父进程和子进程之间,通常父进程创建管道,然后由通信的子进程继承父进程的读端点句柄和写端点句柄,或者父进程有读写句柄的子进程,这些子进程可以使用管道直接通信,不需要通过父进程。
2. 高级管道popen:
将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。
3. 命名管道FIFO:
有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
命名管道是为了解决无名管道只能在父子进程间通信而设计的,命名管道是建立在实际的磁盘介质或文件系统(而不是只存在内存中),任何进程可以通过文件名或路径建立与该文件的联系,命名管道需要一种FIFO文件(有先进先出的原则),虽然FIFO文件的inode节点在磁盘上,但仅是一个节点而已,文件的数据还是存在于内存缓冲页面中,和普通管道相同。
4. 消息队列MessageQueue:
消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
消息队列是消息的链表,包括Posix消息队列和system v消息队列(Posix常用于线程,system常用于进程),有写权限的进程可以向消息队列中添加消息,有读权限的进程可以读走消息队列的消息。
5. 共享内存SharedMemory:
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问(使多个进程可以访问同一块内存空间)。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
6. 信号 sinal :
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
除了用于进程间通信之外,进程还可以发送信号给进程本身。除了系统内核和root之外,只有具备相同id的进程才可以信号进行通信。
7. 信号量Semaphore:
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
8. 套接字Socket:
套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
通信过程如下:
8.1命名socket
SOCK_STREAM 式本地套接字的通信双方均需要具有本地地址,其中服务器端的本地地址需要明确指定,指定方法是使用 struct sockaddr_in 类型的变量。
8.2 绑定
SOCK_STREAM 式本地套接字的通信双方均需要具有本地地址,其中服务器端的本地地址需要明确指定,指定方法是使用 struct sockaddr_in 类型的变量,将相应字段赋值,再将其绑定在创建的服务器套接字上,绑定要使用 bind 系统调用,其原形如下:
int bind(int socket, const struct sockaddr *address, size_t address_len);
其中 socket表示服务器端的套接字描述符,address 表示需要绑定的本地地址,是一个 struct sockaddr_in 类型的变量,address_len 表示该本地地址的字节长度。
8.3 监听
服务器端套接字创建完毕并赋予本地地址值(名称,本例中为Server Socket)后,需要进行监听,等待客户端连接并处理请求,监听使用 listen 系统调用,接受客户端连接使用accept系统调用,它们的原形如下:
-
int listen(int socket, int backlog);
-
int accept(int socket, struct sockaddr *address, size_t *address_len);
其中 socket 表示服务器端的套接字描述符;backlog 表示排队连接队列的长度(若有多个客户端同时连接,则需要进行排队);address 表示当前连接客户端的本地地址,该参数为输出参数,是客户端传递过来的关于自身的信息;address_len 表示当前连接客户端本地地址的字节长度,这个参数既是输入参数,又是输出参数。
8.4 连接服务器
客户端套接字创建完毕并赋予本地地址值后,需要连接到服务器端进行通信,让服务器端为其提供处理服务。
对于SOCK_STREAM类型的流式套接字,需要客户端与服务器之间进行连接方可使用。连接要使用 connect 系统调用,其原形为
int connect(int socket, const struct sockaddr *address, size_t address_len);
其中socket为客户端的套接字描述符,address表示目标服务器端的变量地址,是一个 struct sockaddr_in 类型的变量,address_len 表示服务器变量地址的字节长度。实现连接的代码如下:
connect(client_sockfd, (struct sockaddr*)&serv_address, sizeof(serv_address));
8.5 相互发送接收数据
无论客户端还是服务器,都要和对方进行数据上的交互,这种交互也正是我们进程通信的主题。一个进程扮演客户端的角色,另外一个进程扮演服务器的角色,两个进程之间相互发送接收数据,这就是基于本地套接字的进程通信。发送和接收数据要使用 write 和 read 系统调用,它们的原形为:
-
int read(int socket, char *buffer, size_t len);
-
int write(int socket, char *buffer, size_t len);
其中 socket 为套接字描述符;len 为需要发送或需要接收的数据长度;
对于 read 系统调用,buffer 是用来存放接收数据的缓冲区,即接收来的数据存入其中,是一个输出参数;
对于 write 系统调用,buffer 用来存放需要发送出去的数据,即 buffer 内的数据被发送出去,是一个输入参数;返回值为已经发送或接收的数据长度。
8.6 断开连接
交互完成后,需要将连接断开以节省资源,使用close系统调用,其原形为:
int close(int socket);
8、管道与消息队列对比
消息队列与管道以及有名管道相比,具有更大的灵活性,首先,它提供有格式字节流,有利于减少开发人
员的工作量;其次,消息具有类型,在实际应用中,可作为优先级使用。这两点是管道以及有名管道所不
能比的。同样,消息队列可以在几个进程间复用,而不管这几个进程是否具有亲缘关系,这一点与有名管
道很相似;但消息队列是随内核持续的,与有名管道(随进程持续)相比,生命力更强,应用空间更大。
9、fork进程的底层:读时共享,写时复制
当然不是,父子进程间遵循读时共享写时复制的原则。这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销。
读时共享写时复制这一机制是由MMU来实现的。
注意:只有进程空间的各段的内容要发生变化时(子进程或父进程进行写操作时,都会引起复制),才会将父进程的内容复制一份给子进程。在fork之后两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。即父子进程在逻辑上仍然是严格相互独立的两个进程,各自维护各自的参数,只是在物理上实现了读时共享,写时复制。
父子进程一直共享:1. 文件描述符(打开文件的结构体) ,注意不是共享文件描述符本身这个整形数,而是共享同一个文件对应的FILE *结构体指针,其实一个文件打开后只能有一个FILE结构体,因此对于多有的进程都是共享这一个结构体,不仅仅只是父子进程。 2. mmap建立的映射区 (进程间通信详解)。
10、线程上下文切换的流程
线程切换,同一进程中的两个线程之间的切换
进程切换,两个进程之间的切换
模式切换,在给定线程中,用户模式和内核模式的切换
地址空间切换,将虚拟内存切换到物理内存
CPU切换前把当前任务的状态保存下来,以便下次切换回这个任务时可以再次加载这个任务的状态,然后加载下一任务的状态并执行。任务的状态保存及再加载, 这段过程就叫做上下文切换。
每个线程都有一个程序计数器(记录要执行的下一条指令),一组寄存器(保存当前线程的工作变量),堆栈(记录执行历史,其中每一帧保存了一个已经调用但未返回的过程)。
寄存器 是 CPU 内部的数量较少但是速度很快的内存(与之对应的是 CPU 外部相对较慢的 RAM 主内存)。寄存器通过对常用值(通常是运算的中间值)的快速访问来提高计算机程序运行的速度。
程序计数器是一个专用的寄存器,用于表明指令序列中 CPU 正在执行的位置,存的值为正在执行的指令的位置或者下一个将要被执行的指令的位置。
挂起当前任务(线程/进程),将这个任务在 CPU 中的状态(上下文)存储于内存中的某处
恢复一个任务(线程/进程),在内存中检索下一个任务的上下文并将其在 CPU 的寄存器中恢复
跳转到程序计数器所指向的位置(即跳转到任务被中断时的代码行),以恢复该进程在程序中
11、进程的调度算法
一、先来先去服务
先来先去服务调度算法是一种最简单的调度算法,也称为先进先出或严格排队方案。当每个进程就绪后,它加入就绪队列。当前正运行的进程停止执行,选择在就绪队列中存在时间最长的进程运行。该算法既可以用于作业调度,也可以用于进程调度。先来先去服务比较适合于常作业(进程),而不利于段作业(进程)。
二、时间片轮转法
轮转法是基于适中的抢占策略的,以一个周期性间隔产生时钟中断,当中断发生后,当前正在运行的进程被置于就绪队列中,然后基于先来先去服务策略选择下一个就绪作业的运行。这种技术也称为时间片,因为每个进程再被抢占之前都给定一片时间。
三、最短进程优先
最短进程优先是一个非抢占策略,他的原则是下一次选择预计处理时间最短的进程,因此短进程将会越过长作业,跳至队列头。该算法即可用于作业调度,也可用于进程调度。但是他对长作业不利,不能保证紧迫性作业(进程)被及时处理,作业的长短只是被估算出来的。
四、最短剩余时间优先
最短剩余时间是针对最短进程优先增加了抢占机制的版本。在这种情况下,进程调度总是选择预期剩余时间最短的进程。当一个进程加入到就绪队列时,他可能比当前运行的进程具有更短的剩余时间,因此只要新进程就绪,调度程序就能可能抢占当前正在运行的进程。像最短进程优先一样,调度程序正在执行选择函数是必须有关于处理时间的估计,并且存在长进程饥饿的危险。
五、最高响应比优先
根据比率:R=(w+s)/s (R为响应比,w为等待处理的时间,s为预计的服务时间)
如果该进程被立即调用,则R值等于归一化周转时间(周转时间和服务时间的比率)。R最小值为1.0,只有第一个进入系统的进程才能达到该值。调度规则为:当前进程完成或被阻塞时,选择R值最大的就绪进程,它说明了进程的年龄。当偏向短作业时,长进程由于得不到服务,等待时间不断增加,从而增加比值,最终在竞争中赢了短进程。
和最短进程优先、最短剩余时间优先一样,使用最高响应比策略需要估计预计服务时间。
六、反馈法
如果没有关于进程相对长度的任何信息,则最短进程优先,最短剩余时间、最高响应优先比都不能使用。另一种导致偏向短作业的方法是处罚运行时间较长的作业,换句话说,如果不能获得剩余的执行时间,那就关注已执行了的时间。
方法为:调度基于被抢占原则(按时间片)并使用动态优先级机制。当一个进程第一次进入系统中时,他被放置在一个优先级队列中,当第一次被抢占后并返回就绪状态时,它被放置在下一个低优先级队列中,在随后的时间里,每当被抢占时,他被降级到下一个低优先级队列中。一个短进程很快被执行完,不会在就绪队列中降很多级,一个长进程会逐渐降级。因此先到的进程和短进程优先于长进程和老进程。在每个队列中,除了优先级在最低的队列中之外,都是用简单的先来先去服务机制,一旦一个进程处于优先级最低的队列中,它就不可能在降级,但会重复的返回该队列,直到运行结束。因此,该队列课按照轮转方式调度。
七、多级反馈队列调度算法
多级反馈队列算法,不必事先知道各种进程所需要执行的时间,他是当前被公认的一种较好的进程调度算法。其实施过程如下:
1)设置多个就绪队列,并为各个队列赋予不同的优先级。在优先权越高的队列中,为每个进程所规定的执行时间片就越小。
2)当一个新进程进入内存后,首先放入第一队列的末尾,按照先来先去原则排队等候调度。如果他能在一个时间片中完成,便可撤离;如果未完成,就转入第二队列的末尾,同样等待调度.....如此下去,当一个长作业(进程)从第一队列依次将到第n队列(最后队列)后,便按第n队列时间片轮转运行。
3)仅当第一队列空闲的时候,调度程序才调度第二队列中的进程运行;仅当第1到(i-1)队列空时,才会调度第i队列中的进程运行,并执行相应的时间片轮转。
4)如果处理机正在处理第i队列中某进程,又有新进程进入优先权较高的队列,则此新队列抢占正在运行的处理机,并把正在运行的进程放在第i队列的队尾。
12、阻塞IO与非阻塞IO
1、阻塞IO模型
最传统的一种IO模型,即在读写数据过程中会发生阻塞现象。
当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除block状态。
典型的阻塞IO模型的例子为:
data = socket.read();
如果数据没有就绪,就会一直阻塞在read方法。
2、非阻塞IO模型
当用户线程发起一个read操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。
所以事实上,在非阻塞IO模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。
13、同步与异步的概念
同步是指:当程序1调用程序2时,程序1停下不动,直到程序2完成回到程序1来,程序1才继续执行下去。
异步是指:当程序1调用程序2时,程序1径自继续自己的下一个动作,不受程序2的的影响。
---------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------
同步是指:发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式。
异步是指:发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。
14、静态链接与动态链接的过程
一、静态链接
1.为什么要进行静态链接
在我们的实际开发中,不可能将所有代码放在一个源文件中,所以会出现多个源文件,而且多个源文件之间不是独立的,而会存在多种依赖关系,如一个源文件可能要调用另一个源文件中定义的函数,但是每个源文件都是独立编译的,即每个*.c文件会形成一个*.o文件,为了满足前面说的依赖关系,则需要将这些源文件产生的目标文件进行链接,从而形成一个可以执行的程序。这个链接的过程就是静态链接
2.静态链接的原理
由很多目标文件进行链接形成的是静态库,反之静态库也可以简单地看成是一组目标文件的集合,即很多目标文件经过压缩打包后形成的一个文件
二、动态链接
1.为什么会出现动态链接
动态链接出现的原因就是为了解决静态链接中提到的两个问题,一方面是空间浪费,另外一方面是更新困难。下面介绍一下如何解决这两个问题。
2.动态链接的原理
动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。下面简单介绍动态链接的过程:
假设现在有两个程序program1.o和program2.o,这两者共用同一个库lib.o,假设首先运行程序program1,系统首先加载program1.o,当系统发现program1.o中用到了lib.o,即program1.o依赖于lib.o,那么系统接着加载lib.o,如果program1.o和lib.o还依赖于其他目标文件,则依次全部加载到内存中。当program2运行时,同样的加载program2.o,然后发现program2.o依赖于lib.o,但是此时lib.o已经存在于内存中,这个时候就不再进行重新加载,而是将内存中已经存在的lib.o映射到program2的虚拟地址空间中,从而进行链接(这个链接过程和静态链接类似)形成可执行程序。
15、虚拟内存概念(非常重要)
一个系统中的进程是与其他进程共享CPU和主存资源的。随着对CPU需求的增长,进程以某种合理的平滑的方式慢了下来。 但是如果太多的进程需要太多的内存,那么它们中的一些就根本无法运行。当一个程序中没有空间可以用时,那就是它运气不好了。内存还容易被破坏。如果某个进程不小心写了另一个进程使用的内存,它就可能以某种完全和程序逻辑无关的令人迷惑的方式失败。
为了更加有效的管理内存并且少出错,现代系统提供了一种对主存的抽象的概念,叫做虚拟内存(VM)。虚拟内存时硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,它为每一个进程提供一个大的、一致的和私有的地址空间。通过一个很清晰的机制,虚拟内存提供了三个很重要的能力:
它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,高效的使用了主存。
它为每个进程提供了一致的地址空间,从而简化了内存管理
它保护了每个进程的地址空间不被其他进程破坏。
16、MMU地址翻译的具体流程
同任何缓存一样,虚拟内存系统必须由某种方法来判定一个虚拟页是否缓存在DRAM中的某个地方。如果是,系统还必须确定这个虚拟页存放在哪个物理页面中。如果不命中,系统必须判断这个虚拟页面放在磁盘的哪个位置,在物理内存中选择一个牺牲页,并将虚拟页从磁盘复制到DRAM中,替换这个牺牲页面。
这些功能是软硬件联合提供的,包括操作系统、MMU(内存管理单元)中的地址翻译硬件和一个存放在物理内存中叫作页表(page table)的数据结构,页表将虚拟页映射到物理页面。每次地址翻译硬件将一个虚拟地址转换为物理地址时,都会读取页表。操作系统负责维护页表的内容,以及在磁盘与DRAM之间来回传送页。
下图展示了一个页表的基本组织结构。页表就是一个页表条目(Page Table Entry)的数组。虚拟地址空间中的每个页在页表中都有一个固定的偏移量处都有一个PTE。为了我们的目的,我们假设每个PTE是由一个有效位(valid bit)和一个n位地址字段组成的。有效位表明了该虚拟页面当前是否被缓存在DRAM中。如果设置了有效位,那么地址字段就表示DRAM中相应的物理页的起始位置,这个物理页中缓存了该虚拟页。如果没有设置有效位,那么这个空地址表示这个虚拟页还未被分配。否则,这个地址就会指向该虚拟页在磁盘上的起始位置。
17、缺页处理过程
<1> 首先硬件会陷入内核,在堆栈中保存程序计数器。大多数机器将当前指令的各种状态信息保存在CPU中特殊的寄存器中。
<2>启动一个汇编代码例程保存通用寄存器及其它易失性信息,以免被操作系统破坏。这个例程将操作系统作为一个函数来调用。
(在页面换入换出的过程中可能会发生上下文换行,导致破坏当前程序计数器及通用寄存器中本进程的信息)
<3>当操作系统发现是一个页面中断时,查找出来发生页面中断的虚拟页面(进程地址空间中的页面)。这个虚拟页面的信息通常会保存在一个硬件寄存器中,如果没有的话,操作系统必须检索程序计数器,取出这条指令,用软件分析该指令,通过分析找出发生页面中断的虚拟页面。
<4>检查虚拟地址的有效性及安全保护位。如果发生保护错误,则杀死该进程。
<5>操作系统查找一个空闲的页框(物理内存中的页面),如果没有空闲页框则需要通过页面置换算法找到一个需要换出的页框。
<6>如果找的页框中的内容被修改了,则需要将修改的内容保存到磁盘上,此时会引起一个写磁盘调用,发生上下文切换(在等待磁盘写的过程中让其它进程运行)。
(注:此时需要将页框置为忙状态,以防页框被其它进程抢占掉)
<7>页框干净后,操作系统根据虚拟地址对应磁盘上的位置,将保持在磁盘上的页面内容复制到“干净”的页框中,此时会引起一个读磁盘调用,发生上下文切换。
<8>当磁盘中的页面内容全部装入页框后,向操作系统发送一个中断。操作系统更新内存中的页表项,将虚拟页面映射的页框号更新为写入的页框,并将页框标记为正常状态。
<9>恢复缺页中断发生前的状态,将程序指令器重新指向引起缺页中断的指令。
<10>调度引起页面中断的进程,操作系统返回汇编代码例程。
<11>汇编代码例程恢复现场,将之前保存在通用寄存器中的信息恢复。
其实缺页中断的过程涉及了用户态和内核态之间的切换,虚拟地址和物理之间的转换(这个转换过程需要使用MMU和TLB),同时涉及了内核态到用户态的转换。
18、缺页置换算法:最久未使用算法、先进先出算法、最佳置换算法

浙公网安备 33010602011771号