1 绪论
什么是操作系统?
计算机硬件与用户软件之间的中间件程序集合 OS是一个资源管理程序 OS是一个控制调度程序 OS一般有一个内核程序,开机后一直运行 提供用户和计算机之间的接口
现代操作系统的特点
*并发性:具有处理和调度多个程序同时执行的能力(同一时刻 看起来是同一时刻 计算机只有一个CPU,应用程序可能不会在同一时间完成多个任务,但在应用程序内部一次完成多个任务。要同时在多个任务上取得进展,CPU会在执行期间在不同的任务之间切换) 并行:有多核CPU上 同时在多个人任务上取得进展 *共享性:操作系统程序和多个用户程序公用系统中的资源 *虚拟性:通过技术将一个物理实体变成若干个逻辑上的对应物,物理实体是实际存在的,逻辑对应物是用户感受到的。比如虚拟机和docker *异步性:在多道进程运行的环境下,进程之中指令的执行不是一贯彻底的,而是走走停停,以不可预知的速度向前推进,这就是异步
操作系统的分类
批处理系统是一种基于多道程序设计技术的计算机操作系统,主要用于用户脱机使用。它通过批处理运行程序来执行一系列作业,其中批处理作业的用户交互性较差。这意味着批处理作业通常不需要用户的实时干预或交互,而是事先准备好并按照预定的顺序自动执行。 实时系统:用于实时控制和实时信息处理邻域,主要特点是及时响应和高可靠性,主要用于对实时性比较高的邻域,比如军事方面 分时系统:交互性 多用户 独立性(会提供终端,系统为用户提供一组终端命令,然后在操作系统上设立一个命令解释程序,用户可以在终端) 扩展 多道程序设计:一种计算机处理任务的方式,它允许同时执行多个程序。这里的"多道"指的是计算机系统能够处理多个程序,而不是仅限于一个程序执行完毕后再执行下一个程序。在多道程序设计中,操作系统将多个程序加载到内存中,并且通过分时或分片的方式将处理器的时间划分给这些程序。每个程序被划分为多个小任务或时间片,处理器轮流执行这些任务,从而实现了同时执行多个程序的效果。 它的优点:1、提高了系统的吞吐量:多道程序设计可以充分利用CPU资源,从而提高系统的吞吐量。 2、提高了系统的利用率:多道程序设计允许多个程序并发地执行,从而避免了系统闲置的情况,提高了系统的利用率。 3、提高了用户体验:多道程序设计可以让用户同时运行多个程序,从而提高用户的工作效率和满意度。 4、便于系统管理:多道程序设计可以在一个系统中同时运行多个程序,从而便于系统管理和维护。
计算机系统怎样能够处理多个程序?
计算机系统能够处理多个程序的能力是通过操作系统的支持和实现来实现的。下面是计算机系统处理多个程序的一般步骤: 内存管理:计算机系统需要提供内存管理功能来加载和存储多个程序。每个程序被加载到内存的不同区域,操作系统会记录程序的起始地址和大小等信息。 调度程序:操作系统有一个调度程序,负责决定哪个程序应该在何时执行。调度程序会根据一定的策略(如优先级、时间片轮转等)选择下一个要执行的程序。 上下文切换:当调度程序选择一个新的程序时,操作系统会进行上下文切换。上下文包括程序的寄存器状态、内存指针、程序计数器等信息,操作系统保存当前程序的上下文并加载下一个程序的上下文,以便继续执行。 分时或分片执行:一旦切换到一个程序,处理器开始执行该程序的指令。根据系统设计,处理器可能会以分时(time-sharing)或分片(time-slicing)的方式在多个程序之间切换执行。在分时执行中,处理器会为每个程序分配一小段时间,并在不同程序之间快速切换。在分片执行中,处理器将程序划分为多个时间片,每个时间片执行一小部分指令,然后切换到下一个程序。 资源分配和保护:操作系统负责管理和分配计算机资源,如处理器时间、内存空间、输入/输出设备等。它确保每个程序获得所需的资源,并且在程序之间进行适当的隔离,以防止相互干扰或访问冲突。 中断处理:当发生外部事件或程序内部异常时,操作系统会处理中断。中断可能是来自输入/输出设备的信号、时钟中断、用户输入等。操作系统会暂停当前执行的程序,处理中断事件,然后根据需要切换到相应的程序继续执行。
操作系统的功能
处理器的管理 1 进程创建与删除 2 进程同步 3 进程通信 4 死锁处理 5 CPU调度 6 作业调度 内存管理 1 内存调度分配 设备管理 1 通道 控制器 输入输出的分配与管理 2 设备独立性 文件管理 1 文件的创建与删除 2 在永久性磁盘上存取文件 3 文件共享 保密保护 用户接口 1 程序级别 2 作业级别(提供了操作的命令)
2 操作系统接口与作业管理
3 进程管理
程序:是一组指令的集合,描述了完成特定任务所需的操作步骤。是静态的,就是在磁盘中存放了可执行文件,就是一系列的指令集合
进程:是操作系统进行资源分配和调度的基本单位,是动态的,是程序的一次执行过程
作业:是用户提交给计算机系统执行的任务单元。一个作业可以包含一个或多个程序,通常是由用户或应用程序定义和组织的。作业可以包括运行程序所需的输入数据、相关的配置信息以及其他必要的资源。
进程映像与进程的区别
$$
定义:进程是正在执行的程序实例,它是系统中的一个执行单元。进程是操作系统分配资源和管理任务的基本单位。进程包括代码、数据、堆栈、打开的文件和其他与执行相关的信息。 进程映像是指一个程序在执行之前在磁盘上的静态映像或快照。它是程序的二进制表示,包括可执行文件、静态数据、库文件等。进程映像是进程创建的基础,操作系统从映像中创建进程的运行时实例。 内容:进程是一个动态实体,包括正在执行的指令、运行时数据、堆栈、打开的文件和其他运行时状态。进程不仅包含了程序的代码和数据,还包括了操作系统为其分配的资源和管理信息。 进程映像是一个静态的文件或数据集合,它是程序的静态表示。进程映像通常保存在磁盘上,包括了程序的可执行文件、静态数据、库文件等,以及其他必要的资源和元数据。 生命周期:进程是一个动态的实体,它可以在系统中创建、运行、挂起、终止等不同状态之间切换。进程的生命周期受操作系统的管理,并且可以与其他进程进行通信和交互。 进程映像是一个静态的快照,它代表了程序在执行之前的状态。操作系统可以使用进程映像来创建一个新的进程实例,将其加载到内存中,并启动其执行。
$$
进程的组成(PCB 程序段 数据段 )
进程被创建的时候,操作系统会为该进程分配一个唯一的,不重复的PID(进程号)还有进程所属用户(UID),还要记录给进程分配了那些资源(分配了多少内存,正在使用的I/O设备,正在使用的那些文件)还要记录进程的运行情况,(CPU的使用时间,磁盘的使用情况,网络流量的使用情况) 这些信息都存储在一个数据结构PCB(进程控制块) 操作系统需要对各个并发运行的进程进行管理,但凡管理时所需要的信息,都会被放到PCB中
-
PCB:是进程存在的唯一标志,当进程被创建时,操作系统为其创建PCB,当进程结束时,会回收其PCB
-
程序段:包含程序指令
-
数据段:包含程序运行过程中产生的各种数据
进程的特征
动态性:进程时程序的一次执行过程,是动态的产生,变化,消亡
并发性:内存中有多个进程实体,各进程可并发执行
独立性:进程能够独立运行,独立获得资源,独立接受调度的基本单元
异步性:各进程按各自的独立的,不可预知的速度向前推进,操作系统要提供“进程同步机制”来解决异步问题
结构性:每个进程都会配置一个PCB。结构上看,进程由程序段,数据段,PCB组成
进程的状态与转换
创建态(系统完成创建进程的一系列工作) ---> 就绪态(没有处理机资源 其他的都有) --(进程被调度)--> 运行态(正在处理机上运行的进程) ----(进程用系统调用的方式申请某种系统资源,或者请求等待某个事情发生)-->阻塞态 ----(当申请的资源被分配,或等待的事件发生)--->又回到就绪态 返回上一个运行态----(当进程结束,或者运行过程中遇到不可修复的错误)-----> 终止态
注意 :有时候进程会从运行态转换为就绪态(时间片到,或者处理机被抢占)
不能由阻塞态直接转化为运行态,也不能由就绪态直接转化阻塞态(因为进入阻塞态是进程主动请求的,必然需要进程在运行时才能发出这种请求)
进程控制
主要功能是对系统中所有的进程实施有效的管理,它具有创建新进程,撤销原有进程,实现进程状态转换功能(进程控制就是要是实现进程转化)
如何实现进程控制
在内核中有原语(设备驱动,CPU切换)
原语是一种特殊的程序,它的执行具有原子性,也就是说这段程序的运行必须是一气呵成的,不可中断。指一组原子操作或基本操作,它们是操作系统提供给应用程序或其他系统组件使用的最基本的功能单元。原语是不可再分的,是以原子方式执行的操作,要么完全执行成功,要么完全不执行。
进程的状态转换是要一气呵成的
为什么进程的状态转换要一气呵成呢
假设PCB中的变量state表示进程当前所处状态,1表示就绪状态 2表示阻塞态.... 这是就绪队列指针 ---> PCB5(state = 1) --->PCB2(state = 1) ---> PCB1(state = 1) 这是阻塞队列指针 ---> PCB4(state = 4) --->PCB3(state = 4) ---> PCB6(state = 4) 假设进程2等待的事件发生,则操作系统中,负责进程控制的内核程序至少需要做两件事 1 将PCB2的state设为1 2 将PCB2从阻塞队列放到就绪队列 如果这时收到了中断信号,那么PCB2的state=1,但他却放在阻塞队列 没有放到就绪队列里
进程的创建:操作系统创建一个进程时使用的原语(创建原语):
1. 申请空白的PCB 2. 为新进程分配所需资源 3. 初始化PCB 4. 将PCB插入就绪队列
进程的终止:就绪态-->阻塞态-->运行态-->终止态(撤销原语):
-
从PCB集合中找到终止进程的PCB
-
若进程正在运行,立即剥夺CPU,将CPU分配给其他进程
-
终止其所有子进程
-
将该进程拥有所有资源归还给父进程或操作系统
-
删除PCB
进程的阻塞与唤醒:
阻塞原语:
-
找到要阻塞的进程对应的PCB
-
保护进程运行现场,将PCB状态信息设置为“阻塞态”,暂时停止进程运行
-
将PCB插入相应事件的等待队列
进程的唤醒:
-
从等待(阻塞)事件队列中找出PCB
-
将PCB从等待队列移除,设置进程为就绪态
-
将PCB插入就绪队列,等待被调度
进程的切换:(运行态--->就绪态 就绪态--->运行态)切换原语
-
将运行环境信息存入PCB
运行环境指的是:在执行指令序列中,有很多中间结果是放在各种寄存器中的,这些寄存器不是某个进程独属的,这些寄存器也会被其他进程占用,比如在切换执行另一个进程时,用到了上一个进程用到的一些寄存器,那么这样会将上一个进程在寄存器中储存的中间结果所覆盖掉 解决方法:(在进程切换时 现在PCB中保存这个进程的运行环境,保存一些必要的寄存器信息)
-
PCB移入相应队列
-
选择另一个进程执行,并更新其PCB
-
根据PCB恢复新进程所需的运行环境、
无论哪个进程的控制原语,无非要做三件事:
-
更新PCB的信息
-
将PCB插入合适的队列
-
分配/回收资源
进程间通信
概念:指两个进程之间产生数据交互
方法一:共享存储
基于数据结构的共享: 比如共享空间里只能放一个长度为10的数组,这种通信方式速度很慢,限制多,是一种低级通信方式。
基于存储区的共享: 操作系统在内存中划出一块共享存储区,数据的形式,存放位置,都由通信进程控制,而不是操作系统。这种共享方式很快,是一种高级通信方式
#include <iostream> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <cstring> int main(){ struct stat st{}; //定义一个struct stat结构体变量st,用于存储文件的状态信息。 int fd = open("mmap.txt",O_RDWR); //使用open函数打开一个文件,这里使用的文件名是"mmap.txt",并指定O_RDWR标志表示以可读写的方式打开文件。如果打开文件失败,会输出错误信息并返回-1。 if(fd ==-1){ perror("open func error"); return -1; } if(fstat(fd,&st)==-1){ //使用fstat函数获取打开文件的状态信息,并将其保存在st结构体变量中。如果获取状态信息失败,会输出错误信息并返回-1。 perror("fstat func error"); return -1; } char *mapAdd = nullptr; //声明一个char*类型的指针变量mapAdd,用于保存mmap函数返回的映射区域的地址。 if((mapAdd = static_cast<char*>(mmap(nullptr,st.st_size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0)))==MAP_FAILED){ perror("mapAdd func error"); return -1; } // 使用mmap函数将文件映射到内存中。传递的参数包括映射区域的大小(st.st_size表示文件的大小)、访问权限(PROT_READ | PROT_WRITE表示可读可写)、映射方式(MAP_SHARED表示共享映射)、文件描述符fd和偏移量0。如果mmap函数调用失败,会输出错误信息并返回-1。 close(fd); // 关闭文件描述符fd,因为我们已经将文件映射到内存中,不再需要文件描述符。 strncpy(mapAdd,"hello world",12); //使用strncpy函数将字符串"hello world"复制到映射区域的起始地址mapAdd中,长度为12(包括字符串结尾的空字符'\0')。 }
方法二: 消息传递
进程间的数据交换以格式化的消息 (包含消息头: 包括发送进程ID 接受进程ID 消息长度等格式化的信息 消息体:一个进程给另一个进程传递的具体数据)为单位,进程通过操作系统提供的"发送消息/接受消息"两个原语进行数据交换
直接通行方式: 消息发送进程要指明接受进程的ID
间接通信方式: 通过"信箱"间接地通信,又称“信箱通信方式”
方法三: 管道通信
管道是一个特殊的共享文件,又名pipe文件。其实就是在内存区开辟一个大小固定的内存缓冲区
#include <iostream> #include <unistd.h> #include <sys/types.h> #include <cstring> int main(){ int pipefd[2]{}; //创建一个匿名管道,参数是个管道数组 数组里是文件描述符 0代表标准输入 1代表标准输出 //匿名管道是子进程和父进程都可以享用 只能在具有亲缘关系的进程中中直接传递信息 if(pipe(pipefd) == -1){ perror("pipe func error"); return -1; } pid_t ds = fork(); if(ds == -1){ std::cout << "createfork func error" << std::endl; } else if(ds>0){ //我们让父进程写东西,当我们写的时候,需要关闭管道读的一端 close(pipefd[0]); const char*str = "Hello! chen" ; if(write(pipefd[1],str,strlen(str)) == -1){ perror("write func erroor"); return -1; } close(pipefd[1]); } else{ //子进程读东西,当我们读的时候,需要关闭管道写的一端 close(pipefd[1]); char readbuf[32]{}; if(read(pipefd[0],readbuf,32)==-1){ perror("read func error"); return -1; } std::cout << readbuf << std::endl; close(pipefd[0]); } }
线程的概念
线程是CPU基本的执行单位,也是程序执行流的最小单位,在引入线程之前,不仅是进程之间可以并发,从而进一步提升了系统的并行度,使得一个进程内也可以并发处理各种任务。引入线程之后,进程只作为除CPU之外的系统资源的分配单元
引入线程之后带来的变化:
-
资源分配,调度:在传统机制中,进程是资源分配,调度的基本单位,引入线程后,进程是资源分配的基本单位,线程是调度的基本单位
-
并发性: 传统机中,只能进程间并发。引入线程后,各个线程也能并发,提升了并发度
-
系统开销:传统进程并发,需要切换进程的运行环境,系统开销大。线程间并行,如果是同一进程内线程切换,则不需要切换进程环境,系统开销小。
线程的属性
* 线程是处理机调度单位 * 多核计算机中,各个线程可占不同的CPU (例如,如果一个多线程应用程序在一个四核处理器上运行,操作系统可以将四个线程分别分配给四个不同的CPU核心,使得这些线程可以同时执行。) * 每个线程都有一个线程ID,线程控制块(TCB) * 由于共享内存地址空间,同一进程中的线程间通信甚至无需系统干预 * 同一进程的不同线程共享进程的资源 * 同一进程中的线程切换,不会引起进程切换 * 不同进程中的线程切换,会引起进程切换
线程的实现方式
用户级线程:用户级线程是由应用程序自己创建和管理的线程。这些线程完全在应用程序的控制下,操作系统对它们一无所知。应用程序可以创建、调度和销毁用户级线程,而不需要操作系统的干预。用户级线程的切换是在应用程序内部完成的,操作系统对其无感知。这意味着用户级线程的切换速度相对较快,因为不涉及操作系统的介入。用户级线程的一个限制是:当其中一个线程被阻塞(例如等待I/O操作完成)时,它会阻塞整个进程中的所有线程,因为操作系统无法单独调度和管理用户级线程。这种情况被称为阻塞型调度。
内核级线程:由操作系统内核创建和管理的线程。内核级线程是操作系统的一部分,它负责线程的创建、调度和销毁。内核级线程的切换是通过操作系统的调度器来完成的。当一个内核级线程被阻塞时,操作系统可以切换到其他可执行的线程,从而提高系统的并发性。
4 处理机调度
基本概念:
当有一堆任务要处理,但是资源有限,这些事情没法同时处理,这时就需要确定某种规定来决定处理这些任务的顺序。
调度的三个层次
高级调度(作业调度):按照一定的原则从外存的作业后备队列中挑选一个作业调入内存中,并创建进程,每个作业只调入一次,调出一次。作业调入时会建立PCB,调出时撤销PCB. (外存到内存)
低级调度(进程调度):按照某种策略从就绪队列选取一个进程,将处理机分配给它。进程调度时操作系统中最基本的调度,在一般的操作系统中会配置进程调度,进程调度的频率很高 (内存到CPU)
中级调度(内存调度):按照某种策略决定将哪个挂起状态的进程重新调入内存中。 (内存到CPU)
挂起状态:内存不够时,可将某些进程的数据调入外存,等内存空闲或者进程需要运行时,重新调入内存,暂时调入外存的等待进程被称为挂起状态
高级调度与低级调度(也称为短期调度)是不同的概念。低级调度是在内存中已加载的作业之间进行进程切换的过程,而高级调度是将新的作业从外存加载到内存中的过程。
调度算法的评价指标
CPU利用率:忙碌时间 / 总时间
系统吞吐量:单位时间内完成作业数量
周转时间:从作业被提交给系统开始,到作业完成为止的这段时间间隔。进程从创建到结束运行所经历的时间(完成时间 - 到达时间)或(等待时间 + 运行时间)
平均周转时间:各作业周转时间之和 / 作业数
带权周转时间:作业周转时间 / 作业实际运行时间 = (作业完成时间 - 作业开始时间)/ 作业实际运行时间
平均带权周转时间 :各作业带权周转时间之和 / 作业数
等待时间:指进程或者作业处于等待处理机状态时间之和,等待时间越长,用户满意度越低
调度算法
先来先服务(FCFS):
规则:按照作业和进程到达的先后顺序进行服务
用于作业/进程调度:用于作业调度时,考虑是哪个作业先到达后备队列。用于进程调度时,考虑是哪个进程先到达就绪队列
是否可抢占? 是非抢占的算法
短作业优先(SJF)
规则:最短的作业或进程得到优先服务,每次调度时选择当前已到达 且 运行时间最短的作业/进程
用于作业/进程调度:既可以用于作业调度也可以用于进程调度
是否可抢占?SJF和SPF是非抢占式的算法,但是也有抢占式的版本-最短剩余时间优先算法(SRTN)
抢占式的版本-最短剩余时间优先算法(SRTN)
高响应比优先(HRRN):
规则:在每次调度时先计算各个作业/进程的响应比,选择响应比最高的作业/进程服务
响应比:(等待时间+要求服务时间)/ 要求服务时间 = (1 + 等待时间)/ 运行时间
是否可以抢占:非抢占式算法。只有当前作业或进程主动放弃处理机时才需要调度,才需要计算响应比
进程同步
进程同步是指在多个进程之间协调和控制它们的执行顺序,以确保它们按照一定的规则和要求进行交互和操作。在并发编程中,多个进程可以同时运行,并且可能会共享资源或相互依赖。进程同步的目的是确保在共享资源的访问和操作过程中,不会出现竞争条件、数据不一致或其他不可预期的错误。
例子:在进程通信--管道通信
读进程和写进程一定是并发地运行,由于并发导致异步性,因此“写数据”和“读数据”两个操作执行地先后顺序不一样,而实际应用中,又必须按照“写数据 -- > 读数据”地顺序来执行。
进程互斥
-
对临界资源的访问,需要互斥的进行。即同一段时间内只能允许一个进程访问该资源
-
分为四个部分
进入区:检查是否可进入临界区,若可进入,“需要上锁"
临界区:访问临界资源的那段代码
推出区:负责”解锁“
剩余区:其余代码部分
信号量机制(通过一对原语来对信号量进行操作)
概念:用户进程可以通过使用操作系统提供的一对原语来对信号量进行操作,从而恒方便的实现了进程互斥,进程同步
信号量概念:就是一个变量(可以是一个整数,也可以是一个记录型变量),可以用一个信号量来表示系统中某种资源的数量,比如系统中只有一台打印机,就可以设置一个初值为1的信号量
wait(S) 和 signal(S) 也可以记为 P(S) V(S)操作,这对原语实现系统资源的“申请” 和 "释放"
对信号量S的一次P操作意味着进程请求一个单位资源,因此需要执行S.value--操作,(表示资源数减一),当S.value<0时表示该资源已分配完毕,因此进程应调用block原语自我阻塞(当前运行的进程从运行态->阻塞态),主动放弃处理机。
对信号量S的一次V操作意味进程释放一个单位资源,因此需要执行S.value++操作(资源数加一)
用信号量机制实现进程互斥,同步
用信号量实现进程同步
semaphore S=0; P1(){ 代码1; 代码2; V(S); 代码3; } P2(){ P(S); 代码4; 代码5; } 若先执行V(S)操作,则S++后S=1,之后P(S)操作时,由于S=1, 表示有可用资源,会执行S--,S的值变回0,P2进程不会执行block原语,进而执行 代码4,代码5 若先执行P(S)操作,则S--后S=-1,表示没有可用资源,P2进程会执行block原语,主动请求阻塞。之后执行代码2,进而执行V(S)操作,S++,使S变为0,由于此时进程在该信号量对应的阻塞队列中,因此会在V操作中执行wakeup原语,唤醒P2进程,这样P2就可以执行代码4,代码5
执行步骤:
-
设置同步信号量s,初始值为0;
-
在“前操作“之后执行V(S)操作;
-
在"后操作"之前执行P(S)操作;
用信号量实现进程互斥
设置互斥信号量为1;
临界区之前对信号量执行P操作;
临界区之后对信号量执行V操作;
生产者—消费者问题(同步和互斥地综合问题)
问题描述:系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放进缓冲区中,消费者进程每次从缓冲区中取出一个产品并使用(这里的产品是某种数据,)它们共享一个缓冲区
当缓冲区没有满时,生产者才能把产品放到缓冲区中,否则必须等待 当缓冲区不空的时候,消费者才能从缓存区中取出产品,否则必须等待 缓冲区是临界资源,各个进程必须互斥地访问
死锁
在早期的操作系统中,当进程申请某种资源时,若该资源可分配,则立即将资源分配给这个进程,后来发现,对资源不加限制的分配可能导致进程间由于竞争资源而相互制约以致无法继续运行。这就是死锁
出现死锁必须满足下面4个条件
-
互斥:必须存在互斥使用的资源
-
占有等待:一定存在已分配到了某些资源且在等待另外资源的进程,如果条件不满足,则所有等待资源的进程都不会占有任何资源
-
非剥夺:一定有不可剥夺使用的资源
-
循环等待
那么 我们为了防止死锁,就可以对上面4个条件进行破坏,从而死锁不会形成。
5 存储管理
内存空间的扩充
-
覆盖技术(解决“程序大小超过物理内存总和”)
思想:将程序分为多个段(多个模块),常用的段(模块)常驻内存,不常用的段需要时调用内存
一个固定区:存放最活跃的程序段,在这个区域运行的程序不会被调入调出
若干个覆盖区:不可能同时被访问程序段可共享一个覆盖区,覆盖区中的程序段在运行过程中根据需要调入调出
-
交换技术
内存空间紧张时,系统将内存中某些进程暂时唤出外存,把外存中某些已具备运行条件的进程换进内存
-
应该在外存的什么位置保存被唤出的进程
外存(磁盘存储空间):对换区:只占外存很小部分
-
什么时候应该交换
有许多进程且内存吃紧时进行
-
应该唤出那些进程
可优先换出阻塞进程,可换出优先级较低的进程
-
-
虚拟存储技术
连续分配管理方式(为用户进程分配必须是一个连续的内存空间)
单一的连续
分配方式中,内存被分为系统区和用户区,系统区通常位于内存的低地址部分,用于存放操作系统相关数据,用户区存放用户进程相关数据。内存只能有一道用户程序,用户程序独占整个用户区空间
优点:实现简单,无外部碎片(外部碎片:指内存中的某些空闲分区由于太小难以利用),可以采用覆盖技术。
缺点:只能用于单用户,单任务的操作系统中,有内部碎片,存储器利用率极低 (内部碎片:分配给某进程的内部区域中,如果有些部分没有用上,就是内部碎片)
固定分区分配
将用户空间划分为若干个固定大小的分区,在每个分区装入一道作业,这样就形成了最早的,最简单的一种可运行多道程序的内存管理方式,没有外部碎片,有内部碎片
动态分区分配
又称可变分区分配。这种分配方式不会预先划分内存分区,而是在进程装入内存时,根据进程的大小动态地建立分区(有外部碎片。没有内部碎片)
回收内存分区时,可能存在四种情况
回收区之后有相邻的空闲分区
回收区之前有相邻的空闲分区
回收区前,后都有相邻的空闲分区
回收区前,后都没有相邻的空闲分区
动态分区分配法
首次适应算法
每次都从低地址开始查找,找到第一个能满足大小的空闲分区,空闲分区以地址递增的次序排列。每次分配内存时顺序查找空闲分区表,找到大小能满足的第一个空闲分区
最佳适应算法
由于动态分区分配是一种连续分配方式,为进程分配的空间必须是连续的一整片区域,因此为了保证大进程到来能有连续的大片空间,可以尽可能多的留下大片的空闲区。即优先使用更小的空闲区。空闲分区按容量递增次序链接。每次分配内存时顺序查找空闲分区链,找到大小能满足要求的第一个空闲分区
缺点:会产生出很多的外部碎片
最坏适应算法(最大适应算法)
与最佳适应算法实现方法相反,优先使用更大的分区空间,空闲分区按容量递减次序链接
非连续分配管理方式
-
基本分页存储管理
概念:
将内存空间分为一个个大小相等的分区,每个分区就是一个“页框”(页框 = 页帧 = 内存块 = 物理页面 ),每一个页框有一个编号,即“页框号” ,页框号从零开始。将进程的逻辑地址空间划分为与页框大小相等的一个个部分,每个部分称为一个“页”或者“页面”,每个页面也有一个编号,叫“页号”,也是从0开始。
重要的数据结构---页表
为了知道进程每个页面在内存中的位置,操作系统要为每个进程建立一张页表 页表通常在PCB(进程控制块)中 1. 一个进程对应一张页表 2. 进程的每个页面对应一个页表项 3. 每个页表项由页号和块号组成 4. 页表记录进程页面和实际存放的内存块之间的映射关系
假设:某系统物理内存大小为4GB,页面大小为4KB , 则每个页表项至少应该为多少字节?
解:页面大小和内存块大小相等;
所以 物理内存64GB可以划分为2^32B / 2^12B = 2^20个内存块
页表项中的块号大小:2^20个内存块可以用20 个bit来表示,也就是大约3B
页表项中的页号大小: 由于页号是隐含的,因此每个页表项占3B,存储整个页表至少需要3*(n+1)B
逻辑地址可分解(页面P 页面偏移量W)页号:逻辑地址/页面大小
页内偏移量:逻辑地址%页面大小
如何实现地址转化:1. 找出逻辑地址对应的 [页号, 页内偏移量]
2. 在页表中找出对应页面在内存中的存放位置 2. 物理地址 = 页面始址 + 页内偏移量页内偏移量位数与页面大小之间的关系(只要知道一方就能知道另一方)
页式管理中地址是一维的
块表:又称联想寄存器,是一种访问速度比内存快很多的高速缓存,用来存放访问页表项的副本,可以加速地址变换的速度
6 文件管理
一个文件的信息属性:
-
文件名
-
标识符:一个系统内的各文件标识符唯一,对用户来说毫无可读性,是操作系统用于标识区分各个文件的一种内部名称
-
类型:文件的类型
-
位置:文件存放路径
-
大小
-
保护信息
文件的逻辑结构
无结构:文件内部的数据就是一系列二进制流或字符流组成。“又称流式文件”,
有结构:按照特定格式和规则组织的数据。这些文件遵循预定义的数据结构,使其在读取和处理时更易于解析和理解。常见的有结构文件包括数据库文件(如SQL数据库)、电子表格文件(如Excel)、XML文件、JSON文件和CSV文件等。这些文件通常具有明确定义的字段、标签、分隔符或标记,以便数据可以被正确地提取和处理。
顺序文件
索引文件
索引顺序文件
文件的物理结构
连续分配:要求每个文件在磁盘上占有一组连续的块
链接分配:采用离散分配的方式,可以为文件分配离散的磁盘块。分为隐式链接和显式链接
隐式链接:除文件的最后一个盘块之外,每个盘块中存储指向下一个盘块的指针,文件目录包括文件的第一块的指针和最后一块的指针
显式链接:把用于链接文件个物理块的指针显式的存放在一张表中,即文件分配表(FAT)。一个磁盘只会建立一张文件分配表
索引分配
###
文件目录的实现:
一个文件对应一个FCB, 一个FCB就是一个目录项,多个目录项组成文件目录
目录结构
单级目录结构:整个系统中只建立一个目录表,每个文件占一个目录项,单级目录实现了“按名存储”,但是不允许文件重名。
两级目录结构:早期的多用户操作系统,采用两级目录结构,分为主文件目录结构 ,用户文件目录,
主文件目录:记录用户名 及相应 用户文件目录的存放位置,
用户文件目录:有该用户的文件FCB组成
多级目录结构(又称树形目录结构):
7 设备管理
什么是I/O设备
就是可以将数据输入到计算机,或者可以接受到计算机输出数据的外部设备,属于计算机中的硬件部件
UNIX系统将外部设备抽象为一种特殊的文件,用户可以使用与文件操作相同的方式对外部设备进行操作
I/O设备的分类
按使用特性分类
人机交互类外部设备:鼠标 键盘 打印机
存储设备: 移动硬盘 光盘 用于存方数据
网络通信设备:路由器 交换机 用于网络通信
按信息交换的单位分类
块设备:传输速率高 ,可寻址,即对它可随机读写任意一块
字符设备:数据传输的基本单位是字符 传输速率较慢,不可寻址,在输入/输出时常采用中断驱动方式
I/O控制器
CPU无法直接控制I/O设备的机械部件,因此I/O设备还要有一个电子部件作为CPU和I/O设备机械部件之间的“中介”,用于实现CPU对设备的控制。这个电子部件就是I/O控制器,又称设备控制器。CPU可控制I/O控制器,然后再由I/O控制器控制设备的机械部件。
I/O控制器的功能:
接受和识别CPU发出的命令
向CPU报告设备的状态
数据交换
地址识别
I/O控制器的组成:
CPU与控制器之间的接口(实现CPU与控制器之间的通信)
I/O逻辑(负责识别CPU发出的命令,并向设备发出命令)
控制器与设备之间的接口(实现控制器与设备之间的通信)