1. 进程的五种基本状态
(1) 五状态模型
创建状态:进程在创建时需要申请一个空白PCB,向其中填写控制和管理进程的信息,完成资源分配。如果创建工作无法完成,比如资源无法满足,就无法被调度运行,把此时进程所处状态称为创建状态
就绪状态:进程已经准备好,已分配到所需资源,只要分配到CPU就能够立即运行
执行状态:进程处于就绪状态被调度后,进程进入执行状态
阻塞状态:正在执行的进程由于某些事件(I/O请求,申请缓存区失败)而暂时无法运行,进程受到阻塞。在满足请求时进入就绪状态等待系统调用
终止状态:进程结束,或出现错误,或被系统终止,进入终止状态。无法再执行
(2)上图显示了导致进程状态转换的事件类型,可能的转换如下:
空->新建:创建执行一个程序的新进程,可能的事件有:新的批处理作业、交互登录(终端用户登录到系统)、操作系统因为提供一项服务而创建、由现有的进程派生等。
新建->就绪:操作系统准备好再接纳一个进程时,把一个进程从新建态转换为就绪态。
就绪->运行:需要选择一个新进程运行时,操作系统的调度器或分配器根据某种调度算法选择一个处于就绪态的进程。
运行->退出:导致进程终止的原因有:正常完成、超过时限、系统无法满足进程需要的内存空间、进程试图访问不允许访问的内存单元(越界)、算术错误(如除以0或存储大于硬件可以接纳的数字)、父进程终止(操作系统可能会自动终止该进程所有的后代进程)、父进程请求终止后代进程等。
运行->就绪:最常见的原因是,正在运行的进程到达了“允许不中断执行”的最大时间段,该把处理器的资源释放给其他在就绪态的进程使用了;还有一中原因可能是由于具有更改优先级的就绪态进程抢占了该进程的资源,使其被中断转换到就绪态。
运行->阻塞:如果进程请求它必须等待的某些事件,例如一个无法立即得到的资源(如I/O操作),只有在获得等待的资源后才能继续进程的执行,则进入等待态(阻塞态)。
阻塞->就绪:当等待的事件发生时,处于阻塞态的进程转换到就绪态。
就绪->退出:在上图中没有标出这种转换,在某些进程中,父进程可以在任何时刻终止一个子进程,如果一个父进程终止,所有相关的子进程都被终止。
阻塞->退出:跟上一项原因类似。
2. 进程与线程的区别
(1)进程是资源分配的最小单位,线程是程序执行的最小单位。
(2)地址空间:进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
(3)通信方式:线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
(4)健壮性:但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
3.进程通信的几种方式
进程间通信主要包括管道, 系统IPC(包括消息队列,信号量,共享存储), SOCKET。
管道
管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
管道包括三种:
1)普通管道PIPE, 通常有种限制,一是半双工,只能单向传输;二是只能在父子进程间使用.
2)流管道s_pipe: 去除了第一种限制,可以双向传输.
3)命名管道:name_pipe,去除了第二种限制,可以在许多并不相关的进程之间进行通讯.
有名管道 (namedpipe)
有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
系统IPC
信号量( semophore): 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
消息队列( messagequeue ): 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
信号 ( sinal ): 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
共享内存( sharedmemory ):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC
方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
套接字
套接字( socket ):套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
4. 线程同步几种方式
(一定要会写生产者、消费者问题,完全消化理解)
进程中线程同步的四种常用方式:
(1)临界区(CriticalSection)
当多个线程访问一个独占性共享资源时,可以使用临界区对象。拥有临界区的线程可以访问被保护起来的资源或代码段,其他线程若想访问,则被挂起,直到拥有临界区的线程放弃临界区为止。具体应用方式:
(1、 定义临界区对象
(2、 在访问共享资源(代码或变量)之前,先获得临界区对象
(3、 访问共享资源后,则放弃临界区对象
(2)事件(Event)
事件机制,则允许一个线程在处理完一个任务后,主动唤醒另外一个线程执行任务。比如在某些网络应用程序中,一个线程如A负责侦听通信端口,另外一个线程B负责更新用户数据,利用事件机制,则线程A可以通知线程B何时更新用户数据。
- (3)互斥量(Mutex)
互斥对象和临界区对象非常相似,只是其允许在进程间使用,而临界区只限制与同一进程的各个线程之间使用,但是更节省资源,更有效率。
(4)信号量(Semphore)
当需要一个计数器来限制可以使用某共享资源的线程数目时,可以使用“信号量”对象。Semaphore类对象保存了对当前访问某一个指定资源的线程的计数值,该计数值是当前还可以使用该资源的线程数目。如果这个计数达到了零,则所有对这个Semaphore类对象所控制的资源的访问尝试都被放入到一个队列中等待,直到超时或计数值不为零为止。
生产者-消费者模型:
要理解生产消费者问题,首先应弄清PV操作的含义:
PV操作是由P操作原语和V操作原语组成(原语是不可中断的过程),对信号量进行操作,具体定义如下:
P(S):
①将信号量S的值减1,即S=S-1;
②如果S³0,则该进程继续执行;否则该进程置为等待状态,排入等待队列。
V(S)
①将信号量S的值加1,即S=S+1;
②如果S>0,则该进程继续执行;否则释放队列中第一个等待信号量的进程。
P操作相当于申请资源,而V操作相当于释放资源。
生产者-消费者问题是一个有代表性的进程同步问题,生产者-消费者问题,也称作有界缓冲区问题,两个进程共享一个公共的固定大小的缓冲区。其中一个是生产者,用于将消息放入缓冲区;另外一个是消费者,用于从缓冲区中取出消息。问题出现在当缓冲区已经满了,而此时生产者还想向其中放入一个新的数据项的情形,或者当缓冲区空时,消费者还要从中取出数据项的问题。为了保证这种情况不会发生,我们通常使用信号量和消息传递来解决生产者-消费者问题。
(1)使用信号量解决生产者-消费者问题
一个信号量的取值可以为 0(表示没有保存下来的唤醒操作)或者为正值(表示有一个或多个唤醒操作)。
并且设立了两种操作:
down和 up(也是一般教科书上说的 P/V向量)。
对一个信号量执行 down操作,表示检查其值是否大于 0,如果该值大于 0,则将其值减 1(即用掉一个保存的唤醒信号)并继续;如果为 0,则进程休眠,而且此时 down操作并未结束。另外,就是检查数值,修改变量值以及可能发生的休眠操作都作为单一的,不可分割的原子操作来完成。
下面开始考虑用 信号量 来解决生产者-消费者问题了。
#define N 100 // 缓冲区中的槽数目typedef int semaphore;// 信号量一般被定义为特殊的整型数据semaphore mutex = 1;// 控制对临界区的访问semaphore empty = N;// 计数缓冲区中的空槽数目semaphore full = 0; // 计数缓冲区中的满槽数目/* 生产者进程 */void proceducer(void){int item;while(1){item = procedure_item(); // 生成数据down(&empty);// 将空槽数目减 1down(&mutex);// 进入临界区insert_item(item); // 将新数据放入缓冲区up(&mutex); // 离开临界区up(&full); // 将满槽的数目加 1}}/* 消费者进程 */void consumer(voi){int item;while(1){down(&full);// 将满槽数目减 1down(&mutex);// 进入临界区item = remove_item();// 从缓冲区中取出数据项up(&mutex); // 离开临界区up(&empty); // 将空槽数目加 1consumer_item(item);// 处理数据项}}
该解决方案使用了三个信号量:
一个为 full,用来记录充满的缓冲槽的数目。
一个为 empty,记录空的缓冲槽总数。
一个为 mutex,用来确保生产者和消费者不会同时访问缓冲区。mutex的初始值为 1,供两个或者多个进程使用的信号量,保证同一个时刻只有一个进程可以进入临界区,称为二元信号量(binary semaphore)。如果每一个进程在进入临界区前都执行一个 down(…),在刚刚退出临界区时执行一个 up(…),就能够实现互斥。
另外,通常是将 down和 up操作作为系统调用来实现,而且 OS只需要在执行以下操作时暂时禁止全部中断:测试信号量,更新信号量以及在需要时使某个进程休眠。
这里使用了三个信号量,但是它们的目的却不相同,其中 full和 empty用来同步(synchronization),而 mutex用来实现互斥。
(2)使用消息传递解决生产者-消费者问题
这种 IPC方式使用两条原语 send和 receive,也是系统调用。
如:
send(dest, &msg) // 将消息 msg发送到目标(进程)dest中
receive(src, &msg) // 接收由 src过来的 msg,如果没有消息可用,则可能阻塞接收者
消息传递系统会面临位于网络中不同机器上的通信进程的情形,所以会更加的复杂。如:消息可能被网络丢失,一般使用确认(ACK)消息。如果发送方在一定的时间段内没有收到确认消息,则重发消息。
如果消息本身被正确接收,但是返回的 ACK消息丢失,发送方则重发消息,这样接收方就会收到两份同样的消息。一般使用在每条原始消息的头部嵌入一个连续的序号来解决这个问题。
另外,消息传递系统还需要解决进程命名的问题,在 send和 receive系统调用中指定的进程必须没有二义性的。还有其他的一些问题,如性能问题,身份认证等等,不过那个就会扯多了,还是看看如果解决这个生产者-消费者的问题吧:
#define N 100// 缓冲区中的槽数目/* 生产者进程 */void proceducer(void){int item;messagemsg; // 消息缓冲区while(1){item = procedure_item(); // 生成数据receive(consumer, &msg); // 等待消费者发送空的缓冲区build_msg(&msg, item);// 创建待发送消息send(consumer, &msg);// 发送数据项给消费者}}/* 消费者进程 */void consumer(voi){int item,i;messagemsg;for(i=0;i<N; i++)send(producer, &msg);// 发送给生产者 N 个空缓冲区while(1){receive(producer, &msg); // 接收包含数据项的消息item = extract_item(&msg); // 解析消息,并组装成数据项send(proceduer, &msg);// 然后又将空缓冲区发送回生产者consumer_item(item);// 处理数据项}}
在这个解决方案中,共使用了 N条消息,有点类似于上一个的共享内存缓冲区的 N个槽,消费者进程这边首先通过一个 for循环将 N条空消息发送给生产者。当生产者向消费者传递一个数据项时,是通过取走每一条接收到的空消息,然后送回填充了内容的消息给消费者的。通过这种方式,整个消息传递系统中的总的消息数(包括空的消息 + 存了数据项的消息 == N)是不变的。
如果运行过程中,生产者进程的速度比消费者快,则所有的消息最终都会塞满,然后生产者进程就会等待消费者(即使调用 procedure也是阻塞在 receive处),直到消费者返回一条空的消息;反之亦然。
下面再来看一下消息传递方式的两种变体:
一种是:为每一个进程分配一个唯一的地址,让消息按照这个进程的地址进行编址。也就是 send和
receive调用的第一个参数指定为具体的进程地址。另一种是:引入信箱(mailbox),可以信箱就像一个盒子,里面装了很多的信件,这个信件就是我们要传递的消息,当然信箱是有容量限制的。当使用信箱时,send和
receive系统调用中的地址参数就是信箱的地址,而不是进程的地址。当一个进程尝试向一个容量爆满的信箱发送消息时,它将会被挂起,直到信箱中有消息被取走。
5. 线程的实现方式.
(也就是用户线程与内核线程的区别)
线程的实现可分为两大类:
用户级线程(user-levelthread,ULT)和内核级线程(kernel-levelthread,KLT)。后者又称为内核支持的线程或轻量级进程。
(1)用户级线程
在一个纯粹的用户级线程软件中,有关线程管理的所有工作都由应用程序完成,内核意识不到线程的存在。
使用用户级线程的优点有:
线程切换不需要内核态特权,进程并不需要为了线程管理而切换到内核态可以为应用程序量身定做调度算法而不扰乱底层的操作系统调度程序用户级线程可以在任何操作系统中运行,不需要对底层内核进行修改以支持用户级线程
用户级线程有两个明显的缺点:
许多系统调用都会引起阻塞,当用户级线程执行一个系统调用时,不仅这个线程会被阻塞,进程中的所有线程都会被阻塞在纯粹的用户级线程策略中,一个多线程应用程序不能利用多处理技术
(2)内核级线程
在一个纯粹的内核级线程软件中,有关线程管理的所有工作都是由内核完成的,应用程序部分没有进行线程管理的代码,只有一个到内核线程设施的应用程序编程接口(API)。
该方法克服了用户级线程方法的两个基本缺陷:
内核可以同时把同一个进程的多个线程调度到多个处理器中;如果进程中的一个线程被阻塞,内核可以调度同一个进程中的另一个线程。
相比用户级线程它的主要缺点:
把控制从一个线程传送到进程中的另一个线程时,需要到内核的状态切换。
某些操作系统采用组合用户级线程和内核级线程的方法,同一个应用程序中的多个线程可以在多个处理器上并行的运行,某个会引起阻塞的系统调用不会阻塞整个进程。如果设计正确,该方法会结合两种线程的优点,同时减少他们的缺点。
6.用户态和核心态的区别
当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。即此时处理器在特权级最低的(3级)用户代码中运行。
特权级显然是非常有效的管理和控制程序执行的手段,因此在硬件上对特权级做了很多支持,就Intel x86架构的CPU来说一共有0~3四个特权级,0级最高,3级最低,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查。虽然用户态下和内核态下工作的程序有很多差别,但最重要的差别就在于特权级的不同,即权力的不同。运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。
当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态。
用户态和内核态的转换
1)用户态切换到内核态的3种方式
a. 系统调用
这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。
b. 异常
当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
c. 外围设备的中断
当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。
2)具体的切换操作
从触发方式上看,可以认为存在前述3种不同的类型,但是从最终实际完成由用户态到内核态的切换操作上来说,涉及的关键步骤是完全一致的,没有任何区别,都相当于执行了一个中断响应的过程,因为系统调用实际上最终是中断机制实现的,而异常和中断的处理机制基本上也是一致的,涉及到由用户态切换到内核态的步骤主要包括:
[1] 从当前进程的描述符中提取其内核栈的ss0及esp0信息。
[2] 使用ss0和esp0指向的内核栈将当前进程的cs,eip,eflags,ss,esp信息保存起来,这个过程也完成了由用户栈到内核栈的切换过程,同时保存了被暂停执行的程序的下一条指令。
[3] 将先前由中断向量检索得到的中断处理程序的cs,eip信息装入相应的寄存器,开始执行中断处理程序,这时就转到了内核态的程序执行了。
7.用户栈和内核栈的区别
(1)当进程运行在内核态时,CPU堆栈指针寄存器指向的是内核堆栈地址,使用的是内核堆栈。当进程运行在用户态时。CPU堆栈指针寄存器指向的是用户堆栈地址,使用的是用户堆栈。
当进程由于中断进入内核态时,系统会把一些用户态的数据信息保存到内核栈中,当返回到用户态时,取出内核栈中得信息恢复出来,返回到程序原来执行的地方。
用户栈就是进程在用户空间时创建的栈,比如一般的函数调用,将会用到用户栈。
(2)内核栈是属于操作系统空间的一块固定区域,可以用于保存中断现场、保存操作系统子程序间相互调用的参数、返回值等。
用户栈是属于用户进程空间的一块区域,用户保存用户进程子程序间的相互调用的参数、返回值等。
(3)每个Windows 都有4g的进程空间,系统栈使用进程空间的低端部分,用户栈是高端部分。如果用户要直接访问系统栈部分,需要有特殊的方式。
进程用户栈和内核栈之间的切换
当进程由于中断或系统调用从用户态转换到内核态时,进程所使用的栈也要从用户栈切换到内核栈。系统调用实质就是通过指令产生中断,称为软中断。进程因为中断(软中断或硬件产生中断),使得CPU切换到特权工作模式,此时进程陷入内核态,进程进入内核态后,首先把用户态的堆栈地址保存在内核堆栈中,然后设置堆栈指针寄存器的地址为内核栈地址,这样就完成了用户栈向内核栈的切换。
当进程从内核态切换到用户态时,最后把保存在内核栈中的用户栈地址恢复到CPU栈指针寄存器即可,这样就完成了内核栈向用户栈的切换。
这里要理解一下内核堆栈。前面我们讲到,进程从用户态进入内核态时,需要在内核栈中保存用户栈的地址。那么进入内核态时,从哪里获得内核栈的栈指针呢?
要解决这个问题,先要理解从用户态刚切换到内核态以后,进程的内核栈总是空的。这点很好理解,当进程在用户空间运行时,使用的是用户栈;当进程在内核态运行时,内核栈中保存进程在内核态运行的相关信息,但是当进程完成了内核态的运行,重新回到用户态时,此时内核栈中保存的信息全部恢复,也就是说,进程在内核态中的代码执行完成回到用户态时,内核栈是空的。
因为内核栈是空的,那当进程从用户态切换到内核态后,把内核栈的栈顶地址设置给CPU的栈指针寄存器就可以了。
8. 内存池、进程池、线程池
首先介绍一个概念“池化技术”。池化技术就是:提前保存大量的资源,以备不时之需以及重复使用。池化技术应用广泛,如内存池,线程池,连接池等等。内存池相关的内容,建议看Apache、Nginx等开源web服务器的内存池实现。
由于在实际应用中,分配内存、创建进程、线程都会设计到一些系统调用,系统调用需要导致程序从用户态切换到内核态,是非常耗时的操作。因此,当程序中需要频繁的进行内存申请释放,进程、线程创建销毁等操作时,通常会使用内存池、进程池、线程池技术来提升程序的性能。
线程池:
线程池的原理很简单,类似于操作系统中的缓冲区的概念,它的流程如下:先启动若干数量的线程,并让这些线程都处于睡眠状态,当需要一个开辟一个线程去做具体的工作时,就会唤醒线程池中的某一个睡眠线程,让它去做具体工作,当工作完成后,线程又处于睡眠状态,而不是将线程销毁。
进程池与线程池同理
内存池:
内存池是指程序预先从操作系统申请一块足够大内存,此后,当程序中需要申请内存的时候,不是直接向操作系统申请,而是直接从内存池中获取;同理,当程序释放内存的时候,并不真正将内存返回给操作系统,而是返回内存池。当程序退出(或者特定时间)时,内存池才将之前申请的内存真正释放。
9.死锁的概念,导致死锁的原因
可以把死锁定义为一组相互竞争系统资源或进行通信的进程间的“永久”阻塞。当一组进程中的每个进程都在等待某个事件(典型的情况是等待所请求的资源释放),而只有在这组进程中的其他被阻塞的进程运行完毕后才可以释放该事件,这时就称这组进程发生死锁。因为没有事件能够被触发,所以死锁是永久性的。
产生死锁的原因主要:
1) 因为系统资源不足。
2) 进程运行推进的顺序不合适。
3) 资源分配不相等
10. 导致死锁的四个必要条件
导致死锁的4个必要条件:
1)互斥。一次只有一个进程可以使用一个资源。其他进程不能访问已分配给其他进程的资源。
2)占有且等待。当一个进程等待其他进程时,继续占有已经分配的资源。
3)不可抢占。不能强行抢占进程已占有的资源。
4)循环等待。存在一个封闭的进程链,使得每个进程至少占有此链中下一个进程所需要的一个资源
11. 处理死锁的四种方式
1)死锁预防
2)死锁避免
3)死锁检测
4)一种综合的死锁策略
所有的解决死锁的策略都有其优缺点,在不同的情况下使用不同的策略比将操作系统机制设计比只采用其中一种策略更有效。
把资源分成几组不同的资源类为预防在资源类之间由于循环等待产生死锁,可使用线性排序策略在一个资源类中,使用该类资源最适合的算法
作为这种技术的一个例子,考虑下列资源类:
可交换空间:在进程交换中所使用的外存中的存储块。对于可交换空间,通过要求一次性分配所有请求的资源来预防死锁
进程资源:可分配设备,如磁带设备和文件。 对于这类资源,死锁避免策略常常很有效,因为进程可以事先声明他们将需要的这类资源,采用资源排序的预防策略也是可能的
内存:可以按页或按段分配给进程。对于内存,基于抢占的预防是最合适的策略。当一个进程被抢占后,它仅仅是被换到外存,释放空间以解决死锁
内部资源:诸如I/O通道。可以使用基于资源排序的预防策略
12. 预防死锁的方法、避免死锁的方法
死锁预防策略
在死锁预防策略中,试图设计一种系统来排除发生死锁的可能性,方法分为两类:
间接的死锁预防方法(防止前面三个列出的三个必要条件中的任何一个的发生);直接的死锁的预防方法(防止循环等待的发生)。
死锁避免策略
在死锁预防中,通过约束资源请求,防止4个条件中至少一个的发生,可以通过直接或间接预防方法,但是这都会导致低效的资源使用和低效的进程执行。
死锁避免则相反,它允许前三个必要条件,但是通过明智的选择,确保永远不会到达死锁点,因此死锁避免比死锁预防允许更多的并发。在死锁避免中,是否允许当前资源分配请求是通过判断该请求是否可能导致死锁来决定的。因此,死锁避免需要知道将来的进程资源请求的情况。死锁避免策略并不能确切的预测死锁,它仅仅是预测死锁的可能性并确保永远不会出现这种可能性。
有两种死锁避免的办法:
如果一个进程的请求会导致死锁,则不启动此进程如果一个进程增加的资源请求会导致死锁,则不允许这次分配
银行家算法:
首先需要定义状态和安全状态的概念。
系统的状态是当前给进程分配的资源情况。因此,状态包含两个向量Resource(系统中每种资源的总量)和Available(未分配给进程的每种资源的总量)及两个矩阵Claim(表示进程对资源的需求)和Allocation(表示当前分配给进程的资源)。
安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否还处于安全状态。如果是,同意这个请求;如果不是,阻塞该进程直到同意该请求后系统状态仍然是安全的。
死锁检测策略
死锁预防策略是非常保守的,他们通过限制访问资源和在进程上强加约束来解决死锁的问题。死锁检测则是完全相反,它不限制资源访问或约束进程行为,只要有可能,被请求的资源就被授权给进程。操作系统周期性地执行一个算法检测前面的循环等待的条件。
13.进程调度算法
(周转时间=程序结束时间–开始服务时间,带权周转时间=周转时间/ 要求服务时间)
(1)先来先服务和短作业(进程)优先调度算法
1)先来先服务调度算法:
先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。FCFS算法比较有利于长作业(进程),而不利于短作业(进程)。由此可知,本算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业(进程)。
2)短作业(进程)优先调度算法:
短作业(进程)优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度, 也可用于进程调度。但其对长作业不利;不能保证紧迫性作业(进程)被及时处理;作业的长短只是被估算出来的。
(2)高优先权优先调度算法
1) 优先权调度算法的类型:为了照顾紧迫性作业,使之进入系统后便获得优先处理,引入了最高优先权优先(FPF)调度算法。此算法常被用在批处理系统中,作为作业调度算法,也作为多种操作系统中的进程调度,还可以用于实时系统中。当其用于作业调度,将后备队列中若干个优先权最高的作业装入内存。当其用于进程调度时,把处理机分配给就绪队列中优先权最高的进程,此时,又可以进一步把该算法分成以下两种:
非抢占式优先权算法抢占式优先权调度算法(高性能计算机操作系统)
2)优先权类型 。对于最高优先权优先调度算法,其核心在于:它是使用静态优先权还是动态优先权,以及如何确定进程的优先权。
3)高响应比优先调度算法
为了弥补短作业优先算法的不足,我们引入动态优先权,使作业的优先等级随着等待时间的增加而以速率a提高。 该优先权变化规律可描述为:优先权=(等待时间+要求服务时间)/要求服务时间;即 =(响应时间)/要求服务时间
(3)基于时间片的轮转调度算法
1)时间片轮转法
时间片轮转法一般用于进程调度,每次调度,把CPU分配队首进程,并令其执行一个时间片。 当执行的时间片用完时,由一个记时器发出一个时钟中断请求,该进程被停止,并被送往就绪队列末尾;依次循环。
2)多级反馈队列调度算法
不必事先知道各种进程所需要执行的时间,它是目前被公认的一种较好的进程调度算法。 其实施过程如下:
a. 设置多个就绪队列,并为各个队列赋予不同的优先级。在优先权越高的队列中,为每个进程所规定的执行时间片就越小。
b. 当一个新进程进入内存后,首先放入第一队列的末尾,按FCFS原则排队等候调度。 如果他能在一个时间片中完成,便可撤离;如果未完成,就转入第二队列的末尾,在同样等待调度…… 如此下去,当一个长作业(进程)从第一队列依次将到第n队列(最后队列)后,便按第n队列时间片轮转运行。
c. 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1到第(i-1)队列空时, 才会调度第i队列中的进程运行,并执行相应的时间片轮转。
d. 如果处理机正在处理第i队列中某进程,又有新进程进入优先权较高的队列, 则此新队列抢占正在运行的处理机,并把正在运行的进程放在第i队列的队尾。
14.Windows内存管理的方式
(块式、页式、段式、段页式)
Windows中的内存分为两类:
物理内存即实际内存,实存储器。虚拟内存:用硬盘空间做内存来弥补计算机RAM空间的缺乏。
虚拟内存可行性的理论基础:
(1)进程中的所有内存访问地址都是逻辑地址,这些逻辑地址在运行时动态的被转换为物理地址,这意味着一个进程可以被换入或换出内存,使得进程可以执行过程中的不同时刻占据内存中的不同区域。
(2)一个进程可以划分成许多块,在执行过程中,这些块不需要连续的存放在内存中。
使用虚拟内存的好处:
1、在内存中保留多个进程。由于对任何特定的进程都仅仅装入它的某些块,因此就有足够的空间来放置更多的进程。
2、进程可以比内存的全部空间还大。程序占用的内存空间的大小是程序设计中最大的限制之一。通过基于分页或分段的虚拟内存,这些分块可以按某种覆盖策略分别加载。
常用内存管理技术:
(1)固定分区
说明:在系统生成阶段,内存被划分成许多静态分区。进程可以被装入到大于或等于自身大小的分区。
优势:实现简单,只需要极少的操作系统开销。
缺点:由于有内部碎片,对内存的使用不充分;活动进程的最大数目是固定的。
(2)动态分区
说明:分区是动态创建的,因而使得每个进程可以被装入与自身大小正好相等的分区中。
优势:没有内部碎片;可以更充分的使用内存。
缺点:由于需要压缩外部碎片,处理器利用率低。
(3)虚拟内存分页
说明:内存被划分为许多大小相等的页框;每个进程被划分成许多大小与页框相等的页;不需要装入一个进程的所有页,每次只需将进程运行需要的页装入到内存中不一定连续的页框中。非驻留页在以后需要时自动调入内存。
优势:没有外部碎片;支持更高道数的多道程序设计;巨大的虚拟地址空间。
缺点:复杂的内存管理开销
使用分页技术在内存中为每个进程浪费的空间仅仅是进程最后一页的一小部分形成的内部碎片,没有任何外部碎片。
操作系统维护空闲页框的列表,为每个进程维护一个页表,页表给出了该进程的每一页对应的页框的位置,在程序中,每个逻辑地址包括一个页号和在该页中的偏移量。根据进程页表中页号和对应页框的位置,由对应的页框号和偏移量计算出物理地址。
(4)虚拟内存分段
说明:每个进程被划分为许多段;不需要装入一个进程的所有页,每次只需将进程运行需要的段装入到内存中不一定连续的某些动态分区中;非驻留段在以后需要时自动调入内存。
优势:没有内部碎片;支持更高道数的多道程序设计;巨大的虚拟地址空间;支持保护与共享
缺点:复杂的内存管理开销,分段消除了内部碎片,但是会产生外部碎片
在简单的分段方案中,每个进程都有一个段表,系统也会维护一个内存中的空闲块列表。每个段表项必须给出相应的段在内存中的起始地址,还必须指明段的长度,以确保不会使用无效的地址。
(5)段页式
分段和分页都有它们的长处。分页对程序员是透明的,它消除了外部碎片,因而可以更有效地使用内存,并且移入或移出内存的块是固定的,大小相等的。分段对程序员是可见的,它具有处理不断增长的数据结构的能力以及支持共享和保护的能力。
在段页式的系统中,用户的地址空间被程序员划分成许多段。每个段一次划分成许多固定大小的页,页的长度等于内存中页框的大小。从程序员的角度看,逻辑地址仍然由段号和段偏移量组成,从系统的角度看,段偏移量可视为指定段中的一个页号和页偏移。
15. 内存连续分配方式采用的几种算法及各自优劣
常见内存分配算法及优缺点如下:
(1)首次适应算法
使用该算法进行内存分配时,从空闲分区链首开始查找,直至找到一个能满足其大小需求的空闲分区为止。然后再按照作业的大小,从该分区中划出一块内存分配给请求者,余下的空闲分区仍留在空闲分区链中。
该算法倾向于使用内存中低地址部分的空闲分区,在高地址部分的空闲分区非常少被利用,从而保留了高地址部分的大空闲区。显然为以后到达的大作业分配大的内存空间创造了条件。缺点在于低址部分不断被划分,留下许多难以利用、非常小的空闲区,而每次查找又都从低址部分开始,这无疑会增加查找的开销。
(2)循环首次适应算法。
该算法是由首次适应算法演变而成的。在为进程分配内存空间时,不再每次从链首开始查找,而是从上次找到的空闲分区开始查找,直至找到一个能满足需求的空闲分区,并从中划出一块来分给作业。该算法能使空闲中的内存分区分布得更加均匀,缺点是将会缺乏大的空闲分区。
(3)最佳适应算法。
该算法总是把既能满足需求,又是最小的空闲分区分配给作业。
为了加速查找,该算法需求将所有的空闲区按其大小排序后,以递增顺序形成一个空白链。这样每次找到的第一个满足需求的空闲区,必然是最优的。孤立地看,该算法似乎是最优的,但事实上并不一定。因为每次分配后剩余的空间一定是最小的,缺点是在存储器中将留下许多难以利用的小空闲区。同时每次分配后必须重新排序,这也带来了一定的开销。
(4)最差适应算法。
最差适应算法中,该算法按大小递减的顺序形成空闲区链,分配时直接从空闲区链的第一个空闲分区中分配(不能满足需要则不分配)。非常显然,如果第一个空闲分区不能满足,那么再没有空闲分区能满足需要。这种分配方法初看起来不太合理,但他也有非常强的直观吸引力:在大空闲区中放入程式后,剩下的空闲区常常也非常大,于是还能装下一个较大的新程式。
最坏适应算法和最佳适应算法的排序正好相反,他的队列指针总是指向最大的空闲区,在进行分配时,总是从最大的空闲区开始查寻。
该算法克服了最佳适应算法留下的许多小的碎片的不足,但缺点是保留大的空闲区的可能性减小了,而且空闲区回收也和最佳适应算法相同复杂。
16. 动态链接及静态链接
静态链接就是在编译链接时直接将需要的执行代码拷贝到调用处,优点就是在程序发布的时候就不需要的依赖库,也就是不再需要带着库一块发布,程序可以独立执行,但是体积可能会相对大一些。
动态链接就是在编译的时候不直接拷贝可执行代码,而是通过记录一系列符号和参数,在程序运行或加载时将这些信息传递给操作系统,操作系统负责将需要的动态库加载到内存中,然后程序在运行到指定的代码时,去共享执行内存中已经加载的动态库可执行代码,最终达到运行时连接的目的。优点是多个程序可以共享同一段代码,而不需要在磁盘上存储多个拷贝,缺点是由于是运行时加载,可能会影响程序的前期执行性能。
动态链接库的两种链接方法:
(1)装载时动态链接(Load-time Dynamic Linking):
这种用法的前提是在编译之前已经明确知道要调用DLL中的哪几个函数,编译时在目标文件中只保留必要的链接信息,而不含DLL函数的代码;当程序执行时,调用函数的时候利用链接信息加载DLL函数代码并在内存中将其链接入调用程序的执行空间中(全部函数加载进内存),其主要目的是便于代码共享。(动态加载程序,处在加载阶段,主要为了共享代码,共享代码内存)
(2) 运行时动态链接(Run-time Dynamic Linking):
这种方式是指在编译之前并不知道将会调用哪些DLL函数,完全是在运行过程中根据需要决定应调用哪个函数,将其加载到内存中(只加载调用的函数进内存),并标识内存地址,其他程序也可以使用该程序,并用LoadLibrary和GetProcAddress动态获得DLL函数的入口地址。(dll在内存中只存在一份,处在运行阶段)
上述的区别主要在于阶段不同,编译器是否知道进程要调用的dll函数。动态加载在编译时知道所调用的函数,而在运行态时则必须不知道。
17. 基本分页、请求分页储存管理方式
基本分页储存管理方式具有如下特征:
1) 一次性。要求将作业全部装入内存后方能运行。许多作业在每次运行时,并非其全部程序和数据都要用到。如果一次性地装入其全部程序,造成内存空间的浪费。
2) 驻留性。作业装入内存后,便一直驻留在内存中,直至作业运行结束。尽管运行中的进程会因I/O而长期等待,或有的程序模块在运行过一次后就不再需要(运行)了,但它们都仍将继续占用宝贵的内存资源。
请求分页储存管理:
它是实现虚拟存储器的一种常用方式,它是在基本分页储存管理的基础上实现的。
其基本思想是:
在进程开始运行之前,仅装入当前要执行的部分页面即可运行;
在执行过程中,可使用请求调入中断动态装入要访问但又不在内存的页面;
当内存空间已满,而又需要装入新的页面时,则根据置换功能适当调出某个页面,以便腾出空间而装入新的页面。
为实现请求分页,需要一定的硬件支持,包括:页表机制、缺页中断机构、地址变换机构。
18.基本分段、请求分段储存管理方式
分页管理方式是从计算机的角度考虑设计的,以提高内存的利用率,提高计算机的性能,提升计算机的性能,且分页通过硬件机制实现,对用户完全透明;
而分段管理方式的提出则是考虑用户和程序员,以满足方便编程、信息保护和共享、动态增长及动态链接等多方面的需要。
基本分段储存管理:
段式管理方式是按照用户进程中的自然段划分逻辑空间。
例如,用户进程由主程序、两个子程序、栈和一段数据组成,于是可以把这个用户进程划分为5个段,每段从0开始编址,并分配一段连续的地址空间(段内要求连续,段间不要求连续,因此整个作业的地址空间是二维的)。其逻辑地址由段号S与段内偏移量W两部分组成。
与分页管理类似,分段管理的保护方法主要有两种:
一种是存取控制保护,另一种是地址越界保存。
地址越界保护是利用段表寄存器中的段表长度与逻辑地址中的段号比较,若段号大于段表长度则产生越界中断;再利用段表项中的段长和逻辑地址中的段内位移进行比较,若段内位移大于段长,也会产生越界中断。
基本分段储存管理方式就像基本分页储存管理方式一样,将作业需要的数据段一次性的全部装入内存中,作业装入内存后,便一直驻留在内存中,直至作业运行结束。
请求分段储存管理方式就如同请求分页储存管理方式一样,在进程开始运行之前,仅装入当前要执行的部分段即可运行;在执行过程中,可使用请求调入中断动态装入要访问但又不在内存的程序段;当内存空间已满,而又需要装入新的段时,根据置换功能适当调出某个段,以便腾出空间而装入新的段。
19. 分段分页方式的比较各自优缺点
分段和分页其实都是一种对地址的划分或者映射的方式。 两者的区别主要有以下几点:
1)页是信息的物理单位,分页是为实现离散分配方式,以消减内存的外零头,提高内存的利用率;或者说,分页仅仅是由于系统管理的需要,而不是用户的需要(也是对用户透明的)。段是信息的逻辑单位,它含有一组其意义相对完整的信息(比如数据段、代码段和堆栈段等)。分段的目的是为了能更好的满足用户的需要(用户也是可以使用的)。
2)页的大小固定且由系统确定,把逻辑地址划分为页号和页内地址两部分,是由机器硬件实现的,因而一个系统只能有一种大小的页面。段的长度却不固定,决定于用户所编写的程序,通常由编辑程序在对源程序进行编辑时,根据信息的性质来划分。
3)分页的作业地址空间是维一的,即单一的线性空间,程序员只须利用一个记忆符(线性地址的16进制表示),即可表示一地址。分段的作业地址空间是二维的,程序员在标识一个地址时,既需给出段名(比如数据段、代码段和堆栈段等),又需给出段内地址。
4)页和段都有存储保护机制。但存取权限不同:段有读、写和执行三种权限;而页只有读和写两种权限
20. 几种页面置换算法,要会算所需换页数
(LRU用程序如何实现?)
1)最佳置换算法(OPT)(OPTimalreplacement理想置换算法)
这是一种理想情况下的页面置换算法,但实际上是不可能实现的。该算法的基本思想是:发生缺页时,有些页面在内存中,其中有一页将很快被访问(也包含紧接着的下一条指令的那页),而其他页面则可能要到10、100或者1000条指令后才会被访问,每个页面都可以用在该页面首次被访问前所要执行的指令数进行标记。最佳页面置换算法只是简单地规定:标记最大的页应该被置换,选择下次访问距当前时间最长的那些页。这个算法唯一的一个问题就是它无法实现。当缺页发生时,操作系统无法知道各个页面下一次是在什么时候被访问。虽然这个算法不可能实现,但是最佳页面置换算法可以用于对可实现算法的性能进行衡量比较。
2)先进先出置换算法(FIFO)
最简单的页面置换算法是先入先出(FIFO)法。这种算法的实质是,总是选择在主存中停留时间最长(即最老)的一页置换,即先进入内存的页,先退出内存。理由是:最早调入内存的页,其不再被使用的可能性比刚调入内存的可能性大。建立一个FIFO队列,收容所有在内存中的页。被置换页面总是在队列头上进行。当一个页面被放入内存时,就把它插在队尾上。
这种算法只是在按线性顺序访问地址空间时才是理想的,否则效率不高。因为那些常被访问的页,往往在主存中也停留得最久,结果它们因变“老”而不得不被置换出去。
FIFO的另一个缺点是,它有一种异常现象,即在增加存储块的情况下,反而使缺页中断率增加了。当然,导致这种异常现象的页面走向实际上是很少见的。
3)最近最久未使用(LRU)算法
FIFO算法和OPT算法之间的主要差别是,FIFO算法利用页面进入内存后的时间长短作为置换依据,而OPT算法的依据是将来使用页面的时间。如果以最近的过去作为不久将来的近似,那么就可以把过去最长一段时间里不曾被使用的页面置换掉。它的实质是,当需要置换一页时,选择在最近一段时间里最久没有使用过的页面予以置换。这种算法就称为最久未使用算法(Least Recently Used,LRU)。
LRU算法是与每个页面最后使用的时间有关的。当必须置换一个页面时,LRU算法选择过去一段时间里最久未被使用的页面。虽然LRU策略的性能接近OPT策略,但是问题在于这种策略比较难实现。
21. 虚拟内存的定义及实现方式
虚拟内存是指为了扩充主存空间而在外存上开辟的一块存储空间.虚拟内存用来保存实际内存中暂时不用的程序或数据,使实际内存有更多的空闲空间来存放将要执行的程序或访问的数据,当需要执行的程序或访问的数据不在主存时,就从虚拟内存将其调入到主存,以便处理器执行或访问,这样就扩大了内存空间,操作系统对实际内存和虚拟内存统一编址和统一管理,这就是虚拟内存技术.
大多数的多任务操作系统都采用分页存储方式,使用虚拟内存技术。UNIX操作系统采用页面存储方式,Windows NT采用请求分页的虚拟存储方式,linux系统采用按需调页的模式。在AIX系统中,也使用分页的存储方式管理存储器,并将虚拟内存称为页面空间(Paging Space),所有对实际物理内存和虚拟内存的访问都是由虚拟内存管理器(VMM)完成的。
22. 操作系统的四个特性
1)并发(concurrence)
并行性与并发性这两个概念是既相似又区别的两个概念。并行性是指两个或者多个事件在同一时刻发生,这是一个具有微观意义的概念,即在物理上这些事件是同时发生的;而并发性是指两个或者多个事件在同一时间的间隔内发生,它是一个较为宏观的概念。在多道程序环境下,并发性是指在一段时间内有多道程序在同时运行,但在单处理机的系统中,每一时刻仅能执行一道程序,故微观上这些程序是在交替执行的。
应当指出,通常的程序是静态实体,它们是不能并发执行的。为了使程序能并发执行,系统必须分别为每个程序建立进程。进程,又称任务,简单来说,是指在系统中能独立运行并作为资源分配的基本单位,它是一个活动的实体。多个进程之间可以并发执行和交换信息。一个进程在运行时需要运行时需要一定的资源,如cpu,存储空间,及i/o设备等。在操作系统中引入进程的目的是使程序能并发执行。
2)共享 (sharing)
所谓共享是指,系统中的资源可供内存中多个并发执行的进程共同使用。由于资源的属性不同,故多个进程对资源的共享方式也不同,可以分为:互斥共享方式和同时访问方式。
3)虚拟 (virtual)
是指通过技术把一个物理实体变成若干个逻辑上的对应物。在操作系统中虚拟的实现主要是通过分时的使用方法。显然,如果n是某一个物理设备所对应的虚拟逻辑设备数,则虚拟设备的速度必然是物理设备速度的1/n。
4)异步 (asynchronism)
在多道程序设计环境下,允许多个进程并发执行,由于资源等因素的限制,通常,进程的执行并非“一气呵成”,而是以“走走停停”的方式运行。内存中每个进程在何时执行,何时暂停,以怎样的方式向前推进,每道程序总共需要多少时间才能完成,都是不可预知的。或者说,进程是以一步的方式运行的。尽管如此,但只要运行环境相同,作业经过多次运行,都会获得完全相同的结果。
23.DMA(直接内存存取)
DMA是指外部设备不通过CPU而直接与系统内存交换数据的接口技术。
要把外设的数据读入内存或把内存的数据传送到外设,一般都要通过CPU控制完成,如CPU程序查询或中断方式。利用中断进行数据传送,可以大大提高CPU的利用率。
但是采用中断传送有它的缺点,对于一个高速I/O设备,以及批量交换数据的情况,只能采用DMA方式,才能解决效率和速度问题。DMA在外设与内存间直接进行数据交换,而不通过CPU,这样数据传送的速度就取决于存储器和外设的工作速度。
通常系统的总线是由CPU管理的。在DMA方式时,就希望CPU把这些总线让出来,即CPU连到这些总线上的线处于第三态–高阻状态,而由DMA控制器接管,控制传送的字节数,判断DMA是否结束,以及发出DMA结束信号。DMA控制器必须有以下功能:
1)能向CPU发出系统保持(HOLD)信号,提出总线接管请求;
2)当CPU发出允许接管信号后,负责对总线的控制,进入DMA方式;
3)能对存储器寻址及能修改地址指针,实现对内存的读写操作;
4)能决定本次DMA传送的字节数,判断DMA传送是否结束
5)发出DMA结束信号,使CPU恢复正常工作状态。
24.Spooling
SPOOLING技术(Simultaneous Peripheral Operating On Line)
同时联机外围操作技术,它是关于慢速字符设备如何与计算机主机进行数据交换的一种技术,通常又称假脱机技术。
在多道程序环境下,利用多道程序中的一道或者两道程序来模拟脱机输入/输出中的外围控制机的功能,以达到“脱机”输入/输出的目的。
利用这种技术可把独占设备转变成共享的虚拟设备,从而提高独占设备的利用率和进程的推进速度。
SPOOLING系统 是对脱机输入/输出工作的模拟,它必须有大容量的且可随机存取的存储器
的支持。
其主要思想是:
在联机的条件下,进行两个方向的操作,在数据输入时,将数据从输入设备传送到磁盘或磁带(块设备),然后把这些块设备与主机相连;反过来,在数据输出时,将输出数据传送到磁盘或磁带上,再从磁盘或磁带传送到输出设备。
这样,可以将一台独占的物理设备虚拟为并行使用的多态逻辑设备,从而使该物理设备被多个进程共享。
输入进程SPI
是模拟脱机输入时的外围控制机,它将用户要求处理的数据从输入设备通过输入缓冲区再送到输入井(磁盘上开辟的一块区域),当CPU处理这些数据数据时,就直接从输入井读入内存。
输出进程SPO
是模拟脱机输出时的外围控制机,把用户要求输出的数据,先从内存送到输出井,待输出设备空闲时,再将输出井中的数据通过输出缓冲区(内存中一块区域)传送到输出设备上。
实例——利用打印机实现打印机共享
已经被广泛用于多用户系统和计算机网络中,它实际上就是利用SPOOLING技术将独占的打印机改造为一台供做个用户共享的设备,只要有足够的外存空间和多道程序操作系统的支持即可。
1、当用户进程请求打印输出时,SPOOLING系统立即同意为该进程执行打印输出,但并不是真正地把打印机分配给该用户进程,而只是为该进程做两项工作:一项是由输出进程SPO在输出井中为之申请一个空闲的存储空间,并将要打印的数据传送其中存放;另一项工作就是由输出进程SPO再为用户进程申请一张空白的用户请求打印表,并将用户的打印请求填入其中,然后将该表挂到打印机的请求队列上。这时,如果还有另一个进程请求打印机时,则系统仍同意为该进程执行打印输出,当然,系统所做的工作仍是以上两项内容。
2、在打印机执行实际打印时,如果打印机空闲,输出进程SPO将从请求打印队列的队首取出一张打印表,根据打印表中的要求将要打印的数据从输出井传送到内存输出缓冲区,再传送到打印机打印。打印完后,输出进程SPO将再检查请求打印队列中是否还有待打印的请求表,若有则再取出一张请求打印表,将新的但因要求继续打印。如此反复,直到请求打印队列空为止,输出进程才将自己阻塞起来,并在下次再有打印请求。
25.外存分配的几种方式,及各种优劣
外存,指的是除了cpu缓存和内存以外的存储器,硬盘、光盘、U盘都可以被称为外存。所有的数据,也都存在这里面,故他的分配方式变得极其重要,这直接影响到了计算机的运行速度。
外存分配方式主要有这几种:连续分配,链式分配,索引分配。
(1) 连续分配
原理:创建文件时,分配一组连续的块;FAT(文件分配表)中每个文件只要一项,说明起始块和文件长度。对于顺序文件有利。
优点:1.简便。适用于一次性写入操作。2.支持顺序存取和随机存取,顺序存取速度快。3.所需的磁盘寻道次数和寻道时间最少。(因为空间的连续性,当访问下一个磁盘块时,一般无需移动磁头,当需要移动磁头时,只需要移动一个磁道。)
缺点:1.文件不能动态增长。(可能文件末尾处的空块已经分配给了别的文件)2.不利于文件的插入和删除。3.外部碎片问题。(反复增删文件后,很难找到空间大小足够的连续块,需要进行紧缩。)4.在创建文件时需声明文件大小。
如图:
(2)链式分配
原理:一个文件的信息存放在若干个不连续的物理块中,各块之间通过指针连接,前一个物理块指向下一个物理块。fat中每个文件同样只需要一项,包括文件名、起始块号和最后块号。任何一个自由块都可以加入到链中。
优点:1.提高磁盘的空间利用率,不存在外部碎片问题。2.有利于文件的插入和删除。3.有利于文件的动态扩充。
缺点:1.存取速度慢,一般只适用于信息的顺序存取,不适于随机存取。2.查找某一块必须从头到尾沿着指针进行。3.可靠性问题,如指针出错。4.更多的寻道次数和寻道时间。5.链接指针占一定的空间,将多个块组成簇,按簇进行分配而不是按块进行分配。(增加了磁盘碎片)
如图:
(3)索引分配
原理:每个文件在FAT中有一个一级索引,索引包含分配给文件的每个分区的入口。文件的索引保存在单独的一个块中,FAT中该文件的入口指向这一块。
优点:1.保持了链接结构的优点,又解决了其缺点:按快分配可以消除外部碎片。按大小可改变的分区分配可以提高局部性。索引分配支持顺序访问文件和直接访问文件,是普遍采用的一种方式。2.满足了文件动态增长,插入删除的要求。(只要有空闲块)3.能充分利用外存空间。
缺点:1.较多的寻道次数和寻道空间。2.索引表本身带来了系统开销,如:内外存空间、存取时间。
如图:
4)连续分配和索引分配相结合
原理:对于小文件(3、4块),采用连续分配;当文件大时,自动切换到索引分配。
文件的直接访问:使用连续分配方式。
文件的顺序访问:采用链接分配。
对于这些系统,所使用的访问类型,必须在文件创建时加以说明。
(5)多重索引
原理:首先,多重索引也是索引分配的一种,只不过它是将一个大文件的所有索引表(二级索引)的地址放在另一个索引表(一级索引)中。ps:跟数据库第四范式非常像。
大文件:设一个盘块大小为1kb,长度100kb的文件就需要100个盘块,索引表至少需要100项;若文件大小为1000kb,则索引表项就要有1000项。设盘块号用4个字节表示,则该索引表至少占用4000bye(约4k)。
当文件很大时,存在的问题:1.需要很多磁盘块。2.索引表很大。3.不能将整个索引表放在内存。
解决途径:采用多重索引表结构。
如图:
多重索引表结构图示: