首页 > 技术文章 > 操作系统学习笔记 (持续更新)

huangke 2017-08-25 16:25 原文

学习前言

why?

操作系统涉及底层的知识,如果你能设计,改进操作系统,你就可以控制整个计算机系统

how?

我听到的我会忘记,我看到的我会记住,只有我做过的我才能理解

系统调用、异常、中断

  1. 系统调用:由应用程序向操作系统发出的一条特殊指令(服务请求),让操作系统完成相应的功能, 异步或同步。
  2. 异常:非法指令或者其他坏的处理状态,如内存出,来自于应用程序意想不到的行为, 同步。
  3. 中断:来源于外设,来自不同的硬件设备的计时器和网络的中断, 异步。

为了安全,应用程序不能直接访问外设

事件的发生通常通过硬件或软件中断来表示,硬件可以随时通过系统总线向CPU发出信号,以触发中断,软件通过执行特别操作如系统调用,也能触发中断,当CPU中断时,它暂停正在做的事并立即转到固定的位置去继续执行,该固定位置通常是中断服务程序开始位置的地址,中断服务程序开始执行,在执行完后,CPU重新执行被中断的计算。中断必须将控制转移到合适的中断处理程序,处理转移的简单方法是调用一个通用子程序以检查中断信息,接着,该子程序会调用相应的中断处理程序,处理中断要快,所以可使用中断处理子程序的指针表,通过指针表可间接调用中断处理子程序。中断体系结构也保存被中断指令的地址,现代的结构将返回系统堆栈中的地址,如果中断处理程序需要修改处理器状态,它必须明确地保存当前状态并在返回之前恢复该状态,在处理中断之后,保存的返回地址会装入程序计数器,被中断的计算可以重新开始,就好像中断没有发生过一样。

存储结构

计算机程序必须在内存中以便于运行,它通常是用被称为动态随机访问内存DRAM的半导体技术来实现的,是一组内存字的数组。一个典型指令执行周期首先从内存中获取指令,并保存在指令寄存器中,接着,指令被解码,并可能导致从内存中获取操作数或将操作数保存在内部寄存器中,在指令完成对操作数的执行后,其结果可以存回到内存,

寄存器 - 调整缓存 - 主存 - 电子磁盘 - 磁盘 - 光盘 - 磁带

I/O结构

能用计算机系统由一个CPU和多个设备控制器组成,它们通过共同的总线连接起来,每个设备控制器负责特定类型的设备,设备控制器维护一定量的本地缓冲存储和一组特定用途的寄存器,设备控制器负责在其所控制的外部设备与本地缓冲存储之间进行数据传递,通常操作系统为每个设备控制器提供一个设备驱动程序,这些设备驱动程序理解设备控制,并提供一个设备与其余操作系统的统一接口。

操作系统结构

操作系统最重要的一点是要有多道程序处理能力。多道程序设计通过组织作业使CPU总有一个作业可执行,从而提高了CPU的利用率。
这种思想如下,操作系统同时将多个任务保存在内存中,该作业集可以是作业池中作业集的子集,这是因为可同时保存在内存中的作业数要比可在作业池中的作业数少,操作系统选择一个位于内存中的作业并开始执行,最终该作业可能必须等待另一个任务如I/O操作的完成,对于非多道程序系统,CPU就会空闲,对于多道程序系统,CPU会简单地切换到另一个作业并执行,当该作业需要等待时,CPU会切换到另一个作业,最后,第一个作业完成等待且重新获得CPU,只要有一个任务可以执行,CPU就决不会空闲。

多道程序系统提供了一个可以充分使用各种系统资源的环境,但是它们没有提供与计算机系统直接交互的能力,分时系统是多道程序设计的延伸,在分时系统中,虽然CPU还是通过在作业之间的切换来执行多个作业,但是由于切换频率很高,用户可以在程序运行期间与之进行交互。分时操作系统允许许多用户同时共享计算机,由于分时系统的每个动作或命令都较短,因而每个用户只要少量CPU时间,随着系统从一个用户快速切换到另一个用户,每个用户会感到整个系统只为自己所用,尽管它事实上为许多用户所共享。
分时操作系统采用CPU调度和多道程序以提供用户分时计算机的一小部分,每个用户在内存中至少有一个程序。装入到内存并执行的程序通常称为进程。当进程执行时,它通常只执行较短一段时间,此时它并未完成或者需要进行I/O操作,操作系统为了不让CPU空闲,会将CPU切换到其他用户的程序。
分时和多道程序设计需要在存储器中同时保存有几个作业,通常由于主存较小而不能容纳太多作业,所以这些作业刚开始存储在磁盘的作业池中,该池由所有驻留在磁盘中需要等待分配内存的作业组成,如果多个作业需要调入内存但没有足够内存,那么系统必须在这些作业中做出选择,这样的决策称为作业调度如果有多个任务同时需要执行,那么系统必须做出选择,这样的选择称为CPU调度

操作系统操作

现代操作系统是由中断驱动的,如果没有进程要执行,没有I/O设备要服务,也没有用户请求要响应,操作系统将会静静地等待某件事情的发生,事件总是由中断或陷阱引起的,陷阱是一种软件中断,源于出错或源于用户程序的一个特别请求,这种操作系统的中断特性定义了系统的能用结构。对于每一种中断,操作系统中不同的代码段决定了将要采取的动作。

双重模式操作

为了确保操作系统的正常执行,必须区分操作系统代码和用户定义代码的执行,至少需要两种独立的操作模式:用户模式和监督程序模式(也称管理模式,系统模式,特权模式),在计算机三件中增加一个称为模式位的位以表示当前模式:系统模式为0,用户模式为1.系统引导时,硬件开始牌内核模式,接着,装入操作系统,开始在用户模式下执行用户进程,一旦出现陷阱或中断,硬件会从用户模式切换到内核模式,只要操作系统获得了对计算机的控制,它就处于内核模式,系统在将控制交还给用户程序时会切换到用户模式。双重模式操作提供了保护操作系统和用户程序不受错误用户程序影响的手段,其实现方法为:将能引起损害的机器指令作为特权指令,如果在用户模式下试图执行特权指令,那么三件并不执行该指令,而认为访指令非法,并将其以陷阱的形式通知操作系统。转换到用户模式就是一个特权指令。

指令执行的生命周期

最初的控制发生在操作系统中,在此指令以内核模式来执行,当控制权转到一个用户应用程序后,模式变为用户模式,最后,通过中断,陷阱或系统调用将控制权返回给操作系统。
系统调用为用户程序请求操作系统代表用户程序完成预留给操作系统的任务提供了方法。当系统调用被执行时,硬件会将它作为软件中断,控制权会通过中断向量转交到操作系统的中断处理程序,模式位设置成内核模式,系统调用服务程序是操作系统的一部分。

定时器

必须操作系统能维持对CPU的控制,也必须防止用户程序陷入死循环或不调用系统服务并且不将控制权返回到操作系统。为了实现这一目标可使用定时器,将定时器设置为在给定时间后中断计算机。可变定时器一般通过一个固定速率的时钟和计数器来实现,操作系统设置计数器,每经过一个时钟周期,计数器都要递减,当计数器的值为0时,产生中断。可以使用定时器来防止用户程序运行时间过长,只要计数器的值为正,控制就返回到用户程序,当计数器的值为负时,操作系统会中止程序执行,因为它超过了所赋予时间的限制。

进程管理

处于执行中的程序被称为进程。进程需要一定的资源以完成其任务,这些资源可以在进程创建时分配给进程,也可以在执行进程时分配给进程,除了在创建时得到各种物理或逻辑资源外,进程还可以接受传输过来的各种初始化数据。当进程中止时,操作系统将收回所有可再用的资源。
程序本身并不是进程,程序是被动的实体,而进程是一个活动的实体,单线进程具有一个程序计数器来明确下一个执行的指令。这样一个进程的执行必须是连续的。CPU一个接着一个地执行进程的指令,直到进程终止。多线程进程具有多个程序计数器,每一个指向下一个给定线程要执行的指令。
进程是系统工作的单元,系统由多个进程组成,其中一些是操作系统进程,其余的是用户进程,所有这些进程可以潜在的并发执行。

内存管理

内存是一个大的字节或字的数组,每个字节或字都有自己的地址,内存是可以被CPU和I/O设备所共同快速访问的数据仓库,中央处理器在获取指令周期时从内存中读取指令,而在获取数据周期时对内存的数据进行读出或写入,内存通常是CPU所能直接寻址和访问的唯一大容量存储器,因此,如果CPU需要执行指令,那么这些指令必须在内存中。

存储管理

操作系统对存储设备的物理属性进行了抽象,定义了逻辑存储单元,即文件。文件是由其创建者定义的一线想着信息的集合,如前所述,由于内存太小不能容纳所有数据和程序,再加上掉电会失去所有数据,计数机系统必须提供二级存储器,

调整缓存

信息通常保存在一个存储系统中,当使用它时,它会被临时地复制到更快的存储系统--调整缓存中,当需要特定信息时,首先检查它是否在高速缓存中,如果是,可直接使用调整缓存中的信息,否则,使用位于内存中的信息。同时将其复制到调整缓存中以便下次再用。

分布式系统

分布式系统是将一组物理上分开来的,各种可能异构的计算机系统通过网络连接在一起,为用户提供系统所维护的各种资源的计算机的集合。

操作系统结构

操作提供一个环境以执行程序,它向程序和这些程序的用户提供一定的服务。

用户界面:

命令行界面 ,图形用户界面。

第二部分 进程管理

进程可看做是正在执行的程序,进程需要一定的资源来完成其任务,这些资源在创建进程或执行进程时被分配,进程是大多数系统中的工作单元,这样的系统由一组进程组成:操作系统进程执行系统代码,用户进程执行用户代码,所有这些进程可以并发执行,虽然从传统意义上讲,进程运行时只饮食一个控制线程,但目前大多数现代操作系统支持多线程进程。操作系统负责进程和线程管理,包括用户进程与系统进程的创建与删除,进程调度,提供进程同步机制,进程通信机制与进程死锁处理机制。
进程状态:新的:进程正在被创建 运行:指令正在执行 等待:进程等待某个事件的发生 就绪:进程等待分配处理器 终止:进程完成执行
必须认识到一次只有一个进程可以在一个处理器上运行,但是多个进程可以处于就绪或等待状态。
每个进程在操作系统中用进程控制块PC来表示,它包含了:进程状态、进程编号、程序计数器、寄存器、内存界限、打开文件列表。

进程调度

多道程序设计的目的是无论何时都有进程在运行,从而使CPU利用率达到最大化,分时系统的目的是在进程之间快速切换CPU以便用户在程序运行时能与其进行交互。
进程进入系统时,会被加到作业队列中,该队列包括系统中的所有进程。
驻留在内存中就绪的,等待运行的进程保存在就绪队列中。
等待特定I/O设备的进程列表称为设备队列。
新进程开始处于就绪队列中,它在就绪队列中等待被选中执行或被派遣,

调度程序

进程在其生命周期中会在各种高度队列之间迁移,为了调度,操作系统必须按某种方式从这些队列中选择进程,进程选择是由相应的调度程序来执行的。长期调度程序或作业调度程序从大容量缓存池中选择进程,并装入内存以准备执行,短期调度程序或CPU调度程序从准备执行的进程中选择进程,并为之分配CPU。短期调度程序必须频繁地为CPU选择新进程,长期调度程序执行得并不频繁,绝大多数进程可以分为:I/O为主或CPU为主。

上下文切换

当发生一个中断时,系统需要保存当前运行在CPU中进程的上下文,从而在其处理完后能恢复上下文,即先中断进程,之后再继续,进程上下文用PCB表示,它包括CPU寄存器的值,进程状态和内存管理信息等,将CPU切换到另一个进程需要保存当前进程的状态并恢复另一个进程的状态,这一任务称为上下文切换,当发生上下文切换时,内核会将旧进程的状态保存在其PCB中,然后装入经调度要执行的并已保存的新进程的上下文。

进程创建

进程在其执行过程中,能通过创建进程-系统调用创建多个新进程,创建进程称为父进程,新进程称为子进程,每个新进程可以再创建其他进程,从而形成了进程树。大多数操作系统根据一个唯一的进程标志符(process identifier, pid)来识别进程,pid通常是一个整数值。在一个进程创建子进程时,子进程可能从操作系统那里直接获得资源,也可能只从其父进程那里获得资源。在创建进程时,除了得到各种物理和逻辑资源外,初始化数据由父进程传递给子进程,

进程终止

当进程完成执行最后的语句并使用系统调用exit()请求操作系统删除自身时,进程终止。进程可以返回状态值到父进程,所有进程资源会被操作系统释放。在其他情况下,进程通过适当的系统调用能终止另一个进程,通常只有被终止进程的父进程才能执行这一系统调用,否则,用户可以做生意地终止彼此的作业。

进程间通信

操作系统内并发执行的进程可以是独立进程或协作进程,如果一个进程不能影响其他进程或被其他进程所影响,那么该进程是独立的,显然,不与任何其他进程共享数据的进程是独立的,另一方面,如果系统中一个进程能影响其他或被其他进程所影响,那么该进程是协作的。显然,与其他进程共享数据的进程为协作进程。
进程协作的理由:

  1. 信息共享
  2. 提高运算速度
  3. 模块化
  4. 方便
    协作进程需要一种进程间通信机制来允许进程相互交换数据与信息,进程间通信有两种基本模式;1.共享内存,2.消息传递。在共享内存模式中,建立起一块供协作进程共享的内存区域,进程通过向此共享区域读或写入数据来交换信息,在消息传递模式中,通过在协作进程间交换消息来实现通信。共享内存允许以最快的速度进行方便的通信。
共享内存系统

采用共享内存的进程间通信需要通信进程建立共享内存区域,通常,一块共享内存区域驻留在生成内存段进程的地址空间,其他希望使用这个共享内存段进行通信的进程必须将此放到它们自己的地址空间上。通常操作系统试图阻止一个进程访问另一个进程的内存,内存需要两个或更多的进程取消这个限制,它们通过在共享区域内读或写来交换信息
生产者-消费者模型:生产者进程产生信息以供消费者进程消费。
采用共享内存是解决生产者-消费者总是方法中的一种,为了允许生产者进程和消费者进程并发执行,必须要有一个缓冲来被生产者填充并被消费者所使用,此缓冲驻留在生产者进程和消费者进程的共享内存区域内。当消费者使用一项时,生产者能产生另一项,生产者和消费者必须同步。
可以使用两种缓冲,无限缓冲,对缓冲大小没有限制,消费者可能不得不等待新的项,但生产者总是可以产生新项。有限缓冲:假设缓冲大小固定,对于这种情况,如果缓冲为空,那么消费者必须等待,如果缓冲为满,那么生产者必须等待。

消息传递系统

消息传递提供一种机制以允许进程不必通过共享地址空间来实现通信和同步,这在分布式环境中特别有用。消息传递工具至少提供两种操作:发送和接收,如果进程P和Q需要通信,那么它们必须彼此相互发送消息和接收消息,它们之间必须要有通信线路,该线路的send()/receive()操作方法的逻辑实现:直接或间接通信,同步或异步通信,自动或显式缓冲。
对于直接通信,需要通信的每一个进程都必须明确的命名通信的接收者或发送者,这种方案send():发送消息到进程P,receive()接收来自进程Q的消息。这种方案展示了对称寻址,此方案的一个变形是非对称寻址,send()发送消息到进程P,receive()接收来自任何进程的消息。
在间接通信中,通过邮箱或端口来发送和接收消息,邮箱可以抽象为一个对象,进程可以向其中存放消息,也可以从中删除消息,每个邮箱都有一个唯一的标识符。send():发送一个消息到邮箱A,receive():接收来自邮箱A的消息。

同步

进程间的通信可以通过原语send()和receive()来进行,消息传递可以是阻塞或非阻塞--也称为同步或异步。

  1. 阻塞send: 发送进程阻塞,直到消息被接收进程或邮箱所接收
  2. 非阻塞send:发送进程发送消息并再继续操作
  3. 阻塞receive: 接收者阻塞,走到消息可用
  4. 非阻塞receive: 接收者收到一个有效消息或空消息
    缓冲:不管通信是直接的或是间接的,通信进程所交换的消息都驻留在临时队列中。

3.6.1 Socket
socket可定义为通信的端点,一对通过网络通信的进程需要使用一对socket-即每个进程各有一个。Socket由IP地址与一个端口号连接组成,通常socket采用客户机-服务器结构。服务器通过监听指定端口来等待进来的客户请求。一旦收到请求,服务器就接受来自客户socket的连接,从而完成连接。使用Socket进行通信,虽然常用和高效,但是它属于较为低级的分布式进程通信,原因之一在于socket只允许在通信线程之间交换无结构的字节流,客户机或服务器程序需要负责加上数据结构。

线程

线程是进程内的控制流,线程是CPU使用的基本单元,它由线程ID、程序计数器、寄存器集合和栈组成。它与属于同一进程的其他线程共享代码段、数据段和其他操作系统资源。一个传统重量级的进程只有单个进程线程,如果进程有多个控制线程,那么它能同时做多个任务。运行在现代桌面PC上的许多软件包都是多线程的,一个应用程序通常是作为一个具有多个控制线程的独立进程实现的。
有的时候,一个应用唾弃可能需要执行多个相似任务,例如,网页服务器接收用户关于网页,图像,声音的请求,一个忙碌的网页服务器可能有多个客户并发的访问它,如果网页服务器作为传统单个线程的进程来执行,那么只能一次处理一个请求,这样,客户必须等待很长的处理请求的时间。
一种解决方法是让服务器作为单人进程运行接收请求,当服务收到请求时,它会创建另一个进程以处理请求,这种方法在线程流行之前很常用,但是进程创建很消耗时间和资源。如果一个具有多个纯种的进程能达到同样目的,那么将更为有效。

多线程编程的优点

  • 响应度高: 一个程序的部分阻塞或执行较冗长的操作,该程序仍能继续执行,从而增加了对用户的响应程度
  • 资源共享:线程默认共享它所属进程的内存和资源,代码和数据共享的优点是它能允许一个应用程序在同一地址空间有多个不同的活动线程。
  • 经济:进程创建所需要的内存和资源的分配比较昂贵,由于线程能共享它们所属进程的资源,所以创建和切换线程更为经济。
  • 多处理器结构的利用:多线程的优点之一是能充分使用多处理器体系结构,以便每个进程能并行运行在不同的处理器上。不管有多少CPU,单线进程只能运行在一个CPU上,在多CPU上使用多线程加强了并发功能。

多线程模型

有两种不同方法来提供线程支持:用户层的用户线程,内核层的内核线程。用户线程和内核线程之间存在多种关系:

多对一模型:

多对一模型将许多用户级线程映射到一个内核线程。线程管理是由线程库在用户空间进行的,因而效率较高,但是如果一个线程执行了阻塞系统调用,那么整个进程会阻塞。而且,因为任一时刻只有一个线程能访问内核,多个线程不能并行运行在多处理器上。

一对一模型

一对一模型将每个用户线程映射到一个内核线程,该模型在一个线程执行阻塞系统调用时,能允许另一个线程继续执行,所以它提供了比多对一模型更好的并发功能。它也允许多个线程能并发的运行在多处理器系统上。这种模型的唯一的缺点是每创建一个用户线程就需要创建一个相应的内核线程。由于创建内核线程的开销会影响应用程序的性能,所以这种模型的绝大多数实现都限制了系统所支持的线程数量。

多对多模型

多对多模型多路复用了许多用户线程到同样数量或更小数量的内核线程上,多对多模型没有上面二者的缺点,开发人员可创建任意多的用户线程,并且相应内核线程能在多处理器系统上并发的执行。

线程库

线程库是为程序员提供创建和管理线程的API,主要有两种方法来实现线程库,第一种方法是在用户空间中提供一个没有内核支持的库,此库的所有代码和数据结构都存在于用户空间中,调用库中的一个函数只是导致了用户空间中的一个本地函数调用,而不是系统调用。第二种方法是执行一个由操作系统直接支持的内核级的库,此时,库的代码和数据结构存在于内核空间中,调用库中的一个API函数通常会导致对内核的系统调用

