软测前置基础——操作系统

目录

一、什么是操作系统?

1、功能(计算机的主要组成部分)

2、操作系统

3、OS kernel(操作系统内核)的特征

4、操作系统的组成

二、进程、线程

1、初步认识进程与线程

1.1进程

1.2 线程

2、进程

2.1 进程的概念

 2.2 进程的结构特征

2.3 进程与程序的区别

 3、线程

3.1 线程的概念

三、进程/线程间通讯方式

(一)进程间通信

1. 无名管道

2. 命名管道(先进先出队列)

3. 消息队列

4. 信号量(semaphore)

5. 共享内存

6. 套接字(socket)

(二)Linux线程间通信

(三)Java线程间通信

四、进程调度算法

 五、进程/线程同步方式

六、进程/线程状态

七、死锁

1、什么是死锁?

2、产生死锁的原因?

① 竞争资源

② 进程间推进顺序非法

3、产生死锁的4个必要条件

4、解决死锁的基本方法

预防死锁

避免死锁

检测死锁

解除死锁

死锁检测

八、内存管理

1、内存的基础知识

2、内存管理

2.1 内存空间扩充

2.2 内存空间的分配与回收**

九、局部性原理

1、什么是局部性原理?

2、局部性的形式

3、局部性的应用

4、局部性实例​


一、什么是操作系统?

操作是管理和控制计算机系统中各种硬件和软件资源、合理地组织计算机工作流程的系统软件,是用户与计算机之间的接口。

1、功能(计算机的主要组成部分)

用户角度:操作系统是一个控制程序

  • 管理应用程序
  • 为应用程序提供服务
  • 杀死应用程序

内部:

  • 资源管理
  • 管理外设、分配资源

2、操作系统

  • 操作系统层次:硬件之上,应用程序之下
  • 操作系统实例:Unix;Linux;Windows。。。。
  • Linux、Windows、Android的界面属于外壳(shell),而不是内核(kernel),kernel是我们研究的重点
  • kernel(操作系统内部组件),包括  CPU,内存(物理内存管理、虚拟内存管理),文件系统管理,中断处理与设备驱动(与底层硬件打交道)

3、OS kernel(操作系统内核)的特征

  • 并发(在同一段时间内有多个程序同时进行,区别于并行同一个时间点有多个程序同时运行))
  • 共享(“同时访问”,互斥共享)
  • 虚拟
  • 异步(程序的执行不是一贯到底的,而是走走停停,向前推动的速度不可预知;但程序运行前的环境相同,最后运行得到的结果相同)

4、操作系统的组成

  • 驱动程序
  • 内核(可以分为 单内核 、微内核、混合内核、 外内核等)
  • 接口库
  • 外围

二、进程、线程

1、初步认识进程与线程

1.1进程

CPU从硬盘中读取程序到内存中方便CPU读取,此时内存中的可执行程序实例就叫进程

 一个程序若多次读取到内存中,则变成多个独立的进程;而硬盘中多个程序读取到内存中运行时,当然也创建了更多的进程。

内存中任何一个地方都有相应的地址,方便访问;而在内存中的每一个进程内部,都有一个虚拟独立的地址空间 。

进程是程序执行的完整单位,所以大部分时间都是在进程内。在进程内,就可以通过虚拟地址访问;进程间就需要系统调用,访问回很慢。

1.2 线程

 进程中的程序可分为多段并行的程序段,每个程序段运行时,都有一个程序计数器会记录当前程序执行的位置,会按照程序顺序计算,这里,一个执行流就是一个线程。每个线程会独自运行,并且线程中还有寄存器、堆栈等程序运行时的状态信息。线程间共享的有地址空间、全局变量、打开的文件等信息。

线程是并行的最小单位。

假如进程中只有一个单核的CPU,也就是一次只能执行一个线程,那就需要对每个线程轮流执行,每次单个计算的时间成为一个CPU时间片,实际只有几十毫秒,用户根本感觉不到,对于线程来说,存在等待CPU的状态,成为就绪状态,一旦CPU过来执行,就转变为运行状态,当CPU转而执行其他线程时,又转变成就绪状态。假如程序正在执行中,程序向硬盘发送访问请求,然后等待,这时CPU就变成空转了,所以,线程就变成阻塞状态,CPU转而执行其他线程,等待硬盘的数据回复,线程从阻塞状态变回就绪状态,等待CPU的再次光临,然后继续执行。

那如果是多个CPU,确实可以让多个线程并行计算。但是,往往线程有很多,所以还是需要时间片轮转。那么,为了简化,CPU在内核中为每个线程提供虚拟CPU,每个线程会以为自己独占这CPU,他们就不需要考虑时间片轮转的问题了

