操作系统笔记

本篇内容偏向流程的理解,具体内核代码实现上的补充在操作系统栏的其他博客中;

1. 操作系统结构

1.1 OS kernel的特征:操作系统内核的特征:

  1. 并发:同时存在多个运行的程序,需要OS管理和调度;
  2. 共享:“同时”访问、互斥共享
  3. 虚拟:利用多道程序设计技术,让每个用户觉得一个计算机专门为他服务
  4. 异步:程序的执行并非一贯到底,而是走走停停,时间不可预知;只有运行环境相同,OS要保证程序结果相同;

VxWorks:实时嵌入式,很牛的

平台性是操作系统的重要特征;
主要特点管理硬件和资源;直接和硬件打交道;

操作系统就像计算机上的政府:管理、协调资源、控制程序(出错处理)
什么是一个好政府:公平!这是操作系统的核心理念;
高效(效率)、稳定 、安全(数据)!


Unix使用POSIX的规则,其实就是规定了一些系统调用:完成编程人员与内核的加护;

系统调用:分隔了用户态和内核态

图中的我们设计了一个application,通过open这个系统调用函数去打开一个文件,这需要内核去完成,因此走到了图中的系统调用接口这个接口就是用来保证用户程序不能够随意访问到内核或者修改内核的一个保证;
实际上系统调用会触发INT(中断),同时系统调用的特定编号会传递到内核态中,
内核/操作系统通过这些参数了解这个中断想干嘛,进而执行,最后返回到用户态;
在这里插入图片描述
执行printf的过程 也 利用了系统调用,首先在用户态完成字符串的拼接,在打印的时候需要输出到硬件,这就需要内核/操作系统完成,因此调用了系统调用write函数,向输出设备输出字符;
在这里插入图片描述
函数传参的时候需要入栈,出栈;
系统调用函数调用的时候怎么向内核传递这样的信息呢,实际上参数还是存储在用户空间的内存里,只向内核传递这块内存的地址;传入到指定的寄存器里(寄存器肯定是内核态的)


1.2 操作系统的结构:


  1. 简单结构simple 单内核/ 聚内核 :DOS、早期Unix(DOS不看了)
    Unix的结构如图所示:而后我们现在看到的LInux好像很像
    最下面是硬件;(内核与硬件之间的接口);中间是内核;(内核与用户直接的接口:系统调用);用户
    问题是:内核层的所有东西混杂在一起,互相交织,牵一发动全身,错一处可能全完蛋;这就是为啥叫聚内核
    在这里插入图片描述

2、微内核松耦合
我们以MINIX3 作为一个例子(这个操作系统是Linux的爸爸,但是爸爸觉得儿子不好,因此爸爸重新设计了一个操作系统就是这个,解决了之前内核过于复杂混乱的事儿;)

可以看到这里将内核中的很多东西分离出来到用户态来完成:比如文件系统、网络、。。。
内核态只完成中断处理、进程调度、进程通信;
比如网络挂了/文件系统挂了,其实只是一个进程挂了,还可以重启;
问题:效率低:比如希望通过网络发送磁盘中的数据,需要下面的过程
网络向内核申请访问磁盘、内核向磁盘发出请求、磁盘接收并发送数据给内核、内核发送数据给网络
这样的过程在其他内核,比如聚内he中只需要1个内核态的函数调用,因为两者都是运行在内核态的

微内核的发展:windows最早是微内核的但是实在不好用,后来不断的添加东西,XP、win7等等,已经不能说是一个微内核了;设计师的话是保留了微内核的某些属性;

在这里插入图片描述


服务器使用的VMM虚拟机原理,是在操作系统之下的
在这里插入图片描述


  • macos双内核:聚内he+微内核
  • 集群:机房;硬件之上直接虚拟机,根据需求虚拟机安装操作系统,可以通过虚拟机直接分配资源;

系统的引导:boot:用来加载操作系统的内核

  • BIOS负责选择从哪个设备开始引导;比如硬盘、U盘等:下面按照选择硬盘的逻辑,继续走
  • 工业规定,硬盘的第0个或者第一个扇区存储了512byte字节存储了MBR命令:Master boot record 主引导记录:包含一部分的引导程序 && 分区表;读到7c00中,并跳转到这里执行MBR;
  • MBR会在分区表中找到被激活的引导分区(被激活表示这个分区是引导分区;);并将这个分区的第一个扇区中的512字节byte读入内存0x7c00位置,继续执行引导-》对应引导的操作系统;

在这里插入图片描述


2. 操作系统的启动

2.1 BIOS、BootLoader、OS

启动最基本3部分:CPU + 内存 + IO
注意,加电之后cpu CSIP有默认值,首先去执行这个地址里面的命令,也就是BIOS所在的EPROM只读地址中的指令
DISK:存放OS
BIOS:基本IO处理系统(作用是让整个计算机开始检测各个外设一些简单的硬件初始化工作;并将bootloader加载到内存
将bootloader从磁盘的第一个引导扇区(512B)加载到内存0x7c00处;之后跳转到CS:IP = 0000:7c00也就是bootloader处,开始执行bootloader;
BootLoader:加载OS 从硬盘到内存(本身也在disk中放着)
bootloader将操作系统代码和数据从硬盘加载到内存中,并跳转到操作系统起始处,开始运行操作系统;
BootLoader的工作:
①使能保护模式(从16位寻址到32位寻址,1M到4G)默认地,段机制也会被使能
② 读取OS代码读取到内存固定位置
③ 跳转到 OS入口 执行
其中细节:在①之前需要:
建立GDT表(global Description Table全局描述表)和IDT(中断描述)表;当前地址映射翻译(CS:IP 或者说base + 偏移)从这个表中完成,得到真正的物理地址; 之后再将寄存器置为1 使能保护机制
在这里插入图片描述在这里插入图片描述

2.2 系统调用、异常、中断(特点:源头、同步异步、响应;与 处理机制)

系统调用:来源于应用程序,想操作系统发出服务请求(请求操作系统帮忙)
异常:来源于不良的应用程序,非法指令或者其他坏的处理状态(如内存错误,应用程序不想但不得不请求操作系统帮忙)
中断:来源于外设,不同硬件的计时器和网络的中断
在这里插入图片描述

中断:异步(指 应用程序并不知道什么时候会产生中断,因为是外设,比如键盘)
异常:同步(指 受应用程序控制,想产生异常随时可以产生)
系统调用:异步或者同步;怎么理解都行;产生时同步(虽然是产生中断,但是可控是我自己请求的),返回时异步(比如读数据请求,可以非阻塞,去做别的事,这就是异步的)

在这里插入图片描述

在这里插入图片描述

中断和异常、系统调用处理机制:

从这个图中可以看到,应用程序与内核打交道就这3个路径!
都是通过查中断向量表来找到对应的处理方案:
中断:根据中断号找中断处理函数,对应设备驱动函数,完成交互
异常:根据异常编号找异常处理函数,对应两种方案(直接杀了或者帮助解决)
系统调用:通过系统调用号在系统调用表里面找到实现函数,完成后返回应用程序;
在这里插入图片描述
分为硬件和软件两个部分:需要做一些保护机制和处理工作

中断的处理机制:如下两个图所示:
① 首先硬件产生中断,也就是产生中断标记,告知cpu中断产生(标记主要就是中断号,以便后面操作系统根据中断号进行处理)
② 操作系统接收到中断,并进行处理工作包括:保存现场、根据中断id找到对应的中断处理函数、结束中断处理函数后清除中断标记、恢复原来的执行现场;
在这里插入图片描述在这里插入图片描述

异常的处理机制:

① 首先也是一个应用程序产生异常,有对应的异常编号,标识异常产生的原因;
② 软件上操作系统同样:保存现场、根据异常号来处理异常注意这后面有区别了
如果异常是需要杀死进程的,那就杀了这个应用程序,并回复现场继续干别的
如果异常是请求操作系统帮忙的,那**操作系统在执行相应的异常处理工作之后,会跳转到抛出异常之前,重新执行工作,这时可能就不会发出异常了; **
在这里插入图片描述

中断嵌套:

① 硬件中断可以嵌套,需要按照优先级来执行;但是在中断服务例程中禁止中断请求打断;
② 异常服务例程 不如中断服务例程厉害,它可以被硬件中断打断
③ 异常服务自身也可以套娃,比如在处理异常的时候,虚拟内存发生缺页错。。有一个新的异常
在这里插入图片描述

2.3 系统调用实现与例子

在这里插入图片描述

系统调用与函数调用的区别(原理与开销)

这里是使用的中断指令的不同;中断产生INT ,中断返回IRET;函数调用CALL,函数返回RET;
另外还涉及内核栈的切换,系统调用需要完成内核栈的切换,并在内核栈完成特权操作,也就是系统调用想干的事儿~ 之后再同步到用户态的栈(这好比银行的取钱行为,用户栈是我们手里的存折,内核栈是银行的记录,银行通过内核栈完成取钱操作后返回给我们,并同步我们手里的存折)
在这里插入图片描述

开销:

系统调用要大一些:
①中断产生需要引导机制(软中断,如果是硬件中断那就硬件做引导)
② 内核栈的创建
③ 对于入参的校验
④ 内核需要映射用户地址空间,为了能够在内核访问用户态地址空间中的数据;(缓存变化)
⑤ TLB是虚拟地址映射物理地址的cache,比页表要快,这个也会更新/切换信息;
在这里插入图片描述

2.4 GCC内联汇编

为了满足C语言无法完成的特权级操作,比如初始化GDT表
在这里插入图片描述在这里插入图片描述

2.进程管理

进程的概念: 运行着的程序;
一个进程的内存空间的样子:
在这里插入图片描述
进程的状态:操作系统通过不停的切换进程的状态实现进程之间的切换和控制,达到操作系统的有条不紊!
在这里插入图片描述在这里插入图片描述在这里插入图片描述
ready:一切准备就绪,就等CPU了
(当进程由于被操作系统中断:可能因为:优先级更高的程序、运行太久等,cpu被夺走,返回就绪态)
running:占用着cpu,正在跑着
waiting:又叫阻塞
(当running进程主动让出CPU,
为什么:因为进程不知道当前做什么,需要等待一个事件或者一个输入/IO等等
也就是说,运行态的程序主动请求一个东西,不管是等待输入也好,事件也好,都会让出cpu,自己进入waiting等待态,或者说阻塞态,当等待的事件完成或IO请求满足了 ,重新回到ready就绪态

注意:我们可以将操作系统设计成:等待态结束直接跳转回 running态,但是需要判断的逻辑太多(比如ready队列中的优先级,比如正在runnning的程序处理等)
但是!!不能设计成一个ready态的跳转成waitng态(因为:进入waiting等待/阻塞态的条件是等待事件发生或者请求IO,这是一个系统调用,只有在有cpu的时候才能运行系统调用!!!!ready没有CPU!!)

PCB进程控制块:就是一个结构体,保存一个进程的信息

  • process state:进程状态
  • program counter:程序计数器,或PC。也称为指令指针,或指令地址寄存器(指向下一条需要运行的程序)
  • CPUregisters:CPU寄存器
  • CPU scheduling information:CPU调度信息
  • memory management information:内存管理信息
  • accounting information:统计信息
  • IOstatus information:IO状态信息
    这些都是九牛一毛,不用记录

操作系统中的进程管理:使用什么数据结构呢?(来管理PCB数据结构,也就是每一个进程的结构)

正常情况下,我们应该使用链表 而不是数组,因为进程的频繁创建与删除、频繁的切换使用数组会造成中间元素的空洞
造成空间上的浪费!
但是!!!!!!!!!!!!!!!!
工程上使用的就是 数组 来对进程的PCB数据结构进行管理,因为
1. 内存多,不在乎浪费这点
2. 考虑到“局部性原理”  cache会根据使用结点的局部性进行刷新,这样数组的结构能够保证cache的高效性;

进程切换的细节:

从上面可以看出
进程从运行态running 切换到ready就绪态(被动的中断) 和 切换到等待态/阻塞态waiting(主动请求)是不同的
一个是被动,一个是主动;

前者(running-> ready)一般情况下,是通过中断,这个中断有很多种,比较常见的例如 时钟中断!中断会调用中断服务程序进而进入内核,操作系统从而控制cpu来继续处理另一个进程;
后者(running->waiting)是进程主动请求等待,通过系统调用的方式,将cpu控制权移交给操作系统,从而完成进程切换

进程的创建fork()Linux下
fork()是个系统调用,操作系统将父进程的所有东西,完全复制一份给子进程,包括寄存器的内容,这就决定他们接下来运行的下一句代码也是相同的,唯一不同的是:!操作系统给父子进程的pid是不同的,根据程序用的pid,就可以分支运行父子进程了;

进程之间的通信:1. 消息传递 2. 共享内存

从直觉来看,共享内存好像用起来更加方便,数据直接传进去,读就完了;
但是实际上,第一种方式更加好用

原因:

  • 共享内存的实现需要调用大量的系统调用,编程不方便
  • 共享内存涉及到各个进程之间同步问题不能同时向同一个地址中写数据

进程之间的数据共享:

正常我们知道,父子进程在创建的一瞬间的所有数据都是共享的!
但是! 我们定义一个全局变量,在父子进程中分别打印这个变量的地址!!,结果竟然是相同的!

这是因为:

每一个进程的地址空间都是独立的从 0 开始的,但是这并非实际的地址,而是映射出来的虚拟地址;
这里打印出来的地址就是,到0的偏移量,也就是虚拟地址中的东西,并非同一块物理地址;


线程的共享
除了下图中的内容,全都共享(下图中的 ①线程ID、②PC指针、③寄存器组、④栈空间)
在这里插入图片描述
多线程就是一个进程中多个线程,PC很重要,因为多个PC所以多条执行路径,之所以独立栈,因为栈中存储了整个程序的 路线:(各种函数调用,多层调用的返回路线,各种分支)

线程的优点:
① 响应性 : (但是这个用进程也能完成)
比如进程发出一个IO请求,需要进入等待态,这样与用户交互性就不好了,所以开一个子线程去做费时间的 操作; 又比如: 进程需要进行高复杂度的运算,十分占用cpu,这时进程本身没法维护用户界面导致界面花屏,但是如果开个线程去做,就很方便;
②资源共享:
更多数据的共享,方便处理;
③开销节约一些资源

线程的实现策略:1. 用户级 2.内核级

用户级线程:在用户空间通过编程的方式,不通过操作系统内核,实现类似的并行,自己保存现场,自己定制

优点:1. 效率高(不涉及内核态和用户态切换)
	 2. 定制化好(想怎么着怎么着)
缺点:1. 阻塞调用会阻塞全部用户级线程(因为归根到底,他们都是在一个线程里面运行的 假的多线程)
	 2. 无法使用多个cpu,只能用一个;

进程/ 线程 减小创建和 销毁开销的 策略

进程池/ 线程池:
在多并发的服务器端,需要频繁的与用户进行连接,如果每次都新建一个进程/ 线程,这样用户的体验不会很好,
因此,使用进程池 、 线程池的方法:

在服务器端开启的时候,就会创建一些线程放入线程池中,待命; 当有用户接入的时候两者直接联接,交互;
当用户断开连接后,线程不销毁;直接回收到线程池中;

至于更细节的比如线程池的大小以及,什么时间创建而更多线程满足用户需求就是细节问题了; 

linux线程的实现

linux 中实现的线程是投机取巧的;(现在可能不是,没有仔细研究)
fork() 和 pthread_create都会调用同一个 clone系统调用;只是传入的参数不同,从而实现其中的资源是否共享;

这样带来的问题是: 在系统严重,线程和进程实际上是没有什么区别的,于是没有针对线程的优化,线程的轻量型,快速切换的特点没有体现出来;

在这里插入图片描述


CPU调度:决定下一个运行的进程

目的:最大程度的利用CPU
大多数程序,都是用一会CPU,等待一会IO(纯IO/纯CPU的程序都几乎没有)

进程的分类:按照占用cpu更多还是等待IO更多可以分为:CPU绑定进程、IO绑定进程
在这里插入图片描述
cpu调度在什么时候起作用呢?????

1. 一个进程/ 线程**从running运行态 进入 等待态/阻塞态waiting**:CPU空出来了!通过调度选择下一个运行的
2. 一个进程/ 线程**从running运行态 进入 就绪状态 ready**:CPU空出来了!通过调度选择下一个运行的
3. 一个进程/ 线程从**等待态/阻塞态waiting 进入 就绪状态 ready**:等待队列中来了新成员,根据调度安排
4. running 状态的**进程 退出了**

抢占式、非抢先式的调度

非抢占式的调度:如果这个进程不让出cpu,那么其他进程永远无法获得;
抢占式: 可以通过抢占的方式获得cpu;

上面1、4是自愿放弃cpu,适用于非抢占式的调度情形下;
2、3为抢占式的调度下的状况;

这两种都是很有意义的:卫星的太阳能帆板 、 我们电脑中的流氓软件(一直占cpu)

分配的概念:
调度算法运行结束以后,进行分配过程:1、 上下文切换恢复 2、切换到用户模式下 3、 跳转到正确位置继续运行

调度的时间开销:
1、调度算法开销 2、分配的时间(一帮通过硬件特定指令提升速度)

调度的评价指标
1、 CPU占用率(越高越好啊)
2、 吞吐率:单位时间完成的进程(越高越好)
3、周转时间:从一个进程创建到结束一共经历了多长时间(running + waiting + ready的所有时间、也教Wall Time)
4、 等待时间:在ready就绪队列里面的时间(注意不是waiting 态的时间)
5、 响应时间:在waiting 队列中等待一个事件,到这个事件被处理完成(数据库查询花费的时间 、 将字符回显到终端上花费的时间)(我理解是waiting(这时后台处理) + ready(后台处理完) + running在用户空间反应出来)

评价指标分类:
1、2是针对整个系统的,所有进程的
3、4、5是针对单个进程的;将所有进程的3、4、5平均一下,可以代表系统

调度算法:

调度最有效的依据就是进程在将来在CPU上消耗的时间,并期望用最短的进程最优先运行;
  1. 古时候,批处理计算机,使用的策略是:1) SJF:Shortest Job First 最短作业优先调度;
    它还分为抢占式的和非抢占式的抢占式的就是说:如果新来的作业需要的运行时间小于当前进程剩余运行时间,则先运行新来的作业;非抢占式:先来先服务,当一个进程结束后,再去判断当前队列中的进程哪个时间最短,哪个先运行;
  2. 但是怎么知道一个进程他需要运行多久呢?:是根据预测的方法得到的
    基于他以前的表现:看他以前用的CPU比较多,还是IO用的比较多

优先级调度: 并不是一种调度算法,而是一种调度的实现策略;比如:先来先服务:谁先来,谁优先级高;(剩余)时间最短优先:谁剩下的时间最短,谁优先级最高;

时间片轮准法:给每个进程一个设定好的时间(片),最多可以使用时间片这么久的时间,必须进入ready;(如果时间片太大:变成了先来先服务;太小:调度算法运行时间+上下文切换时间占比太多)

优先级调度+时间片轮转:策略很简单:**首先假设非抢占式:**首先看谁优先级高,高的先运行时间片,当进程结束,谁的优先级高,就运行谁的时间片;

谁的优先级高呢??看下图:多级队列
操作系统中使用多级队列的方式,每一级队里中的进程,其优先级差不多
1、系统级进程 2、3、交互级进程(IO占用高的进程) 4、批进程(CPU占用高的进程)5.学生级进程(古老的说法)
根据不同的队列采用最适合这个队列的调度算法:比如1、可以用时间片轮转等等

在这里插入图片描述
现在常用的:多级反馈队列(Multilevel Feedback Queue)

啥是反馈:对进程的行为进行统计,基于以前的行为决定未来怎么调度她
如何实现呢?
1、 刚来的所有进程都扔到 优先级最高的(时间片 = 8)的队列中
2、 若它一下子都用光了,把她放到下一个优先级队列中(时间片=163、 所以优先级高的一般都是IO占用高,CPU占用低的,反之亦然;
4、 这是一个动态变化的过程;
5、 下方低优先级的进程,通过使用IO可以进入waiting->ready,这个过程会提高它的优先级;

在这里插入图片描述


死锁更新:
死锁产生的必要条件(4个):
  1. 互斥条件:只有对必须互斥使用的资源的争抢才会导致死锁(如哲学家的筷子,打印机设备等)。
    像内存、扬声器这样可以同时被多个进程使用的资源是不会导致死锁的,因为进程不用阻塞等待这种资源);
  2. 不可剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放
  3. 请求和保持条件(情景条件):进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程所占有;
    此时请求进程被阻塞,但又对自己的资源保持不放;
  4. 循环等待条件(必要非充分):存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求;
    注意:发生死锁时一等有循环等待,但是发生循环等待时未必是死锁
死锁处理策略:

在这里插入图片描述

  1. 预防死锁: (不让死锁情况出现)破坏死锁产生的4个必要条件中的一个或几个
  2. 避免死锁:(不让死锁发生:运行态)用某种方法防止系统进入不安全状态,从而避免死锁(银行家算法);
  3. (解决死锁)死锁的检测和解除:允许死锁发生,不过操作系统会负责检测出死锁的发生,然后才去某种措施,解决死锁;
死锁处理策略展开:
1. 预防死锁:

在这里插入图片描述

针对死锁产生的必要条件进行破坏,从而预防死锁的发生

  1. 破坏互斥条件:互斥条件为:只有对必须互斥使用的资源的争抢才会导致死锁;
    方案:把只能互斥使用的资源改造为允许共享使用的资源,则系统不会进入死锁状态;
    比如SPOOLing技术:将打印机改造为逻辑上共享的资源,设计队列持续接收请求,请求进程发送请求后做自己的事情;
    缺点是:并非所有的都能这样做,有时候为了系统的安全性,也必须保持这种互斥性;使用范围并不大;

  2. 破坏不可剥夺条件:不可剥夺条件为:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放;
    方案:直接可以被其他进程夺走 或 可以抢来其他进程的资源;
    这可能涉及进程的优先级等等,因为谁竞争胜利,谁得到资源;
    缺点:① 实现起来比较复杂;②释放已经获得的资源可能导致前面工作白做了,因此只适用于易保存和恢复的资源,如cpu;
    ③反复申请释放增加系统开销,降低了系统的吞吐量;④如果一直被剥夺可太惨了,可能导致进程饥饿;

  3. 破坏请求和保持条件:请求和保持条件为:进程已经有了一个资源,再请求另一个,但是被其他占有,自己直接阻塞并且不释放已有资源;
    方案:静态分配方法:在进程运行前,一次性申请完他所有需要的资源,不满足就不运行,一旦运行就一直归他所有;
    缺点:资源利用率极低; 这个策略也可能导致某些进程直接饥饿;

  4. 破坏循环等待条件: 循环等待条件为:存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下个进程所请求;
    方案:采取顺序资源分配法,每个进程必须按标号递增的顺序请求资源,因此一个进程只有占据了小号的资源,才有可能申请大号资源,有大号资源的进程,不可能返过来申请小号,因而小号进程申请大号时,大号会申请更大号,大号一定会释放,小号能够继续申请;不会产生循环等待现象;
    缺点:不方便增加新设备,会导致资源浪费w,用户编程麻烦
    在这里插入图片描述

2. 避免死锁:

避免死锁在下图中处于:不允许死锁发生的动态策略

在这里插入图片描述

  1. 安全序列:
    指系统按照这种顺序分配资源,不会产生死锁,系统会处于安全状态;安全序列可能多个;
    **注意:**系统处于安全状态,一定不会发生死锁,但是如果系统进入不安全状态,可能发生死锁而不是一定发生;
    与银行家算法的联系:在资源分配之前预先判断这次分配是否会导致系统进入不安全状态,以此来决定是否分配请求,这就是银行家算法

  2. 银行家算法:
    在这里插入图片描述

  3. 总结:
    在这里插入图片描述

3. 检测和消除死锁:

这是死锁已经发生了,如何检测和接触;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

死锁(原)

一:死锁的预防:(前期条件控制死锁不会发生)
攻击互斥条件:将一部分资源释放,变成共享的;
但是游侠是不可共享的;
解决方案:

  1. 分时(例如cpu)
  2. . 虚拟合并(显示器,声卡都是多通道并行的,你想其中写入的数据都是一个虚拟的声卡,需要声卡驱动去合并多通道)
  3. spooling缓冲池(对于一些只能串行工作的设备,比如打印机). 每一个进程都可以使用打印机,也是构建一个虚拟的打印机,将所有的任务按照顺序放到真正打印机的队列中;一个一个使用

二:死锁的避免(可能发生,在将要发生的时候让他不发生)
前提:需要知道当前进程最大资源需求数量
在这里插入图片描述
就类似这种图,如果能找到一个顺序,将资源按照这个顺序分配给各个进程,能够有效完成,不发生死锁,就按照这个顺序来,如果这样做不行,就不分配资源给这个进程;

或者使用图论的方法;

三:死锁的检测和恢复
上面的避免,会使用图论算法,或者银行家算法,这样太消耗资源了,因此不太推荐;

  1. 死锁的检测:每个周期检测一次当前操作系统是否发生死锁,如果发生了再去恢复;(比避免节省资源 ):检测方法其实还是使用上面的:图论或者是银行家算法;但是!实际当中用的比较多的是看门口进程;
    看门狗进程:看门狗进程用于监视其他进程的情况,其他进程(又称作喂狗进程)每隔一段时间就要发送一段消息给看门狗进程(喂狗操作),如果一旦进程出现问题,不管是死锁还是其他什么的挂了,看门狗进程会杀死这个进程,并进行其他操作;

  2. 死锁的恢复:这是容错技术的一种体现
    1)杀进程:对于进程而言 abort(丢弃,放弃):
    把所有的进程都杀死,重新运行,重新调度;(先杀一个,看看好没好,没好继续杀)
    先杀什么样的?:折中,,代价最小化,没有一定固定的方案。。。
    2)回滚(rollback )
    回滚之后为了避免发生同样的死锁,应该设置一些随机性因素,让进程调度产生变化;
    对于多次被杀死或者回滚的进程,应该记录下他的次数,下次就别搞他了;


计算机存储与内存


页表更新:
页表存在的目的:

对于逻辑地址与物理地址之间的映射,可以有很多种方式,当我们选择分块的映射,那么物理地址被分成了一块一块,同样逻辑地址也是一块一块的;
我们希望块与块之间是离散的,无序的;块内是连续的;因此我们需要一个东西来帮助我们建立逻辑地址中的一个块(页面)与物理地址中的一个块(页面)之间的联系——这就是页表;

页表是之于进程的,每一个进程都有一个自己的页表,这部分数据存储在PCB中,当然,这一些最根本都是存储在物理地址中的,所谓的多个进程的多个页表,是虚拟内存映射的功劳,它能够帮助我们将一台计算机更加有效的利用起来,运行更多的进程;

单级页表

我们以一个4GB内存的计算机为例,其按照字节编址(一个存储单元为一个字节的数据8bit),其页框(块、页面)大小规定为4KB;
计算:
对于一个进程而言,如果所有逻辑地址进行分页,从0 - 4G,那么会有 4G/ 4K = 220个页面;
也就对应了页表中有220个页表项;对于所有的页表项,我们给他们编号则为 0 - 220-1;计算机在运行过程中,会利用这个编号去页表中查询这一个编号的页面对应到物理地址中的哪一个块;同时剩下的(32-20) = 10位地址则为当前想要存取的数据在这个块内的偏移量;