线程取消

线程取消是在线程完成之前来终止线程的任务,要取消的线程通常称为目标线程,目标线程的取消可在如下两种情况下发生:1异步取消, 2延迟取消。

线程池

多线程服务器有一些潜在问题:1 关于在处理请求前用以创建线程的时间,2线程在完成工作之后就要被丢弃的事实。
线程池的主要思想是在进程开始时创建一定数量的线程,并放入到池中以等待工作,当服务器收到请求时,它会唤醒池中一个线程,并将要处理的请求传递给它,一旦线程完成了服务,它会返回池中再等待工作,如果池中没有可用的线程,那么服务器会一直等待直到有空线程为止。
线程池的优点:

  1. 通常用现有线程处理请求要比等待创建新的线程要快
  2. 线程池限制了在任何时候可用线程的数量,这对那些不能支持大量并发线程的系统非常重要。
线程特定数据:

同属于一个进程的线程共享进程数据,这种数据共享提供了多线程编程的一种优势。不过在有些情况下,每个线程可能需要一定数据的自己的副本,这种数据称为线程特定数据。

调度程序激活

许多实现多对多模型或二级模型的系统在用户和内核线程间设置一种中间数据结构(通常是轻量级进程LWP),对于用户线程库,LWP表现为为一种应用程序可以调度用户线程来运行的虚拟处理器。每个LWP与内核线程相连,该内核线程被操作系统调度到物理处理器上运行,如果内核线程阻塞,LWP也阻塞,在这个关系链的顶端,与LWP相连的用户线程也阻塞。
一种解决用户线程库与内核间通信的方法被称为调度器激活,它按如下方式工作:内核提供一组虚拟处理器给应用程序,应用程序可调度用户线程到一个可用的虚拟处理器上,进一步说,内核必须告知与应用程序有关的特定事件,这个过程被称为upcall,upcall由具有upcall处理句柄的线程库处理,upcall处理句柄必须在虚拟处理器上运行,当一个应用线程将要阻塞时,事件引发一个upcall。

CPU调度

对于单处理系统,每次只允许一个进程运行,任何其他进程必须等待,直到CPU空闲能被调度为止,多道程序的目标是在任何时候都有某些程序在运行,以使CPU的使用率最大化。多道程序设计的思想:当一个进程必须等待时,操作系统就会从该进程拿走CPU的使用权,而将CPU交给其他进程。

CPU调度程序

每当CPU空闲时,操作系统就必须从就绪队列中选择一个进程来执行,进程选择由短期调度程序或CPU调度程序执行,调度程序从内存中选择一个能够执行的进程,并为之分配CPU。

抢占调度

  1. 当一个进程从运行状态切换到等待状态
  2. 当一个进程从运行状态切换到就绪状态
  3. 当一个进程从等待状态切换到就绪状态
  4. 当一个进程终止时
    当调度只能发生在第1和第4两种情况下时,称调度方案是非抢占的或协作的,否则称调度方案是抢占的。

分派程序

分派程序是一个模块,用来将CPU的控制权交给由短期调度程序选择的进程。其功能包括:切换上下文,切换到用户模式,跳转到用户程序的合适位置。

调度算法

  1. 先到先服务调度FCFS:采用这种方案,先请求CPU的进程先分配到CPU,FCFS策略可以用FIFO队列来容易的实现。
  2. 最短作业优先调度SJF:当CPU空闲时,它会赋给具有最短CPU区间的进程,如果两个进程具有同样长度,那么可以使用FCFS调度来处理。
  3. 优先级调度:每个进程都有一个优先级与之关联,具有最高优先级的进程会分配到CPU,具有相同优先级的进程按FCFS顺序调度。
  4. 轮转法调度:
  5. 多级队列调度
  6. 多级反馈队列调度

银行家算法

以银行贷款分配的策略为基础,通过判断系统是否处于安全状态来确定我这一次的资源分配是否同意。

  1. 客户在申请的时候,必须告诉银行你最大的申请量是多少,并且保证在项目执行完毕后能及时归还贷款
  2. 银行要保证用户贷款的数量不超过银行拥有的最大值
  3. 在客户贷款数量不超过银行拥有的最大值时,银行家尽量满足客户的需要

安全状态判断:

  1. 先把当前可用的资源复制一下,作为当前进行判断的工作场所
  2. 在这个状态下是不是可以满足当前已经分配过资源的这些线程的总的需求,如果能满足就安全
  3. 在这些线程里,查找某线程未来需要的资源总量,是否小于我当前有的资源总量,如果满足,此线程就是可以回收的。

第七章 死锁