2、进程

2.1 进程的概念

  • 狭义定义:进程是正在运行的程序的实例。
  • 广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

 2.2 进程的结构特征

  • 进程由进程控制块PCB、程序段、数据段三部分组成
  • 多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。

2.3 进程与程序的区别

①程序是静态的,进程是动态的。

  • 程序是指令和数据的有序集合,本身没有任何运行的含义
  • 进程是程序在处理机上的一次执行过程

②程序是永久的,进程是暂时的。

  • 程序可以作为一种软件资料长期存在
  • 进程有一定的生命期

③进程具有并发特征,而程序没有

④程序和进程不是一一对应的关系

  • 通过多次执行,一个程序可以对应多个进程
  • 通过调用关系,一个进程可包括多个程序

 3、线程

3.1 线程的概念

一条线程指的是进程中一个单一顺序的控制流。

  • 一个进程中可以并行多个线程,每条线程并行执行不同的任务。
  • 线程是程序执行流的最小单位

三、进程/线程间通讯方式

原文链接:https://blog.csdn.net/qq_42052956/article/details/111499122

(一)进程间通信

1. 无名管道

  • 半双工(数据流向仅有一个方向),具有固定的读端和写端
  • 单向流动:只能用于父进程或兄弟线程之间通信(具有血缘关系的线程之间)
  • 一种特殊文件,可以用普通的read、write函数进行读写,但又不是普通文件,不属于任何其它文件系统,仅存在于内存之中
  • 通信的数据是无格式的流并且大小受限

2. 命名管道(先进先出队列)

不同于无名管道之处在于它提供一个与之关联的路径名,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信,因此,不相关的进程也能通过FIFO交换数据

FIFO 常用于客户 - 服务器应用程序中,FIFO 用作汇聚点,在客户进程和服务器进程之间传递数据。如图

3. 消息队列

  • 消息队列,是消息的链接表,存放在内核中。其中的消息具有特定的格式以及特定的优先级
  • 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
  • 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取

4. 信号量(semaphore)

信号量是一个计数器,用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

5. 共享内存

现代操作系统,对于内存管理,采用的是虚拟内存技术,也就是每个进程都有自己独立的虚拟内存空间,不同进程的虚拟内存映射到不同的物理内存中。所以,即使进程 A 和 进程 B 的虚拟地址是一样的,其实访问的是不同的物理内存地址,对于数据的增删查改互不影响。但是,如果两个进程通过页表将虚拟地址映射到物理地址时,有一部分物理内存重合了,那么这部分重合的内存就是即共享内存,它可以被两个进程同时看到。在共享内存中,一个进程进行写操作,另一个进程进行读操作,这样它们就可以实现进程间通信。但是,我们要确保一个进程在写的时候不能被读,因此我们使用信号量来实现同步与互斥。

共享内存是最快的一种 IPC,因为进程是直接对内存进行存取
因为多个进程可能会同时操作共享的内存,所以需要进行同步
信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问

6. 套接字(socket)

与其他进程通信方式不同,它可用于不同机器间的进程通信

(二)Linux线程间通信

操作系统中线程间的通信有两种情况:

  • 不同进程的线程之间进行通信,由于两个线程只能访问自己所属进程的地址空间和资源,故等同于进程间的通信。(上面已经介绍了)
  • 同一个进程中的两个线程进行通信。由于同一进程中的线程之间有共享内存,因此它们之间的通信是通过共享内存实现的。

(三)Java线程间通信

1、从根本上来说,线程间的通信有两种方式:

  • 一种是在共享内存的并发模型中,线程之间通过读-写共享内存来实现通信(公共状态)
  • 一种是在消息传递的并发模型里,线程之间通过发送消息来进行通信(没有公共状态)

而Java的并发采用的是共享内存模型,所以Java线程之间的通信是基于共享内存实现的。具体来讲Java线程之间的通信由Java内存模型控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。

2、如果线程A与B之间要通信的话,必须经历下面两个步骤:

  • 线程A把本地内存中更新过的共享变量刷新到主内存中去。
  • 线程B到主内存中去读取线程A之前更新过的共享变量。(如图)

现在问题来了,假设共享内存中的共享变量a,如果多个线程同时读a,不会出现任何问题,但是如果这些线程中有至少一个线程对a执行写操作,就可能出现数据不一致问题,也就是线程不安全了,那这是绝对不允许的。怎么办?

答案就是依靠线程同步,来保证即使多个线程并发访问共享变量时,依然能够保证数据一致!

所以,就出现了个各种各样的锁呀,并发工具包呀