按理说:页表中的一个页表项存储了一个逻辑页面序号 和其 对应的物理页面(物理块)的序号,这只需要20bit,也就是凑整24bit-》3字节;但是为了方便地址对齐,我们使用4B作为一个页表项的大小;

在这里插入图片描述

如果说所有的逻辑页面都需要对应到物理页面块上,那么共需要 220*4B = 4MB的大小,而且这4MB在物理内存中需要是连续的;
那么每一个进程都有一个4M的页表,这太大了, 所以需要修改页表的结构是,也就是下面多级页表结构

多级页表

本质上就是:对连续很大的且连续的页表,再进行一次划分分组,整成二级的
页目录表(顶级页表)通过前10bit查询—>得到对应的二级页表所在的实际物理内存地址;
取出二级页表内容,通过中间10bit查询—>得到对应的进程逻辑页面的物理内存块号or地址;
使用这个块号,拼接上逻辑地址的最后10bit得到逻辑地址所对应的实际物理内存地址位置;
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


基础知识:

  1. 当前的内存管理需要依靠,两个寄存器limit 和 base 分别存放当前进程在内存中的 限制大小 和 起始地址;
    当前的内存管理并非是静态管理,也就是说可以修改上面两个寄存器的值,这很重要,代表着进程能够动态的对内存大小进行修改;
  2. 物理地址和 虚拟地址(逻辑地址);
    虚拟地址:generate by CPU:我们看到的,由cpu反应给我们的地址;
    物理地址:我们真正的内存,硬件内存条;
    MMU:内存管理单元:操作系统利用MMU将 虚拟地址映射成物理地址,最终从用户到内核态完成操作;
  3. dll:dynamic link library动态链接库:一种用来节省内存资源的代码编写方式
    1)概念:进程正常启动的时候,所有编译好的相关文件(包括库文件)都一起加载到内存中,但是DLL不加载进去!,只有当相关的功能使用到dll中的代码的时候,才加载进去;
    2)功能: 节省内存、共享:节省内存很好理解,不加载到内存;共享:在windows中,比如窗口的ok,关闭等按键执行的功能是一样的,并不需要每个进程都实现一份并加载进内存,因此操作西中只实现一份,需要的时候加载进来大家一起用;
    3)应用:例如win中菜单键(文件、配置、插入等)、win中系统更新大多更新的就是dll文件,因为这个文件很多东西共享,所以优化这个文件就ok了;linux中的cout实际就在dll中实现,这里涉及调用时利用stub(存根)来作为中间件,调用时我们自己的内存中会映射一个(重定向的)printf函数,调用这个函数会通过stub找到DLL物理地址真正的printf函数;
  4. 逻辑地址和物理地址的转换方式
    1)汇编中最基本的:base寄存器指向的地址 称之为(换个名字)relocation register(重定位寄存器),逻辑地址的编址都是从0开始的, 在逻辑地址中的地址 + relocation register地址,就是真正的物理地址,也就是说,只有一个偏移的地址;
    2)使用dll
    3)swapping(交换技术):利用外存(磁盘)进行交换,以解决内存不够的技术
    有些进程可能进入了wating状态,但是仍然占据这内存;如果内存不够,新的进程无法创建,因此将这个睡着的进程交换(swap out)到外存中,唤醒的时候再(swap in)到主存中;