在多道程序环境下,多个进程可能竞争一定数量的资源,某个进程申请资源,如果这时资源不可用,那么该进程进入等待状态,如果所申请的资源被其他等待进程占有,那么该等待进程有可能再也无法改变其状态,这种情况称为死锁。
某系统拥有一定数量的资源,分布在若干竞争进程之间,这些资源可分成多种类型,每种类型有一定数量的实例。(如果系统有两个CPU,那么资源类型CPU就有两个实例)如果一个进程申请某个资源类型的一个实例,那么分配这种类型的任何实例都可满足申请,否则这些实例就不相同,且资源类型的分类也没有正确定义。进程在使用资源前必须申请资源,在使用资源之后必须释放资源。在正常操作模式下,进程只能按如下顺序使用资源:

  1. 如果申请不能立即被允许,那么申请进程必须等待,直到它获得该资源为止。
  2. 使用:进程对资源进行操作
  3. 释放:进程释放资源

进程的申请与释放为系统调用,对于进程或线程的每次使用,操作系统会检查以确保使用进程已经申请并获得了资源,系统表记录了每个资源是否空闲或已经被分配,分配给了哪个进程,如果进程所申请的资源正在为其他进程所使用,那么该进程会增加到该资源的等待队列。当一组进程中的每个进程都在等待一个事件,而这一事件只能由这一组进程的一个进程引起。
当出现死锁时,进程永远不能完成,并且系统资源被阻碍使用,阻止了其他作业开始执行。
死锁出的必要条件:

  1. 互斥:有一个资源必须处于非共享模式,即一次只有一个进程使用如果另一个进程申请该资源,那么申请进程必须等到该资源被释放为止。
  2. 占有并等待:一个进程必须占有至少一个资源,并等待另一资源,而该资源为其他进程所占有。
  3. 非抢占:资源不能抢占,即资源只能在进程完成任务后自动释放。
  4. 循环等待:进程等待另一进程的资源,多个进程形成循环。

根据资源分配图的定义,可以证明:如果分配图没有环,那么系统就没有进程死锁,如果分配图有环,那么可能存在死锁。

死锁处理方法:

  1. 可使用协议以预防或避免死锁,确保系统不会进入死锁状态
  2. 可允许系统进入死锁状态,然后检测它,并加以恢复
  3. 可忽视这个问题,认为死锁不可能在系统内发生

死锁预防:确保至少一个必要条件不成立
死锁避免:要求操作系统事先得到有关进程申请资源和使用资源的额外信息,有了这些额外信息,系统可确定:对于一个申请,里程是否应该等待。

死锁预防

出现死锁有4个必要条件,只要确保至少一个必要条件不成立,就能预防死锁发生。

  1. 互斥:共享资源不要求互斥访问,因此不会涉及死锁
  2. 占有并等待:为了确保占有并等待条件不会在系统内出现,必须保证,当一个进程申请一个资源时,它不能占有其他资源,思路有二:每个进程在执行前申请并获得所有资源/允许进程在没有资源时才可申请资源。
  3. 非抢占:如果一个进程占有资源并申请另一个不能立即分配的资源,那么其现已分配的资源都可被抢占。
  4. 循环等待:

死锁避免

每次申请要求系统考虑现有可用资源,现已分配给每个进程的资源和每个进程将来申请与释放的资源,以决定当前申请是否满足或必须等待,从而避免死锁发生的可能性。

安全状态

如果系统能按某个顺序为每个进程分配资源,并能避免死锁,那么系统状态就是安全的,更为准确的说,如果存在一个安全序列,那么系统处于安全状态。

银行家算法

当新进程进入系统时,它必须说明其可能需要的每种类型资源实例的最大数量,这一数量不能超过系统资源的总和,当用户申请一组资源时,系统必须确定这些资源的分配是否仍会使系统处于安全状态,如果是,就可分配资源,否则,进程必须等待直到某个其他进程释放足够为止。

死锁恢复

一个方法是简单地终止一个或多个进程以打破循环等待,另一个方法是从一个或多个死锁进程那里抢占一个或多个资源。

  1. 终止进程:终止所有死锁进程/一次只终止一个进程直到取消死锁循环为止。
  2. 资源抢占:通过抢占资源以取消死锁,逐步从进程中抢占资源给其他进程使用,直到死锁环被打破为止。

第八章 内存管理

计算机系统的主要用途是执行程序,在执行时,这些程序及其所访问的数据必须在内存里,为改善CPU的使用率和对用户的响应速度,计算机必须在内存里保留多个进程,即共享内存。
CPU所能直接访问的存储器只有内存和处理器内的寄存器,机器指令可以用内存地址作为参数,而不能用磁盘地址作为参数,因此,执行指令以及指令使用的数据必须在这些直接可访问的存储设备上,如果数据不在内存中,那么在CPU使用前必须先把数据移到内存中。CPU内置寄存器通常可以在一个CPU时钟周期内完成访问,对于寄存器中的内容,绝大多数CPU可以在一个时钟周期内解析并执行一个或多个指令,而对于内存,完成内存访问可能需要多个CPU时钟周期,由于没有数据以便完成正在执行的指令,CPU通常需要暂停,解决方法是在CPU与内存之间,增加调整内存,这种协调速度差异的内存缓存区,称为高速缓存。
要确保每个进程都有独立的内存空间,需要确定进程可访问的合法地址的范围,并确保进程只访问其合法地址。通过两个寄存器即基地址寄存器和界限地址寄存器,可以实现这种保护。基地址寄存器含有最小合法物理内存地址(起点),而界限地址寄存器决定范围的大小(范围)。
内存空间保护的实现,是通过CPU硬件对用户模式所产生的每一个地址与寄存器的地址进行比较来完成的。
操作系统在内核模式下执行,可以无限制地访问操作系统和用户的内存。