四、进程调度算法

  • 先来先服务:是最简单的调度算法,按先后顺序进行调度。
  • 轮转法:是让每个进程在就绪队列中的等待时间与享受服务的时间成正比例。
  • 短作业优先调度算法:是从后备队列中选择一个或若干个估计运行时间最短的作业,将它们调入内存运行。
  • 高优先权优先调度算法:指的是一种在紧迫型作业进入系统后能得到优先处理的计算机算法。
  • 高响应比优先调度算法:是一种对CPU中央控制器响应比的分配的一种算法。既考虑作业等待时间又考虑作业运行时间,既照顾短作业又不使长作业等待时间过长,改进了调度性能。

 五、进程/线程同步方式

  • 互斥量: 采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。
  • 信号量: 它允许同一时刻多个线程来访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。
  • 事件(信号):通过通知操作的方式来保持多线程同步,还可以方便实现多线程优先级的比较作。
  • 临界区:临界区对象和互斥对象非常相似,只是互斥量允许在进程间使用,而临界区只限制与同一进程的各个线程之间使用,但是更节省资源,更有效率。当多个线程访问一个独占性共享资源时,可以使用临界区对象。拥有临界区的线程可以访问被保护起来的资源或代码段,其他线程若想访问,则被挂起,直到拥有临界区的线程放弃临界区为止。

六、进程/线程状态

  • 就绪状态
  • 运行状态
  • 阻塞状态(阻塞被消除后是回到就绪状态,不是运行状态)

七、死锁

参考博文:https://blog.csdn.net/hd12370/article/details/82814348

1、什么是死锁?

        所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。

因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。如下图所示:

2、产生死锁的原因?

① 竞争资源

② 进程间推进顺序非法

  • 若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁。
  • 例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁。

3、产生死锁的4个必要条件

  1. 互斥条件:在一段时间内某资源仅为一进程所占用,不能为多进程使用
  2. 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  4. 环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。比如:线程1等待线程2释放资源,而线程2也在等待线程1释放资源,形成了一个环路。

4、解决死锁的基本方法

预防死锁

  • 资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件
  • 只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件
  • 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件
  • 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件

1、以确定的顺序获得锁

如果必须获取多个锁,那么在设计的时候需要充分考虑不同线程之前获得锁的顺序。按照上面的例子,两个线程获得锁的时序图如下:

 如果此时把获得锁的时序改成:

 那么死锁就永远不会发生。 针对两个特定的锁,开发者可以尝试按照锁对象的hashCode值大小的顺序,分别获得两个锁,这样锁总是会以特定的顺序获得锁,那么死锁也不会发生。问题变得更加复杂一些,如果此时有多个线程,都在竞争不同的锁,简单按照锁对象的hashCode进行排序(单纯按照hashCode顺序排序会出现“环路等待”),可能就无法满足要求了,这个时候开发者可以使用银行家算法,所有的锁都按照特定的顺序获取,同样可以防止死锁的发生,该算法在这里就不再赘述了,有兴趣的可以自行了解一下。

2、超时放弃

当使用synchronized关键词提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。 还是按照之前的例子,时序图如下:

避免死锁

    预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全的状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法
    银行家算法:首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。因此,状态包含两个向量Resource(系统中每种资源的总量)和Available(未分配给进程的每种资源的总量)及两个矩阵Claim(表示进程对资源的需求)和Allocation(表示当前分配给进程的资源)。安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否还处于安全状态。如果是,同意这个请求;如果不是,阻塞该进程知道同意该请求后系统状态仍然是安全的。

检测死锁

  1. 首先为每个进程和每个资源指定一个唯一的号码;
  2. 然后建立资源分配表和进程等待表。

解除死锁

当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:

  • 剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
  • 撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。

死锁检测

1、Jstack命令

jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。 Jstack工具可以用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。

2、JConsole工具

Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者远程的JVM,对运行在Java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大的可视化界面。而且本身占用的服务器内存很小,甚至可以说几乎不消耗。
 

Java写死锁简单示例视频:

八、内存管理

参考博文:https://blog.csdn.net/weixin_38836273/article/details/114224486

1、内存的基础知识

  • 内存是用于存放数据的硬件,程序执行前需要将数据放入内存中才能被CPU处理。
  • 内存通过对内部的存储单元编址来解决并发执行下各个程序的数据区分,根据计算机字长的不同,存储单元大小不同(如64位计算机每个字大小为64位,8个字节)。
  • 内存存放的相对寻址:编译语言编译后的指令寻址,是通过相对地址实现的,实际存入内存时再根据进程在内存中的起始地址得到绝对地址。编译生成的机器语言中的地址实际都是逻辑地址,再通过链接(形成完整逻辑地址)后,通过装入模块装入内存运行,这时将逻辑地址转换成物理地址(主要使用动态重定位装入方法:CPU内有重定位寄存器保存了程序存放内存的起始位置,结合逻辑地址可以实现动态重定位)。