实际操作系统中的应用:
5. 连续分配(contiguous Allocation):在这里插入图片描述
1)类似上面提到的:OS在低内存地址,进程在高内存地址;MMU判断base和limit指针是否超过限制;
2)有一些问题:下图中的进程应该放在哪个内存空间中呢?
在这里插入图片描述
有这么几种方案:①从上看,永远向第一个里面放 ②从上向下依次放 ③按照大小排序后,放到与当前进程大小最相近的大一些的 ④每次都放到最大的里面;
在这里插入图片描述
看起来好像差别很大,其实效果差不多,现在一般只使用第一种 First-fit首次适应;
这种方案已经基本见不到单独使用的了;这种方法在现在的应用:在堆内存中申请和释放空间,频繁而且顺序不确定,这样其实和这个问题等价,都是内存的fit问题;这并不由操作系统管理,由语言库管理

  1. 外部碎片与内部碎片:
    1)外部:就是上面说的物理空间中,每一个进程之间的空隙;
    2)内部:由于页式内存管理方式造成的,比如最小内存分配为4K ,但是我申请4K+1的大小会给我8K,这样就会造成碎片;

  2. .页式内存管理:
    1)概念:
    物理内存划分为固定大小的物理页面,size在512字节到16MB之间;
    逻辑内存划分为固定大小的逻辑页面,size与物理页面一样;
    在这里插入图片描述
    2)页表:
    上图左侧为逻辑页面,是一个进程的空间,被划分为了很多页面,右侧是物理页面:杂乱无章,没有规律; 中间使用一个页表,将逻辑页面与物理页面之间对应起来,每次使用对应页面的数据都要去查找;如今的计算机都是这样的;

    3)地址翻译: 如何使用页表完成虚拟地址和物理地址之间的转换?
    首先:一个进程的逻辑内存空间大小为 2m,一个逻辑页面的大小为2n,—>一个内存空间存在2m-n个页面。
    接着:将一个给定的逻辑地址,从末尾左移n划分为两部分; 前半部分即为本逻辑地址在当前进程的页表中的编号(页面号);后半部分为在屋里页表中的偏移量(页内偏移地址);
    也就是:使用页面号到当前进程的页表中查找到对应的物理页面号,使用页面偏移地址得到物理页表中的实际物理地址;将两者接起来!前后一接,直接就是对应的物理地址了!!!!!!

    注:实际上转换过程就是二进制左移表示除2,同时将一个数学问题转换为了工程问题;在这里插入图片描述在这里插入图片描述
    举例:在这里插入图片描述
    上图中 一个进程的逻辑内存为16字节 m = 4,4字节一个逻辑页面 n = 2
    通过 逻辑地址 + 页表能够得到其真正的物理地址;假设我们选择第8个字节(i),其对应的物理地址计算过程如下:
    1000 -> 10 | 00 -> 10b = 2 | 00 -> page table -> 1 | 00 -> 01 | 00 -> 100
    100即为物理地址;

    4)页面大小选择:
    页面越小:碎片少,页表变大
    页面越大:碎片可能更多,页表小;
    4K、8K、4M:一般4K用的多;
    5)空闲物理页面的管理:使用一个列表,保存着空闲的物理页面的编号;

    6)页表的实现:
    现在看来,每次数据访问都是两次内存访问:一次到内存中访问页表,一次根据页表得到的信息访问数据;
    实际上并非如此,这里有一个类似于cache的TLB(快表),TLB会将当前经常访问的(局部性原理)页表中的内存复制一份到TLB(类似cache),并不需要我们两次访问,访问这种类似cache的速度是很快的
    其查找方式也并非软件上的二分查找,而是硬件上的并行搜索,如果在TLB中命中,就直接得到物理地址,没有命中就去页表中重新找;(下图)
    设命中率为 α,访问TLB的时间为ε,访问一次内存的时间为1: EAT(有效访问时间)
    在这里插入图片描述
    在这里插入图片描述
    所以想要让这个时间小:1. 减小ε:提升TLB硬件访问速度 2. 增大 α,增大TLB的大小;

  3. 关于存储的一个知识盲区:安全问题
    这个程序在每次编译运行的时候,i的地址不会变,而a的地址会发生改变,为什么?
    i是全局变量,存储在一个进程对应的虚拟内存的全局数据区,a是一个局部变量,存储在栈区;
    按理说,我们打印出来的是虚拟地址,应该每次都一样的,如果是物理地址的话,每次基本不会一样;
    这是由于**处于安全角度对栈区数据的保护;**在C/C++中,易产生缓冲区溢出式漏洞,容易导致黑客直接攻击栈中数据;因此,每次初始化一个进程的虚拟内存时,栈底位置会遵循一定的随机生成;