地址绑定

通常程序以二进制可执行文件的形式存储在磁盘上,为了执行,程序被调入内存并放在进程空间内。根据所使用的内存管理方案,进程在执行时可以在磁盘和内存之间移动,在磁盘上等待调入内存以便执行的进程形成输入队列。通常的步骤是从输入队列中选取一个进程并装入内存,进程在执行时,会访问内存中的指令和数据,最后,进程终止,其地址空间将被释放。
源程序中的地址通常是用符号来表示的,编译器通常将这些符号地址绑定在可重定位的地址,链接程序或加载程序再将这些可重定位的地址绑定成绝对地址,每次绑定都是从一个地址空间到另一个地址空间的映射。

逻辑地址空间与物理地址空间

CPU所生成的地址通常称为逻辑地址,而内存单元所看到的地址(即加载到内存地址寄存器中的地址)通常称为物理地址。由程序所生成的所有逻辑地址的集合称为逻辑地址空间,与这些逻辑地址相对应的所有物理地址的集合称为物理地址空间。

动态加载

一个进程的整个程序和数据必须处于物理内存中,以便执行,因此进程的大小受物理内存大小的限制,为了获得更好的内存空间利用率,可以使用动态加载,采用动态加载时,一个子程序只有在调用时才被加载,所有子程序都以可重定位的形式保存在磁盘上,主程序装入内存并执行,当一个子程序需要调用另一个子程序时,调用子程序首先检查另一个子程序是否已加载,如果没有,可重定位的链接程序将用来加载所需要的子程序。并更新程序的地址表以反映这一变化,接着控制传递给新加载的子程序。
动态加载的优点是不用的子程序决不会被加载,如果大多数代码需要用来处理异常情况,如错误处理,这种方法特别有用。对于这种情况,虽然总体上程序比较大,但是所使用的部分(即加载的部分)可能小很多。

动态链接

动态链接不是将加载延迟到运行时,而是将链接延迟到运行时,如果有动态链接,二进制镜像中对每个库程序的引用都有一个存根,存根是一小段代码,用来指出如何定位适当的内存驻留库程序,或如果该程序不在内存时应如何装入库,当执行存根时,它首先检查所需子程序是否已在内存中,如果不在,就将子程序装入内存,不管如何,存根会用子程序地址来替换自己,并开始执行子程序。

交换

进程需要在内存中以便执行,不过进程可以暂时从内存中交换到备份存储上,当需要再次执行时再调回到内存中。如果有一个更高优先级的进程且需要服务,内存管理器可以交换出低优先级的进程,以便可以装入和执行更高优先级的进程,当更高优先级的进程执行完后,低优先级进程可以交换回内存以继续执行,这种交换有时称为滚出和滚入。

8.3 连续内存分配

内存通常分为两个区域,一个用于驻留操作系统,另一个用于用户进程。
通常需要将多个进程同时放在内存中,因此需要考虑如何为输入队列中需要调入内存的进程分配内存空间,采用连续内存分配时,每个进程位于一个连续的内存区域。

内存映射与保护

通过采用重定位寄存器和界限地址寄存器,可以实现这种保护,重定位寄存器含有最小的物理地址值,界限地址寄存器含有逻辑地址的范围值。MMU动态地将逻辑地址国上重定位寄存器的值后映射成物理地址,映射后的物理地址再送交内存单元。当CPU调度器选择一个进程来执行时,作为上下文切换工作的一部分,调度程序会用正确的值来初始化重定位寄存器和界限地址寄存器,由于CPU所产生的第一地址都需要与寄存器进行核对,所以可以保证操作系统和其他用户程序和数据不受该进程的运行所影响。
重定位寄存器机制为允许操作系统动态改变提供了一个有效方法,许多情况都需要这一灵活性。

内存分配