2、内存管理

操作系统是系统资源的管理者,也需要对内存进行管理。

操作系统对内存管理主要从以下方面入手:

  • 提供某种技术从逻辑上对内存空间进行扩充
  • 内存空间的分配与回收
  • 提供地址转换功能,负责程序从逻辑地址到物理地址的转换(动态重定位,依赖于重定位寄存器)
  • 提供内存保护功能,保证各个进程在各自存储空间内运行,互不干扰。

2.1 内存空间扩充

操作系统对内存空间的扩充主要有覆盖技术、交换技术和虚拟存储技术(后面详细介绍):

  • 覆盖技术:用于早期操作系统中。
  • 交换技术:实际就是进程调度中的内存调度,进程在内存和磁盘间动态调度(进程未结束前内存中始终保留PCB信息)。进程从内存中被换出是保存在磁盘的对换区中(这个区域I/O速度比文件区快,详细在文件部分讲解)。

2.2 内存空间的分配与回收**

分页存储管理:将内存空间分为大小相等的小分区,每个分区称为页框,编号从0开始。相应地,将用户进程的地址空间也分为 与页框大小相等 的一个个区域,每个部分称为,编号同样从0开始。(注:进程的最后一个页可能没有一个页框那么大,因此页框不能过大,否则可能产生过大的内部碎片)
        那么分页存储是离散存储的,如何实现逻辑地址到物理地址的转换
将动态重定位的思想使用到分页存储中,首先计算逻辑地址对应的页号(逻辑地址/页面大小)和页内偏移量(逻辑地址%页面大小),得到该页号在内存中存放的起始地址,由此物理地址=页面地址+页内偏移量。而要知道进程的每个页在内存中存放的位置,操作系统需要为每个进程建立一张页表。

分段存储管理:按照程序自身逻辑关系划分为若干个段,每个段有一个段名,每段从0开始编址。每个段在内存中占据连续空间,但各个段之间可以不相邻。与分页存储类似,操作系统在分段存储时为每个进程建立一张段表来保存各个段离散装入内存对应的段长、基址(与分页相似,段表项长度相同,可以通过段表计算段号,所以段号是隐含的不占存储空间)等信息。分段存储管理逻辑地址到物理地址的转换如下图:

 可以看出分段存储中由于每个段的长度都不同,因此对比分页存储多了段内地址越界检查。

分段与分页的对比

  • 页是信息的物理单元对用户不可见,段是信息的逻辑单元对用户可见。
  • 页的大小固定且由系统决定,段的长度不固定且决定于用户编写的程序。
  • 分页的用户进程地址是一维的(只需要一个记忆符),分段的用户进程地址是二维的(需要给出段名和段内地址)。
  • 分段更容易实现信息的共享和保护(只能共享不属于临界资源的代码),当需要让内存中的某个片段共享给多个进程,只需要将各个进程的段表指向同一个段即可。

加快分页过程

  • 如何提高逻辑地址和物理地址的映射速度?(快表)

    系统一旦访问了某一个页,就会在一段时间内稳定工作在这个页上。所以为了提高访问页表的速度,计算机配备了一组能容纳部分页表的硬件寄存器。当系统再次需要将地址转换时,先访问这组硬件寄存器(即,快表)。

  • 页表过大怎么解决?

    页表存在的问题是,页表必须连续存放在多个连续的页框中,页表过大时离散存储失去了其本质意义,所以可以再建一级索引(二级页表)来让原页表连续页表项分组离散存储。

九、局部性原理

参考博主:https://blog.csdn.net/iva_brother/article/details/80463702

1、什么是局部性原理?

一个编写良好的计算机程序,它们倾向于引用邻近于其他最近引用过的数据项的数据项,或者最近引用过的数据项本身,我们称这种程序具有良好的局部性。这种倾向性,我们称之为局部性原理,是一个持久的概念,对于硬件和软件系统的设计和性能都有着极大的影响。

2、局部性的形式

时间局部性空间局部性

  • 在一个具有良好时间局部性的程序中,被引用过一次的存储器在不远的将来会被再次引用。
  • 在一个具有良好空间局部性的程序中,如果一个存储器位置被引用了一次,那么程序很可能在不远的将来引用附近的一个存储器的位置。

3、局部性的应用

  • 硬件层,局部性原理允许计算机设计者通过引入称为高速缓存存储器的小而快速的存储器来保存最近被引用的指令和数据项,从而提高对主存的访问速度。
  • 操作系统级,局部性原理允许系统使用主存作为虚拟地址空间最近被引用块的高速缓存。

4、局部性实例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值