#include <stdio.h>
int i = 0; 
int main(void)
{
	int a = 0;
	printf("%p\t%p\n",&i,&a);
	return 0;
}
  1. 内存保护
    1)当前知道的:页表就是对于内存的一种保护,对于一个进程只能访问这个进程自己对应的页表(页表在内存中,这里忽略了TLB), 因此无法通过页表访问到自己屋里地址以外的其他空间;
    2)只读只写只执行等权限保护:
    在页表中,不仅仅存放着逻辑页面和物理页面的对应关系,还有对于这个读写权限;
    举个例子:当我们在程序中尝试去写一个(页表中记录着)只读的内存时,cpu会发出一个错误指令,产生中断,陷入内核,触发中断处理,杀死当前进程并返回错误:段错误!
    char* string = "hello world!"; // 这是一个字符串常量,string是指向他的指针,不可以通过这个指针修		改它;
    char string[] = "hello world!";	// 这是一个变量,可以通过 *string = xxxx来进行修改
    
    3)有效无效位:使用指针去访问超过当前页表的内存时,会根据页表中的 valid-invalid bit有效无效位来判断,能否对这块内存进行访问;如果错误和上面处理一样,都触发中断等等;
    在这里插入图片描述
    4)共享内存的实现
    下图中的ed1、2、3都是ed进程的代码段,通过forkl复制了3份,但是并没有将全部的内存进行拷贝,其代码段是完全一样的,对应着完全相同的物理页面;
    注:fork的实现,并非直接复制一份内存,除了上面讲到的,还有 写时复制策略copy on wirte
    只有在对内存数据修改的时候,才会复制原来的页表到到新的物理内存中;
    在这里插入图片描述
    4)页表对于内存的消耗
    32位计算机中,虚拟地址和物理地址都是32位二进制的范围也就是 0 ~ 232-1
    按照上面的页表方式:对于每一个进程页表都是预留式的,进程创建直接建立所有内存对应的页表。
    一个页表会占据多大的空间呢?
    常用计算机每个页面大小为4K = 212(这代表着每个页面能够表示的地址范围为,0~212)
    计算机总共的地址有232,也就是说需要220 = 1M 个页面;
    每个页表项的大小为4字节
    算一下,一个进程需要 4B * 220 = 4MB;这太大了,开机之后假设有100个进程就已经将近400M的内存用来做页表了;
    所以不可以这样!在这里插入图片描述
    5)实际使用的页表形式:两级页表结构(Two-Level Page-Table Scheme)
    将虚拟地址的前20位(作为页面号)分割,前十位表示外层页表,后十位内层;
    在进程内存使用较少的时候,不会全部吧这些页表创建出来,用多少建立多少
    比如一个外层+一个内层能表示210个页面(外层10位,内层10位) = 4K * 210 = 4M个地址空间,当内存使用不超过4M,只需要俩就ok,页表本身占
    如果继续增加就1个外层2个内层,第二个内层对应外层变更一位的对应的内层的210个页面,也就增加到了内存8M;

    在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
    6)64位系统使用的页表方案
    使用哈希表+链表,哈希值可能碰撞;所以后面维护一个链表,来进行查找;
    在这里插入图片描述
    目前64位计算机,实际使用3级页表

虚拟内存
main中申请 1G内存:char* p = (char*)malloc(1024*1024*1024);

下图为实际:VSZ表示当前进程虚拟内存占用,RSS表示当前进程物理内存占用;
在这里插入图片描述
当我们实际使用多少内存,操作系统才会真正分给我们多少内存;

对p进行操作赋值
for(int i = 0; i < 1024*1024; i++){
	p[i] = '\0';
}

这样操作之后,RSS变为1MB,因为我们真正访问了1MB的内存空间;

简单讲:一个进程访问内存空间,请求页,逻辑页面肯定是有的,但是物理页面并不全在内存中,按照使用过的内存才建立相应的页表并将外存中的数据写到内存对应的物理页面中

内部发生一个缺页错中断,第一次访问逻辑内存页面,并没有对应的物理页面,这时向操作系统发送这个缺页中断,操作系统建立相应的物理页面,并将存储在外存中的数据假造到对应的内存(物理页面中)

这个过程是很消耗时间的,我们电脑发生卡慢很多时候就是因为这个;操作系统为了保证内存够用,会将后台一些不常用的进程的内存资源暂时回收,放到外存中,我们使用的时候再从外存中调出来;(这回导致逻辑页面对应的物理页面消失,容易发生缺页错误;)
这种情况下一旦发生缺页错误,相当于要将正在运行的程序的数据从内存转到外存,再将另一个程序的数据从外存转回内存;所以会很卡;
解决方法现在就是升级内存,内存大了,很多东西不会进行回收,进而发生缺页错的概率就会降低;

页面置换的流程:
①从内存中选择(算法)一个页面swap out,同时外存中读入当前需要的页面
②更新页表,继续运行进程;

