目录
一、什么是操作系统?
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、局部性实例
操作是管理和控制计算机系统中各种硬件和软件资源、合理地组织计算机工作流程的系统软件,是用户与计算机之间的接口。
用户角度:操作系统是一个控制程序
内部:
CPU从硬盘中读取程序到内存中方便CPU读取,此时内存中的可执行程序实例就叫进程。
一个程序若多次读取到内存中,则变成多个独立的进程;而硬盘中多个程序读取到内存中运行时,当然也创建了更多的进程。
内存中任何一个地方都有相应的地址,方便访问;而在内存中的每一个进程内部,都有一个虚拟独立的地址空间 。
进程是程序执行的完整单位,所以大部分时间都是在进程内。在进程内,就可以通过虚拟地址访问;进程间就需要系统调用,访问回很慢。
进程中的程序可分为多段并行的程序段,每个程序段运行时,都有一个程序计数器会记录当前程序执行的位置,会按照程序顺序计算,这里,一个执行流就是一个线程。每个线程会独自运行,并且线程中还有寄存器、堆栈等程序运行时的状态信息。线程间共享的有地址空间、全局变量、打开的文件等信息。
线程是并行的最小单位。
假如进程中只有一个单核的CPU,也就是一次只能执行一个线程,那就需要对每个线程轮流执行,每次单个计算的时间成为一个CPU时间片,实际只有几十毫秒,用户根本感觉不到,对于线程来说,存在等待CPU的状态,成为就绪状态,一旦CPU过来执行,就转变为运行状态,当CPU转而执行其他线程时,又转变成就绪状态。假如程序正在执行中,程序向硬盘发送访问请求,然后等待,这时CPU就变成空转了,所以,线程就变成阻塞状态,CPU转而执行其他线程,等待硬盘的数据回复,线程从阻塞状态变回就绪状态,等待CPU的再次光临,然后继续执行。
那如果是多个CPU,确实可以让多个线程并行计算。但是,往往线程有很多,所以还是需要时间片轮转。那么,为了简化,CPU在内核中为每个线程提供虚拟CPU,每个线程会以为自己独占这CPU,他们就不需要考虑时间片轮转的问题了
①程序是静态的,进程是动态的。
②程序是永久的,进程是暂时的。
③进程具有并发特征,而程序没有
④程序和进程不是一一对应的关系
一条线程指的是进程中一个单一顺序的控制流。
原文链接:https://blog.csdn.net/qq_42052956/article/details/111499122
不同于无名管道之处在于它提供一个与之关联的路径名,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信,因此,不相关的进程也能通过FIFO交换数据
FIFO 常用于客户 - 服务器应用程序中,FIFO 用作汇聚点,在客户进程和服务器进程之间传递数据。如图
信号量是一个计数器,用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
现代操作系统,对于内存管理,采用的是虚拟内存技术,也就是每个进程都有自己独立的虚拟内存空间,不同进程的虚拟内存映射到不同的物理内存中。所以,即使进程 A 和 进程 B 的虚拟地址是一样的,其实访问的是不同的物理内存地址,对于数据的增删查改互不影响。但是,如果两个进程通过页表将虚拟地址映射到物理地址时,有一部分物理内存重合了,那么这部分重合的内存就是即共享内存,它可以被两个进程同时看到。在共享内存中,一个进程进行写操作,另一个进程进行读操作,这样它们就可以实现进程间通信。但是,我们要确保一个进程在写的时候不能被读,因此我们使用信号量来实现同步与互斥。
共享内存是最快的一种 IPC,因为进程是直接对内存进行存取
因为多个进程可能会同时操作共享的内存,所以需要进行同步
信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问
与其他进程通信方式不同,它可用于不同机器间的进程通信
操作系统中线程间的通信有两种情况:
1、从根本上来说,线程间的通信有两种方式:
而Java的并发采用的是共享内存模型,所以Java线程之间的通信是基于共享内存实现的。具体来讲Java线程之间的通信由Java内存模型控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。
2、如果线程A与B之间要通信的话,必须经历下面两个步骤:
现在问题来了,假设共享内存中的共享变量a,如果多个线程同时读a,不会出现任何问题,但是如果这些线程中有至少一个线程对a执行写操作,就可能出现数据不一致问题,也就是线程不安全了,那这是绝对不允许的。怎么办?
答案就是依靠线程同步,来保证即使多个线程并发访问共享变量时,依然能够保证数据一致!
所以,就出现了个各种各样的锁呀,并发工具包呀
参考博文:https://blog.csdn.net/hd12370/article/details/82814348
所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。如下图所示:
1、以确定的顺序获得锁
如果必须获取多个锁,那么在设计的时候需要充分考虑不同线程之前获得锁的顺序。按照上面的例子,两个线程获得锁的时序图如下:
如果此时把获得锁的时序改成:
那么死锁就永远不会发生。 针对两个特定的锁,开发者可以尝试按照锁对象的hashCode值大小的顺序,分别获得两个锁,这样锁总是会以特定的顺序获得锁,那么死锁也不会发生。问题变得更加复杂一些,如果此时有多个线程,都在竞争不同的锁,简单按照锁对象的hashCode进行排序(单纯按照hashCode顺序排序会出现“环路等待”),可能就无法满足要求了,这个时候开发者可以使用银行家算法,所有的锁都按照特定的顺序获取,同样可以防止死锁的发生,该算法在这里就不再赘述了,有兴趣的可以自行了解一下。
2、超时放弃
当使用synchronized关键词提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。 还是按照之前的例子,时序图如下:
预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全的状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。
银行家算法:首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。因此,状态包含两个向量Resource(系统中每种资源的总量)和Available(未分配给进程的每种资源的总量)及两个矩阵Claim(表示进程对资源的需求)和Allocation(表示当前分配给进程的资源)。安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否还处于安全状态。如果是,同意这个请求;如果不是,阻塞该进程知道同意该请求后系统状态仍然是安全的。
当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:
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
操作系统是系统资源的管理者,也需要对内存进行管理。
操作系统对内存管理主要从以下方面入手:
- 提供某种技术从逻辑上对内存空间进行扩充
- 内存空间的分配与回收
- 提供地址转换功能,负责程序从逻辑地址到物理地址的转换(动态重定位,依赖于重定位寄存器)
- 提供内存保护功能,保证各个进程在各自存储空间内运行,互不干扰。
操作系统对内存空间的扩充主要有覆盖技术、交换技术和虚拟存储技术(后面详细介绍):
分页存储管理:将内存空间分为大小相等的小分区,每个分区称为页框,编号从0开始。相应地,将用户进程的地址空间也分为 与页框大小相等 的一个个区域,每个部分称为页,编号同样从0开始。(注:进程的最后一个页可能没有一个页框那么大,因此页框不能过大,否则可能产生过大的内部碎片)
那么分页存储是离散存储的,如何实现逻辑地址到物理地址的转换?
将动态重定位的思想使用到分页存储中,首先计算逻辑地址对应的页号(逻辑地址/页面大小)和页内偏移量(逻辑地址%页面大小),得到该页号在内存中存放的起始地址,由此物理地址=页面地址+页内偏移量。而要知道进程的每个页在内存中存放的位置,操作系统需要为每个进程建立一张页表。
分段存储管理:按照程序自身逻辑关系划分为若干个段,每个段有一个段名,每段从0开始编址。每个段在内存中占据连续空间,但各个段之间可以不相邻。与分页存储类似,操作系统在分段存储时为每个进程建立一张段表来保存各个段离散装入内存对应的段长、基址(与分页相似,段表项长度相同,可以通过段表计算段号,所以段号是隐含的不占存储空间)等信息。分段存储管理逻辑地址到物理地址的转换如下图:
可以看出分段存储中由于每个段的长度都不同,因此对比分页存储多了段内地址越界检查。
分段与分页的对比:
加快分页过程:
系统一旦访问了某一个页,就会在一段时间内稳定工作在这个页上。所以为了提高访问页表的速度,计算机配备了一组能容纳部分页表的硬件寄存器。当系统再次需要将地址转换时,先访问这组硬件寄存器(即,快表)。
页表存在的问题是,页表必须连续存放在多个连续的页框中,页表过大时离散存储失去了其本质意义,所以可以再建一级索引(二级页表)来让原页表连续页表项分组离散存储。
参考博主:https://blog.csdn.net/iva_brother/article/details/80463702
一个编写良好的计算机程序,它们倾向于引用邻近于其他最近引用过的数据项的数据项,或者最近引用过的数据项本身,我们称这种程序具有良好的局部性。这种倾向性,我们称之为局部性原理,是一个持久的概念,对于硬件和软件系统的设计和性能都有着极大的影响。
时间局部性和空间局部性。