部分补充图片和内容来自csdn
文章目录
1 操作系统的运行
1.1运行状态
- 行于用户态的进程可以执行的操作和访问的资源都会受到极大的限制
- 而运行在内核态的进程则可以执行任何操作并且在资源的使用上没有限制
1.1.1 用户态
程在执行用户自己的代码时,则称其处于用户运行态(用户态),相当于运行用户自己的代码函数都是用户态,linux系统的底层调用接口不属于用户态。
- 访管指令是用户态调用的
1.1.2 核心态
- 内核态,在操作系统内核运行的状态。
- 核心态运行时的特
- 负责管理进程、存储
用户态什么时候会进入核心态?就是下面提到的系统调用、中断和异常。
1.2 系统调用
- 系统调用概念:从用户进程自身堆栈 切换为系统堆栈
- 执行系统调用后,就会进入核心态
- 时钟管理属于核心态的概念(用于进程切换计时和系统时间)
- 和系统资源有关的都要用到系统调用,如下:
- 存储
- IO传输
- 文件管理
- 系统调用需要在用户态触发trap命令
陷入指令是指用户程序所依靠的指令,用于发起系统调用,请求操作系统提供服务。陷入指令有其中一点特殊在于,其只能在用户态下执行,而不可以在核心态下执行。
trap命令具体流程:
- 用户态程序将一些数据值放在寄存器中, 或者使用参数创建一个堆栈(stack frame), 以此表明需要操作系统提供的服务
- 用户态程序执行陷阱指令
- CPU切换到内核态, 并跳到位于内存指定位置的指令, 这些指令是操作系统的一部分,
- 后面的这些指令会读取程序放入内存的数据参数, 并执行程序请求的服务
- 系统调用完成后, 操作系统会重置CPU为用户态并返回系统调用的结果
1.3 中断和异常
- 外中断 = 中断: 即外设请求或者人为程序干预
- 内中断= 异常: 指令中断(溢出)、 软件故障、硬件故障。
即中断就是正常行为, 异常则可能是故障引发。
1.4 linux延申
https://www.cnblogs.com/bakari/p/5520860.html
2 进程
2.1 概念和特征
-
是资源分配、调度的独立单位
-
进程包含以下内容:
- 程序段(代码段)
- 数据段(存数据)
- PCB进程控制块( 进程存在的唯一标志)
-
进程具有动态性,是一个动态的概念。
-
每个进程具有独立的PCB结构, 资源也独立。
2.2 进程状态机图
2.3 进程状态变化时的具体过程
2.3.1 进程创建
- 创建过程:
- 分配进程;申请PCB
- 分配资源
- 初始化PCB
- 插入到系统的就绪队列
- 父进程创建子进程:
子进程可以继承父进程的所有资源。
父进程退出时,需要杀光子进程。
2.3.2 进程的终止
终止有3种触发情况
- 正常结束——运行完毕
- 异常结束——越界、非法
- 外界干预——直接kill进程
终止的触发过程:
- 根据进程标识符,找到PCB控制块,读取进程状态
- 如果正常执行,则终止进程。 如果有子进程,则杀死子进程
- 归还资源给父进程或者操作系统
- 将PCB从系统PCB队列删除
2.3.3 进程的阻塞
阻塞的过程:
- 根据标识符找到PCB
- 切换PCB状态为阻塞状态
- 把PCB插入到等待队列中
2.3.4 进程唤醒
唤醒的过程:
- 根据标识符从等待队列中找到PCB
- 移出等待队列,修改运行状态为就绪
- 插入到就绪队列
2.3.5 进程切换
- 保存当前处理器上下文信息,更新PCB并移入队列
- 选择另一个进程,更新PCB状态
- 更新内存管理
- 恢复处理器上下文
上面可以看到基本是围绕 PCB、等待队列、就绪队列、进程状态更新这4个概念展开的。
注意调度是一个决策行为, 而切换是一个执行行为, 因此都是先调度,再切换。
2.4 进程的组成
2.4.1 PCB进程控制块
pcb控制块在进程创建后, 就常驻内存中
是进程的唯一标志
PCB控制块包含以下内容:
- 进程描述
包括进程标识符 和 用户标识符 - 进程控制和管理
包括 当前运行状态、 优先级、 入口地址、 外村地址、 进入内存时间、 CPU占用时间、信号量 - 资源分配清单
包括 3个段指针(代码段、数据段、堆栈段), 文件描述符, 键盘/鼠标资源 - 处理器相关信息
寄存器值
程序状态字
PCB控制块的组织方式:
- 链接方式: 各运行状态都有1个队列, 相关状态PCB都会放在链表队列中(因此可以非顺序取出)。
- 索引方式: 建立一个索引表,指向对应pcb, 即用数组去组织
2.5 进程之间的通信
进程的通信有3种方式
-
共享存储方式
由操作系统系统存储空间调用方式。
注意用户进程的空间是互相独立的,不会互相访问。 -
消息传递
直接通信: 发送到对方的消息队列,从消息队列去
间接通信: 发信到中间邮箱(非双方进程), 接收方自己去取。 -
管道通信:
管道: 链接读写进程之间的共享文件
功能为互斥、同步
属于一次性操作, 一旦被读取,数据就被从管道中抛弃
半双工, 只有1方能操作,要么存入要么写。
如果要实现双方互动,则需要2个管道。
2.6 linux延申
3 线程
3.1 概念
- 是基本的CPU执行单元
- 是程序执行流的最小胆小
- 被系统调度和分配的基本单位
3.1.1 目的
- 提高系统性能
- 减小程序并发执行时的时空开销
3.1.2 组成
- 线程id
- 程序计数器PC
- 寄存器集合
- 堆栈
3.1.3 实现方式
一般会有2种线程:
- 用户级线程ULT: 创建发生在用户空间
- 系统级线程KLT: 完成系统内所有线程的管理,提供给用户一定的线程管理接口。
ULT和KLT的映射关系分类(即用户线程能给哪些系统线程管理):
- 多用户线程对应1个系统线程。 即所有线程公用一个线程管理接口
这样如果一旦阻塞就全部阻塞 - 一对一: 每个用户线程单独使用1个系统线程做管理
这样会导致系统线程过多,开销大 - 多对多: n个用户线程对应m个内核线程(n>=m)
这个比较好
linux用的是哪种呢?
3.2 进程和线程的区别
- 从调度上看
线程是CPU调度的最小单位
进程是拥有资源的基本单位 - 切换
同一进程内的线程之间切换,不用引起进程切换
从a进程的线程跳到b进程的线程,需要引起进程切换 - 资源
线程只独立拥有一点点小资源(线程栈、共享虚拟地址空间)
进程则拥有很多 - 通信: 线程可通过读写进程的全局变量来通信,但是进程之间通信则无法依赖变量。
- 开销
线程的系统/时空开销少 - 并发
都支持并发。
4 操作系统调度
4.1 调度的概念
从就绪队列中选一个进程分配给处理机
处理机调度是操作系统进行多道程序运行的核心
4.1.1 三级调度
操作系统中有三种调度:
- 作业调度(外存到内存)
属于一种高级调度
从外存队列中选一批作业进入内存进行执行。
可以理解为有人一次性执行了10000个脚本,这时候的脚本作业是有调度机制的,没法一次性全部加载到内存中执行。 - 内存调度(外存->内存)
就是决定将哪个挂起状态的进程从外存重新调回内存。 但是此时还没开始跑任务,只是从外存切换到内存就绪队列 - 进程调度(内存->CPU)
从内存就绪队列中选择进程, 进行执行。
分别是从高级到低级, 频率从低到高
4.1.2 常见调度方式
剥夺式调度: 按优先级、短时间、时间片进行抢占式调度,会强制暂停某些进程
非剥夺调度: 当某个进程完成,或者出现阻塞,才会重新分配CPU。 不可用于分时和实时系统。
linux属于剥夺还是费伯多?
4.1.3 调度系统设计的评价准则
CPU利用率: CPU运行时间/ 空闲时间加运行时间
系统吞吐量: 单位时间内完成的作业数量
周转时间: 从提交到作业完成的时间, 包括了等待\IO等
等待时间: 在就绪队列中等待的时间之和
响应时间: 从提交请求到系统首次擦还是你哼响应的时间。
4.2 进程调度算法
4.2.1 FCFS先来先服务
- 不可抢占式
- 用于作业调度和进程调度
- 有利于CPU繁忙作业,即基本都在高强度计算,不会有IO操作等。这时候一般不允许抢占。
4.2.2 短作业优先
- 从优先就绪队列中选择 预估运行时间最短的作业
- 可能导致长作业的饥饿
- 但是能让 平均等待时间最短, 但一般不考虑这个
4.2.3 优先级调度算法
- 选择优先级最高的作业运行
- 有2种优先级判断方式:
- 静态优先:进程创建时已经决定了优先级
- 动态优先: 根据CPU占有时间、等待时间,判断他是否属于IO繁忙,并调整优先级
4.2.4 高相应比优先
- 相应比= (等待时间+ 估计运行时间) / 估计运行时间
这样等待的越久,就越有可能出队,避免4.2.2中的长作业饥饿
4.2.5 时间片轮转
- 时间片用完,立刻切换到下一个进程
- 时间片的长短,取决于系统的处理能力、队列中的进程数量、系统响应时间
4.2.6 多级反馈队列调度算法
- 设置多个不同优先级的队列。
- 队列优先级越高, 运行的时间片就越小
- 每当有一个作业跑完1次时间片,就放到下一级队列(相当于降低优先级)
- 当新进程进入,默认放入高优先级队列。对于短作业而言可以立刻响应,而他如果是长作业,则会逐步降低优先级。
- 相当于结合了短作业优先、 优先级调度、 时间片3个算法的优势
4.2.7 linux和windows中的调度算法
- NOOPNOOP算法的全写为No Operation。该算法实现了最最简单的FIFO队列,所有IO请求大致按照先来后到的顺序进行操作
- CFQ(Completely Fair Queuing)
按照IO请求的地址进行排序,而不是按照先来后到的顺序来进行响应
Deadline scheduler
除了CFQ本身具有的IO排序队列之外,DEADLINE额外分别为读IO和写IO提供了FIFO队列
读FIFO队列的最大等待时间为500ms,
写FIFO队列的最大等待时间为5s。FIFO队列内的IO请求优先级要比CFQ队列中的高
而读FIFO队列的优先级又比写FIFO队列的优先级高
4. Anticipatory scheduler
ANTICIPATORY的在DEADLINE的基础上,为每个读IO都设置了6ms的等待时间窗口。如果在这6ms内OS收到了相邻位置的读IO请求,就可以立即满足
可以看到linux基于IO对调度算法进行设计
5 进程的同步
5.1 同步的概念
不同进程之间需要相互制约,或者存在先后顺序,就会用到同步。
- 临界资源: 1次只允许1个进程使用
临界资源访问过程分为四步:
- 进入区: 检查和设置临界区标识
- 临界区: 访问和使用资源
- 退出区: 清楚临界区标志
- 剩余区:?
-
同步: 也称作直接制约关系
为完成某个任务,多个进程需要协调工作次序并等待,即这个制约关系应该是意料之中出现的。 -
互斥: 间接制约关系
因临界资源冲突而引发访问和等待,即不是他想要的制约关系。 -
同步机制准则:
空闲让进: 资源空闲的时候允许进入
忙则等待: 临界区被占用,其他等待
有限等待: 不能一直等待,出现出现饥饿
让权等待: 无法进入临界区时,要放弃CPU挂起一段时间,避免一直占用CPU
5.2 临界区的互斥实现原理
5.2.1 软件实现方式
- 单标志法:就是在临界区前后判断turn标志确认临界区谁可进, 当从临界区粗来,再修改临界区标志为另一个进程可进。
- 双标志法先检查: flag[i]标识低i个进程是否在临界区。 每次检查flag确认是否能进
可能检查时两个人都通过了,就出错。
属于先查,后改 - 双标志法后检查:
即先改后查,可能2个人同时改,引发饥饿 - peterson法:
turn和flag[]一起用。
用turn判断谁可以先进。这样避免2个人同时改。
5.2.2 硬件方式
- 中断屏蔽——禁止一切中断发生??? 这啥意思
- 硬件指令
TestAndSet 原子操作, 读取lock并修改lock为true
swap: 原子交换命令, 交换2个字节内容。
5.3 常见同步方式
5.3.1 信号量
共享资源数目有限时一般使用信号量。
-
P操作——wait 申请和获取资源
先减去信号量,再判断, 如果信号量<0,则进程加入阻塞队列 -
V操作——signal 释放资源
每次操作时,直接增加信号量
此时说明已经有人空闲了一个资源了。
当发现信号量此时<=0, 则从阻塞队列里选一个出队
5.3.2 管程
在内存中而非外村中
是一个共享数据结构
1次只允许1个进管道
可以理解为信号量变成了 只有1的管道
为满时,不可写
为空时,不可读
管程和信号量的区别:
1、信号量可以并发,并发量是取决于s的初始值,而管程则是在任意时刻都是只能有一个。
2、信号量的P操作在操作之前不知道是否会被阻塞,而管程的wait操作则是一定会被阻塞。
3、管程的进程如果执行csignal后,但是没有在这个条件变量上等待的任务的话,则丢弃这个信号。进程在发出信号后会将自己置于紧急队列之中,因为他已经执行了部分代码,所以应该优先于入口队列中的新进入的进程执行。
4、当前进程对一个信号量加1后,会唤醒另一个进程,被唤醒进程程与当前进程并发执行
5.4 同步经典例子
5.4.1 生产者消费者模型
简单的信号量模型
5.4.2 读写者问题
读可以并发读
写的话,必须等读的人都结束了才能写。
5.4.3 哲学家吃饭问题
每个人都先拿自己最左边的筷子,就会吃不上饭
1次拿2双即可,而不是先拿左手,再拿右手。
5.4.4 吸烟者问题
3种原材料的供应问题
每个人只有1种材料。
但是吸烟要3种材料。
供应者用完了再上新的
于是吸烟者每次要直接拿2个,而不能1次只拿1个。
5.5 死锁
5.5.1 原因
- 进程请求资源的顺序不对, 出现交叉申请的情况
- 共同竞争不可剥夺资源
5.5.2 死锁的4个条件和预防
- 互斥——1个资源只能1个进程使用
- 不可剥夺——不允许主动抢占,必须等对方释放了才能用。
- 请求和保持——
请求:自己占用资源的情况下,还可以继续请求其他资源。
保持: 被阻塞的情况下, 自己持有的资源仍然不会释放 - 循环等待: 出现循环等待链
根据上面4个条件,可以给出4个预防措施
- 破坏互斥——让资源可共享使用——无法实现
- 破坏不可剥夺—— 当请求不成功时,会释放自己的资源给别人使用(这样确认不会有人卡着,要么是有人正在全力计算,要么就是阻塞后直接释放,下次再请求)—— 可能引发反复申请,开销大
- 破坏请求和保持—— 一次性申请全部资源,不会申请到一半的情况(资源浪费,比如有时候确实得先用一半,接着再用另一半)
- 破坏循环等待—— 请求资源时,按照顺序请求资源,给资源编号按优先级申请。——太麻烦
5.5.3 死锁检测和解除
该方法不会影响资源分配。 只会定时地去检测,然后看是否解除某些死锁
以进程和资源构建一张DAG图
- 死锁检测定理:
- 找到申请资源充足的进程,消去该进程的所有边
- 继续消除,如果边被全部消完,说明无死锁。如果无法消除完,则存在死锁
- 死锁接触:
资源剥夺——把死锁进程暂时挂起, 以释放资源,等ok后再放回来。
撤销进程法——按优先级,强制kill一些进程
进程回退法—— 把请求资源的操作回退,直到回退到不会发生死锁的DAG
5.5.4 死锁避免算法
这个算法会影响分配过程,即在资源分配前判断以下。
5.5.4.1 系统安全状态(什么时候系统是安全的):
- 存在一个安全序列P1\P2\P3, 理解为3个进程,并给出3个进程此时所需的资源
- 先把剩余资源都给P1,然后P1用完后再给P2, P2用完再给P3
- 按这个顺序下来都没发生资源不够的情况的花,则认为至少是安全的(即至少可通过顺序给资源保证顺利完成剩余任务)
5.5.4.2 银行家算法
假设进程数量有n个
假设资源有r种
定义以下数组
各资源当前可用数量 Avaialbe[r]
各进程最大需求 max[n][r]
各进程当前已占资源 allocation[n][r]
各进程还需要的资源量 need[n][r]
此时的某进程i对各资源的请求量 request[r]
银行家算法描述:
- request必须小于等于need,否则request有错误(即你肯定不能要得过多,都超出需求了)
- 检查request是否比avali大,如果过大,则资源不足
- 如果1和2都检验通过, 则分配资源,修改avalible、allocation、need
- 执行安全性算法, 判断按照最优情况给资源时,是否会出现资源不足
5.若分配不会导致系统进入不安全状态,则分配,否则等待一段时间,再判断
安全性算法描述:
按顺序,每次把进程剩余need的资源都占去,并剪掉avalid, 看下遍历完是否会出现不够用的情况
5.6 linux延申
linux的死锁检测、避免机制Lockdep
http://kernel.meizu.com/linux-dead-lock-detect-lockdep.html
6 内存
6.1 内存管理的概念
-
内存管理指操作系统对内存的划分和动态分配
-
地址空间:
逻辑地址空间: 相对地址, 从0开始编址
物理地址空间: 地址转换的最终地址
程序运行时和
编译: 吧源代码编译成目标模块
链接: 吧目标模块、库函数链接成1个装入模块
链接属于形成进程逻辑地址的过程
装入:
绝对装入: 编译时就确定了装入地址
可重定位装入: 根据内存情况, 把程序装到适当位置
运行时动态装入:运行前才真正把程序装起来(前面2个都是先分配,再装,再运行)
6.2 内存防溢出机制
即怎么防止内存越界
- 设置上下限寄存器:
存放内存中该进程的 上下限地址
每次访问时,判断是否越界
重定位+界地址:
重定位寄存器——存放物理地址的最小地址
界地址寄存器——存放逻辑地址的最大值
先把访问地址(相对地址) 与界地址比较是否越界
再加到重定位寄存器上,作为物理地址
min + x, 且x <max, 这样保证地址在min到min+max之内
6.3 内存分配机制
6.3.1 连续分配内存
连续分配指 为用户程序分配的内存空间一定是连续的
6.3.1.1 单一连续:
内存分为系统区和用户区2个区
每次用户区只能放1个程序, 这样可确保不会越界
6.3.1.2 固定分区分配
用户区分成若干个大小的分区, 每个分区只能装一个作业。
程序如果大了会装不下
程序小了则有内存碎片
6.3.1.3 动态分区分配
程序装入内存时,按照所需大小动态生成1个分区。 有多少碎片空间就给多少
可能会存在碎片, 比如中间的进程结束了, 于是中间就空出来一个内存碎片,而可能因为太小,其他进程帆布进来。
动态分配策略:
- 首次适应: 从上往下找第一个满足的分区——最简单也最好
- 最佳适应: 找一个大小差距最小的分区——最烂,碎片最多
- 最坏适应: 直接找最大的分区转入
- 邻近适应: 从上次查找位置开始找,而不是从第一个碎片位置开始找。——末尾碎片会很多
6.3.2 非连续内存分配
非连续指进程内存可以 分成不同地址存放,不一定全部集中在一起。
6.3.2.1 分页
把内存划分成固定大小的块, 进程以块为单位申请多个不同位置的块作为空间。
- 页表:
每个进程PCB中会有一个页面寄存器PRT, 告知页表的起始位置和起始长度
找到页表后, 页面中会告知你所持有的页号和偏移。
通过 页号 * 块大小 + 偏移, 可知道这段内存的起始位置。
进程每次想通过虚拟地址去定位物理地址时,都需要先去页表中找到虚拟地址对应的页,然后再得到物理地址。
-
快表TBL(Translation Lookaside Buffer )):
为了避免每次斗取页表换算地址, 快表会缓存 虚拟地址->物理地址的直接映射,加快速度
-
多级页表
地址空间超级大, 1页装不下怎么办?
用多级
一级页表指明二级页表的地址
二级页表再去实际地址
这样就可以有多页了。
6.3.2.2 分段
分页的话, 页的长度时固定的, 所以偏移量的最大值是固定的
分段的话不限制偏移量最大值,即可以很长一段。
分段属于二维地址空间, 因为他除了给出逻辑地址,还得给出段长
有利于做动态链接: 程序动态修改
6.3.2.3 段页结合
作业先分成若干段, 再把段分页, 每个段可以找到一个也变
段号S 页号P 页内偏移
6.4 虚拟内存
6.4.1 概念
虚拟地址可以让进程获得比实际内存要大的内存
特征:
- 多次性——作业可分多次装入内存
- 对换性——可在运行时对内存做兑换处理
- 虚拟性——逻辑上可充分扩充容量
要求:
必须使用非连续分配方式——分页、分段、段页
硬件需要支持 页表、中断、地址变换机构
理论依据:
时间局部性—— 指令和数据总是会在一段时间内被连续访问
空间局部性——某单元被访问,那么他附件的单元也很大概率会被访问
6.4.2 请求分页机制
再分页的基础上, 增加了2个功能:
请求调页——当页面不在内存中时,从外村申请调入
页面置换——把暂时不用的内存换出去,给其他需要进来的页腾出空间
页表项:
页号、物理块号
状态位P:是否已经调入内存
访问字段A: 记录访问次数或者访问标记,用于置换策略判断
修改维 M: 记录是否被修改过
外村地址——当页被换出去时,指明这个页在外存的何处
缺页中断机构: 当页面不存在时, 负责产生缺页中断,进行页面置换操作。
缺页只能高端和系统中断不同, 属于指令中的操作,在执行期产生多次
地址变换机构:
1.先检索快表,如果能找到,则直接修改页表项的访问位。
2.快表中没有,则去 再检索内存中的页表,通过状态为P确认是否在内存中
如果不在,则产生缺页中断。
6.4.3 工作集概念
驻留集:指系统给每个进程分配的内存中实际页面集合
但是可能分配了10个, 却只有5个经常在用
工作集: 某时间段内,这个进程访问和使用的页面集合
通过工作集, 系统可以评估这个驻留集是否需要做删减,以及哪些页应该持续保留。
这样可以减少抖动,即减少内外村之间频繁的交换页
6.4.4 页面置换算法
- 最佳置换算法:
选未来最长时间不会被用到的页
这个要基于预测,比较难 - 先进先出FIFO
可能引发bleady异常:
较早调入的页往往是经常被访问的页,这些页在FIFO算法下被反复调入和调出,并且有Belady现象。所谓Belady现象是指:采用FIFO算法时,如果对一个进程未分配它所要求的全部页面,有时就会出现分配的页面数增多但缺页率反而提高的异常现象。
- 最近最久未使用(LRU)
选之前最长时间没访问的, 引入优先队列(最大堆)
需要设置访问时间字段 - 简单时钟clock(最近未使用NRU)
每个页有个标记。
刚换入内存或者被访问时,都会置1
如果需要换页时,步骤如下:
- 扫描围成换的页链表
- 如果标记为1,则改成0,继续往下扫
- 如果位0, 则替换,并让指针指向下一页。
- 改进的clock
把标记为改成 访问位u和修改维m - 1类(A =0, M = 0):表示该页面最近既未被访问,又未被修改,是最佳淘汰页。
- 2类(A =0, M = 1):表示该页面最近未被访问,但已被修改,并不是很好的淘汰页。
- 3类(A =1, M = 0):表示该页面最近已被访问,但未被修改,该页有可能再被访问。
- 4类(A =1, M = 1):表示该页最近已被访问且被修改,该页可能再被访问。
- 先优先找u=0和m=0的页,有就直接替换
- 没有,则找 u = 0 且m=1的页( 没访问的最优先替换), 做替换
- 如果中间遇到U=1的, 则都会置0, 如果m=1的也会置0
- 如果一圈都没有,则下一圈肯定有01或者00的。
6.4.5 页面分配量策略
- 固定分配,局部替换
每个进程分配固定的物理块, 且只能自己的块之间做替换 - 可变分配,全局替换
缺页时,可以从全局队列的页替换 - 可变分配,局部置换
自己替换自己,但是不够的时候可以加块
分配来源:
对换区:频繁切换的区
文件区:补怎么会变动和修改的
6.5 linux中的内存机制
7 文件
7.1 基本概念
7.1.1 文件定义
文件是以计算机硬盘为载体的信息集合
需要提供文件系统进行管理
7.1.2 文件结构
- 数据项:
类似于int、string这种,或者 学号、姓名等值(类似sql表中的某个值内容) - 记录:
一组数据项集合
比如学号和姓名,组成了学生记录(例如sql表中的某一行) - 文件
按逻辑结构可以分为2类:
✳ 无结构文件: 一般都是二进制文件流文件,直接按顺序封装。这里面不包含记录的概念
✳ 有结构文件: 包含记录的概念
顺序文件:按记录中的某个关键字排序过,可二分查找记录行
索引文件: 需要1个索引表,记录每个记录的位置
索引顺序文件:先按关键字索引,在部分擦护照
直接、散列文件:用哈希键值确定记录位置
7.1.3 文件属性
名称
标识符——确认文件唯一性
类型
位置——设备文件指针
大小——文件大小或者文件最大值
保护——访问控制权限
时间、日期
用户标志——哪个用户可用
7.1.4 文件常见操作
- 创建文件
- 写文件——要指定一个写指针确认从哪开始写
- 读文件——读写共用一个指针,避免文件不同步
- 重定位——修改文件位置
- 删除文件——先删除文件目录项,再去清理空间,避免还没清理完有人却正要用
- 截断文件——把文件长度截断
7.1.5 open打开文件的过程
- 从外存把文件属性拷贝进内存,建立文件目录表
- 返回一个文件编号给用户
- 用文件打开计数器,记录有多少个进程正在打开该进程。当没有进程在使用时,回收内存。
- 进程获取文件的以下信息:
- 文件指针
- 文件打开计数
- 文件磁盘位置
- 访问权限
7.2 目录
目录就是文件的外部组织结构
7.2.1 文件的组织结构
7.2.1.1 文件控制块FCB(文件目录项)
- 存放了操作文件所需要的信息
- 文件目录中存放了许多FCB,可以通过FCB按名查取。
- 包含的信息有:
基本信息: 文件名/物理位置/逻辑结构/物理结构
存取控制信息: 访问权限
使用信息:建立时间、修改时间
7.2.1.2 索引节点
-
存放文件描述信息, 但是不包含文件名
-
文件目录中只存放文件名和i节点指针
-
可减少外村磁盘的查找速度
-
节点内容包括:
标识符、类型、权限、存取时间
地址长度、链接计数
节点编号、状态(是否上锁)
访问计数、设备号 -
文件打开时,会把索引节点从磁盘复制进内存,称为内存索引节点
7.2.1.3 文件控制块和索引节点的区别
只需要文件位置信息等时,只需要拿文件控制块来唯一确认一个文件即可。
但是需要真正使用文件时,则需要索引节点里的信息
但如果把索引节点提前加载进内存的话,容易导致开销浪费。所以只有需要的时候,才回去用到索引节点,减少要拷贝的信息。
7.2.2 目录的结构
单级目录
两级目录
多级目录:树形结构,有相对路径和绝对路径
7.2.3 文件层次结构
从用户层从上往下排列
- 用户接口:
若干程序模块组织曾,每一个模块对应一个系统调用 - 文件目录系统: 查找目录项,找到索引节点
存取控制:用户要访问的权限和FCB中的权限做比对
逻辑文件系统: 把要读取的记录转为罗季葵爱好
物理文件系统——把逻辑块号转为实际物理地址
设备管理模块——分配磁盘设备做输入输出
7.2.4 目录的实现原理
线性列表——宠用目录项, 顺序检索。
哈希表——根据文件名得到key值
linux的目录实现原理:
7.3 文件共享
7.3.1 软连接
符号链接
建立一个文件, 文件中存放共享文件的路径
只有文件拥有者才能有指向文件的i节点指针
不会出现指针悬空, 不存在时,软链接会被删除。
7.3.2 硬连接
即一个文件名的指针
各用户文件目录表用i节点指针指向同一个索引节点
当文件被删除,另一个用户就不能访问了。
容易出错。
7.4 文件的实现原理
7.4.1 文件分配
指怎么给文件分配磁盘块
7.4.1.1 连续分配:
每个文件在磁盘上占一组连续的块
目录项记录文件的起始位置和长度
这样能让作业的磁盘寻道数最少
适用于长度固定的文件
7.4.1.2 链接分配:
-
隐式链接:
每个文件有一个链表, 每个节点是一个磁盘块,next指针指向下一个磁盘块 -
显式链接:
用一个二维数组表替代next指针, [0]是盘块,[1]是下一个盘块
这个表就是文件分配表FAT
文件的第一个盘号也就是链表头 会放入FCB的物理地址中
7.4.1.3 索引分配
为了防止这个表占据太大空间或者不够用
给每个文件有一个索引表,按顺序给出盘块
同时可以多级索引, 即可以给出磁盘中下一级索引表的盘块号。然后再去把盘中的二级索引表导入到内存中使用
7.4.2 存储空间管理
即把哪些磁盘块分给文件呢?
- 文件卷、逻辑卷
以下是空闲块分配方法:
- 空闲表法—— 从空闲盘块表中选取, 释放的时候要做前后空闲区合并
- 空闲链块法:把空闲块组成链表
- 位视图法: 用010101确定是否空闲,可减少空闲块信息的大小
- 成组链接法:
把n个空闲区分组,存入不同的空闲盘块栈中。并且有各种指针指向其他组的空闲盘块
当某组内的空闲块变化时, 可以修改上一个块号栈的指针指向位置。
7.5 磁盘
7.5.1 常见磁盘概念
7.5.1.1 结构
- 磁道:可以立即为磁盘上的圈圈
- 扇区
1个磁道会划分为多个扇区
扇区即块(盘块,即上面文件里提到的盘块)
存储能力受限于内道的最大记录密度 - 磁盘地址
地址=(柱面,盘面,扇区号)
以簇为单位作为各文件的磁盘分配
7.5.1.2 分类
根据磁头动作,可以分为 固定头磁盘 / 活动头磁盘
根据磁盘是否固定,可分为 固定盘磁盘 / 可换盘磁盘
7.5.1.3 存取时间
这个时间是衡量磁盘设计好坏的重要因素
-
寻找时间Ts (先找磁道,即找在圈里的第几层)
指找到磁道的时间
Ts = 0.2ms * 跨越磁道数 + st(磁臂启动时间,即扫描的那个东西开始动的时间) -
延迟时间(磁道找完,定位扇区,即在圈上的哪个方位)
定位置某一扇区的时间
转一圈的时间/2作为延迟时间, 即对转一圈的时间求个平均(毕竟实际速度和扫描算法有关)
Tr = 1/2r -
传输时间(找到位置了,接着开始读数据)
就是读写数据时间
扇区占一圈的几分之几 * 转一圈的描述
其实就是磁臂按正常速度扫完这个内容要花的时间。
7.5.2 调度算法
其实就是收到多个盘块的请求,选择扫描磁道的方法
- FCFS 先来先服务
按盘块请求里的顺序来扫描
适合那种聚在一起的盘块 - SSTF 最短时间优先
一直找最近的磁道
如果频繁收到比较近的磁道,可能造成其他磁道饥饿,无法被访问 - SCAN 扫描、电梯算法
从上倒下,再从下到上,再从上到下,扫到请求里的盘块就算检测到。
对两端的节点不公平,扫描频率会比中间的低 - C-SCAN 循环扫描算法
方向固定,每次都是从下往上, 当到达顶部时, 直接快速转回到底部,然后再继续从下往上(快速转回的过程不会响应任何盘块请求)
7.5.3 扇面划分
- 交替编号
1 3 2 4这样编号
因为读出数据需要时间,这时候如果让磁道继续转动,可能已经移动到后面第2个扇区了。
但数据又是希望能连续读的,所以交替放,保证读完后,不用再移动,直接用这个区的 - 错位命名
第一层磁道的扇区是1 3 2 4
第二层磁道的扇区是4 1 3 2
第三层的磁道是 2 4 1 3
可以看到每一层磁道的扇区,整体都右移了一位
这是为了减少切换磁道时,重新扫圈的时间
7.5.4 磁盘管理
7.5.4.1 初始化
- 低级格式化
初始化扇区号和纠错代码(ECC),ECC用于检验磁盘正确性
物理分区 - 逻辑格式化
初始化磁盘数据结构
分为C\D\E盘, 对linux就是那些opt分区之类的
7.5.4.2 引导块
磁盘的启动程序会放在磁盘的引导块上
7.5.4.3 坏块
对于损坏的磁盘块, 他会检测并避免再使用这些坏块。
7.6 IO管理
7.6.1 IO控制方式
- 程序直接控制
- 中断驱动方式
- DMA方式
特点:
以块位单位
数据直接进入内存
开始和结束需要CPU干预,无法通过外设自己完成
DMA会设置4个寄存器:
命令/状态寄存器CR——用于接收CPU发来的控制命令,存储当前状态
内存地址MAR——存放内存地址
数据寄存器DR——暂存数据
数据计数器DC——用于传输计数
工作过程:
- CPU收到程序发来的DMA请求——发出控制命令
- 启动DMA->DMA存取->结束
- 发送中断信号——CPU中断,通知调用程序DMA结束
DMA和中断的区别
- DMA:是一种无须CPU的参与就可以让外设与系统内存之间进行双向数据传输的硬件机制,使用DMA可以使系统CPU从实际的I/O数据传输过程中摆脱出来,从而大大提高系统的吞吐率.
- 中断:是指CPU在执行程序的过程中,出现了某些突发事件时CPU必须暂停执行当前的程序,转去处理突发事件,处理完毕后CPU又返回源程序被中断的位置并继续执行。
所以中断和DMA的区别就是DMA不需CPU参与,而中断是需要CPU参与的。
- 通道控制方式
是DMA的升级版
指令单一,和CPU共享内存
是一种硬件、特殊的处理器,无内存,指令存在主机内存中。
传输数据的带线啊哦、内存存放位置,通道可以自主决定!
即比DMA更高级了
7.6.2 IO层次性结构
从上往下介绍
- 用户层IO软件
- 由用户提供的库函数
- 需要一系列系统调用
- 设备独立性软件
曹旭哦提供相关调用
- 功能: 指向设备的共有操作,向用户层提供统一的IO接口(例如unix底层和IO相关的c函数)
- 支持设备独立,程序和设备可独立切换,不强依赖。
- 设备驱动程序
商家提供
- 实行系统对设备的具体操作
- 具体的硬件设备会配置1个驱动
- 中断处理程序
- 硬件设备
7.6.3 设备控制器(适配器)
- 通过寄存器与CPU进行通信
具有内存映像,占有CPU内存
寄存器独立编址,采用IO专用地址 - 功能
接收、识别CPU的命令
实现数据交换
提供设备状态、提供地址识别 - 组成部分
与CPU的接口——数据、地址、命令线
与设备的接口——1个接口一个设备
IO控制器(地址、命令译码)
7.6.4 IO核心子系统
- 是一个进行设备控制的子系统
- 内核无需做太多的IO设备管理
- 子系统实现IO调度队列,改善IO的响应时间,比如磁盘调度
7.6.4.1 缓冲区
磁盘高速缓存: 在内存中开辟单独空间,利用未利用的页做缓冲池
缓冲区的目的:
减少CPU\IO速度的不匹配
减少CPU中断
提高IO和CPU的并行性
缓冲分类
- 单缓冲:
IO和CPU之间只有1个缓冲区。 处理数据的时候,可以提前读进来1份。 - 双缓冲
2个缓冲区。
缓冲1满了之后,装2. CPU可以接连取
缓冲区1满了之后, - 循环缓冲
有一个缓冲循环队列, 有2个指针
in指针指向空的缓冲,用于进行外部数据的IO输入。
out指针指向满的缓冲,用于把数据读进系统中 - 缓冲池
要用到的时候,就从池中取一个缓冲区。 用完就归还
7.6.4.2 设备分配和挥手机制
设备分配分类
- 独占设备——只允许单进程
- 分时设备——分时交替IO
- spooling设备——虚拟设备
设备分配表
DCT设备控制表—— 1个控制表代表一个设备。
COCT控制器控制表——与内存交换数据。请求通道服务——指向CHCT
CHCT通道控制表
SDT系统设备表——记录系统中已连接的设备情况
7.6.4.3 spooling技术(虚拟化设备)
- SPOOLing 技术是用于将 I\O 设备进行虚拟化的技术,
- 它是专门用于欺骗进程的。
- 就拿打印机举栗吧,我就买了一台打印机,但此时我打开了 word 和 pdf,想要打印 word 和 pdf 中的内容;此时计算机中有2个进程,word 进程和 pdf 进程,这两个进程都认为自己拥有一个打印机,那么是否此时我作为计算机的主人就拥有2台打印机了呢?当然不是啊,我又不是睁眼瞎,我就看到了一台打印机啊
- 这就是通过虚拟化技术欺骗了2个无知的进程。具体的实现道理我都懂,那么怎么实现欺骗进程的目的呢(也就是怎么实现SPOOLing技术呢)?
- SPOOLing 技术首先需要提供统一的调用接口,每一个进程都可以调用该接口,这样在进程看了自己是拥有该设备的。需要将磁盘设备和内存作为缓冲区,磁盘设备上的缓冲区称作井,而内存上的缓存区被称作缓冲区。需要一个专门的输入输出进程来实现对 I/O 设备的读写数据。
以向 I/O 设备写数据为例子,做出概念图,如下所示:
首先某一个进程(例如 word)调用了统一的接口,然后进入内核。内核例程负责将 word 想要打印的内容做成一个打印申请表,将这个申请表放入打印输出队列中(这个队列在输出井中)。然后由输出进程从打印队列中取打印申请表,根据表格内容将用户数据从磁盘中取出放入内存输出缓冲区,然后再输出到 I/O 设备中。输出进程会不断的查看打印输出队列,直到队列为空,则输出进程被阻塞。
即对于进程来说,看起来都已经输出或者输入了,实际上看还没有响应,有延迟,物理上共用1个设备。逻辑上是2个设备