其中:
页面置换算法:(选择哪个页面从内存置换到外存的方法)
实际的算法中,页表项中存在一位脏位dirty bit用来记录本页页表是否被修改过;
如果一个未被修改过,那么其并不需要重新写入到外存中;只需要将新的外存的页面读入即可;
页面置换算法的目的是降低缺页错率,下面看看一些常见的方法
① FIFO(实在不好用,连最近是否使用都没有记录未使用dirty bit)×××××××××××××
② 优化算法:需要置换页面时,将目前内存中已经存在的页面里,在未来最长时间不会被使用的那个,置换掉(没有实际意义,但是有理论意义,是最优的)×××××××××××××
Least Recently Used Algorithm(LRU最近最少使用算法
选择当前内存中,最长时间没有被访问的那个页面置换出去;(需要使用一个计数器来记录各个页面最后一次被使用的时间,需要访问一次内存,写入一次内存,同时这个计数器位数不能太小,消耗内存;不实用××××××××××××××××××××××××××××
在这里插入图片描述
LRU的近似算法时钟算法
使用一个指针,循环扫描页面的R位;
如果为1,则置为0,臊面下一个;
如果为0,则直接把这个置换出去;
其中这是一个队列结构,先来的页面在最前面;R位为置换标志位;

在这里插入图片描述
改进:和 dirty bit配合使用(R,M),优先级如下图所示,但是(0,1)和(1,0)视情况而定;
在这里插入图片描述在这里插入图片描述

页面缓冲池:(这是当前使用的一种策略)
当页表中满了的时候,需要进行置换,只对页表中的内容进行修改,而不把内存中实际的这个页表中的数据放回硬盘,而是放入操作系统的页表缓冲池
这样在空闲时间可以
① 完成缓冲池中dirty 页面的写入硬盘;
② 需要重新放入页表中的时候,不需要从硬盘读入;


全局置换与局部置换:
全局:可以将其他进程中的页面置换掉;
局部:只能置换当前进程中的页面;
抖动:
一个进程频繁的执行换入换出动作(内存 <–> 硬盘)

工作集:一个进程Δ时间内访问过的页面的集合,称为当前的工作集
利用工作集概念,找到了一个进程的使用的局部内存,这是动态变化的;
如果当前所有进程的工作集之和大于系统总体物理内存,则一定会发生抖动,可以干掉某些进程,让系统保持稳定;是健康程度的一个指标
具体一点: 页表每一项中留几位,记录时间Δ时间内被访问的次数;


当前工程使用:通过缺页错率修改工作集大小;
初始化所有工作集大小相同,通过最近发生缺页错的多少,修改进程的工作集大小(拥有的页面多少);


虚拟内存+缺页错的实际应用:文件mmap()系统调用
在访问文件的时候可以使用mmap()系统调用函数完成将整个文件读入内存中,然后像操作数组一样操作这个文件;
原理是和虚拟内存的实现是完全一样的:
1. copy_on_write写时读入 2. 两个进程读入一个文件直接共享这块内存;
在这里插入图片描述


文件系统接口

常见的文件系统:
FAT32、NTFS、EXT1/2/3/4
特性:
速度、节约空间、稳定、安全
一般定义:
外存里面的一种数据结构
FCB:文件控制块
类似于进程控制块PCB,每一个文件对应一个,存储着文件的大小、文件权限、时间、拥有者、存放在外存中的位置等等;
文件系统的整体框架
应用程序 访问 同一的虚拟(逻辑)文件系统 通过文件管理模块基础文件系统 进行访问;

在这里插入图片描述
文件系统的应用:
外存(on disk)和 内存(in memory)两个方面
外存比较慢,操作系统运行后要在内存和外存利用一定的数据结构建立联系,提升速度;

  • 外存上(On disk)的数据结构包括:
    Boot control block(启动控制块)、volume control block(卷控制块)、Directory structure(目录结构)、FCB(文件控制块)

  • 内存上(In memory)的数据结构:
    Directory - structure cache(目录结构cache)、System-wide open-file table(系统层面文件打开表)、Per-process open-file table(当前进程打开的文件表)、mount table (文件挂载表 )

简单的文件读写结构示意图:
这里实际上是两个系统调用open和read之间的关系

  • open系统调用将外存中的文件通过目录结构读取到内存中,这个读取这里不是内容上的读取,是FCB的读取,存入当前进程和操作系统总体的打开文件管理模块中(就是上面的两个打开表;)
  • read系统调用通过两级访问读取外存上的文件数据;
    首先通过文件描述符(句柄)在当前进程文件打开表中找到该文件的指针,指向操作系统维护的整个系统打开文件表,这里面存储着,所有进程打开文件的FCB;通过FCB可以完成内存和外存之间的数据交换;

在这里插入图片描述

虚拟文件系统的概念:
虚拟文件系统下,可以使得多个disk挂载在同一pc上面,不管是本地的还是远程的;提供统一的接口给应用;
在这里插入图片描述
文件系统的实现:
主要包含两大部分:Directory Implementation(目录的实现)Disk blocks allocated(磁盘块分配)

  • 目录的实现:
    1、Linear List线性列表:FCB一个挨一个,如果有目录就设置一个指针指向下一个位置;顺次排下来如下图
    优点:稳定
    缺点:当一个目录下文件很多的时候,查找速度为On,很慢;
    加粗样式
    2、Hash Table哈希表:不同的hash算法重复率会不同;
    3、B-、B+树(目前用的比较多的): 倾向于搜索的优化;文件不多且很大的情况下不是很有用;

  • 磁盘块空间分配的方法:
    磁盘的分配中,块是最小的单位:512B(字节)是最小的一块(此时1块 == 一个扇区 );也可以多个扇区构成一个块(此时分配的最小单位就变成了了 n* 512B);
    1、 Contiguous allocation(连续的/ 邻接的分配:光盘,其他很少用)
    每个文件占据一系列完全连续的磁盘块:文件在磁盘物理上是完全连续的;
    特点:
    (优点:)简单稳定、FCB在记录文件时,是需要记录文件的开始块和文件占据的块数量;方便seek操作,能够很快的找到随机访问的位置;
    (缺点:)删除文件之后的空洞、文件改变大小时无法连续;

    2、 Linked allocation (链(表)式的分配:FAT32)
    对于一个文件的各个块之间采用链式存储方式,使用指针连接起来;FCB记录起始点即可,最后一块指向NULL;
    特点:
    优点:防止了外部碎片(内部碎片是指一个块没存满,外部是指块和块之间的空缺块);
    缺点:访问速度慢,跳来跳去;对于随机访问块数据,就得从头来;磁头一顿跳;
    FAT32:在此基础上增加一个数组,index代表块号,内容为该块号的下一个块号;这样通过查找这个表,就能够完成快速查找到随机访问的数据位置,而不同磁头来回蹦了;

    3、 Indexted allocation (索引式的分配:inode:Linux)
    每个文件有个inode数据结构,这里面存储着这个文件所有数据块的索引,通过inode的顺序访问就能找到所有数据块;(如下左图所示);在文件系统中,通过文件名会找到这个文件对应的index block,这里面存放着的不是真正的数据,就是这个文件的inode;
    特点:
    优点:能够随机增加,能够随机访问;速度快
    在这里插入图片描述 在这里插入图片描述
    改进:每一个inode最多只能存放 n 个数据块,太有限了
    1、 使用分级的inode; 使用类似于内存管理中的页表思路:(下图左图)
    2、 混合式的,对于一个文件的第一个inode,它类似于FCB的功能,存储着这个文件的一些信息,同时,会划分出位置①direct blocks指向直接存储的数据、位置②作为单层分级inode的指针、位置③作为双层分级inode的指针、位置④作为三层分级inode的指针…这样就能够实现不管文件大还是小,都能够很好的存储(下图右图)
    在这里插入图片描述 在这里插入图片描述


基于日志的文件系统

NTFS、 Ext3/4、Reiserfs、 ZFS
对文件系统引入事务的概念(transaction)
在数据库中的概念是指原子操作;
日志文件系统中:
可以实现如下图的结构,每一次对于磁盘的写操作都通过日志的方式记录下来
向磁盘中写入的操作是事务,原子的;完成后清除掉日志中的相关内容;
一旦断电等问题,只需要将log信息记录的操作从新执行就好;(这里很像数据库)

在这里插入图片描述
写性能:

  1. 向log区写的操作是同步写操作;
  2. 向磁盘数据区的写操作是异步写的操作;

读取性能:
log日志文件系统的设计读性能并不好;
因为这种方案下,每次写操作都是在当前数据区的最后添加数据,而不是空闲块也不是寻找连续的位置添加;
这是由于当前内存很大,可以提供很大的cache来用来预读取;因此是硬件上的生机带动文件系统并不去考虑读取性能的问题;


FAT16和FAT32的目录项结构:(上面是16 下面是 32)

在这里插入图片描述
在这里插入图片描述


NTFS 和 ZFS:这俩是 Windows(与NTFS绑定) 和Linux里面最为推崇的两个文件系统(现代文件系统);
NTFS的特性:

  1. ADS:可以在文件名后添加“ :”加密文件,或者标识文件
    应用:ie从网上下载的exe文件在属性之前会询问是否运行来自于网络的文件,因为这个文件下载到硬盘的时候被添加了标识;
  2. Quotas:可以实现本地不同用户的磁盘空间限制;
  3. Sparse files:可以创建一个随意大小的文件,但是没有向其中写数据的时候,它只是显示这么大,其中真正占用的空间只有写入数据的大小;
  4. Volume mount point:可以像linux一样将U盘挂载到某一个目录下
  5. Volume shadow copy:快照功能保存历史版本,能够实现在某两个时间点的文件系统快照之间进行恢复;
  6. File compression :文件压缩功能;
  7. SIS:copy on wirte的方法,写的时候才会修改文件内容,两个东西复制完并不占用内存,修改才占用;
  8. 硬盘加密;

ZFS的特性:(128位文件系统,与soleris绑定,开源可移植)


大容量存储系统:Mass-Storage Systems

硬盘的构造;
track:磁道
sector:扇区(组成块的最小单位:512B)
在这里插入图片描述
接口协议:
SATA、USB、SCSI(太老了)、Fiber Channel(光纤通道)

与操作系统的沟通
通过磁盘控制器来完成操作系统访问的逻辑接口到磁盘本身的物理接口的映射;
在操作系统看来,所谓的逻辑映射就是一个数组,我们要访问这个数组的哪一块;其中一块的大小可能是1个扇区512B也可能是俩扇区,1KB

云端存储
在服务器和存储设备之间通过专门的SAN网络来对存储设备进行访问;

在这里插入图片描述

磁头的寻道算法:
在这里插入图片描述
对于IO请求队列中的当前请求,他们对应的磁道是不同的,先去响应哪一个的问题;
并非时间最短就一定是最好的~下面是实际中的一些算法;

  1. FIFS:先来先服务
    按照队列中的顺序进行访问;
  2. SSTF:shortest seek time first最短寻道时间优先
    总是选择距离当前磁头最近的那个请求先进行访问;
    优点:最快
    缺点:可能有些请求被饿死
  3. SCAN/C-SCAN 电梯算法/c-电梯算法
    不完全的仿造电梯的运行方式,首先随机选择上下,之后一直单方向运行到请求的尽头;
  4. LOOK / C-LOOK 电梯算法改进
    完全仿造电梯的运行方式;

如今实际采用的最多的是2SSTF,实验效果最好,几乎不会存在被饿死的情况


RAID:redundancy arrays of inexpensive Disk(廉价的磁盘阵列)

在这里插入图片描述

  • RAID0: 将几个硬盘连接在一起,形成一个逻辑上的硬盘
    在存储的时候,将一个文件分成几块,分别存储在不同的硬盘上面,这样就能实现分别并行地读取;
    问题:稳定性、可靠性较差,如果其中一个磁盘坏掉了,文件完整性直接被破坏了;
  • RAID1: 弄几个完全相同的硬盘,其中存储着使用的硬盘的镜像;就是备份;
    问题:空间浪费
  • RAID2、RAID3、RAID4: 分别使用 3块、1块、1块来对数据进行校验,如果出现问题能够及时恢复并提醒;
  • RAID5: 将用来做校验的扇区分布地放到每一个磁盘中,使得校验的负荷均匀化;
    问题:与RAID0相似,如果有一个磁盘坏掉,损失惨重;

总体上来说,RAID很好,但是需要所有的硬盘牌子,速度,性能完全相同;


下面是一些关于计算机组成原理知识点

第一部分

  1. 计算机工作过程:在这里插入图片描述
  2. 计算机编程语言在翻译程序中的过程:
    在这里插入图片描述
    上图中的高级语言:解释程序和编译程序是两种不同的翻译程序的方案;
  3. 计算机系统的层次结构
    在这里插入图片描述
  4. 冯诺依曼机的特点:
    在这里插入图片描述
  5. 存储器中的结构
    主存中由 ①存储体 + ②地址&&数据寄存器构成
    1)其中仔细看①中的微小单元称其为存储元:也就是下方左下角的电路结构;在这个结构中,读写操作通过上方的开关控制,开关闭合电路导通,可以进行读写工作,左侧的电容为当前存储的数据,右侧的电路端口为读取入口/写入入口
    2)一般而言8个存储元构成一个存储单元,而N个存储单元又构成一个存储体
    3)MAR:存储器地址寄存器,在去存储体中读取数据or指令之前,我们需要知道这个数据or指令存放在存储器的何处(也就是地址),MAR会根据情况得到这个地址,而后cpu根据这个地址从存储体中取出数据or指令,存放到MDR存储器数据寄存器)中(根据情况可以参见1.计算机工作过程的例子)在这里插入图片描述
  6. 存储器中的寻址问题
    如下图:地址寄存器中存储的地址通过译码器得到实际的电位控制,控制之后得到实际的存储在存储元中的数据注意,寻址的过程
  7. 存储器的容量
    内存的总容量: = 存储单元个数(行数)× 存储字长(一行存储元的个数,每个存储元存储一bit(位)) (单位:bit)
    上面的结果 / 8 就是(单位:字节byte)
    下面我们以32位地址&&32位数据线的计算机
32位计算机- > 地址总线共32条(n = 32-> 通过译码器能够翻译出2^32 种电路选择(最多2^32or 存储单元个数)
对于每一种选择:
设当前存储字长(每行存储元个数 BBB) = 8(这并不代表MDR是8位的,也不代表数据总线长度,数据总线是cpu一次访问数据大小可以与存储字长不同)

如何计算机容量(也就是当前32位地址总线的计算机能够访问的存储范围)?
最大存储单元个数(行数)(2^32)× 存储字长(列数)(32= 2^(32+3)bit = 2^32 byte/ 字节

在这里插入图片描述
8. 进制之间的转化:十进制与二进制之间的转化
整数部分:/ 2取余,依次得到二进制的低位到高位
*小数部分:2取整, 依次得到二进制的高位到低位
下面看图:原理如下
整数部分:每次 / 2(r)都使得末尾的r^0项被取出
在这里插入图片描述
小数部分:每次*2(r)都使得r^-1次项变为整数部分,取整就将这部分取出了;
在这里插入图片描述


第二部分

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值