多分区方法:最简单的内存分配方法之一就是将内存分为多个固定大小的分区,每个分区只能容纳一个进程。
可变分区:在此方案里,操作系统有一个表,用于记录哪些内存可用和哪些内存已被占用,一开始,所有内存都可用于用户进程,因此可以作为一大块可用内存,称为孔,当有新进程需要内存时,为该进程查找足够大的孔,如果找到,可以从该孔为该进程分配所需的内存,孔内未分配的内存可以下次再用。 随着进程进入系统,它们将被加入到输入队列,操作系统根据所有进程的内存需要和现有可用内存情况来决定哪些进程可分配内存,当进程分配到空间时,它就装入内存,并开始竞争CPU,当进程终止时,它将释放内存,该内存可以被操作系统分配给输入队列中的其他进程。
在任意时候,有一组可用孔大小列表和输入队列,操作系统根据调度算法来对输入队列进行排序,内存不断地分配给进程,直到下一个进程的内存需求不能满足为止,这时没有足够大的可用孔来装入进程,操作系统可以等到有足够大的空间,或者往下扫描输入队列以确定是否有其他内存需求较小的进程可以被满足。
通常,一组不同大小的孔分散在内存中,当新进程需要内存时,系统为该进程查找足够大的孔,如果孔太大,那么就分为两块,一块分配给新进程,另一块还回孔集合。当进程终止时,它将释放内存,该内存将还给孔集合,如果新孔与其他孔相邻,那么将这些孔合并为大孔,这时系统可以检查是否有进程在等待内存空间,新合并的内存空间是否满足等等进程。
这种方法是通用动态存储分配问题的一种情况,从一组可用孔中选择一个空闲孔的最为常用方法有首次适应,最佳适应,最差适应。
首次适应:分配第一个足够大的孔。查找可以从头开始,也可以从上次首次适应结束时开始 ,一旦找到足够大的空闲孔,就可以停止。
最佳适应:分配最小的足够大的孔。必须查找整个列表,除非列表按大小排序,这种方法可以产生最小剩余孔。
最差适应:分配最大的孔,同样,必须查找整个列表 ,除非列表按大小排序,这种方法可以产生最大剩余孔。

碎片

随着进程装入和移出内存,空闲内存空间被分为小片段,当所有总的可用内存之和可以满足请求,但并不连续时,就出现了外部碎片问题。
碎片的解决方法有二:一是紧缩,紧缩的上的是移动内存内容,以便所有的空闲空间合并成一整块。另一种方法是允许物理地址空间为非连续,这样只要有物理内存就可为进程分配。

如何选择非连续分配中的内存分块大小:

  1. 段式存储管理
  2. 页式存储管理

MMU:存储管理单元

分页

分页内存管理方案允许进程的物理地址空间可以是非连续的,分页避免了将不同大小的内存块匹配到交换空间上这样的麻烦。
页帧:把物理地址空间划分为大小相同的基本分配单位。
页面:把逻辑地址空间也划分为相同大小的基本分配单位,帧和页的大小必须是相同的。
页面到页帧的转换:页表 MMU/TLB
内存物理地址的表示:二元组(f,o) f - 帧号(F位,共有2^F个帧) o - 帧内偏移(S位,每帧有2^S字节)
物理地址 = f*2^s + o
实现分页的基本方法涉及将物理内存分为固定大小的块,称为帧,大小为2的N次幂。而将逻辑内存也分为同样大小的块,称为页,当需要执行进程时,其页从备份存储调入到可用的内存帧中,备份存储也分为固定大小的块,其大小与内存帧一样。
由CPU生成的每个地址分为两个部分:页号和页偏移,页号作为页表中的索引,页表包含每页所在物理内存的基地址,这些基地址与页偏移的组合就形成了物理地址。

分段

概念:段表示访问方式和存储数据等属性相同的一段地址空间,对应一个连续的内存块,若干个段组成进程逻辑空间
逻辑地址空间是由一组段组成的,每个段都有名称和长度,地址指定了段名称和段内偏移。用户通过两个量来指定地址:段名称和偏移。为实现简单起见,段是编号的,是通过段号而段名来引用的,因此,逻辑地址由有序对组成: < segment-number, offset >
在段式存储管理中,我们把进程的地址空间看成由若干个段组成,进程 -->(主代码段,子模块代码段,公用库代码段,堆栈段,堆数据,初始化符号表等),把进程的地址空间以更精细,更灵活的方式分离开,以实现更好的共享。
逻辑地址空间是连续在,但是在物理地址空间里不是连续的

虚拟内存

虚拟内存技术允许执行进程不必完全在内存中,这种方案的明显优点是程序可以比物理内存大,而且,虚拟内存将内存抽象成一个巨大、统一的存储数组,进而将用户看到的逻辑内存与物理内存分开。在许多情况下并不需要将整个程序放到内存中。
能够执行只有部分在内存中的程序有很多好处:

  1. 程序不再受现有的物理内存空间限制,用户可以为一个巨大的虚拟地址空间编写程序。
  2. 因为每个用户程序使用了更少的物理内存,所以更多的程序可以同时执行,CPU使用率也相应的增加,而响应时间或周转时间并不增加。
  3. 由于载入或交换每个用户到内存内所需要的I/O更少,用户程序会运行得更快。
    虚拟内存将用户逻辑内存与物理内存分开,这在再有物理内存有限的情况下,为程序员提供了巨大的虚拟内存。
    进程的虚拟地址空间就是进程如何在内存中有些话的逻辑视图。

第13章 I/O输入系统

计算机有两个主要任务:I/O操作 与 计算处理
设备与计算机通信通过一个连接点连接,如果一个或多个设备使用一级共同的线,那么这种连接线则称为总线。总线是一组线和一组严格定义的可以描述在线上传输信息的协议。信息是通过线上的具有一定时序的电压模式来传递的。如果设备A通过电缆连接到设备B上,设备B又通过电缆连接到设备C上,设备C通过端口连接到计算机,那么这种方式称为链环。
控制器是用于操作端口,总线或设备的一组电子器件,控制器有一个或多个用于数据和控制信号的寄存器,处理器通过读写这些寄存器的位模式与控制器通信。

推荐阅读