操作系统
【考后感悟】本次考试考察了:操作系统的4大特征、线程和进程的区别、页表与页的基本地址变换机构、磁盘调度算法、银行家算法、调度算法(短作业优先、时间片轮转)、Linux的一些基本知识、shell读程序题以及PV操作编程。知识点基本涵盖到了,线程一块的知识有所缺漏,shell编程那块考试应该考不到那么多。
✍Created by Sikevin in UESTC
✍博客主页:电子科大不知名程序员
知识点分布(仅供参考):
第一章:引论
操作系统是一个进行软硬件资源管理的软件
OS看做是用户与计算机硬件系统之间的接口
OS作为计算机系统资源的管理者
OS实现了对计算机资源的抽象
1、操作系统发展
单道批处理系统
系统对作业的处理都是成批进行的,且在内存中始终仅存一道作业运行,运行结束或出错,才自动调另一道作业运行,故称为单道批处理系统。
多道批处理系统
在内存中存放多道作业运行,运行结束或出错,自动调度内存中的另一道作业运行。
多道程序带来的好处:
1、提高CPU的利用率。
2、提高内存和I/O设备利用率。
3、增加系统吞吐率。
分时操作系统
分时系统是指,在一台主机上连接了多个带有显示器和键盘的终端,同时允许多个用户通过自己的终端,以交互方式使用计算机,共享主机中的资源。
分时系统能很好地将一台计算机提供给多个用户同时使用,提高计算机的利用率。还可以满足用户对人机交互的需求。
特点:
多路性:一个主机与多个终端相连;
交互性:以对话的方式为用户服务;
独占性:每个终端用户仿佛拥有一台虚拟机。
及时性:用户的请求能在很短的时间内获得响应。
实时操作系统
所谓实时系统:是计算机及时响应外部事件的请求,在规定的时间内完成对该事件的处理,并控制所有实时设备和实时任务协调一致的运行。
微机操作系统的发展:
单用户单任务操作系统------>单用户多任务操作系统
------>多用户多任务操作系统(UNIX OS, Windows)
允许多个用户通过各自的终端使用同一台机器,共享主机系统中的各种资源,而每个用户程序又可进一步分为几个任务,使它们能并发执行,从而可进一步提高资源利用率和系统吞吐量。
2、操作系统的基本特性
现代OS的四个基本特征:
1、并发性
2、共享性
3、虚拟性
4、异步性
并发是最重要的特征,其它特征都以并发为前提。
并发性和共享性是现代操作系统的两个最基本的特征
并发
并行性:是指两个或多个事件在同一时刻发生。
并发性:是指两个或多个事件在同一时间间隔内发生。
在多道程序环境下,并发性是指在一段时间内,宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却仅能有一道程序执行,故微观上这些程序只能是分时地交替执行。
共享
是指系统中的资源可供内存中多个并发执行的进程共同使用
1、互斥共享方式:
- 把在一段时间内只允许一个进程访问的资源,称为临界资源。
- 系统中的临界资源可以提供给多个进程使用,但一段时间内仅允许一个进程使用,称为互斥共享方式。
2、同时访问方式:
- 一段时间内,多个进程可以同时使用这个资源。
- 从微观上看,多个进程交替互斥地使用系统中的某个资源。例如磁盘。
虚拟
是指通过某种技术把一个物理实体变为(映射为)若干个逻辑上的对应物,用于实现虚拟的技术称为虚拟技术
异步
1、执行结果不确定,程序不可再现。
2、异步性,多道程序环境下程序(进程)以异步的方式执行,每道程序在何时执行、各自执行的顺序、完成时间都是不确定的,也是不可预知的。
3、操作系统的主要功能
操作系统应具有五方面的功能:
1、处理机管理(CPU)
2、存储器管理
3、设备管理
4、文件管理
5、操作系统与用户之间的接口
4、操作系统的体系结构
第一代的OS是无结构的;
第二代OS采用了模块式结构;
第三代是层次式结构
现代OS结构是微内核结构;
①无结构操作系统
各过程之间可以相互调用,在操作系统内部不存在任何结构,因此,有人把它称为整体系统结构。
②模块化OS结构
使用分块结构的系统包含若干module(模块);其中,每一块实现一组基本概念以及与其相关的基本属性。
块与块之间的相互关系:所有各块的实现均可以任意引用其它各块所提供的概念及属性。
优点:
①提高了OS设计的正确性、可理解性和可维护性。
②增强了0S的可适应性。
③加速了OS的开发过程。
缺点:
① 对模块的划分及对接口的规定要精确描述很困难。
②从功能观点来划分模块时,未能将共享资源和独占资源加以区别;
③分层式OS结构
使用分层系统结构包含若干layer(层);其中,每一层实现一组基本概念以及与其相关的基本属性。
层与层之间的相互关系:
- 所有各层的实现不依赖其以上各层所提供的概念及其属性,只依赖其直接下层所提供的概念及属性;
- 每一层均对其上各层隐藏其下各层的存在。
④微内核OS结构
所谓微内核技术,是指精心设计的、能实现现代OS核心功能的小型内核,它与一般的OS(程序)不同, 它更小更精炼,它不仅运行在核心态,而且开机后常驻内存, 它不会因内存紧张而被换出内存。
第二章:进程的描述与控制
1、前驱图和程序执行
前趋图是一个有向无循环图,用于描述进程之间执行的前后关系。图中的每个结点可用于描述一个程序段或进程,乃至一条语句;结点间的有向边则用于表示两个结点之间存在前趋关系→
(Pi, Pj)∈→
,可写成Pi→Pj
,称Pi是Pj的直接前趋,而称Pj是Pi的直接后继。
在前趋图中,把没有前趋的结点称为初始结点(Initial Node),把没有后继的结点称为终止结点(Final Node)。
每个结点还具有一个重量(Weight),用于表示该结点所含有的程序量或结点的执行时间。
前趋图中必须不存在循环!
程序顺序执行
特征:
(1)顺序性:处理机的操作严格按照程序所规定的顺序执行。
(2)封闭性:程序运行时独占全机资源,程序一旦开始执行,其执行结果不受外界因素影响。
(3)可再现性:只要程序执行时的环境和初始条件相同,都将获得相同的结果。
(不论它是从头到尾不停顿地执行,还是“停停走走”地执行)
程序的并发执行
特征:
(1)间断性:程序并发执行是,由于共享系统资源,这些程序形成相互制约的关系,具有“执行-暂停-执行”特征。
(2))失去封闭性 :程序并发执行时,多个程序共享系统资源,因而这些资源的状态将由多个程序来改变,从而导致程序的运行失去封闭性。(3) 不可再现性:程序并发执行,由于失去了封闭性,从而也失去了可再现性。
2、进程的描述
进程是计算机中正在运行的程序实例,它是系统进行资源分配和调度的一个独立单位。
进程的结构:程序段+数据段+PCB(对进程的描述)
所谓创建进程,实质上是创建进程实体中的PCB;而撤消进程,实质上是撤消进程的PCB。
PCB是进程存在的唯一标识
进程的基本状态及转换
就绪状态:进程在CPU运行队列中
执行状态:进程已获得CPU,其程序正在执行
阻塞状态:当进制等待某种非CPU类资源时,该资源还未就绪,进程PCB在该资源等待队列中,即为阻塞状态
转换关系如下:
挂起状态:当内存不足时,操作系统会将短期内不会调度执行的进程的代码和数据从内存中替换出去
3、进程管理中的数据结构
PCB通常包含以下信息:
- 进程标识符(Process Identifier,PID):唯一标识一个进程的数字或字符。
- 程序计数器(Program Counter,PC):指向当前正在执行的指令的地址。
- 寄存器状态(Register State):保存了进程在执行过程中寄存器的内容,包括通用寄存器、堆栈指针、程序状态字等。
- 进程状态(Process State):表示进程当前所处的状态,如运行、就绪、阻塞等。
- 进程优先级(Process Priority):用于确定进程调度的优先级顺序。
- 程序和数据的内存管理信息:包括进程的内存起始地址、内存大小、页表等信息。
- 文件描述符表(File Descriptor Table):保存了进程打开的文件和设备的相关信息。
- 资源使用情况(Resource Usage):包括进程使用的CPU时间、内存、I/O设备等资源的情况。
- 进程间通信信息(Interprocess Communication,IPC):用于进程之间进行通信和同步的相关信息,如消息队列、信号量等。
- 父进程和子进程关系的信息:包括父进程的PID、子进程的PID等。
- 进程调度信息(Scheduling Information):包括进程的调度策略、调度队列等信息。
- 进程完成状态(Process Termination Status):保存了进程完成执行后的状态信息,如退出码。
4、进程控制块PCB
进程控制块的主要内容:
进程标识符信息
处理器状态信息
进程调度信息
进程控制信息
①进程标识符pid
每一个进程在系统中,都会存在一个唯一的标识符,用来标识唯一的一个进程,也叫做pid(
process id
)。
进程id:PID 父进程id:PPID
②处理器状态信息
③进程调度信息
a.进程状态信息:
Linux操作系统具体的状态,状态在LInux内核源代码中定义如下:
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */ //运行状态
"S (sleeping)", /* 1 */ //阻塞状态(浅度睡眠)
"D (disk sleep)", /* 2 */ //一种阻塞状态(深度睡眠)
"T (stopped)", /* 4 */ //暂停状态
"t (tracing stop)", /* 8 */ //暂停状态
"X (dead)", /* 16 */ //死亡状态
"Z (zombie)", /* 32 */ //僵尸状态
};
在Linux内核当中,进程状态可以理解为就是一个整数
b.进程优先级
c.进程调度所需的其它信息
d.事件
④进程控制信息
5、Linux下的PCB
Linux操作系统下的PCB是: task_struct
struct task_struct 内核结构体 -> 内核对象task_struct —> 将该结构与对应代码和数据关联起来
进程的是硬盘中的程序加载到内存中,每个加载入内存的可执行程序,即对应一个描述它们属性的struct task_struct
,如图:
管理的本质是先描述再组织,通过task_struct结构体对进程信息进行描述,在通过某种数据结构将PCB结构体组织起来,有以下几种组织方式:
1、链接方式
把具有同一状态的PCB,用其中的链接字链接成一个队列。
2、索引方式
相同状态进程的PCB组织在一张表格中,系统根据所有进程的状态建立几张索引表,系统分别记载各PCB表格的起始地址
3、多级队列
按照进程状态不同分别组织PCB队列,同一状态进程PCB按照优先级高低(或者到达的先后顺序)用链接指针连接起来。
6、进程控制
进程控制一般是由OS内核中的一组原语来实现的。
原语是由若干条指令组成的,用于完成一定功能的一个过程。是“原子操作”,即一个操作中的所有动作要么全做,要么全不做,换言之,是一个不可分割的基本单位,在执行过程中不允许被中断。
①进程图
②进程创建
申请空白PCB
为新进程分配资源
初始化进程控制块PCB
将新进程插入就绪队列
Linux下使用fork函数创建进程:
fork函数能从已存在进程中创建一个新的进程;新进程为子进程,而原进程为父进程
//头文件
#include <unistd.h>
pid_t fork();
返回值:父进程返回子进程pid,子进程返回0
③进程终止
进程为何会退出呢?一定是发生了以下的某种情况:
代码运行完毕,结果正确;
代码运行完毕,结果不正确;
代码异常终止;
正常退出方法:
1、从main函数返回
2、调用exit
3、_exitz(系统调用接口)
异常退出:
CTRL+C 信号终止
④进程同步
采用多道程序设计技术的操作系统,允许多个进程同时驻留内存并发执行。
如何协调多个进程对系统资源,如内存空间、外部设备等的竞争和共享?
如何解决多个进程因为竞争资源而出现执行结果异常,甚至导致系统不稳定、失效等问题?
例如,多个进程同时申请文件打印,如何有效分配打印机?
进程同步机制的主要任务,是对多个相关进程在执行次序上进行协调,使并发执行的诸进程之间能按照一定的规则(或时序)共享系统资源,并能很好的相互合作,从而使程序的执行具有可再现性。
7、临界控制
进程竞争资源首先必须解决“互斥”问题。某些资源必须互斥使用,如打印机、共享变量、表格、文件等。
这类资源又称为临界资源,访问临界资源的那段代码称为临界区。
任何时刻,只允许一个进程进入临界区,以此实现进程对临界资源的互斥访问。
①临界区使用原则
(1) 空闲让进。如果临界区空闲,则只要有进程申请就立即让其进入。
(2) 忙则等待。每次仅允许一个进程处于临界区。
(3) 有限等待。进程只能在临界区内逗留有限时间,不得使其他进程在临界区外无限期等待。
(4) 让权等待。当进程不能进入自己的临界区时,应立即释放处理机,以免进程陷入“忙等”状态。
②信号量机制
信号量方法能实现进程互斥与同步
信号量按照功能来分:互斥信号量和资源信号量。
互斥信号量:用于申请或释放资源的使用权,常初始化为1。
资源信号量:用于申请或归还资源,可以初始化为大于1的正整数,表示系统中某类资源的可用个数
一对原语:
wait(S)
和signal(S)
,简称为P操作和V操作
整型信号量
缺点:会导致忙等,不满足让权等待
记录型信号量
S.value的初值表示系统中某种资源的数目。
对信号量s的一次Р操作意味着进程请求一个单位的该类资源,因此需要执行S.value–,表示资源数减1,当S.value <0时表示该类资源已分配完毕,因此进程应调用block原语进行自我阻塞(当前运行的进程从运行态→阻塞态),主动放弃处理机,并插入该类资源的等待队列s.L中。可见,该机制遵循了“让权等待”原则,不会出现“忙等”现象。
对信号量s的一次V操作意味着进程释放一个单位的该类资源,因此需要执行S.value++,表示资源数加1,若加1后仍是S.value <=o,表示依然有进程在等待该类资源,因此应调用wakeup原语唤醒等待队列中的第一个进程(被唤醒进程从阻塞态→就绪态)。
8、信号量机制实现进程互斥、同步、前驱关系
①互斥
设置互斥信号量mutex,初值为1
进入临界区:P(mutex),申请资源;退出临界区:V(mutex),释放资源(P、V操作是成对出现的)
对不同的临界资源需要设置不同的互斥信号量
实现示例:
②同步
要让并发进程按照要求有序地推进,即必须保证一前一后执行两个操作
实现方式:
设置同步信号量S,初始值为0,前V后P
代码1和2必须在代码4前执行,可以把代码1、2视为一种资源,当代码1和2执行完后,执行V操作释放资源,代码4才能使用P操作申请这个资源
③前驱
9、经典问题
①生产者消费者问题
【进程同步互斥问题】
设置同步信号量:初始值为资源数量;设置互斥信号量:初始值为1
semaphore mutex = 1; //互斥信号量,实现对缓冲区的互斥访问
semaphore empty = n; //同步信号量,表示空闲缓冲区的数量
semaphore full = 0; //同步信号量,表示产品的数量,也即非空缓冲区的数量
实现互斥的P操作一定要在实现同步的P操作之后
②多生产者-多消费者问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CyH8zup1-1687269743623)(C:\Users\kevin\AppData\Roaming\Typora\typora-user-images\image-20230615201946084.png)]
semaphore mutex = 1; //实现互斥访问盘子(缓冲区)
semaphore apple = 0; //盘子中有几个苹果
semaphore orange = 0; //盘子中有几个橘子
semaphore plate = 1; //盘子中还可以放多少个水果
③吸烟者问题
④读者写者问题
⑤哲学家进餐问题
防止死锁:
方案一:最多允许四个哲学家同时进餐
方案二:要求奇数哲学家先拿左边筷子,然后再拿右边筷子,偶数则相反
方案三:仅当一个哲学家左右筷子都可用时才允许他抓起筷子
第三章:处理机调度与死锁
1、处理机调度的基本概念
在多道程序系统中,一个作业被提交后必须经过处理机调度后,方能获得处理机执行。
2、高级、中级、低级调度
①高级调度(作业调度): 高级调度是指决定从作业队列中选择哪些作业调入内存执行的过程。(算法)
②中级调度(内存调度): 中级调度是指在内存中选择哪些进程处于运行状态、哪些进程处于暂停或挂起状态的过程。它决定了在内存中有多少进程能够同时执行,以及如何动态地管理内存中的进程。(重新调入内存)
③低级调度(进程调度): 低级调度是指在就绪队列中选择哪个进程将获得处理器的执行时间片,进而进行实际的运算和执行。(运行频率最高)
3、调度方式
①非抢占性
在采用这种调度方式时,一旦把处理机分配给某进程后,不管它要运行多长时间,都一直让它运行下去,决不会因为时钟中断等原因而抢占正在运行进程的处理机,也不允许其它进程抢占已经分配给它的处理机。
直至该进程完成,自愿释放处理机,或发生某事件而被阻塞时,才再把处理机分配给其他进程
严格的实时系统中,不宜采用这种调度方式
②抢占性
这种调度方式允许调度程序根据某种原则去暂停某个正在执行的进程,将已分配给该进程的处理机重新分配给另一进程。可以防止一个长进程长时间占用处理机,能为大多数进程提供更公平的服务,特别是能满足对响应时间有着较严格要求的实时任务的需求。
但抢占方式比非抢占方式调度所需付出的开销较大
4、调度算法的评价指标
①CPU利用率
②系统吞吐量
③周转时间
周转时间指:从作业提交给系统开始,到作业完成为止的这段时间的间隔
带权周转时间:
带权周转时间一定是大于等于1的,带权周转时间越小越好
④等待时间
等待时间是指进程/作业等待处理机状态时间之和
⑤响应时间
从用户提交请求到首次产生响应所用的时间
5、作业调度算法
①先来先服务调度算法(FCFS)
缺陷:对待短作业(进程)不公平,如果他们排在队列后面,则其等待时间远大于其执行时间
②短进程/作业优先调度算法
缺点:
对长作业不利
不能保证紧迫性作业(进程)会被及时处理
进程执行时间是估计的,致使该算法不一定能真正做到短作业优先调度
③时间片轮转调度法
系统将所有的就绪进程按先来先服务的原则,排成一个队列,轮流让各个进程执行一个时间片,当执行的时间片用完时,由一个计时器发出时钟中断请求,操作系统便根据此信号来停止该进程的执行,并将它送往就绪队列的末尾
时间片过大:退化为先来先服务的算法;时间片过小:进程切换频繁,系统会花大量时间来处理进程切换
常用于分时系统及事务处理系统
④优先级调度算法
该算法是把处理机分配给就绪队列中优先级最高的进程。(抢占和非抢占都有)
通常系统进程优先级高于用户进程,前台进程优先级高于后台进程,操作系统更偏好IO型进程(让IO设备尽早的投入工作)
先来先服务算法的优点是公平,短作业优先算法是能尽快的完成短作业,平均等待和周转时间很优秀,时间片轮转算法让各个进程得到及时的响应,优先级调度算法可以灵活地调整各个进程被服务的机会;
对这些算法折中权衡:多级反馈队列调度算法
⑤多级反馈队列调度算法
它是一种抢占式算法
引例:
设置多级就绪队列,各级队列优先级从高到低,时间片由小到大
新进程到达先进入第一级队列,按先到先服务原则排队等待被分配时间片。若用完时间片进程还未结束,则进入下一级队列队尾,如果此时已经在最下级的队列,则重新放回最下级队列的队尾
只有第k级队列为空时,才会为k+1级队头分配时间片
被抢占处理机的进程重新放回原队列队尾(而不是进入下一级队列中)
⑥高相应比优先算法
它是一个非抢占式算法
6、进程切换
当一个进程执行完(或不能继续执行),则换另一个进程占处理机执行,称为进程切换
在进程切换时,要保护执行现场。执行现场称为进程的上下文。包括CPU主要寄存器,如SP,PC,通用寄存器。
步骤:
保存进程上下文环境
更新当前进程PCB中内容,将其更改为就绪/阻塞状态
将当前进程PCB移入对应队列(阻塞/就绪队列)
改变需要投入运行进程的PCB
恢复投入运行的进程的上下文环境
7、死锁概述
所谓死锁(Deadlock),是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
①产生死锁的四个必要条件
(1)互斥条件:指进程对所分配到的资源进行排它性使用。
(2)请求和保持条件:指进程已经保持了至少一个资源,但又提出了新的资源请求 。
(3)不可抢占条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
(4)环路等待条件:指在发生死锁时,必然存在一个进程:资源的环形链 。
②预防死锁
预防死锁的方法是使四个必要条件中的第2、3、4条件之一不能成立,来避免发生死锁。至于必要条件1,因为它是由设备的固有属性所决定的,不仅不能改变,还应加以保证。
方法1:摒弃“请求和保持”条件
系统规定所有进程在开始运行之前,都必须一次性地申请其在整个运行过程所需的全部资源。
方法2:破坏“不可抢占”条件
当一个已经保持了某些资源的进程,再提出新的资源请求而不能立即得到满足时,必须释放它已经保持了的所有资源。待以后需要时再重新申请。从而破坏了“不剥夺”条件。
缺点:这种预防死锁的方法,实现起来比较复杂且要付出很大代价。因为一个资源在使用一段时间后,它的被迫释放可能会造成前段工作的失效。还会使进程前后两次运行的信息不连续。
方法3:破坏“环路等待”条件
这种方法中规定,系统将所有资源按类型进行线性排队,并赋予不同的序号。所有进程对资源的请求必须严格按照资源序号递增的次序提出。
存在严重问题:
第一,为系统中各类资源分配的序号必须相对稳定,这就限制了新设备类型的增加;
第二,会经常发生作业使用的顺序与系统规定顺序不同的情况,造成资源浪费;
第三,增加了程序设计难度。
③避免死锁
避免死锁与预防死锁的方法不同,不是实现采取某种限制措施,破坏产生死锁的必要条件,而是在资源动态分配过程中,防止系统进入不安全状态,以避免发生死锁。
安全状态
系统处于安全状态,就一定不会发生死锁;处于不安全状态,不一定发生死锁
银行家算法
包含以下数据:
可利用资源向量Available
最大需求矩阵Max
已分配矩阵Allocation
需求矩阵Need
Max=Allocation+Need
方案:用可利用资源与Need比较,若Available>=Need,则分配给它,等到用完资源后回收资源,更新Available,再继续比较,找到其它能分配的Need
④死锁的检测与解除
简化资源分配图
这种方式用于检验是否发生死锁
首先找到能成功请求资源的进程(图中的P1)(看分配边),那么代表该进程能顺利执行,当它执行完后会归还资源,在图中消除P1的所有边,此时P2能成功请求资源,再次消除P2的所有边:
可完全简化,代表没有死锁发生!P1–>P2:安全序列
【总结】:依次消除与不阻塞进程相连的边,直到无边可消
解除死锁
第四章:存储器管理
1、存储器的层次结构
2、程序的装入和链接
用户的源程序—>内存中的可执行程序:
编译:由编译程序(Compiler)将用户源代码编译成若个目标模块。
链接:由链接程序(Linker)将编译后形成的一组目标模块,以及它们所需要的库函数链接在一起,形成一个完整的装入模块。
装入:由装入程序(Loader)将装入模块装入内存。
①程序的装入
装入到内存中
绝对装入
如果知道程序将驻留在内存的什么位置,那么,编译程序将产生绝对地址的目标代码。绝对装入程序按照装入模块中的地址,将程序和数据装入内存。
装入模块被装入内存后,由于程序中逻辑地址与实际内存地址完全相同,故不需对程序和数据的地址进行修改。
可重定位装入
由装入程序将装入模块装入内存后,装入模块中程序所访问的所有逻辑地址与实际装入内存的物理地址不同 ,必须进行变换。
把在装入时对目标程序中指令和数据的变换过程称为重定位。
因为地址变换是在装入时一次完成的,以后不再改变,故称为静态重定位。采用静态重定位方法将程序装入内存,称为可重定位装入方式。
动态运行时装入方式
装入程序将目标模块装入内存后,并不立即把装入模块中的相对地址转换为绝对地址,而是把这种地址转换推迟到程序执行时进行,在硬件地址变换机构的支持下,随着对每条指令或数据的访问自动进行地址变换,故称为动态重定位。
④程序的链接
静态链接
静态链接是指在编译链接时,把库文件的代码全部加入到可执行文件中;因此生成的文件比较大,但在运行时也就不再需要库文件了一定程度上提高了执行速度。
动态链接
动态链接与静态相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库文件
运行时动态链接不仅可加快程序的装入过程,而且可节省大量的内存空间。该方法是目前最常使用的链接方式。
3、连续分配方式
连续分配方式,是指为一个用户程序分配一个连续的内存空间。
①单一连续分配
整个内存的用户区中只驻留一个用户的一个程序
会产生外部碎片
②固定分区分配
将内存用户空间划分为若干个固定大小的区域,每个区域称为一个分区(region),在**每个分区中只装入一道作业 **,从而支持多道程序并发设计。
分区划分有以下两种方式:
采用分区描述表记录每个分区的状态信息:
当有作业要装入内存时,内存分配程序检索分区描述表,从中找出尚未使用的最接近大小的分区分配给该作业,然后修改分区的状态;如果找不到合适的分区就拒绝为该作业分配内存。
当程序运行完成时,系统回收内存资源,并修改分区描述表中分区的状态。
无外部碎片,但会产生内部碎片
③动态分区分配
分区数量和大小都不固定,根据进程的实际需要,动态地为之分配内存空间
分区组织:记录空闲分区情况,有两种组织方式
动态分区没有外部碎片,但有内部碎片
分配:当很多空闲分区都能满足需求时,应该选哪个分区进行分配呢?——动态分区分配算法
回收:当进程运行完毕释放内存时,需合并相邻的空闲分区,形成大的分区,称为合并技术。
a.首次适应算法FF
要求空闲分区链以地址递增的次序链接。在分配内存时,从链首开始顺序查找,直至找到一个大小能满足要求的空闲分区为止; 然后再按照作业的大小,从该分区中划出一块空间分配给请求者,余下的空闲分区仍留在空闲链中。
优点:优先利用内存中的低地址部分的空闲分区,从而保留了高地址部分的大空闲区,因此为大作业分配大的内存空间创造了条件。
缺点:低址部分不断被划分,会留下许多难以利用的、很小的空闲分区。
b.循环匹配算法
将所有的空闲分区构成一个循环链表。每次查找时不是从头开始,而是从上次找到的空闲分区的下一个空闲分区开始查找,直到找到一个能满足要求的空闲分区,从而划出一块与请求大小相等的内存空间分配给作业。
优缺点:能使内存中的空闲分区分布得更均匀,从而减少了查找空闲分区时的开销,但这样会缺乏大的空闲分区。
c.最佳适应算法
该算法要求将所有的空闲分区按其容量以从小到大的顺序形成一空闲分区链。从头开始查找,将表中第一个大于所需求空间大小的空闲区分配给作业。
优点:为大作业分配大的内存空间创造了条件。
缺点:每次分配后所切割下来的剩余部分总是最小的,这样,在存储器中会留下许多难以利用的小空闲区。
d.最坏适应算法
算法思想:为了解决最佳适应算法的问题——留下太多难以利用的小碎片。可以在每次分配时优先使用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用。
该算法要求将所有的空闲分区按其容量以从大到小的顺序形成一空闲分区链。从头开始查找,总是挑选一个最大的空闲分区分配给作业。
优点:产生碎片的几率最小,对中、小作业有利,同时最坏适应分配算法查找效率很高。
缺点:使存储器中缺乏大的空闲分区。
4、可重定位分区分配
在动态分区算法中内存中无法利用的空闲分区称为外部碎片,使用紧凑技术可以解决该问题。
紧凑技术——将内存中的所有作业进行移动,使它们全都相邻接,这样,可把原来分散的多个小分区合成一个大分区的方法,称为紧凑。
在动态运行时装入的方式中,作业装入内存后的所有地址都仍然是相对地址,将相对地址转换为物理地址的工作,被推迟到程序指令要真正执行时进行。
动态重定位机制需要硬件的支持, 即须在系统中增设一个重定位寄存器,用它来存放程序(数据)在内存中的起始地址。程序在执行时,真正访问的内存地址是相对地址与重定位寄存器中的地址相加而形成的。
5、对换
所谓“对换”,是指把内存中暂时不能运行的进程或者暂时不用的程序和数据,调出到外存(后备队列)上,以便腾出足够的内存空间;再把后备队列上已具备运行条件的进程或进程所需要的程序和数据,调入内存。对换是提高内存利用率的有效措施
①应该在外存的什么位置被换出进程
具有对换功能的操作系统中,通常把磁盘空间分为文件区和对换区两部分。对换区的I/o速度比文件区的更快。
②对换区的空间的分配和回收
对换分区分配采取连续分配方式,因此其空间分配和回收与内存动态分区分配方式雷同。分配算法也一样。
6、进程的换出与换入
进程的换出:
系统选择处于阻塞状态(或者休眠状态)且优先级最低的进程作为换出进程,将该进程的程序和数据传送到磁盘的对换区上。然后回收该进程所占用的内存空间,并对该进程的进程控制块做相应的修改。
进程的换入:
系统定时地查看所有进程的状态,从中找出“就绪”状态但已换出的进程;将其中换出时间最久的进程作为换入进程;有能满足进程需要的内存时可将之换入。
注意:为了避免频繁的换进换出,设置换出的一个时间限制。例如在内存至少驻留2秒钟才能换出。
离散分配方式
7、分页存储管理方式
上述内存分配方式为连续分配,现介绍离散分配方式。
离散分配方式:为进程分配空间不要求具有连续的空间,可以是多个分离的空间为进程占用。
采用离散分配方式的存储管理有:
分页存储管理
段式存储管理
段页式存储管理
①分页存储管理的基本方法
页面
将一个进程的逻辑地址空间分成若干个大小相等的片,称为页面或页。
把内存空间分成与页面相同大小的若干个存储块,称为**(物理)块或页框**。
在为进程分配内存时,以块为单位将进程中的若干个页分别装入到多个可以不相邻接的物理块中;由于进程的最后一页经常装不满一块而形成了不可利用的碎片,称之为“页内碎片”或称为“内零头”。
页面大小
如果选择的页面较大,虽然可以减少页表的长度,提高页面换进换出的速度,但却又会使页内碎片增大。页面的大小应选择得适中,且页面大小应是2的幂,通常为512B~8KB。
②地址结构
地址空间为程序限定的空间。
物理空间为内存限定空间。
在页式管理系统中将程序地址空间分成大小相同页面。将内存空间分成与页面相同大小的存储块。
若给定一个逻辑地址空间中的地址为A,页面的大小为L,则页号P和页内地址d可按下式求得:
页号是从0开始的
③页表
在分页系统中,进程可能有多个页,允许将进程的每一页离散地存储在内存的任一物理块中。
系统应能保证进程的正确运行,即能在内存中找到每个页面所对应的物理块。为此,系统又为每个进程建立了一张页面映像表,简称页表。
页表放在内存中的系统区中。
④基本地址变换机构
目标:逻辑地址到物理地址
基本地址变换机构可以借助进程的页表将逻辑地址转换为物理地址。
通常会在系统中设置一个页表寄存器(PTR),存放页表在内存中的起始地址F和页表长度M。进程未执行时,页表的始址和页表长度放在进程控制块(PCB)中,当进程被调度时,操作系统内核会把它们放到页表寄存器中。
具体步骤如下:
设页表大小为L,逻辑地址A到物理地址E的变换过程如下:
step1:页号P=A/L,页内偏移量W=A%L
step2:检查越界,比较页号P和页表长度M,P>=M则越界
step3:页表中页号P对应的页表项地址=页表起始地址F+页号P*页表项长度,取出该页表项内容B(内存块号)。
step4:计算E= B*L+W,用得到的物理地址E去访存。
分页系统中处理机每次存取指令或数据至少需要访问两次物理内存:第一次访问页表,第二次存取指令或数据
⑤快表
访问快表速度>访问内存速度
为了提高地址变换速度,为进程页表设置一个专用的高速缓冲寄存器,称为快表(TLB)。快表用来专门保存当前进程最近访问过的一组页表项(因为进程最近访问过的页面在不久的将来还可能被访问)。
步骤如下:
step1:CPU给出逻辑地址,由某个硬件算得页号、页内偏移量,将页号与快表中的所有页号进行比较。
step2:如果找到匹配的页号,说明要访问的页表项在快表中有副本,则直接从中取出该页对应的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表命中,则访问某个逻辑地址仅需一次访存即可。
step3:如果没有找到匹配的页号,则需要访问内存中的页表,找到对应页表项,得到页面存放的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表未命中,则访问某个逻辑地址需要两次访存(注意:在找到页表项后,应同时将其存入快表,以便后面可能的再次访问。但若快表已满,则必须按照一定的算法对旧的页表项进行替换)
⑥两级和多级页表
解决单个页表过大的情况
引入:32位逻辑地址空间,假设页面大小为4KB(2^12),则在每个进程页表中的页表项数可达1MB(2^32/2^12=2^20=1MB),由于每个页表项占用8个字节,故每个进程仅仅其页表就要占用8MB的连续内存空间。不可能将8MB的页表保存在一个连续区中!
解决方案:
① 采用离散分配方式来解决难以找到一块连续的大内存空间的问题,(即引入两级页表);
② 只将当前需要的部分页表项调入内存, 其余的页表项仍驻留在磁盘上,需要时再调入。
二级页表
多级页表
对于64位的机器,采用两级页表仍然有困难,必须采用多级页表。
将外层页表再进行分页,也就是将各分页离散地装入到不相邻接的物理块中,再利用第2级的外层页表来映射它们之间的关系 ,来实现分页存储管理。
8、分段存储管理方式
①引入分段的原因
在分段存储管理方式中,作业的地址空间被划分为若干个段,每个段定义了一组逻辑信息。例如,有主程序段MAIN、子程序段X、数据段D及栈段S等。
段号的位数决定了每个进程最多可以分几个段,段内地址位数决定了每个段的最大长度是多少
②段表
为每个分段分配一个连续的分区,而进程中的各个段可以离散地移入内存中不同的分区中。
像分页系统那样,在系统中为每个进程建立一张段映射表,简称段表。每个段在表中占有一个表项,其中记录了该段在内存中的起始地址(又称为“基址”)和段的长度。
③地址变换机构
④段表寄存器
类似快表
段表同样被保存在物理内存中。
段表寄存器 :实现快速地址变换,用来存放当前执行进程的段表在物理内存中的起始地址(即基址)。
当创建进程,将进程的程序和数据装入内存时,系统为之建立段表,并将段表的起始地址填入进程的PCB中。当进程被调度执行时,取出其PCB中的段表首址,填入段表寄存器中。
9、分段、分页管理的对比
页是信息的物理单位。分页的主要目的是为了实现离散分配,提高内存利用率。分页仅仅是系统管理上的需要,完全是系统行为,对用户是不可见的。
段是信息的逻辑单位。分段的主要目的是更好地满足用户需求。一个段通常包含着一组属于一个逻辑模块的信息。分段对用户是可见的,用户编程时需要显式地给出段名。
页的大小固定且由系统决定。段的长度却不固定,决定于用户编写的程序。
分页的用户进程地址空间是一维的,程序员只需给出一个记忆符即可表示一个地址。
分段的用户进程地址空间是二维的,程序员在标识一个地址时,既要给出段名,也要给出段内地址。
10、段页式存储管理方式
分页和分段存储管理方式都各有其优缺点。分页系统能有效地提高内存利用率,而分段系统则能很好地满足用户需要,将两者结合成一种新的存储管理方式系统,称为“段页式系统”。
①基本原理
段页式系统的基本原理:**采用分段方法组织用户程序,采用分页方法分配和管理内存。**先将用户程序分成若干个段,再把每个段分成若干个页,并为每一个段赋予一个段名。
逻辑地址由3部分组成:段号、段内页号、页内偏移量,如图:
段号的位数决定了每个进程最多可以分几个,段页号位数决定了每个段最大有多少页,页内偏移量决定了页面大小、内存块大小是多少
数据结构:
②段表寄存器
段表寄存器:加速地址变换,用于存放执行进程段表的起始地址。
③地址变换
在段页式系统中,为了获得一条指令或数据,须三次访问内存
④评价
综合了分段和分页技术的优点,既能有效地利用存储空间,又能方便用户进行程序设计。但是,实现段页式存储管理系统需要增加硬件成本,系统的复杂度和管理开销也大大增加。
因此,段页式存储管理技术适合于大、中型计算机系统,不太适合小型、微型计算机系统。
第五章:虚拟存储器
1、虚拟存储器的概述
情景:
有的作业很大,其所要求的内存空间超过了内存总容量,作业不能全部被装入内存,致使该作业无法运行。
有大量作业要求运行,但由于内存容量不足以容纳所有这些作业,只能将少数作业装入内存让它们先运行,而将其它大量的作业留在外存上等待。
①局部性原理
在一个较短的时间段内,程序的执行仅限于某个部分;相应的,它所访问的存储空间也局限于某个区域。因此,只要保证进程执行所需的部分程序和数据驻留在内存,一段时间内进程都能顺利执行。
分为时间局部性和空间局部性。
②虚拟存储的定义
应用程序在运行之前,没有必要全部装入内存,仅需要将一部分页面或段装入内存,便可启动运行,其余部分暂时保留在磁盘上。
③抖动
当进程要求装入新的页面或程序段时,如果当前没有足够的空闲空间,需要交换一些页面或段到外存。如果被交换出去的页面或段很快将被进程使用,则又需要将其换入内存。
如果系统花费大量的时间把程序和数据频繁地装入和移出内存而不是执行用户指令,那么,称系统出现了抖动。出现抖动现象时,系统显得非常繁忙,但是吞吐量很低,甚至产出为零。
根本原因:选择的页面或段不恰当。
④虚拟存储管理的实现方法
分页请求系统:
这是在分页系统的基础上,增加了请求调页功能和页面置换功能所形成的页式虚拟存储系统。允许只装入部分页面的程序(及数据),便启动运行。
以后,再通过调页功能及页面置换功能,陆续地把即将要运行的页面调入内存,同时把暂不运行的页面换出到外存上。
置换时以页面为单位。
请求分段系统:
它允许只装入若干段的用户程序和数据,即可启动运行。以后再通过调段功能和段的置换功能,将暂不运行的段调出,同时调入即将运行的段。
2、请求分页管理方式
①页表机制
②缺页中断机构
在请求分页系统中,每当要访问的页面不在内存时,便产生一个缺页中断,然后由操作系统的缺页中断处理程序处理中断。
此时缺页的进程阻塞,放入阻塞队列,调页完成后再将其唤醒,放回就绪队列。
如果内存中有空闲块,则为进程分配一个空闲块,将所缺页面装入该块,并修改页表中相应的页表项。
如果内存中没有空闲块,则由页面置换算法选择一个页面淘汰,若该页面在内存期间被修改过,则要将其写回外存。未修改过的页面不用写回外存。
③地址变换机构
重点:与基本分页管理方式的不同点
新增步骤1:请求调页(查到页表项时进行判断)
新增步骤2:页面置换(需要调入页面,但没有空闲内存块时进行)
新增步骤3:需要修改请求页表中新增的表项
3、页面置换算法
页面置换算法应该追求更少的缺页率
①最佳置换算法OPT
不现实
每次选择淘汰的页面将是以后永不使用,或者在最长时间内不再被访问的页面,这样可以保证最低的缺页率。但实际上,只有在进程执行的过程中才能知道接下来会访问到的是哪个页面。操作系统无法提前预判页面访问序列。因此,最佳置换算法是无法实现的。
②先进先出置换算法
引起异常
每次选择淘汰的页面是最早进入内存的页面
实现方法:把调入内存的页面根据调入的先后顺序排成一个队列,需要换出页面时选择队头页面即可。队列的最大长度取决于系统为进程分配了多少个内存块。
该算法会引起Belady异常:当为进程分配的物理块数增大时,缺页次数不减反增的异常现象。
只有FIFO算法会产生Belady异常。另外,FIFO算法虽然实现简单,但是该算法与进程实际运行时的规律不适应,因为先进入的页面也有可能最经常被访问。因此,算法性能差
③最近最久未使用置换算法LRU
向前看
每次淘汰的页面是最近最久未使用的页面
实现方法:赋予每个页面对应的页表项中,用访问字段记录该页面自上次被访问以来所经历的时间t。当需要淘汰一个页面时,选择现有页面中t值最大的,即最近最久未使用的页面。
④时钟置换算法
访问位=1,表示最近访问过
最多两轮扫描
⑤改进型的时钟置换算法
简单的时钟置换算法仅考虑到一个页面最近是否被访问过。事实上,如果被淘汰的页面没有被修改过,就不需要执行I/o操作写回外存。只有被淘汰的页面被修改过时,才需要写回外存。
其它条件相同时,应优先淘汰没有被修改过的页面
从算法我们可以总结出淘汰的优先级:
第一优先级:最近没访问,且没修改的页面
第二优先级:最近没访问,但修改过的页面
第三优先级:最近访问过,但没修改的页面
第四优先级:最近访问过,且修改过的页面
4、工作集
由局部性原理可知:程序在运行期间,仅局限于较少的页面;
工作集定义为:在时间间隔里,进程实际所要访问页面的集合。把进程在时间t的工作集记为w(t, ⊿),其中的⊿称为工作集的“窗口尺寸”。
如图可得,工作集的大小前后分别为4和3,可知:
工作集大小可能小于窗口尺寸,实际应用中,操作系统可以统计进程的工作集大小,根据工作集大小给进程分配若干内存块。如:窗口尺寸为5,经过一段时间的监测发现某进程的工作集最大为3,那么说明该进程有很好的局部性,可以给这个进程分配3个以上的内存块即可满足进程的运行需要。
5、请求分段存储管理方法
第六章:输入输出系统
1、I/O系统的功能、模型和接口
①基本功能
方便用户使用I/O设备
提高 CPU和 I/O设备的利用率
为用户在共享设备时提供方便
②I/O软件组织层次
需要记住I/O请求的处理次序!
用户层
实现了与用户交互的接口(库函数、系统调用)
设备独立性软件
向上层提供统一的调用接口writr、read
设备保护
差错检查
设备的分配与回收(临界资源管理)
数据缓冲区管理
建立逻辑设备名和物理设备名的映射关系(逻辑设备表LUT)
设备驱动程序
直接与硬件相关的管理
不同的设备有不同的驱动程序
主要负责对硬件设备的具体控制,将上层发出的一系列命令(如read/write)转化成特定设备“能听得懂”的一系列操作。包括设置设备寄存器;检查设备状态等
中断处理程序
当I/O任务完成时,I/O控制器会发送一个中断信号,系统会根据中断信号类型找到相应的中断处理程序并执行。
2、I/O设备和设备控制器
①设备类型
按传输速率分类:
低速设备,传输速率仅为每秒钟几个字节至数百个字节的一类设备。如键盘、 鼠标器、语音的输入和输出等设备。
中速设备,传输速率在每秒钟数千个字节至数万个字节的一类设备。典型的中速设备有行式打印机、激光打印机等。
高速设备, 传输速率在数十兆字节至数百兆字节的一类设备。 典型的高速设备有硬盘、 光盘、U盘、显示器等。
按信息交换的单位分类:
块设备,以数据块为单位, 属于有结构设备。如磁盘,每个盘块的大小为512 B~4 KB。输速率较高,DMA方式。
字符设备,以字符为单位, 如打印机、串口等。传输速度低,中断驱动方式。
网络设备,将计算机通过网络与网络上的其它计算机进行通信。传输速度快。
按设备的共享属性分类:
独占设备,一段时间内仅允许一个进程访问的设备。
共享设备, 一段时间内允许多个进程同时访问的设备。
虚拟设备,通过虚拟技术将一台独占设备变换为若干台逻辑设备,供若干个用户(进程)同时使用。
②设备与控制器间的接口
2、设备控制器
①概念
CPU无法直接控制I/O设备的机械部件,因此I/O设备还要有一个电子部件作为CPU和I/O设备机械部件之间的“中介”,用于实现CPU对设备的控制。
这个电子部件就是I/o控制器,又称设备控制器。CPU可控制I/O控制器,又由I/O控制器来控制设备的机械部件。
②I/O控制器的组成
3、I/O控制方式
①程序直接控制方式
核心:轮询检查(状态寄存器)
流程:CPU发出指令------>轮询检查设备是否就绪(状态寄存器)------>I/O设备传输数据并报告状态------->将数据放入数据寄存器重------>将数据寄存器的内容读入到CPU寄存器中,再放入内存
数据传送的基本单位是字!
数据流向:
缺点:CPU和I/O只能串行工作,CPU需要一直作轮询检查,长期处于“忙等状态”,CPU利用率低
②中断驱动方式
引入中断机制。由于I/O设备速度很慢,因此在CPU发出读/写命令后,可将等待I/O的进程阻塞,先切换到别的进程执行。当I/O完成后,控制器会向CPU发出一个中断信号,CPU检测到中断信号后,会保存当前进程的运行环境信息,转去执行中断处理程序处理该中断。处理中断的过程中,CPU从I/O控制器读一个字的数据传送到CPU寄存器,再写入主存。接着,CPU恢复等待l/O的进程(或其他进程)的运行环境,然后继续执行。
数据传送的基本单位是字!
数据流向:
缺点:每个字在I/O设备与内存之间的传输,都需要经过CPU。而频繁的中断处理会消耗较多的CPU时间。
③DMA方式
CPU指派DMA干活
DMA方式(Direct Memory Access,直接存储器存取)有这样几个改进:
①**数据的传送单位是“块”!**不再是一个字、一个字的传送;
②数据的流向是从设备直接放入内存,或者从内存直接到设备。不再需要CPU作为“快递小哥”。
③仅在传送一个或多个数据块的开始和结束时,才需要CPU干预。
数据流向:(不再经过CPU)
缺点:CPU每发出一条l/O指令,只能读/写一个或多个连续的数据块。
④通道控制方式
通道:一种硬件,可以理解为是“弱鸡版的CPU”。通道可以识别并执行一系列通道指令
引入通道后,CPU可以一次性给予通道多道指令,CPU的干预频率极低,只有完成后才会发出中断信号请求CPU干预;每次可以读/写一组数据块
数据流向:(在通道控制下进行)
4、I/O调度
用某种算法确定一个好的顺序来处理各个I/O请求。由设备独立性软件实现
由于磁盘也是I/O设备,因此I/O调度算法与磁盘调度算法(后续会介绍)很类似:
先来先服务
优先级高者优先
5、假脱机技术(SPOOLing技术)
脱机:脱离主机控制,实现输入/输出操作
作用:缓解CPU与I/O设备速度矛盾
6、设备分配与回收
设备的固有属性可分为三种:独占设备、共享设备、虚拟设备。
独占设备:一个时段只能分配给一个进程(如打印机)
共享设备:可同时分配给多个进程使用(如磁盘),各进程往往是宏观上同时共享使用设备,而微观上交替使用。
虚拟设备:采用SPOOLing技术将独占设备改造成虚拟的共享设备,可同时分配给多个进程使用(如采用SPOOLing技术实现的共享打印机)
①静态分配/动态分配
静态分配:进程运行前为其分配全部所需资源,运行结束后归还资源(破坏了“请求和保持”条件,不会发生死锁)
动态分配:进程运行过程中动态申请设备资源
② 设备分配管理中的数据结构
设备控制表:DCT,用于记录设备情况
控制器控制表:COCT,设备控制器对应COCT
通道控制表:CHCT,每个通道对应一个CHCT
系统设备表:SDT,记录了系统中全部设备的情况
设备分配步骤:
①根据进程请求的物理设备名查找系统控制表SDT(注:物理设备名是进程请求分配设备时提供的参数)
②根据SDT找到设备控制表DCT,若设备忙碌则将进程PCB挂到设备等待队列中,不忙碌则将设备分配给进程。
③根据DCT找到控制器控制表COCT,若控制器忙碌则将进程PCB挂到控制器等待队列中,不忙碌则将控制器分配给进程。
④根据COCT找到通道控制表CHCT,若通道忙碌则将进程PCB挂到通道等待队列中,不忙碌则将通道分配给进程。
7、缓冲管理
缓冲区在内存中
①单缓冲
操作系统会在主存中为其分配一个缓冲区(若题目中没有特别说明,一个缓冲区的大小就是一个块)。
注意:当缓冲区数据非空时,不能往缓冲区冲入数据,只能从缓冲区把数据传出;当缓冲区为空时,可以往缓冲区冲入数据,但必须把缓冲区充满以后,才能从缓冲区把数据传出。
题目:工作区满,缓冲区为空,输入速度和输出速度大小不同的两种情况。
②双缓冲
操作系统会在主存中为其分配两个缓冲区(若题目中没有特别说明,一个缓冲区的大小就是一个块)
双缓冲题目中,假设初始状态为:工作区空,其中一个缓冲区满,另一个缓冲区空
③循环缓冲区
多个大小相等的缓冲区链接形成一个循环队列
橙色代表缓冲区充满,绿色代表为空:
④缓冲池
缓冲池由系统中共用的缓冲区组成。这些缓冲区按使用状况可以分为:
空缓冲队列emq
装满输入数据的缓冲队列(输入队列)inq
装满输出数据的缓冲队列(输出队列)outq
根据一个缓冲区在实际运算中扮演的功能不同,又设置了四种工作缓冲区:
用于收容输入数据的工作缓冲区(hin)
用于提取输入数据的工作缓冲区(sin)、
用于收容输出数据的工作缓冲区(hout)
用于提取输出数据的工作缓冲区(sout)
8、磁盘存储器
①磁盘结构
磁盘中的一个盘片:
- 磁道: 磁盘表面被分为许多同心圆,每个同心圆称为一个磁道,每个磁道都有一个编号,最外面的是0磁道。
- 扇区: 每个磁道被划分成若干个扇区,每个扇区的存储容量为512字节,每个扇区都有一个编号。
- 柱面:一个磁盘由多个盘片叠加而成,每个盘片有两个盘面,所有盘面中半径相同的同心磁道构成一个柱面。
- 有多少个磁道,就有多少个柱面
- 磁头:每个盘面都有一个对应的磁头,每个磁头都有一个编号,所有的磁头都是连在同一个磁臂上的。
三元组定位:可用(柱面号,盘面号,扇区号)来定位任意一个“磁盘块”。
读取数据时,需要将磁头移动到目标位置(磁道),旋转盘片(定位扇区),将对应扇区划过磁道才能完成读写。
磁道号、扇区号、盘面号均由0开始编号
②磁盘读写操作所需时间
寻道时间Ts
指在读/写数据前,将磁头移动到指定磁道所花的时间。
①启动磁头臂是需要时间的。假设耗时为s;
②移动磁头也是需要时间的。假设磁头匀速移动,每跨越一个磁道耗时为m,总共需要跨越n条磁道。
则寻道时间Ts =s + m*n
旋转延迟Tr
通过旋转磁盘,使磁头定位到目标扇区所需要的时间。设磁盘转速为r(单位:转/秒,或转/分)
则平均所需的延迟时间TR=(1/2)*(1/r)= 1/2r
传输时间Tt
从磁盘读出或写入数据所经历的时间,假设磁盘转速为r,此次读/写的字节数为b,每个磁道上的字节数为N。
则传输时间Tt=(1/r)*(b/N)= b/(rN)
(每个磁道要可存N字节的数据,因此b字节的数据需要b/N个磁道才能存储。而读/写一个磁道所需的时间
刚好又是转一圈所需要的时间1/r)
旋转延迟和传输时间都与磁盘转速线性相关,转速是磁盘的固有属性,无法优化
9、磁盘调度算法
磁盘调度算法只能影响寻道时间
①先来先服务算法FCFS
根据进程请求访问磁盘的先后顺序进行调度
优点:公平;如果请求访问的磁道比较集中的话,算法性能还算过的去。
缺点:如果有大量进程竞争使用磁盘,请求访问的磁道很分散,则FCFS在性能上很差,寻道时间长。
②最短寻找时间优先SSTF
贪心算法
优先处理的磁道是与当前磁头最近的磁道。可以保证每次的寻道时间最短,但是并不能保证总的寻道时间最短。(其实就是贪心算法的思想,只是选择眼前最优,但是总体未必最优)。
优点:性能较好,平均寻道时间短
缺点:可能产生“饥饿”现象(只要一个小区域内有源源不断的请求,那么它将长期处于这个小区域内移动)
③扫描算法SCAN
只有磁头移动到最外侧磁道的时候才能往内移动,移动到最内侧磁道的时候才能往外移动。这就是扫描算法(SCAN)的思想。由于磁头移动的方式很像电梯,因此也叫电梯算法。
优点:性能较好,平均寻道时间较短,不会产生饥饿现象
缺点:
①只有到达最边上的磁道时才能改变磁头移动方向,事实上,处理了184号磁道的访问请求之后就不需要再往右移动磁头了。
②对于各个位置磁道的响应频率不平均
④LOOK调度算法
边走边看
扫描算法(SCAN)中,只有到达最边上的磁道时才能改变磁头移动方向。LOOK调度算法就是为了解决这个问题,如果在磁头移动方向上已经没有别的请求,就可以立即改变磁头移动方向。(边移动边观察,因此叫LOOK)
⑤循环扫描算法C-SCAN
移到起点
SCAN算法对于各个位置磁道的响应频率不平均,而C-SCAN算法就是为了解决这个问题。规定只有磁头朝某个特定方向移动时才处理磁道访问请求,而返回时直接快速移动至起始端而不处理任何请求。
⑥C-LOOK调度算法
C-SCAN算法的主要缺点是只有到达最边上的磁道时才能改变磁头移动方向,并且磁头返回时不一定需要返回到最边缘的磁道上。C-LOOK算法就是为了解决这个问题。如果磁头移动的方向上已经没有磁道访问请求了,就可以立即让磁头返回,并且磁头只需要返回到有磁道访问请求的位置即可。
第七章:文件管理
1、文件和文件系统
文件管理系统管理的对象有:文件、目录、磁盘存储空间
①数据项,记录
基本数据项:用于描述一个对象的某种属性的字符集。例如,用于描述一个学生的基本数据项有: 学号、 姓名、班级等。
组合数据项:**由若干个基本数据项组成的,简称组项。**例如,工资是个组项,它可由基本工资、工龄工资和奖励工资等基本项所组成。
基本数据项除了数据名外,还应有数据类型。
记录是一组相关数据项的集合,用于描述一个对象在某方面的属性。
②文件
文件是一组有意义的信息的集合,存放在外存中
分类: 从逻辑上和物理上两方面思考
有结构的文件:文件由若干个相关记录组成;
无结构文件:被看成是一个字符流
文件属性:文件名、类型、标识符、位置、大小、保护信息等
文件类型:
按用途分类
(1)系统文件:这是指由系统软件构成的文件。大多数的系统文件只允许用户调用,但不允许用户去读,更不允许修改;有的系统文件不直接对用户开放。
(2)用户文件:由用户的源代码、目标文件、可执行文件或数据等所构成的文件。
(3) 库文件:这是由标准子例程及常用的例程等所构成的文件。这类文件允许用户调用,但不允许修改。
按文件中数据形式分类
(1)源文件:指由源程序和数据构成的文件。
(2)目标文件:指把源程序经过相应语言的编译程序编译过,但尚未经过链接程序链接的目标代码所构成的文件。它属于二进制文件。
(3)可执行文件:指把编译后所产生的目标代码再经过链接程序链接后所形成的文件。
按存取控制属性分类
(1)只执行文件:该类文件只允许被核准的用户调用执行,既不允许读,更不允许写。
(2)只读文件:该类文件只允许文件主及被核准的用户去读,但不允许写。
(3) 读写文件:这是指允许文件主和被核准的用户去读或写的文件。
按组织形式和处理方式分类
(1) 普通文件:由ASCII码或二进制码组成的字符文件。一般用户建立的及操作系统的文件都是普通文件,它们通常存储在外存储设备上。
(2) 目录文件:由文件目录组成的,用来管理和实现文件系统功能的系统文件。对其可进行与普通文件一样的文件操作。
(3) 特殊文件:特指系统中的各类I/O 设备。为了便于统一管理,系统将所有的输入/输出设备都视为文件,按文件方式提供给用户使用。
操作系统向上提供的功能:
文件系统的层次结构:
③文件操作
建立文件
分配外存空间,建立目录项,记录属性
打开文件
由文件名查找目录,将文件信息装入主存,建立文件控制块FCB,返回一个文件内部标识符
关闭文件
将文件FCB中有关信息写入外存目录中,撤销其FCB,释放该文件占用的资源
删除文件
系统找到该目录项,使之成为空项,回收文件占用的外存空间
复制文件
拷贝文件内容及目录项,用目录项找到该文件外存地址—>找到文件内容,将他们拷贝至指定位置
修改文件名
找到指定目录项,更新名字
读操作
给出文件名和所读字节数
首先查找目录文件,找到指定文件的目录项,从中找出该文件的外存地址;从该文件读指针所指位置开始,读取指定长度的字节数到缓冲区,同时该文件的读指针顺延指定长度的位置。返回最新读指针位置值。如果读指针遇到文件结束标志,则给出相应提示信息
写操作
必须给出文件名和需要写的字节数。
系统从缓冲区中将指定长度的信息写入指定文件写指针位置,将文件的写指针顺延指定长度的位置
2、文件目录
①文件控制块
FCB:目录项
FCB的合集称为文件目录,一个FCB就是一个目录项
FCB的核心是实现文件名与文件的物理地址间的映射关系
②目录结构
单级目录结构
不允许重名,不适合多用户
两级目录结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VqJukjDh-1687269743642)(https://gitee.com/how-vague-and-fragile-love-is/images/raw/master/img2/image-20230618205117335.png)]
多级目录结构(树形目录)
注意绝对路径与相对路径!
无环图目录结构
共享文件:两个用户指向同一个文件,因此更改可以看到(区别于复制)
3、文件的逻辑结构
文件分为有结构文件和无结构文件
对于有结构文件,探讨如下:
①顺序文件
顺序文件:文件中的记录一个接一个地顺序排列(逻辑上),记录可以是定长的或可变长的。各个记录在物理上可以顺序存储或链式存储。
讨论:
一般说的顺序文件默认为物理上顺序存储的文件,可支持随机访问
②索引文件
检索速度极高,使用关键字作为索引号
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zk13oRMG-1687269743644)(https://gitee.com/how-vague-and-fragile-love-is/images/raw/master/img2/image-20230618210223134.png)]
③索引顺序文件
索引文件的缺点:每个记录对应一个索引表项,因此索引表可能会很大。
检索性能:
如果在一个顺序文件中所含有的记录数为N,则为检索到具有指定关键字的记录,平均须查找N/2 个记录;但对于索引顺序文件,则为能检索到具有指定关键字的记录,平均只要查找根号n个记录数。
④直接哈希文件
对于直接文件,则可根据给定的记录键值,直接获得指定记录的物理地址。换言之,记录键值本身就决定了记录的物理地址。这种由记录键值到记录物理地址的转换被称为键值转换。
4、文件共享
共享:系统中只有这一份文件
①基于索引结点
索引结点,是一种文件目录瘦身策略。由于检索文件时只需用到文件名,因此可以将除了文件名之外的其他信息放到索引结点中。这样目录项就只需要包含文件名、索引结点指针。
本质是不同用户的目录项指向同一个索引结点。
②基于符号链
类似快捷方式
若软链接指向的共享文件被删除,Link型文件依然存在,只是通过Link文件去查找该路径下的文件会失败
5、文件保护
第八章:磁盘存储器的管理
1、对非空闲磁盘块的管理
在内存管理中,进程的逻辑地址空间被分为一个一个页面
同样的,在外存管理中,为了方便对文件数据的管理,文件的逻辑地址空间也被分为了一个一个的文件“块”。于是文件的逻辑地址也可以表示为(逻辑块号,块内地址)的形式。
核心问题:如何把逻辑块映射到物理块
①连续分配
连续分配方式要求每个文件在磁盘上占有一组连续的块
用户给出要访问的逻辑块号,操作系统找到该文件对应的目录项FCB,物理块号=起始块号+逻辑块号
优点:连续分配支持顺序访问和直接访问,由于磁盘的结构特点(磁头移动),连续分配在顺序读写时速度最快
缺点:物理上连续分配很难拓展文件,会产生难以利用的磁盘碎片(可以紧凑解决,但开销大)
②链接分配
链接分配采取离散分配的方式,可以为文件分配离散的磁盘块。分为隐式链接和显式链接两种。
隐式链接
默认的链接分配方式
很方便拓展文件,不会产生磁盘碎片
显式分配
区别:FCB只记录了起始块号,引入了文件分配表FAT
由于FAT常驻于内存中,因此逻辑块号转化为物理块号的过程(查询FAT)不需要读磁盘操作。
采用该方式支持顺序访问和随机访问,相比于隐式链接来说访问速度快的多。
一个磁盘只会对应一张文件分配表
③索引分配
索引分配允许文件离散地分配在各个磁盘块中,系统会为每个文件建立一张索引表,索引表中记录了文件的各个逻辑块对应的物理块(索引表的功能类似于内存管理中的页表――建立逻辑页面到物理页之间的映射关系)。
索引表存放的磁盘块称为索引块。文件数据存放的磁盘块称为数据块。
访问文件—>找到目录项FCB—>找到索引块—>查询索引表
支持随机访问,文件拓展:增加索引表的表项即可
如果一个文件的索引表过大,一张索引表装不下,如何管理多张索引表呢?
链接分配
如果索引表太大,一个索引表装不下,那么可以将多个索引块链接起来存放
访问文件的最后一个逻辑块—>最后第n一个索引块—>必须要顺序的读完前n-1个索引块:磁盘I/O次数多
多层索引
类似多级页表,第一层指向第二层
采用K层索引结构,且顶级索引表未载入内存,则访问一个数据库需要k+1次读磁盘操作
混合索引
它是多种索引分配方式的结合。例如一个文件的顶级索引表中,既包含直接地址索引(直接指向数据块),又包含一级简介索引(指向单层索引表),还包含两级间接索引(指向两层索引表)
2、对空闲磁盘块的管理
存储空间的初始化:将各个文件卷划分为目录区和文件区
目录区存放文件目录信息(FCB),用于管理磁盘存储空间;文件区用于存放文件数据
①空闲表法
记录空闲区间的起始位置和空闲长度,适用于连续分配方式
回收磁盘块:注意表项合并即可
②空闲链表法
盘块:以块为单位;盘区:连续的空闲盘块组成一个空闲盘区
操作系统保存了链头、链尾指针
空闲盘块:适用于离散分配;空闲盘区:适用于离散、连续分配
③位示图法
核心是字号和位号如何转化为盘块号
0代表盘块空闲,1代表已分配
分配:顺序扫描位示图,分配给文件,将相应位置设置为1
回收:由盘块号计算出相应的字号和位号,设置位0
缺点:位示图较大,很难一次性将它读入到内存中(降低系统性能)
④成组链接法
文件卷的目录区,专门使用一个磁盘块作为超级块,系统启动时会将超级块读入内存
超级块存放于内存中:
分组信息所在的磁盘块在被分配之前,需要将其保存的链接信息复制到超级块中,再分配。
第九章:进程运行与监控
1、进程替换
子进程创建有两个目的:
1、想让子进程执行父进程代码的一部分:执行父进程对应的磁盘代码中的一部分
2、想让子进程执行一个全新的程序:让子进程想办法,加载磁盘上指定的程序
此时,让子进程加载磁盘上指定的程序,执行新程序的代码,即为进程的程序替换
①替换原理
进程替换的实现原理是通过调用 exec 系统调用来实现的。exec 系统调用会将新的程序加载到当前进程的地址空间中,然后替换掉原有的代码和数据。这个过程是通过以下步骤完成的:
- 应用程序调用 exec 系统调用,并将需要执行的程序名和参数列表传递给 exec 系统调用。
- 内核在执行 exec 系统调用之前,会将当前进程的用户空间数据清空,然后保留内核栈和其他内核数据。
- 内核根据程序名找到可执行文件,并将其加载到内存中。
- 重新建立页表映射,谁执行程序替换,就重新建立谁的页表映射效果;让我们的父进程和子进程彻底分离,并让子进程执行一个全新的程序。
这样,原有进程的代码和数据就被新的程序替换掉了,从而实现了进程替换(调用exec并不创建新进程,所以调用exec前后该进程的id并未改变)
②替换函数
inux中有六种以
exec
开头的函数,它们都可以用来进行进程替换。
这些函数的名称如下:
man 3 exec #查看man的三号手册
#include <unistd.h>`
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
函数返回值:
exec
函数只有在出错的情况下才会返回-1,如果调用成功,则新程序会取代当前进程,从新程序的入口点开始执行,不会再返回到原来的程序中。因此,对于exec
函数来说,成功的情况下并不会有返回值。
命名含义:
l(list)
:表示参数采用列表;v(vector)
:参数用数组形式传递p(path)
:有p自动搜索环境变量PATH;e(env)
:表示自己维护环境变量;
函数名 | 参数格式 | 是否带路径 | 是否使用当前环境变量 |
---|---|---|---|
execl | 列表 | 不是 | 是 |
execlp | 列表 | 是 | 是 |
execle | 列表 | 不是 | 不是,必须自己组装环境变量 |
execv | 数组 | 不是 | 是 |
execvp | 数组 | 是 | 是 |
execve | 数组 | 不是 | 不是,须自己组装环境变量 |
execl
int execl(const char *path, const char *arg, ...);
参数说明:
path
是可执行文件的路径;
arg
是替换的程序名字;
...
代表可变参数列表(必须以NULL结尾),用于传递给被调用程序的命令行参数(选项);
要执行一个程序:①提供该程序路径,②程序所带的选项;
下面是我们使用程序替换execl
函数的示例:
//execl_test.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id=fork();
if(id==0)
{
//子进程
printf("这是子进程pid:%d,ppid:%d\n", getpid(),getppid());
printf("下面执行进程替换:\n");
// arg表示目标程序名,而...参数列表必须以NULL结尾
//execl("/usr/bin/ls", "ls", "-al", NULL); //带参数
int ret1=execl("/usr/bin/ls", "ls", NULL); //不带参数
printf("进程替换成功!\n");
if(ret1==-1)
{
printf("替换失败!\n");
exit(-1);
}
}
else if(id>0)
{
//父进程
printf("这是父进程pid:%d,ppid:%d\n", getpid(),getppid());
pid_t ret2 = waitpid(id, NULL, 0);
if(ret2 > 0)
{
printf("子进程回收成功,ret:%d\n", ret2);
}
}
return 0;
}
结果如下:
【结论】:
调用
execl
后的printf
并没有执行;这是因为一旦替换成功,会将当前进程的代码和数据全部替换了,因此执行完execl函数进程中的代码和数据已经全部被替换了。子进程进行程序替换,不会影响父进程(因为进程具有独立性,在数据层面发生写时拷贝:当程序替换的时候,可以理解为代码和数据都发生了写时拷贝,完成了父子进程的分离)
execv
int execv(const char *path, char *const argv[]);
参数说明:
path
是可执行文件的路径;
argv
通过数组的方式进行参数传递;
因为该函数是通过数组的形式向它传参,因此数组应该包含目标程序名+程序的选项,例如我们要执行ls -a -l
,那么数组中第一个元素argv[0]
即为程序名,下面依次是选项,最后仍需以NULL
结尾:
char *const myexe[]=
{
(char*)"ls",
(char*)"-a",
(char*)"-l",
NULL
};
该函数实现如下(执行pwd):
char *const myexe[]=
{
(char*)"pwd",
NULL
};
execv("/usr/bin/pwd", myexe);
结果如下:
execlp
int execlp(const char *file, const char *arg, ...);
参数说明:
file
是目标程序名;
arg
是替换的程序名字;
...
代表可变参数列表(必须以NULL结尾),用于传递给被调用程序的命令行参数(选项);这个函数命名带
p
代表表示可以不带路径,只需要说明目标程序名;系统会通过环境变量PATH
查找。
该函数实现如下:
// 使用execlp,可以不带路径
// 其中两个ls含义不同
// 第一个为供系统查找,后面一个加上选项表示如何执行
execlp("ls", "ls", "-a", NULL);
结果如下:
execle
int execle(const char *path, const char *arg, ...,char *const envp[]);
参数说明:
path
是目标程序所在路径;
arg
是替换的程序名字;
...
代表可变参数列表(必须以NULL结尾),用于传递给被调用程序的命令行参数(选项);
envp
用户传给目标程序的环境变量(必须以NULL结尾)。
该函数实现如下:
char *const myenv[] =
{
(char*)"MYPATH=HELLOWORLD",
NULL
};
//路径-选项(没有选项就是程序名)-NULL-传的环境变量
execle("./envTest", "envTest", NULL, myenv);
其中envTest
是一个用于检验所传环境变量的程序:
//envTest.c
#include<stdio.h>
#include<stdlib.h>
int main()
{
printf("MYPATH=%s\n",getenv("MYPATH"));
printf("hello world\n");
printf("hello world\n");
return 0;
}
结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aVmSRvgL-1687269743653)(https://gitee.com/how-vague-and-fragile-love-is/images/raw/master/img2/image-20230426195547173.png)]
此时,我们更改envTest.c
,让它打印环境变量PATH,再执行execle
进程替换的结果是什么?
结果如下:
【结论】:带e
的exec函数会添加环境变量给目标进程,执行的是覆盖式的。
execvp
int execvp(const char *file, char *const argv[]);
参数说明:
file
是目标程序名;
argv
通过数组的方式进行参数传递;这个函数命名带
p
代表表示可以不带路径,只需要说明目标程序名;系统会通过环境变量PATH
查找。
该函数实现如下:
char *const myexe[] =
{
(char*)"ls",
(char*)"-a",
(char*)"-l",
NULL
};
//程序名称-数组
execvp("ls", myexe);
结果如下:
execve
execve
函数是系统调用接口,其它的几个均是对它进行封装以适用于不同的使用场景
int execve(const char *path, char *const argv[], char *const envp[]);
参数说明:
path
是目标程序所在路径;
argv
通过数组的方式进行参数传递;
envp
用户传给目标程序的环境变量(必须以NULL结尾)。
该函数实现如下:
char *const myexe[] =
{
(char *)"ls",
NULL
};
char *const myenv[] =
{
(char *)"MYPATH=HELLOWORLD",
NULL
};
execve("./envTest", myexe, myenv);
结果如下:
2、环境变量
第十章:shell的交互功能与shell程序设计
1、输入输出重定向和管道
①输入重定向
cat file #把文件内容输出到标准输出文件
cat file > newfile #输出重定向,结果等于拷贝文件
cat file >> newfile #追加file内容
#示例,按照字母顺序显示当前系统中已登录的用户
who > temp1
sort temp1 > temp2
more temp2 #逐屏显示排好序的用户名单
②输出重定向
#command < filename 进程的输入来自文件filename
$cat #命令后无文件名,等待键盘输入
abcd
abcd
$cat < abc #输入文件
aaa
bbb
ccc
#将重定向文件abc输入cat进程
#标准错误输出重定向
#command 2> filename
$cat file1 file2 > file3 2> errfile
$grep string6 data_sav > count_log 2> &1
#将错误输出也输出到count_log中
③管道
管道用于连接命令,将前一个命令的标准输出重定向给后一个命令作为标准输入
command1 | command2
#对command1来说标准输出被重定向到管道,对command2来说标准输入也重定向到管道
$who | wc -l #查看系统当前有多少用户在上机使用系统
wc -l
是一个常用的命令,用于统计文件或标准输入中的行数
④后台命令
$ls -lR > file_list &
#普通命令行的行尾加上& 就表示在后台执行
2、简单shell命令
单命令执行:打印功能
#!/bin/bash
#必须指明#!/bin/bash
echo "helloworld"
多命令执行:
#!/bin/bash
cd /home/ty/
touch test.txt
#重定向
echo "Something" >> test.txt
3、shell中的变量
①常用的系统变量(环境变量)
PATH
:指定命令的搜索路径;HOME
:指定用户的主工作目录;SHELL
:当前Shell,它的值通常是/bin/bash。PWD
:当前路径
我们可以通过echo
指令查看PATH中保存系统中存放指令程序文件的路径:
echo $PATH
②自定义变量
定义变量:变量=值 等号两边不能留有空格
撤销变量:unset 变量
输出变量:echo $变量
声明静态变量: readonly 变量,注意:不能unset
注意:
- 变量名称可以由字母,数字和下划线组成,不能以数字开头,环境变量名建议大写
- 等号两侧不能有空格
- 在bash中,变量默认类型都是字符串类型,无法直接进行数值运算
- 变量的值如果有空格,需要使用双引号或单引号括起来
可以使用
export
指令来定义环境变量
格式:export 环境变量名=$环境变量名:[追加值]
③特殊变量
$n
:n为数字,$0代表脚本名称,10以内参数用$1-9 表 示 , 10以上的需要用大括号包含 {10}
作为命令行参数
$#
:获取所有输入参数个数,常用于循环
$?
:最后一次执行命令的状态,0:正确执行
④单引号、双引号、反撇号、花括号
-
单引号
' '
:-
单引号用于创建字符串,其中的内容会被原样输出,不进行变量扩展、命令替换或者转义字符处理。
-
示例:
name='Kevin' echo 'Hello, $name' # 输出:Hello, $name echo 'Hello, world!' # 输出:Hello, world!
-
-
双引号
" "
:-
双引号用于创建字符串,其中的内容可以进行变量扩展、命令替换和转义字符处理。
-
示例:
name='Kevin' echo "Hello, $name" # 输出:Hello, Kevin echo "Today is $(date)" # 输出:Today is [当前日期] echo "Hello, \"world!\"" # 输出:Hello, "world!"
-
-
反撇号``或者是$():
-
反撇号或者$( )用于执行命令,并将命令的输出结果赋值给一个变量。
-
示例:
date=`date` #左边的date是变量,右边的date代表date命令 echo "Today is $date" # 输出:Today is [当前日期]
或者
date=$(date) echo "Today is $date" # 输出:Today is [当前日期]
-
-
花括号
{ }
:-
花括号在Shell中用于进行变量扩展、字符串替换和生成序列。
-
示例:
name='Alice' echo "Hello, ${name}!" # 输出:Hello, Alice! echo "Numbers: {1..5}" # 输出:Numbers: 1 2 3 4 5 echo "Files: /path/{a,b,c}" # 输出:Files: /path/a /path/b /path/c
-
4、进程监控
①ps -f
大多数进程信息可以使用
ps
用户级工具来获取
例如我们要查看Test.exe
程序的进程信息:
ps ajx | grep Test.exe
使用上述grep命令后我们发现屏幕上会显示有两个进程信息,这是因为grep指令也是一个进程,可以通过如下指令去掉grep进程信息:
//-v表示匹配上的不显示
ps ajx | grep Test.exe | grep -v grep
而我们发现进程信息为我们显示了该进程的各个属性,但它们名没有名称,我们可以加上:
//显示各项属性名称,且不显示grep的进程
ps ajx | head -1 && ps ajx | grep Test.exe | grep -v grep
通过该指令可以得到该进程的详细信息,我们最常用的即是它的PID
和PPID
使用ps -f
就行:
②kill
kill pid
正常结束
kill -1 pid
先挂起,终止子进程
kill -9 pid
直接杀死
5、read
read
是用于从控制台读取用户输入的命令。它允许脚本与用户进行交互,并将输入的值存储到一个变量中供后续处理。
格式:
read [options] [variable]
其中,options
是一些可选的参数,用于控制 read
命令的行为。variable
是用于存储用户输入的变量名。
下面是一些常用的 read
命令选项:
-p
:指定一个提示信息,显示给用户以引导输入。-s
:使输入不可见,常用于密码输入。-t
: 指定读取值时等待的时间(秒)
示例1:
echo "What is your name?"
read name
echo "Hello, $name!"
示例2:带有提示的密码输入
read -p "Username: " username #提示消息
read -s -p "Password: " password #提示+隐藏
echo "Username: $username"
echo "Password: $password"
示例3:读取多个值
echo "Enter your first name and last name:"
read first last #读入多个值
echo "First name: $first"
echo "Last name: $last"
6、运算符
expr+运算符
+
:加法-
:减法*
:乘法/
:除法%
:取模(取余数)**
:幂运算
示例:
$((运算式))
或 $[运算式]
a=10
b=3
echo $((a + b)) # 输出:13
echo $((a - b)) # 输出:7
echo $((a * b)) # 输出:30
echo $((a / b)) # 输出:3
echo $((a % b)) # 输出:1
echo $((a ** b)) # 输出:1000
7、条件判断
[ condition ]
(注意 condition前后要有空格)
整数之间的比较:
符号 | 描述 |
---|---|
-lt | (less than)小于 |
-le | (less equal) 小于等于 |
-eq | (equal)等于 |
-gt | (greater than) 大于 |
-ge | (greater equal) 大于等于 |
-ne | (not equal) 不等于 |
文件权限判断:
- -r 有读的权限
- -w 有写的权限
- -x 有执行的权限
文件类型判断:
- -f 文件存在并且是一个常规文件
- -e 文件存在
- -d 文件存在并是一个目录
示例:
# 判断23是否大于50
$ [ 23 -gt 50 ]
$ echo $? #上一次执行指令的状态
1
# 判断file.txt是否有写入权限
$ [ -w file.txt ]
$ echo $?
0
多条件判断:&& ||
8、if流程控制
语法:
if condition
then
#condition is true
else
#condition is false
fi
fi
是 if
语句的结束标记
示例1:
age=25
if [ $age -gt 18 ]
then
echo "You are an adult."
else
echo "You are not an adult yet."
fi
示例2:
#测试命令行参数是否为一个已经存在的文件或目录的用法
#!/bin/bash
if [ -f $1 ]
then
echo "File $1 exists"
fi
if [ -d $HOME/$1 ]
then
echo "File $1 is a directory"
fi
#!/bin/bash
表示要使用 Bash 解释器执行脚本。
语句1:$1
是否为一个已存在的文件(命令行参数)。-f
是一个条件测试运算符,用于检查文件是否存在且为普通文件。如果条件为真,即 $1
是一个已存在的文件,那么输出一条消息 “File $1 exists.”。
语句2:$1
是否为一个已存在的目录(命令行参数)。-d
是一个条件测试运算符,用于检查是否为目录。这里将 $HOME/$1
组合成一个路径来检查目录是否存在。如果条件为真,即 $1
是一个已存在的目录,那么输出一条消息 “Directory $1 exists.”。
9、test语句
在Shell中,test
是一个用于条件测试的命令。它也可以使用方括号 [ ]
来表示,因为方括号实际上是 test
命令的一个别名。
test
命令用于检查条件是否为真或假,并根据结果返回退出状态码。它通常与 if
语句一起使用,用于控制程序的流程。
test
命令的基本语法如下:
test condition
或者
[ condition ]
在上述语法中,condition
是一个用于测试的条件表达式,可以是各种比较、逻辑和文件测试表达式。根据条件的结果,test
命令将返回 0(真)或 1(假)的退出状态码。
test可以测试三种对象:
- 字符串
- 整数
- 文本
10、case语句
语法:
case expression in
pattern1)
# Code1
;;
pattern2)
# Code2
;;
pattern3)
# Code3
;;
*)
# Code4
;;
esac
示例:
#!/bin/bash
case $1 in
1)
echo "班长"
;;
2)
echo "学习委员"
;;
3)
echo "体育委员"
;;
esac
11、for循环
语法1:
for variable in list
do
# Code
done
variable
是一个变量名,用于存储列表中的每个元素。list
是一个包含要迭代的元素的列表,可以是用空格分隔的多个值,也可以是数组、文件内容等。
例如:
for fruit in apple banana orange
do
echo "I like $fruit."
done
list也可以从命令行参数获取:使用$*
获得命令行参数
#!/bin/bash
for i in $*
do
echo $i
done
语法2:
#!/bin/bash
s=0
for((i=1;i<=100;i++))
do
s=$[$s+$i]
done
echo $s
12、while循环
语法:
while condition
do
# Code
done
示例:
#!/bin/bash
s=0;
i=1;
while [ $i -le 100]
do
s=$[$s+$i]
i=$[$i+1]
done
echo $s
13、函数
①系统函数
basename
basename
是一个用于提取文件路径中的基本文件名(即不包含路径和扩展名)的命令
语法:
basename path [suffix]
path
是要提取基本文件名的文件路径。suffix
是可选的后缀字符串,用于指定要去除的文件名后缀。
示例:
#!/bin/bash
path="/home/Kevin/file.txt"
filename=$(basename $path)
echo "Filename:$filename"
dirname
dirname
是一个用于提取文件路径中的目录部分的命令。它通常用于获取文件所在的目录路径,以便在脚本中进行处理或输出。
语法:
dirname path
在上述语法中,path
是要提取目录的文件路径。
示例:
#!/bin/bash
path="/home/Kevin/file.txt"
directory=$(dirname $path)
echo "Directory: $directory"
②自定义函数
语法:
function_name() {
# Code block defining the function's functionality
# You can include any valid Shell commands here
}
- 函数说明必须放在调用程序之前
- 调用程序可以传递参数给函数,函数可用return将运行结果返回给调用程序
示例:
check_user()
{
user=`who | grep $1`
if [ -n "$user" ]
then
return 0
else
return 1
fi
}
while true
do
echo -n "Input username:"
read uname
check_user $uname
if [ $? -eq 0 ]
then echo "user $uname online"
else echo "user $uname offline"
fi
done
who | grep $1
:使用who
命令获取当前登录用户的信息,并使用grep
过滤出包含给定用户名$1
的行。这里的$1
表示函数的第一个参数,即要检查的用户名。[ -n "$user" ]
:检查变量user
是否非空,即判断是否有匹配到的用户信息。echo -n "Input username:"
:输出提示,要求用户输入用户名。-n代表换行read uname
:读取用户输入的用户名,并将其存储在变量uname
中。check_user $uname
:调用之前定义的check_user
函数,传递用户输入的用户名作为参数进行检查。if [ $? -eq 0 ]
:检查上一条命令的返回值是否为 0。$?
用于获取上一条命令的返回值。
14、shell工具
①cut
cut
是一个用于提取文件中指定字段的命令。它的基本语法如下:
cut [options] [file]
-f
:按字段分隔符提取字段。-d
:指定字段分隔符。
示例:
设置cut.txt
cut -d " " -f 1 cut.txt
②awk
③sed
15、实验题
(1)清屏;
(2)提示用户输入要检测其状态的文件名;
(3)显示该文件的状态信息(提示:该状态信息可由命令ls –l 来得到),或找不到该文件时的错误提示;
(4)用cut命令,或用sed或awk命令来截取状态信息中文件的大小并保存;
(5)每隔5秒钟检测一次该文件大小的信息,并与保存的文件原来的大小相比较;
(6)如果文件大小未改变,则屏幕显示不变,并继续每隔5秒钟检测一次;
(7)如果文件大小已改变,则保存新的文件大小,并在屏幕上显示:file [ filename ] size changed(括号中的filename为本程序运行时用户输入的被检测的文件名)。程序继续每隔5秒钟检测一次文件的大小;
(8)程序循环执行5~7步的操作。当被检测的文件或者已累计改变了两次大小,或者已连续被检测了十次还未改变大小时,给出相应提示,然后清屏退出。
#!/bin/bash
tput clear
read -p "Please Input the filename:" file
echo $file
pre=`ls -l $file | awk '{print $5}'`
echo $pre
i=1
count=0
while [ $i -le 10 ] #循环10次
do
if [ $count -eq 2 ] #判断文件是否被更改了两次
then
echo "已经改变两次"
exit #退出程序
fi
cur=`ls -l $file| awk '{print $5}'` #提取ls -l的第五个字段:文件大小
echo "当前文件大小:$cur"
if [ $cur -ne $pre ] #ne:not equal
then
pre=$cur #不相等说明改变了,更新pre值
echo "file$file size changed"
count=`expr $count + 1` #count+1
fi
i=`expr $i + 1` #i+1
sleep 3 #每3秒检查一次
done
echo "十次未改变大小"
分析:
使用 ls -l
命令获取指定文件的详细信息,并通过 awk
提取文件大小字段(第5个字段)的值,并将其存储在变量 pre
中。其中第五个字段即为文件大小
One more thing
1、地址变换和求FAT表大小