0%

Java内存模型

简介

Java 内存模型主要由以下三部分构成:1 个主内存、n 个线程、n 个工作内存(与线程一一对应),数据就在它们三者之间来回倒腾。

依靠Java提供的8个原子操作:lockunlockreadloaduseassignstorewrite,其操作流程示意图如下:

1

一个变量从主内存拷贝到工作内存,再从工作内存同步回主内存的流程为:

|主内存| -> read -> load -> |工作内存| -> use -> |Java线程| -> assign -> |工作内存| -> store -> write -> |主内存|

Java 内存模型中的 8 个原子操作

  • lock:作用于主内存,把一个变量标识为一个线程独占状态。
  • unlock:作用于主内存,释放一个处于锁定状态的变量。
  • read:作用于主内存,把一个变量的值从主内存传输到线程工作内存中,供之后的 load 操作使用。
  • load:作用于工作内存,把 read 操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use:作用于工作内存,把工作内存中的一个变量传递给执行引擎,虚拟机遇到使用变量值的字节码指令时会执行。
  • assign:作用于工作内存,把一个从执行引擎得到的值赋给工作内存的变量,虚拟机遇到给变量赋值的字节码指令时会执行。
  • store:作用于工作内存,把工作内存中的一个变量传送到主内存中,供之后的 write 操作使用。
  • write:作用于主内存,把 store 操作从工作内存中得到的变量值存入主内存的变量中。

8 个原子操作的执行规则

有关变量拷贝过程的规则

  • 不允许 readloadstorewrite 单独出现
  • 不允许线程丢弃它最近的 assign 操作,即工作内存变化之后必须把该变化同步回主内存中
  • 不允许一个线程在没有 assign 的情况下将工作内存同步回主内存中,也就是说,只有虚拟机遇到变量赋值的字节码时才会将工作内存同步回主内存
  • 新的变量只能从主内存中诞生,即不能在工作内存中使用未被 loadassign 的变量,一个变量在 usestore 前一定先经过了 loadassign

有关加锁的规则

一个变量在同一时刻只允许一个线程对其进行 lock 操作,但是可以被一个线程多次 lock(锁的可重入)
对一个变量进行 lock 操作会清空这个变量在工作内存中的值,然后在执行引擎使用这个变量时,需要通过 assign 或 load 重新对这个变量进行初始化
对一个变量执行 unlock 前,必须将该变量同步回主内存中,即执行 store 和 write 操作
一个变量没有被 lock,就不能被 unlock,也不能去 unlock一个被其他线程 lock 的变量

可见性问题 -> 有序性问题

通过上图可以发现,Java 线程只能操作自己的工作内存,其对变量的所有操作(读取、赋值等)都必须在工作内存中进行,不能直接读写主内存中的变量。这就有可能会导致可见性问题:

  • 因为对于主内存中的变量 A,其在不同的线程的工作内存中可能存在不同的副本 A1、A2、A3。
  • 不同线程的 read 和 load、store 和 write 不一定是连续执行的,中间可以插入其他命令。Java 只能保证 read 和 load、store 和 write 的执行对于一个线程而言是连续的,但是并不保证不同线程的 read 和 load、store 和 write 的执行是连续的

    Happens-Before 规则

根据语义,Happens-Before,就是即便是对于不同的线程,前面的操作也应该发生在后面操作的前面,也就是说,Happens-Before 规则保证:前面的操作的结果对后面的操作一定是可见的。
Happens-Before 规则本质上是一种顺序约束规范,用来约束编译器的优化行为。就是说,为了执行效率,我们允许编译器的优化行为,但是为了保证程序运行的正确性,我们要求编译器优化后需要满足 Happens-Before 规则。
根据类别,我们将 Happens-Before 规则分为了以下 4 类:

  • 操作的顺序:
    程序顺序规则: 如果代码中操作 A 在操作 B 之前,那么同一个线程中 A 操作一定在 B 操作前执行,即在本线程内观察,所有操作都是有序的。
    传递性: 在同一个线程中,如果 A 先于 B ,B 先于 C 那么 A 必然先于 C。
  • 锁和 volatile:
    监视器锁规则: 监视器锁的解锁操作必须在同一个监视器锁的加锁操作前执行。
    volatile 变量规则: 对 volatile 变量的写操作必须在对该变量的读操作前执行,保证时刻读取到这个变量的最新值。
  • 线程和中断:
    线程启动规则: Thread#start() 方法一定先于该线程中执行的操作。
    线程结束规则: 线程的所有操作先于线程的终结。
    中断规则: 假设有线程 A,其他线程 interrupt A 的操作先于检测 A 线程是否中断的操作,即对一个线程的 interrupt() 操作和 interrupted() 等检测中断的操作同时发生,那么 interrupt() 先执行。

    volatile 的实现原理

线程池的使用

ThreadPoolExecutor

ThreadPoolExecutor 是线程池的核心实现类,用来执行被提交的任务。一般通过 Executors 工具类创建,我们可以通过 Executor 创建如下三种 ThreadPoolExecutor:

  • FixedThreadPool
  • CacheThreadPool
  • SingleThreadExecutor

首先,我们需要介绍一下 ThreadPoolExecutor 的构造方法,因为以上三种 ThreadPoolExecutor 其实都是被赋予了不同的构造参数的 ThreadPoolExecutor 对象。

FixedThreadPool

特点: 固定长度的线程池,每当提交一个任务时就创建一个线程,直到达到线程池的最大数量,
如果某个线程由于发生了未预期的 Exception 而结束,那么线程池会补充一个新的线程。

CacheThreadPool

特点: 可缓存的线程池,如果线程池的当前规模超过了处理需求时,那么将回收空闲的线程,
而当需求增加时,则可以添加新的线程,线程池的规模不存在任何限制

注意:

  • 池中不会有空闲线程,也不会有等待的线程
  • 一旦任务到达的速度大于线程池处理任务的速度,就会创建一个新的线程给任务
  • 与另外两个线程池不同的地方在于,这个工作队列并不是用来放还没有执行的任务的,
    而是用来放执行过任务后空闲下的线程的,空闲下来的线程会被:SynchronousQueue#poll(keepAliveTime, TimeUnit.NANOSECONDS) poll 到工作队列中等待 60s,如果这 60s 有新的任务到达了,这个线程就被派出去执行任务,如果没有,就销毁。

SingleThreadPool

Future 接口 & FutureTask 实现类

Runnable 接口 & Callable 接口

Executor 的生命周期

Java并发机制的底层实现

volatile的应用

​ 在多线程并发编程中synchronized和volatile都扮演着重要的角色,volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。本文将深入分析在硬件层面上Intel处理器是如何实现volatile的,通过深入分析帮助我们正确地使用volatile变量。

​ 有volatile变量修饰的共享变量进行写操作的时候会多出第二行汇编代码,通过查IA-32架构软件开发者手册可知,Lock前缀的指令在多核处理器下会引发了两件事情 [1]

1)将当前处理器缓存行的数据写回到系统内存。

2)这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。

​ 为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完不知道何时会写到内存。如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题。所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

volatile的两条实现原则。

​ 1)Lock前缀指令会引起处理器缓存回写到内存 。

​ 2)一个处理器的缓存回写到内存会导致其他处理器的缓存无效 。

synchronized的实现原理与应用

在多线程并发编程中synchronized一直是元老级角色,很多人都会称呼它为重量级锁。但是,随着Java SE 1.6对synchronized进行了各种优化之后,有些情况下它就并不那么重了。本文详细介绍Java SE 1.6中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁,以及锁的存储结构和升级过程。

先来看下利用synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式。

·对于普通同步方法,锁是当前实例对象。

·对于静态同步方法,锁是当前类的Class对象。

·对于同步方法块,锁是Synchonized括号里配置的对象。

当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。那么锁到底存在哪里呢?锁里面会存储什么信息呢?

从JVM规范中可以看到Synchonized在JVM里的实现原理,JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步是使用另外一种方式实现的,细节在JVM规范里并没有详细说明。但是,方法的同步同样可以使用这两个指令来实现。

monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。

Java对象头

synchronized用的锁是存在Java对象头里的。如果对象是数组类型,则虚拟机用3个字宽(Word)存储对象头,如果对象是非数组类型,则用2字宽存储对象头。在32位虚拟机中,1字宽等于4字节,即32bit↓

1

Java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位。32位JVM的Mark Word的默认存储结构↓

1

在运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。Mark Word可能变化为存储以下4种数据↓

1

在64位虚拟机下,Mark Word是64bit大小的↓

1

锁的升级与对比

偏向锁

HotSpot [1] 的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

(1)偏向锁的撤销

偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态;如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。

(2)关闭偏向锁

偏向锁在Java 6和Java 7里是默认启用的,但是它在应用程序启动几秒钟之后才激活,如有必要可以使用JVM参数来关闭延迟:-XX:BiasedLockingStartupDelay=0。如果你确定应用程序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,那么程序默认会进入轻量级锁状态。

轻量级锁

(1)轻量级锁加锁

线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

(2)轻量级锁解锁

轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。

因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争。

锁的优缺点对比

1

原子操作的实现原理

1

处理器如何实现原子操作

32位IA-32处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。首先处理器会自动保证基本的内存操作的原子性。处理器保证从系统内存中读取或者写入一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器不能访问这个字节的内存地址。Pentium 6和最新的处理器能自动保证单处理器对同一个缓存行里进行16/32/64位的操作是原子的,但是复杂的内存操作处理器是不能自动保证其原子性的,比如跨总线宽度、跨多个缓存行和跨页表的访问。但是,处理器提供总线锁定和缓存锁定两个机制来保证复杂内存操作的原子性。

(1)使用总线锁保证原子性

(2)使用缓存锁保证原子性

但是有两种情况下处理器不会使用缓存锁定。

第一种情况是:当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行(cache line)时,则处理器会调用总线锁定。

第二种情况是:有些处理器不支持缓存锁定。对于Intel 486和Pentium处理器,就算锁定的内存区域在处理器的缓存行中也会调用总线锁定。

Java如何实现原子操作

(1)使用循环CAS实现原子操作

(2)CAS实现原子操作的三大问题

  • ABA问题
  • 循环时间长开销大
  • 只能保证一个共享变量的原子操作

(3)使用锁机制实现原子操作

mybatis和mybatis-plus配置问题

在调研ShardingJdbc做分库分表底层是否适用mybatis的时候,
突然mapper文件扫描不到了。
出现了Property ‘mapperLocations‘ was not specified的bug
初始配置如下

1
2
3
4
5
6
7
8
9
10
mybatis:
type-aliases-package: com.example.demo.domain # ����POJO�����ڰ�·��
mapper-locations: classpath:mapper/*.xml # mapperӳ���ļ�
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
cache-enabled: true
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

因为使用了mybatis-plus所以mybatis-plus也要加上mapper路径.
查看mybatis-plus的locations源码,发现默认在
classpath*:/mapper/**/*.xml下面
需要做修改

1
2
3
4
5
6
7
8
9
10
11
12
13
mybatis:
type-aliases-package: "com.example.demo.box.pojo"
mapper-locations: "classpath:mybatis/mapper/*.xml"
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
cache-enabled: true
check-config-location: true
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mybatis/mapper/*.xml
type-aliases-package: com.example.demo.box.pojo

mybatis-plus源码↓

1
private String[] mapperLocations = new String[]{"classpath*:/mapper/**/*.xml"};

字符串类

拼接

CONCAT

CONCAT函数用于连接两个字符串,形成一个字符串。

1
2
3
4
5
6
SQL> SELECT CONCAT('FIRST ', 'SECOND');
+----------------------------+
| CONCAT('FIRST ', 'SECOND') |
+----------------------------+
| FIRST SECOND |
+----------------------------+

GROUP_CONCAT

GROUP_CONCAT()函数将组中的字符串连接成为具有各种选项的单个字符串。

1
group_concat([DISTINCT] 要连接的字段 [Order BY  排序字段 ASC/DESC] [Separator '分隔符'])

例题:1484. 按日期分组销售产品!
编写一个 SQL 查询来查找每个日期、销售的不同产品的数量及其名称。
每个日期的销售产品名称应按词典序排列。
返回按sell_date排序的结果表。

1
2
3
select sell_date ,COUNT(DISTINCT product)  as num_sold ,
GROUP_CONCAT(DISTINCT product ORDER BY product ASC SEPARATOR ',') products
from Activities group by sell_date

##查找

LOCATE(substr,str)

LOCATE(substr,str)
第一个语法返回字符串 str中子字符串substr的第一个出现位置。
如若substr 不在str中,则返回值为0。

LEFT(str,length)

LEFT()函数接受两个参数:

  • str是要提取子字符串的字符串。
  • length是一个正整数,指定将从左边返回的字符数。

LEFT()函数返回str字符串中最左边的长度字符。如果str或length参数为NULL,则返回NULL值。
如果length为0或为负,则LEFT函数返回一个空字符串。如果length大于str字符串的长度,则LEFT函数返回整个str字符串。

操作

UNION

UNION 操作符用于合并两个或多个 SELECT 语句的结果集。
请注意,UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同。
Union:对两个结果集进行并集操作,不包括重复行,同时进行默认规则的排序;

1
2
3
SELECT column_name(s) FROM table_name1
UNION
SELECT column_name(s) FROM table_name2

UNION ALL

Union All:对两个结果集进行并集操作,包括重复行,不进行排序;

1
2
3
SELECT column_name(s) FROM table_name1
UNION ALL
SELECT column_name(s) FROM table_name2

日期

运算

DATEDIFF

DATEDIFF函数接受两个任何有效日期或日期时间值的参数。如果您传递DATETIME或TIMESTAMP值,则DATEDIFF函数仅将日期部分用于计算,并忽略时间部分。
DATEDIFF函数在许多情况下很有用,例如,您可以计算产品需要发送给客户的间隔时间。

1
DATEDIFF(a.recordDate, b.recordDate) = 1

日期相差一天

创建型

单例

Intent
确保一个类只有一个实例,并提供该实例的全局访问点。

Class Diagram
使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。

私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class Singleton {
/*
以下实现中,私有静态变量 uniqueInstance 被延迟实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 uniqueInstance,从而节约资源。

这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 if (uniqueInstance == null) ,并且此时 uniqueInstance 为 null,
那么会有多个线程执行 uniqueInstance = new Singleton(); 语句,这将导致实例化多次 uniqueInstance。
*/
private static Singleton uniqueInstance;
private Singleton(){}
public static Singleton getUniqueInstance(){
if (uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
/*
Ⅱ 饿汉式-线程安全
线程不安全问题主要是由于 uniqueInstance 被实例化多次,采取直接实例化 uniqueInstance 的方式就不会产生线程不安全问题。
但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。
*/
private static Singleton uniqueInstance2 = new Singleton();
/*
Ⅲ 懒汉式-线程安全
只需要对 getUniqueInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了实例化多次 uniqueInstance。

但是当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,
即使 uniqueInstance 已经被实例化了。这会让线程阻塞时间过长,因此该方法有性能问题,不推荐使用。
*/
public static synchronized Singleton getUniqueInstance2(){
if (uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
/*
Ⅳ 双重校验锁-线程安全
uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行,
只有当 uniqueInstance 没有被实例化时,才需要进行加锁。

双重校验锁先判断 uniqueInstance 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。
*/
public static Singleton getUniqueInstance3(){
if (uniqueInstance == null){
synchronized (Singleton.class){
if (uniqueInstance==null){
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
/*
考虑下面的实现,也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下,如果两个线程都执行了 if 语句,
那么两个线程都会进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 uniqueInstance = new Singleton();
这条语句,只是先后的问题,那么就会进行两次实例化。因此必须使用双重校验锁,也就是需要使用两个 if 语句:
第一个 if 语句用来避免 uniqueInstance 已经被实例化之后的加锁操作,而第二个 if 语句进行了加锁,
所以只能有一个线程进入,就不会出现 uniqueInstance == null 时两个线程同时进行实例化操作。
if (uniqueInstance == null) {
synchronized (Singleton.class) {
uniqueInstance = new Singleton();
}
}
*/
/*
Ⅴ 静态内部类实现
当 Singleton 类被加载时,静态内部类 SingletonHolder 没有被加载进内存。
只有当调用 getUniqueInstance() 方法从而触发 SingletonHolder.INSTANCE 时 SingletonHolder 才会被加载,
此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。

这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。
*/
private static class SingletonHolder{
private static final Singleton INSTANCE =new Singleton();
}
public static Singleton getUniqueInstance4(){
return SingletonHolder.INSTANCE;
}

}

简单工厂

Intent
在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。

Class Diagram
简单工厂把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。

这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。客户类往往有多个,如果不使用简单工厂,那么所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public interface Product {
}
public class ConcreteProduct implements Product {
}
public class ConcreteProduct1 implements Product {
}
public class ConcreteProduct2 implements Product {
}
public class Client {

public static void main(String[] args) {
int type = 1;
Product product;
if (type == 1) {
product = new ConcreteProduct1();
} else if (type == 2) {
product = new ConcreteProduct2();
} else {
product = new ConcreteProduct();
}
// do something with the product
}
}
/**
实现
*/
public class SimpleFactory {

public Product createProduct(int type) {
if (type == 1) {
return new ConcreteProduct1();
} else if (type == 2) {
return new ConcreteProduct2();
}
return new ConcreteProduct();
}
}

工厂方法

Intent
定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类。

Class Diagram
在简单工厂中,创建对象的是另一个类,而在工厂方法中,是由子类来创建对象。

下图中,Factory 有一个 doSomething() 方法,这个方法需要用到一个产品对象,
这个产品对象由 factoryMethod() 方法创建。该方法是抽象的,需要由子类去实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public abstract class Factory {
abstract public Product factoryMethod();
public void doSomething() {
Product product = factoryMethod();
// do something with the product
}
}
public class ConcreteFactory extends Factory {
public Product factoryMethod() {
return new ConcreteProduct();
}
}
public class ConcreteFactory1 extends Factory {
public Product factoryMethod() {
return new ConcreteProduct1();
}
}
public class ConcreteFactory2 extends Factory {
public Product factoryMethod() {
return new ConcreteProduct2();
}
}

抽象工厂

Intent
提供一个接口,用于创建 相关的对象家族 。

Class Diagram
抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂方法模式只是用于创建一个对象,这和抽象工厂模式有很大不同。

抽象工厂模式用到了工厂方法模式来创建单一对象,AbstractFactory 中的 createProductA() 和 createProductB() 方法都是让子类来实现,这两个方法单独来看就是在创建一个对象,这符合工厂方法模式的定义。

至于创建对象的家族这一概念是在 Client 体现,Client 要通过 AbstractFactory 同时调用两个方法来创建出两个对象,在这里这两个对象就有很大的相关性,Client 需要同时创建出这两个对象。

从高层次来看,抽象工厂使用了组合,即 Cilent 组合了 AbstractFactory,而工厂方法模式使用了继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class AbstractProductA {
}
public class AbstractProductB {
}
public class ProductA1 extends AbstractProductA {
}
public class ProductA2 extends AbstractProductA {
}
public class ProductB1 extends AbstractProductB {
}
public class ProductB2 extends AbstractProductB {
}
public abstract class AbstractFactory {
abstract AbstractProductA createProductA();
abstract AbstractProductB createProductB();
}
public class ConcreteFactory1 extends AbstractFactory {
AbstractProductA createProductA() {
return new ProductA1();
}

AbstractProductB createProductB() {
return new ProductB1();
}
}
public class ConcreteFactory2 extends AbstractFactory {
AbstractProductA createProductA() {
return new ProductA2();
}

AbstractProductB createProductB() {
return new ProductB2();
}
}
public class Client {
public static void main(String[] args) {
AbstractFactory abstractFactory = new ConcreteFactory1();
AbstractProductA productA = abstractFactory.createProductA();
AbstractProductB productB = abstractFactory.createProductB();
// do something with productA and productB
}
}

生成器

Intent
封装一个对象的构造过程,并允许按步骤构造。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 设计模式的生成器
* 模仿StringBuilder
* @Author 87312
*/
public class AbstractStringBuilder {

protected char[] value;

protected int count;
//构造方法
public AbstractStringBuilder(int capacity){
count=0;
value = new char[capacity];
}

public AbstractStringBuilder append(char c){
ensureCapacityInternal(count+1);
value[count++] = c;
return this;
}

private void ensureCapacityInternal(int minimumCapacity){
if (minimumCapacity - value.length>0){
expandCapacity(minimumCapacity);
}
}

void expandCapacity(int minimumCapacity){
int newCapacity = value.length*2+2;
if (newCapacity - minimumCapacity < 0){
newCapacity = minimumCapacity;
}
if (newCapacity < 0 ){
if (minimumCapacity<0) throw new OutOfMemoryError();// overflow
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value,newCapacity);
}
}

原型模式

Intent
使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象。
一般原型模式写的是深克隆,即克隆出一个完全一样和本地不是一个地址的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class Prototype implements Cloneable{
interface Pro{
Object myClone() throws CloneNotSupportedException;
}
public static class Plane implements Cloneable, Serializable {
private String name; //附件名

private Date data;

public Date getData() {
return data;
}

public void setData(Date data) {
this.data = data;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
public void download() {
System.out.println("下载附件"+name);
}
@Override
public Object clone() throws CloneNotSupportedException{
Object object = super.clone();
// 实现深度克隆(deep clone)
Plane plane = (Plane)object;
plane.data = (Date) this.data.clone();
return object;
}
}
@Test
public void ll() throws CloneNotSupportedException {
Plane plane = new Plane();
plane.setData(new Date());
System.out.println(plane);
System.out.println(new Plane());
System.out.println(plane.clone());
}
}

行为型

责任链

Intent
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链发送该请求,直到有一个对象处理它为止。

Class Diagram
Handler:定义处理请求的接口,并且实现后继链(successor)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public class ChainOfResponsibility {
public class Request {

private RequestType type;
private String name;


public Request(RequestType type, String name) {
this.type = type;
this.name = name;
}


public RequestType getType() {
return type;
}


public String getName() {
return name;
}
}
public enum RequestType {
TYPE1, TYPE2
}
abstract class Handler {

protected Handler successor;


public Handler(Handler successor) {
this.successor = successor;
}


protected abstract void handleRequest(Request request);
}
public class ConcreteHandler1 extends Handler {

public ConcreteHandler1(Handler successor) {
super(successor);
}


@Override
protected void handleRequest(Request request) {
if (request.getType() == RequestType.TYPE1) {
System.out.println(request.getName() + " is handle by ConcreteHandler1");
return;
}
if (successor != null) {
successor.handleRequest(request);
}
}
}
public class ConcreteHandler2 extends Handler {

public ConcreteHandler2(Handler successor) {
super(successor);
}


@Override
protected void handleRequest(Request request) {
if (request.getType() == RequestType.TYPE2) {
System.out.println(request.getName() + " is handle by ConcreteHandler2");
return;
}
if (successor != null) {
successor.handleRequest(request);
}
}
}
@Test
public void test(){
Handler handler1 = new ConcreteHandler1(null);
Handler handler2 = new ConcreteHandler2(handler1);

Request request1 = new Request(RequestType.TYPE1, "request1");
handler2.handleRequest(request1);

Request request2 = new Request(RequestType.TYPE2, "request2");
handler2.handleRequest(request2);

}
}

命令

Intent
将命令封装成对象中,具有以下作用:

使用命令来参数化其它对象
将命令放入队列中进行排队
将命令的操作记录到日志中
支持可撤销的操作
Class Diagram
Command:命令
Receiver:命令接收者,也就是命令真正的执行者
Invoker:通过它来调用命令
Client:可以设置命令与命令的接收者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

/**
* @Author 87312
*/
public class CommandModel {
interface Command {
void execute();
}
public class Light {

public void on() {
System.out.println("Light is on!");
}

public void off() {
System.out.println("Light is off!");
}
}
public class LightOnCommand implements Command {
Light light;

public LightOnCommand(Light light) {
this.light = light;
}

@Override
public void execute() {
light.on();
}
}
public class LightOffCommand implements Command {
Light light;

public LightOffCommand(Light light) {
this.light = light;
}

@Override
public void execute() {
light.off();
}
}
public class Invoker{
private Command[] onCommands;
private Command[] offCommands;
private final int slotNum = 7;
public Invoker() {
this.onCommands = new Command[slotNum];
this.offCommands = new Command[slotNum];
}
public void setOnCommand(Command command, int slot) {
onCommands[slot] = command;
}

public void setOffCommand(Command command, int slot) {
offCommands[slot] = command;
}

public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
}

public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
}
}
@Test
public void test(){
Invoker invoker = new Invoker();
Light light = new Light();
Command lightOnCommand = new LightOnCommand(light);
Command lightOffCommand = new LightOffCommand(light);
invoker.setOnCommand(lightOnCommand, 1);
invoker.setOffCommand(lightOffCommand, 1);
invoker.onButtonWasPushed(1);
invoker.offButtonWasPushed(1);
}
}

解释器

提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。

主要解决:对于一些固定文法构建一个解释句子的解释器。

Implementation
以下是一个规则检验器实现,具有 and 和 or 规则,通过规则可以构建一颗解析树,用来检验一个文本是否满足解析树定义的规则。

例如一颗解析树为 D And (A Or (B C)),文本 “D A” 满足该解析树定义的规则。

这里的 Context 指的是 String。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/**为语言创建解释器,通常由语言的语法和语法分析来定义。
* @Author 87312
*/
public class Interpreter {
public abstract class Expression {
public abstract boolean interpret(String str);
}
public class TerminalExpression extends Expression {

private String literal = null;

public TerminalExpression(String str) {
literal = str;
}

public boolean interpret(String str) {
StringTokenizer st = new StringTokenizer(str);
while (st.hasMoreTokens()) {
String test = st.nextToken();
if (test.equals(literal)) {
System.out.println("执行interpret");
return true;
}
}
System.out.println("执行interpret");
return false;
}
}
public class AndExpression extends Expression {

private Expression expression1 = null;
private Expression expression2 = null;

public AndExpression(Expression expression1, Expression expression2) {
this.expression1 = expression1;
this.expression2 = expression2;
}

public boolean interpret(String str) {
System.out.println("执行interpret");
return expression1.interpret(str) && expression2.interpret(str);
}
}
public class OrExpression extends Expression {
private Expression expression1 = null;
private Expression expression2 = null;

public OrExpression(Expression expression1, Expression expression2) {
this.expression1 = expression1;
this.expression2 = expression2;
}

public boolean interpret(String str) {
System.out.println("执行interpret");
return expression1.interpret(str) || expression2.interpret(str);
}
}
/**
* 构建解析树
*/
public Expression buildInterpreterTree() {
// Literal
Expression terminal1 = new TerminalExpression("A");
Expression terminal2 = new TerminalExpression("B");
Expression terminal3 = new TerminalExpression("C");
Expression terminal4 = new TerminalExpression("D");
// B C
Expression alternation1 = new OrExpression(terminal2, terminal3);
// A Or (B C)
Expression alternation2 = new OrExpression(terminal1, alternation1);
// D And (A Or (B C))
return new AndExpression(terminal4, alternation2);
}
@Test
public void test(){
Expression define = buildInterpreterTree();
String context1 = "D A";
String context2 = "A B";
System.out.println(define.interpret(context1));
System.out.println(define.interpret(context2));
}
}

迭代器

Java 和 .Net 编程环境中非常常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。迭代器模式属于行为型模式。

主要解决:不同的方式来遍历整个整合对象。
分为三步:

  • 1、创建接口:
  • 2、创建实现了接口的实体类。
  • 3、使用.来获取迭代器,并打印名字。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    /**迭代器 提供一种顺序访问聚合对象元素的方法,并且不暴露聚合对象的内部表示。
    * @Author 87312
    */
    public class Iterator {

    public interface Aggregate {
    Iterator2 createIterator();
    }
    public class ConcreteAggregate implements Aggregate {

    private Integer[] items;

    public ConcreteAggregate() {
    items = new Integer[10];
    for (int i = 0; i < items.length; i++) {
    items[i] = i;
    }
    }

    @Override
    public Iterator2 createIterator() {
    return new ConcreteIterator<Integer>(items);
    }
    }
    public interface Iterator2<Item> {

    Item next();

    boolean hasNext();
    }
    public class ConcreteIterator<Item> implements Iterator2 {

    private Item[] items;
    private int position = 0;

    public ConcreteIterator(Item[] items) {
    this.items = items;
    }

    @Override
    public Object next() {
    return items[position++];
    }

    @Override
    public boolean hasNext() {
    return position < items.length;
    }
    }
    @Test
    public void test(){
    ConcreteAggregate aggregate = new ConcreteAggregate();
    Iterator2 iterator = aggregate.createIterator();
    while (iterator.hasNext()){
    System.out.println(iterator.next());
    }
    }
    }

    中介者

    备忘录

    观察者

    状态

    策略

    模板方法

    访问者

    空对象

    结构型

    适配器

    桥接

    组合

    装饰

    外观

    享元

    代理

uwsgi配置

切换conda环境

1
conda activate paddle_ang

img
flask项目目录下新建uwsgi.ini文件

1
vim uwsgi.ini

输入以下内容注意#注释需要去掉,不然可能会报错

1
2
3
4
5
6
7
8
9
10
[uwsgi]
http = 0.0.0.0:3000 #用http与nginx通信
chdir = /aobosaisi/antigener_detection/ #项目根目录
wsgi-file = main.py # 运行的py主程序
callable = app #app名
buffer-size = 65536


stats=%(chdir)/uwsgi.status
pidfile=%(chdir)/uwsgi.pid

nginx配置

修改conf文件
代理添加

1
server 172.19.239.123:3000;

img

启动uwsgi

uwsgi守护进程运行

1
uwsgi -d --ini uwsgi.ini

img
uwsgi关闭

1
uwsgi --stop uwsgi.pid

uwsgi重启

1
uwsgi --reload uwsgi.pid

安装Anaconda

下载Anaconda

去清华镜像下载文件

conda清华镜像

1

下载完成,放到虚拟机

2

安装(以及需要的模块)

在下载目录执行

1
bash Anaconda3-2019.10-Linux-x86_64.sh

看到是否接受,选择yes

3

安装目录

4

初始化

5

ok

6

环境生效

1
source ~/.bashrc

已经是base环境了

7

添加清华源输入如下

1
2
3
4
5
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/msys2/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/

安装需要的模块

更新pip

1
2
pip3 install --upgrade pip
pip install --upgrade pip

8

首先生成需要的环境

1
pip freeze > requirements.txt

然后在虚拟机下载需要的模块

1
pip install -r requirements.txt -i  https://pypi.tuna.tsinghua.edu.cn/simple

下载完成

9

下载uwsgi

1
conda install -c conda-forge uwsgi

11

运行main.py

1
python main.py

10

创建虚拟环境

第一章计算机网络体系结构

学习要求

了解计算机网络概念、组成与功能,计算机网络的分类,计算机网络与互联网的发展历史,计算机网络的标准化工作。了解因特网的组成,理解客户/服务器方式和P2P方式的工作原理。掌握电路交换、报文交换与分组交换的技术特点。掌握评价加算计网络的性能参数,掌握计算机体系结构的OSI和TCP/IP模型以及实体、协议、服务等基本概念。

时延的组成:

  • 发送时延
  • 传播时延
  • 处理时延
  • 排队时延

相关内容

PPT1-73

1

PPT1-93

发送时延

2

PPT1-94

传播时延

3

网络协议,简称协议,是未进行网络总的数据交换而建立的规则、标准或约定。PPT1-111

4

PPT1-112

三要素

5

体系结构PPT1-124

6

第二章 物理层

学习要求

要求掌握物理层的基本功能。了解掌握网络的传输介质的特性,理解信道复用技术的概念和基本原理。

相关内容

信道复用PPT2-43

7

8

9

10

11

12

13

第三章 数据链路层

学习要求

要求掌握数据链路层的三个基本问题:封装成帧、差错控制、透明传输。了解ppp协议的基本原理。掌握CSMA/CD协议的基本原理和特点。掌握局域网扩展的几种方式和层次,理解冲突域的概念。熟悉集线器和交换机的功能,理解交换机的运行机制,掌握交换机转发成帧的过程和交换表的构建方法。

相关内容

争用期PPT3-64

14

15

最短有效帧PPT3-67

16

10BASE-T PPT3-73

17

三个基本问题PPT3-11

18

集线器PPT3-76

19

交换机隔离冲突域,不隔离广播域。

路由器隔离冲突域,隔离广播域。

第四章 网络层

学习要求

要求掌握网络层的基本功能。要求掌握虚拟电路和数据报服务的特点与区别。掌握IP地址及其分类,了解IPv4数据报结构,掌握首部各字段的含义。掌握划分子网及的方法,理解子网掩码的作用,掌握无分类编址CIDR。理解网络层主要协议ARP协议和ICMP协议的原理与特点。了解路由器基本功能以及运用机制,掌握路由表的结构,理解路由转发原理,掌握分组转发的过程。掌握路由选择协议RIP的工作原理和举例向量法,能够使用路由选择算法分析网络路由选择;了解路由选择协议OSPF的原理和链路状态协议的特点;了解BGP路由选择协议。了解下一网络协议IPv6,掌握IPv6基本地址空间以及IPv4向IPv6过度的方法。熟悉私有IP地址的使用及NAT协议的基本原理。

相关内容

协议的作用PPT4-63

20

路由选择协议PPT4-160

21

23

24

25

26

OSPF

27

28

BGP

29

路由表PPT4-103

22

子网的各种计算PPT4-119

30

IPV6PPT4-239

31

第五层 传输层

学习要求

要求掌握传输层的基本功能。理解进程间的通信,了解端口的作用了解UDP协议的基本特点。熟悉TCP协议的主要特点;掌握可靠传输的工作原理及停止-等待协议。连续ARQ协议;掌握可靠传输的实现及滑动窗口协议;理解TCP协议的流量控制与拥塞控制机制。掌握TCP运输连接管理、连接建立和释放的过程与消息交互。

相关内容

TCP、UDPPPT5-14

32

33

34

利用滑动窗口实现流量控制PPT5-112

35

传输效率的计算PPT5-117

36

拥塞控制与流量控制PPT5-124__125

37

38

TCP连接的建立PPT5-178

39

TCP连接的释放PPT5-185

40

第六章 应用层

学习要求

了解DNS、SMTP、POP3、HTTP、DHCP、等应用层协议的概念与技术,掌握DNS地址解析过程,掌握SMTP、POP3、IMAP协议的作用。

相关内容

应用层协议PPT6-2

41

电子邮件的一些标准

42

一、选择题(共30小题,每题1分,共 30 分)
二、名词解释(共5小题,每题2分,共 10 分)
三、简答题:(共5小题,每题 4分,共 20 分)
四、综合计算题(共4小题,每题5分,共 20 分)
五、综合应用题(共2小题,每题10分,共20分)

选择题30道,复习范围:题库

简答题:

如何理解计算机组成和计算机体系结构?P7

1、计算机组成是指如何实现计算机体系结构所体现的属性,他包含了许多对程序员来说是透明的硬件细节;

2、计算机体系结构是程序员所看到的计算机的属性,即概念性结构与功能特性。

冯诺依曼计算机的特点是什么?P8

1.冯·诺依曼计算机主要由五大部件组成,分别是:运算器、控制器、存储器、输入设备和输出设备;

2.冯诺依曼体系结构的指令和数据均采用二进制码表示;

3.指令和数据以同等地位存放于存储器中,均可按地址寻访;

4.指令由操作码和地址码组成,操作码用来表示操作的性质,地址码用来表示操作数所在存储器中的位置;

5.指令在存储器中按顺序存放,通常指令是按顺序执行的,特定条件下,可以根据运算结果或者设定的条件改变执行顺序;

6.机器以运算器为中心,输入输出设备和存储器的数据传送通过运算器。

请指出计算机的5大硬件组成及计算机硬件的3个主要技术指标。P9 P17

控制器:整机的指挥中心,它使计算机的各个部件自动协调工作。

运算器:对数据信息进行处理的部件,用来进行算术运算和逻辑运算。

存储器:存放程序和数据,是计算机实现“存储程序控制”的基础。 

输入设备:将人们熟悉的信息形式转换成计算机可以接受并识别的信息形式的设备。

输出设备:将计算机处理的结果(二进制信息)转换成人类或其它设备可以接收和识别的信息形式的设备
计算机硬件的主要技术指标是:1、机器字长,;2、运算速度;3、存储容量,指存放二进制信息的总位数。CPU一次能处理数据的位数与CPU中的寄存器位数有关。

常见的总线集中控制优先权仲裁方式有哪几种?P57

s常见的集中式总线控制有三种:
链式查询——只需很少几根线就能按一定优先次序实现总线控制,并且很容易扩充设备,但对电路故障很敏感,且优先级别低的设备可能很难获得请求。
计数器定时查询——计数可以从“0”开始,此时一旦设备的优先次序被固定,设备的优先级就按0,1….,n的顺序降序排列,而且固定不变;计数也可以从上一次计数的终止点开始,即是一种循环方法,此时设备使用总线的优先级相等;计数的初始值还可以由程序设置,故优先顺序可以改变。
独立请求方式——响应速度快,优先顺序控制灵活,但控制线数量多,总线控制更复杂。

请写总线周期的4个阶段分别是什么?P59

1)申请分配阶段:由需要使用总线的主模块(或主设备)提出申请,经总线仲裁机构决定将下一传输周期的总线使用权授予某一申请者。也可将此阶段细分为传输请求和总线仲裁两个阶段。
2)寻址阶段:获得使用权的主模块通过总线发出本次要访问的从模块的地址及有关命令,启动参与本次传输的从模块。
3) 传数阶段:主模块和从模块进行数据交换,可单向或双向进行数据传送。
4)结束阶段:主模块的有关信息均从系统总线上撤除,让出总线使用权。

存储系统层次结构主要体现在哪两个存储层次上?分别主要解决什么问题?P71

储存系统层次结构主要体现再缓存——主存和主存——辅存这两个储存层次上。

缓存——主存层次主要解决CPU和主存速度不匹配的问题。主存和缓存之间数据调用是由硬件自动完成的。

主存——辅存层次主要解决储存系统的容量问题。主存和辅存之间的数据调用时由硬件和操作系统共同完成。

动态RAM和静态RAM的区别。P87

原理:动态RAM运用电容存储电荷,通过充放电来保存01代码,充完电为1,否则为0。静态RAM运用触发器原理来保存数据,触发器一端为源端,一端为非端。一端为0,一端为1。

集成度:动态RAM为一个单元电路有一个晶体管和一个电容器,静态RAM一个单元电路拥有六个晶体管。

芯片引用脚:动态RAM行、列分别传输,地址条数少一半。静态RAM行列一起传输,芯片引脚较多。

功耗:动态RAM较小,静态RAM较大。

价格:动态较低,静态较高。

速度:动态RAM需要不断刷新,速度相对较慢,静态RAM速度较快。

一般而言,动态RAM价格较低,集成度较高,容量大,较为适合作为主存,而静态RAM价格较高,速度较快,容量小,比较适合应用于缓存cache。

常用的提高访存速度的区别。P103

寻找高速原件和采用层次结构,调整主存的结构。

1.单体多字系统

每次从存储器中取出4个值或指令放到数据寄存器中,CPU使用时,通过单字长寄存器从其中取出一个即可。

2.多体并行系统

3.高性能存储芯片
(1)SDRAM(同步DRAM)
(2)RDRAM
(3)带Cache的DRAM

输入输出系统的发展大致分几个阶段?P156

四个阶段

—-1、早期阶段

早期的I/O设备种类较少,I/O设备与主存交换信息都必须通过CPU,

—2、接口模块和DMA阶段

这个阶段I/O设备通过接口模块与主机连接,计算机系统采用了总线结构

—3、具有通道结构的阶段

在小型和微型计算机中,采用DMA方式可实现高速I/O设备与主机之间成组数据的交换,但在大中型计算机中,I/O设备配置繁多,数据传送频繁,若仍采用DMA方式会出现一系列问题。① 如果每台I/O设备都配置专用的DMA接口,不仅增加了硬件成本,而且为了解决众多DMA接口同时访问主存的冲突问题,会使控制变得十分复杂。②CPU需要对众多的DMA接口进行管理,同样会占用CPU的工作时间,而且因频繁地进入周期挪用阶段,也会直接影响CPU的整体工作效率。因此在大中型计算机系统中,采用I/O通道的方式来进行数据交换。
—4、具有I/O处理机的阶段

输入输出系统发展到第四阶段,出现了I/O处理机。I/O处理机又称为外围处理机(Peripheral Processor),它基本独立于主机工作,既可完成I/O通道要完成的I/O控制,又可完成码制变换、格式处理、数据块检错、纠错等操作。具有I/O处理机的输入输出系统与CPU工作的并行性更高,这说明I/O系统对主机来说具有更大的独立性。

简述I/O设备与主机信息传输的控制方式。P162

1.程序查询方式:
这种方式的特点是主机与I/O串行工作。当CPU启动I/O后,时刻查询I/O是否准备好,若设备准备就绪,CPU便转入处理I//O与主机间传送信息的程序;若设备未做好准备,则CPU反复查询,“踏步”等待直到I/O准备就绪为止。这种方式CPU效率很低。

2.程序中断方式:
这种方式的特点是主机与I/O并行工作。当CPU启动I/O后,不必时刻查询I/O是否准备好,而是继续执行程序。当I/O准备就绪时,向CPU发中断请求信号,CPU在适当的时候响应I/O的中断请求,暂停现行程序为I/O服务。这种方式消除了“踏步”现象,提高了CPU的效率。

3.DMA方式:
这种方式的特点是主机与I/O并行工作,主存和I/O之间有一条直接数据通路。CPU启动I/O后,不必查询I/O是否准备好,当I/O准备就绪后,发出DMA请求,此时CPU不直接参与I/O和主存间的信息交换,只是把外部总线 (地址线、数据线及有关控制线) 的使用权暂时交赋予DMA,仍然可以完成自身内部的操作 (如加法、移位等),故不必中断现行程序,只需暂停一个存取周期访存 (即周期挪用),CPU的效率更高。

指令一般是由哪两部分组成的?各部分的作用分别是什么?P300

指令通常有操作码和地址码两部分组成,操作码指出指令应该执行什么性质的操作和具有何种功能;地址码指出指令中操作数所在的存储器地址、寄存器地址或I/O地址。

相比CISC机,RISC机的主要优点有哪些?P333

复杂指令集计算机CISC结构主要优点是:
1.指令丰富,功能强大
2.寻址方式灵活。
3.以微程序控制器为核心,指令存储器与数据存储器共享同一个物理存储空间,性能强大。

精简指令集计算机RISC结构主要优点是:
1.具备结构简单、易于设计
2.指令精简,使用率均衡
3.程序执行效率高

构成CPU的主要四个部件分别是什么?P338

中央处理器主要包括运算器(算术逻辑运算单元,ALU,Arithmetic Logic Unit)和寄存器、中断系统、控制单元CU)。

影响流水线性能的因素有哪些?P349

结构相关(资源冲突)

结构相关是当指令在重叠执行过程中,不同指令征用同一功能部件产生资源冲突时产生的,故又有资源相关之称。

数据相关(数据冲突)

数据相关是流水线中的各条指令因重复操作,可能改变对操作数的读写访问顺序,从而导致了数据相关冲突。

控制相关(控制冲突)

控制相关主要是由转移指令引起的。当流水线遇到转移指令和其他改变pc值的指令而造成断流时,会引起控制相关。

简述组合逻辑设计控制单元的步骤。P401

1.列出操作时间表

2.写出微操作最简表达式

3.画出逻辑图

简述微程序设计控制单元的步骤。P415

1、 写出对应机器指令的微操作命令及节拍安排。

  • (1)取指阶段的微操作及节拍安排
  • (2)执行阶段的微操作及节拍安排

2、确定微指令格式

  • (1)微指令的编码方式
  • (2)后续微指令地址的形成方式
  • (3)微指令字长

3、编写微指令码点

名词解释:

1.7. 解释下列概念:

主机、CPU、主存、存储单元、存储元件、存储基元、存储元、存储字、存储字长、存储容量、机器字长、指令字长、寻址方式、存储器带宽、总线周期。

解:P9-10
主机:是计算机硬件的主体部分,由CPU和主存储器MM合成为主机。

CPU:中央处理器,是计算机硬件的核心部件,由运算器和控制器组成;(早期的运算器和控制器不在同一芯片上,现在的CPU内除含有运算器和控制器外还集成了CACHE)。
主存:计算机中存放正在运行的程序和数据的存储器,为计算机的主要工作存储器,可随机存取;由存储体、各种逻辑部件及控制电路组成。
存储单元:可存放一个机器字并具有特定存储地址的存储单位。
存储元件:存储一位二进制信息的物理元件,是存储器中最小的存储单位,又叫存储基元或存储元,不能单独存取。
存储字:一个存储单元所存二进制代码的逻辑单位。
存储字长:一个存储单元所存储的二进制代码的总位数。
存储容量:存储器中可存二进制代码的总量;(通常主、辅存容量分开描述)。
机器字长:指CPU一次能处理的二进制数据的位数,通常与CPU的寄存器位数有关。
指令字长:机器指令中二进制代码的总位数。

寻址方式:是指确定本条指令的数据地址以及下一条将要执行的指令地址的方法,与硬件结构紧密相关,而且直接影响指令格式和指令功能。

存储器带宽:存储器的带宽指单位时间内从存储器进出信息的最大数量。

总线周期:指令执行的一个阶段中的,对外部其它资源(内存,I/O设备)进行一次访问的时间。

1.8. 解释下列英文缩写的中文含义:

CPU、PC、IR、CU、ALU、ACC、MQ、X、MAR、MDR、I/O、MIPS、CPI、CMDR、FLOPS

解:全面的回答应分英文全称、中文名、功能三部分。
CPU:Central Processing Unit,中央处理机(器),是计算机硬件的核心部件,主要由运算器和控制器组成。
PC:Program Counter,程序计数器,其功能是存放当前欲执行指令的地址,并可自动计数形成下一条指令地址。
IR:Instruction Register,指令寄存器,其功能是存放当前正在执行的指令。
CU:Control Unit,控制单元(部件),为控制器的核心部件,其功能是产生微操作命令序列。
ALU:Arithmetic Logic Unit,算术逻辑运算单元,为运算器的核心部件,其功能是进行算术、逻辑运算。
ACC:Accumulator,累加器,是运算器中既能存放运算前的操作数,又能存放运算结果的寄存器。
MQ:Multiplier-Quotient Register,乘商寄存器,乘法运算时存放乘数、除法时存放商的寄存器。
X:此字母没有专指的缩写含义,可以用作任一部件名,在此表示操作数寄存器,即运算器中工作寄存器之一,用来存放操作数;
MAR:Memory Address Register,存储器地址寄存器,在主存中用来存放欲访问的存储单元的地址。
MDR:Memory Data Register,存储器数据缓冲寄存器,在主存中用来存放从某单元读出、或要写入某存储单元的数据。
I/O:Input/Output equipment,输入/输出设备,为输入设备和输出设备的总称,用于计算机内部和外界信息的转换与传送。
MIPS:Million Instruction Per Second,每秒执行百万条指令数,为计算机运算速度指标的一种计量单位。

总线:是一种能由多个部件分时共享的公共信息传送线路。
总线宽度:通常指数据总线的根数;
总线带宽:总线的数据传输率,指单位时间内总线上传输数据的位数;
总线复用:指同一条信号线可以分时传输不同的信号。
总线的主设备(主模块):指一次总线传输期间,拥有总线控制权的设备(模块);
总线的从设备(从模块):指一次总线传输期间,配合主设备完成数据传输的设备(模块),它只能被动接受主设备发来的命令;
总线周期:通常指完成一次总线操作的时间;
总线的通信控制:指总线传送过程中双方的时间配合方式。

辅存:辅助存储器,用于存放当前暂不执行的程序和数据,以及一些需要永久保存的信息。
Cache:高速缓冲存储器,介于CPU和主存之间,用于解决CPU和主存之间速度不匹配问题。
RAM:半导体随机存取存储器,主要用作计算机中的主存。
SRAM:静态半导体随机存取存储器。
DRAM:动态半导体随机存取存储器。
ROM:掩膜式半导体只读存储器。由芯片制造商在制造时写入内容,以后只能读出而不能写入。
PROM:可编程只读存储器,由用户根据需要确定写入内容,只能写入一次。
EPROM:紫外线擦写可编程只读存储器。需要修改内容时,现将其全部内容擦除,然后再编程。擦除依靠紫外线使浮动栅极上的电荷泄露而实现。
EEPROM:电擦写可编程只读存储器。
CDROM:只读型光盘。
Flash Memory:闪速存储器。或称快擦型存储器。

CPI:执行一条指令所需要的时钟周期数 = 总时钟周期数/IC;IC:总指令数

CMDR:控制存储器地址寄存器

  1. 说明存取周期和存取时间的区别。
    解:存取周期和存取时间的主要区别是:存取时间仅为完成一次操作的时间,而存取周期不仅包含操作时间,还包含操作后线路的恢复时间。即:
    存取周期 = 存取时间 + 恢复时间
  2. 什么是存储器的带宽?若存储器的数据总线宽度为32位,存取周期为200ns,则存储器的带宽是多少?
    解:存储器的带宽指单位时间内从存储器进出信息的最大数量。
    存储器带宽 = 1/200ns ×32位 = 160M位/秒 = 20MB/秒 = 5M字/秒
    注意:字长32位,不是16位。(注:1ns=10-9s)

机器指令:是人们习惯把每一条机器语言的语句称为机器指令。
指令系统:全部机器指令的集合称为机器的指令系统。

寻址方式:确定本条指令的数据地址以及下一条将要执行的指令地址的方法。

向量地址:是硬件电路(向量编码器)产生的中断源的内存地址编号;
中断入口地址:是中断服务程序首址。
中断向量地址和入口地址的联系: 中断向量地址可理解为中断服务程序入口地址指示器(入口地址的地址),通过它访存可获得中断服务程序入口地址。 (两种方法:在向量地址所指单元内

指令周期:CPU每取出并执行一条指令所需的全部时间。
机器周期:是在同步控制的机器中,执行指令周期中一步相对完整的操作(指令
步)所需时间,通常安排机器周期长度等于主存周期;
时钟周期:是指计算机主时钟的周期时间,它是计算机运行时最基本的时序单位,
对应完成一个微操作所需时间,通常时钟周期等于计算机主频的倒数。

大题6—7个: 扩容、磁盘、cache、十进制的整数、小数化成二进制数,求真值的原码反码补码,求相反数的补码,求两数相加或减的补码计算过程,7章例题,9.1 、 10.1这些大题必须会。

第一章 引论

操作系统定义。现代操作系统通常向用户提供三种接口。

操作系统定义:

操作系统是控制和管理计算机系统内各种硬件和软件资源,有效组织多道程序运行的系统软件(或程序集合),是用户与计算机之间的接口。

OS为用户提供三种接口:

  • 图形用户接口(GUI)——图形界面
    • 鼠标、窗口、菜单、图标、工具条。
  • 命令行接口——命令界面
    • 键盘输入命令,由命令解释程序(比如unix中的shell)接收并解释,传递给OS内部程序执行。
  • 程序接口——系统调用接口
    • 系统调用是OS内核与用户程序、应用程序之间的接口,位于OS系统的最外层(Linux以C函数形式出现)。所有内核之外的程序都必须经由系统调用才能获得OS的服务。
    • Unix/Linux中:fork()、sleep()、wakeup()、exit()

系统执行原语操作禁止中断。

一般来说,操作系统有4种结构。

1 整体结构 2 虚拟机结构 3 层次结构 4 客户-服务器结构

操作系统也叫做虚拟机。

分时系统、实时系统概念。

分时系统:一台计算机连接多个终端的计算机系统。

实时系统是指计算机能及时响应外部事件的请求,在规定时间内完成对该事件的处理,并控制所有设备和任务协调一致工作的操作系统。

特征
同时性:若干用户可以同时上机使用计算机系统
交互性:人机对话方便
独立性:各终端彼此独立工作,互不干扰
及时性:用户在很短时间内得到系统响应

实时系统特征:实时性(先实时、后效率)、高可靠性、容错能力强

多道批处理系统两大特征是, 并发和共享(多道和成批)。

操作系统的四大功能是:存储器管理,设备管理,文件管理和处理器管理。

处理机的核心态和用户态概念、做课后习题。(13.只在核心态下执行的指令有:①屏蔽所有中断。③设置时钟日期。⑤启动打印机。⑥清内存。)

核心态(系统态、管理态)
执行os程序时所处的状态。此时有较高特权,可执行一切指令。

用户态
执行用户程序时所处的状态。权限较低,只能执行指令集中的非特权指令。

第二章 进程和线程

若一个信号量的初值经过多次P、V操作,正值和负值的含义。

创建进程要完成的操作。

进程创建的过程
(1)申请一个空闲的PCB
(2)为新进程分配资源
(3)将新进程的PCB初始化
(4)将新进程加到就绪队列中

理解进程状态转换图。

1

进程的状态:

  • 运行状态
    • 进程正在被执行,占用处理资源;处于此状态的进程数目小于等于CPU数目。
  • 就绪状态
    • 进程已获得除处理机外的所需资源,等待分配处理机资源;只要分配CPU就可执行。
  • 阻塞状态
    • 进程等待某个事件的发生(如I/O操作或进程同步),在该事件发生前,即使把处理机分配给进程,他也无法运行。
  • 创建状态
    • 进程刚创建还不能运行,OS还没有把它加入到可执行进程组中,通常是还没有加载到主存中的新进程。
  • 结束状态
    • OS从可执行进程组释放出的进程。
    • 进程结束,回收除PCB之外的其他资源,并让其他进程从PCB中收集有关信息。

进程的状态转换:

  • 新建——》就绪(提交admit)
    • 接纳一个新进程,进入就绪状态。
  • 就绪——》运行(分派dispath)
    • 从就绪进程表中选择一个进程,进入运行状态。
  • 运行——》阻塞(事件等待event wait)
    • 进程要求的事件未出现而进入阻塞。
  • 阻塞——》就绪(事件出现event occurs)
    • 进程等待的事件出现。
  • 运行——》就绪(超时timeout)
    • 由于用完时间片或高级进程就绪等导致进程暂停运行。
  • 运行——》退出(释放release)
    • 由于进程完成或失败而终止进程运行。
    • 分为正常和异常退出。

有三个进程PA、PB、PC协作,见书后习题。经典进程同步问题之理发师。

有三个进程A,B,C协作解决文件打印问题。
A将文件记录从磁盘读入内存的缓冲区1,每执行一次读一个记录;
B将缓冲区1的内容复制到缓冲区2,每执行一次复制一个记录;
C将缓冲区2的内容打印,每执行一次打印一个记录。
缓冲区的大小和一个记录大小一样。
使用P、V操作来保证文件的正确打印。

答案↓

2

Pa与pb共用一个缓冲区1;
Pb与pc共用一个缓冲区2;
当缓冲区1为空时,PA可将一个记录读入其中;
若缓冲区1有数据,缓冲区2为空时,PB可将记录从缓冲区1复制到缓冲区2;
若缓冲区2有数据,PC可以打印记录;
其他条件下,相应进程必须等待。

设置如下信号量

Empty1,empty2为同步信号量,初值为1,分别表示缓冲区1,2空闲,可以存放记录。
Full1,full2为同步信号量,初值为0,分别表示缓冲区1,2还没有被取用的记录。

3

问题描述:
理发店有一名理发师、一把理发椅、几把等待座椅。
如果没有顾客,理发师就打盹。顾客到来,唤醒理发师。如果顾客到来时理发师正在理发,则顾客坐在椅子上等待;如果座满了,直接离开。
用P、V操作协调理发师和顾客的同步行为。

第一种思路

  • 设三个信号量,一个计数器:
    customers:用来记录等候理发的顾客数,初值为0。
    barbers:用来记录等候顾客的理发师数,初值为0。
    waiting:整型计数器,表示等候理发的顾客数,初值为0。waiting是customers的副本,但不是信号量,可以在程序中对它做增减操作。
    mutex:用于控制对waiting的互斥操作,初值为1。

  • #define CHAIRS 5
    typedef struct{

         int value;
         struct PCB *list;
    

    } semaphore;

    semaphore customers=0;
    semaphore barbers=0;
    semaphore mutex=1;
    int waiting=0;

    void barber(void)
    {
    while(TRUE){

    P(customers);  
    P(mutex);       
    waiting--; 
    V(barbers);
    V(mutex); 
    cut_hair();
    }
    

    }

    void customer(void)
    {
    P(mutex);
    if(waiting﹤CHAIRS){

      waiting++;
      V(customers);       
      V(mutex);
      P(barbers);         
      get_haircut();  
    

    }else

      V(mutex);
    

    }

第二种思路

  • 将理发椅(1个bchair)与等待椅(n个wchair)分开看作两种不同的资源,顾客分别申请使用。

  • 考虑等待的顾客数(坐在凳子上的顾客数),设置一个整型变量waiting,初值为0。当一个顾客到达时waiting加1,当一个顾客开始理发时waiting减1。

  • 考虑对waiting的互斥操作,设互斥信号量mutex,初值为1。

  • 考虑空凳子数量,设信号量wchair,初值为5。

  • 考虑理发椅的数量,设信号量bchair,初值是1。

  • 考虑顾客和理发师的同步操作,设ready和finish两个信号量,前者表示顾客是否准备好,后者表示理发师是否完成理发,初值均为0。

  • typedef struct{
             int value;
             struct PCB *list;
    } semaphore;
    
    int waiting=0;
    semaphore mutex=1;
    semaphore bchair=1;
    semaphore wchair=5;
    semaphore ready=finish=0;
    
    barber( )
    {
       while(true){
        P(ready); cut_hair();  
        V(finish);
        }
    }
    
    customer( )
    {
       P(mutex);      
       if(waiting﹤=5) {
          waiting++;
          V(mutex); 
       } else
       { V(mutex); 离开; }
       P(wchair);
       P(bchair);
       V(wchair);
       V(ready);       
       P(finish); 
       V(bchair); 
       P(mutex);
       waiting--;
       V(mutex);
    }
    

现代操作系统处理机调度和分配的对象是线程。

线程(Thread)是进程中实施调度和分派CPU的基本单位。

操作系统在撤销进程时,必须完成的操作。

终止进程的过程
(1)找到指定进程的PCB
(2)终止该进程的运行
(3)回收该进程所占用的全部资源
(4)终止其所有子孙进程,回收它们所占用的全部资源
(5)将被终止进程的PCB从原来队列中摘走

UNIX/Linux系统中的终止原语exit()

第三章 死锁

安全状态计算。

死锁的四个必要条件,破坏死锁必要条件的几个预防死锁的方法。

当计算机系统同时具备下面4个必要条件时,会发生死锁。
1.互斥条件
2.占有且等待条件(请求并保持)
3.不可抢占条件(不可剥夺)
4.循环等待条件(环路等待)

方法:

  • 破坏互斥条件
    有些资源本身要求必须互斥访问,这是资源的固有属性,所以,用否定互斥条件的办法不能预防死锁。
  • 破坏占有且等待条件
    一种办法是“空手”申请资源策略:
     不占有资源的时候,才能申请。
    
    一种办法是预分资源策略:
         静态分配:执行之前申请到全部资源
    
  • 破坏不可抢占条件
    • 隐式抢占方式(被抢)
      如果一个进程占有某些资源,它还要申请被别的进程占有的资源,该进程就一定处于等待状态,则进程当前所占有的全部资源可被抢占。
      抢占等待者的资源(去抢)
      进程申请资源,如果没有可用,可以从等待进程那里抢占资源。这些办法常用于资源状态易于保留和恢复的环境中,如CPU寄存器、内存,但不能用于打印机、磁带机等。
  • 破坏循环等待条件
    • 一种方法是实行资源有序分配策略。
    • 另一种方法:先弃大,再取小。

什么是死锁?死锁的预防和避免的概念。

所谓死锁,是指在一个进程集合中的每个进程都在等待仅由该集合中的另一个进程才能引发的事件而无限期地僵持下去的局面。(系统中多个进程无限地等待永远不会发生的状态)

  1. 死锁的预防——静态策略
    破坏死锁的四个必要条件之一。
  2. 死锁的避免——动态策略
    利用某些协议避免死锁,保证系统不会进入死锁状态。

静态策略:死锁预防——破坏产生死锁的必要条件;静态策略的缺点是降低资源利用率和减少系统吞吐量。

动态策略:不限制进程有关申请资源的命令,而是对进程所发出的每个申请资源命令加以检查,根据检查结果决定是否进行资源分配。

第四章 调度

时间片轮转、先来先服务、短进程优先、优先级法(抢占和非抢占)、高响应比优先。几种调度算法概念、特点。最短剩余时间优先法的计算平均周转时间。课后习题8题,9题。

先来先服务法

  • 实现思想:排队买票
    每次从就绪队列中选择一个最先进入该队列的进程,把CPU分给它,令其运行。该进程一直运行下去,直至完成或由于某些原因而阻塞,才放弃CPU。
  • 特点
    • 非抢占式
      简单,易于理解,容易实现。效率低。
      有利于长作业(进程),不利于短作业(进程),
      有利于CPU繁忙型作业(进程),不利于I/O繁忙型作业(进程)。

      适用于作业调度、进程调度。通常不单独使用,与其他算法结合起来使用。

短作业优先法

  • 所谓长短
    是指作业(进程)要求运行时间的多少。

  • 实现思想
    当分配CPU时,选择所需处理时间最短的进程。短进程将越过长进程,跳到队列头。一个进程一旦分得处理机,便执行下去,直到该进程完成或阻塞时,才释放处理机。

  • 特点

    • 非抢占式
      优点:对于一组给定的作业,SJF算法能给出较小的平均等待时间,提高了系统的吞吐量。
      缺点:
      实现上有困难,需要知道或至少需要估计每个作业/进程所需要的处理时间。
      对长作业(进程)不利。不能保证及时处理紧迫作业(进程)。

      适用于作业调度和进程调度。作业调度用的多,进程调度用的少。

最短剩余时间优先法

  • 实现思想
    当新进程进入就绪队列时,如果它需要的运行时间比当前运行的进程所需的剩余时间还短,则执行切换,当前运行进程被剥夺CPU的控制权,调度新进程运行。
  • 特点
    • 抢占式
      优点:
      保证新的短作业一进入系统就能很快得到服务,平均等待时间短。
      缺点:
      为保存进程断点现场,统计进程剩余时间增加了系统开销。
      不利于长作业。
      适用于作业调度和进程调度。作业调度用的少,进程调度用的多。

高响应比优先法

  • 调度思想:在调度作业时,挑选响应比最高的作业运行。
  • 4
  • 特点
    • 非抢占式
      优点
      是对FCFS和SJF调度算法的综合平衡,同时考虑每个作业的等待时间和估计需要的运行时间。
      缺点
      调度前需要计算作业的响应比,增加系统开销。
      适用于作业调度和进程调度。作业调度用的多。

优先级法

  • 实现思想:
    优先级调度算法是从就绪队列中选出优先级最高的进程,让它在CPU上运行。

轮转法

  • 实现思想:
    将系统中所有的就绪进程按照FCFS原则,排成一个队列。每次调度时将CPU分派给队首进程,让其执行一个时间片。
    在一个时间片结束时,发生时钟中断。调度程序据此暂停当前进程的执行,将其送到就绪队列的末尾,并通过上下文切换执行当前的队首进程。
  • 5

多级队列法

多级反馈队列法

进程调度程序执行的时机。

调度时机(引起进程调度的原因):
(1)当前进程运行结束。
(2)当前运行进程因某种原因(I/O请求、P操作、阻塞原语等),从运行状态进入阻塞状态。
(3)执行完系统调用等系统程序后返回用户进程,这时可以看做系统进程执行完毕,从而可以调度一个新的用户进程。
(4)在采用抢占调度方式的系统中,一个更高优先级的进程要求使用cpu,则使当前运行进程进入就绪队列。
(5)分时系统中,分配给该进程的时间片用完。

三级调度指的是什么?

一个作业从提交开始直到完成,一般要经历三级调度:

  • 高级调度(作业调度)
    中级调度(内存调度、进程挂起与对换)
    低级调度(进程调度)

第五章 存储管理

分页系统,逻辑地址的有效位、物理地址有效位的计算。

分页技术:允许一个进程的存储空间不必连续,可以分散地放在各个空闲的内存区域中。解决了外部碎片,并提高了内存的利用率。

各种存储方法中,内碎片和外碎片的情况。

单道连续分配只有内部碎片。多道固定连续分配既有内部碎片,又有外部碎片。

不会产生内部碎片的存储管理是:分段式存储管理。

按需分配没有内碎片,例如:动态分区分配、分段存储管理
固定分配没有外碎片,例如:固定分区分配、分页存储管理

内部碎片与外部碎片
在一个分区内部出现的碎片(即被浪费的空间)称做内部碎片,固定分区法会产生内部碎片。
在所有分区之外新增的碎片称做外部碎片,动态分区法会产生外部碎片。

动态分区分配的三种算法:最先适配法、最佳适配法、循环适配法。

最先适应法——首次适应法

空闲表按空闲块地址递增排序。
分配内存时,顺序查找满足大小要求的第一个可用块。

循环首次适应法——邻近适应法

由首次适应法演变而成,不同之处是,分配内存时从上次查找结束的位置开始继续查找。
该算法的分配和释放的时间性能较好,使空闲分区分布得更均匀,但较大的空闲分区不易保留。

最佳适应算法

空闲表以空闲块大小为序,按增量形式排列。
接到内存申请时,顺序查找第一个满足大小要求的空闲块。
特点:用最小空间满足要求。
选择分区时总是寻找其大小最接近所要求的存储区域。

课后练习题第10题,分页系统中逻辑地址转变成物理地址。

10

最佳页面置换算法(OPT)、 先进先出页面置换算法(FIFO)和最近最久未使用(LRU)页面置换算法,计算缺页次数。

最佳置换法(OPT)

思想:为调入新页面而必须预先淘汰某个老页面时,所选择的老页面应在将来不被使用,或者是在最远的将来才被访问。以此保证获得最低的缺页率。

6

先进先出法(FIFO)

思想:为调入新页面而必须预先淘汰某个老页面时,总是淘汰在内存中停留时间最长的一页,即先进入内存的页,先被换出。

7

最近最少使用置换法(LRU)

思想:当需要置换一页时,选择在最近一段时间里最久没有使用过的页面予以淘汰。
理由:认为过去一段时间内未被访问过的页面,在最近的将来可能也不会被访问。

8

第六章 文件系统

在UNIX系统中,通常把输入/输出设备当做什么。

字符特别文件.

位示图概念理解。

利用一串二进制位值反映磁盘空间的分配情况,也称位向量(Bit Vector)法。
每个盘块对应一个二进制位,1表示盘块空闲,0表示已分配。
设下列盘块是空闲的:2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 17, 18, 25, 26, 27,…,则位示图向量是:
0011110011111100011000000111…

文件课后练习题13题。平均需要访问多少次磁盘。

多重索引文件分配表中,计算文件最大为多少字节(各级)、访问某逻辑地址40KB需要访问磁盘几次?每个索引盘块最多存储多少个索引项?文件的字节偏移量转换为物理块号分别需要用到什么索引?假设一个块1024B,一个文件索引项4B。

第七章 设备管理

假脱机技术(SPOOLing)把独占设备变为共享设备,成为虚拟设备的。

以下I/O控制方式中,中断控制方式、DMA控制方式、通道控制方式的特点。

中断控制方式

利用中断技术解决了CPU忙等的问题。
基本工作过程:
① CPU执行设备驱动程序,发出启动I/O设备的指令,使外设处于准备工作状态。然后,CPU继续运行程序,进行其他信息的处理。
② I/O控制器按照I/O指令的要求,启动并控制I/O设备的工作。

③ 当输入就绪、输出完成或发生错误时,I/O控制器便向CPU发送一个中断信号。
④ CPU接收到中断信号后,保存少量的状态信息。
然后将控制传送给中断处理程序。
⑤ 中断处理程序确定中断原因,执行相应的处理工作,最后退出中断,返回中断前的执行状态。
⑥ CPU恢复对被中断任务的处理工作。

中断控制方式一般用于随机出现的I/O请求。每传送一个字节,控制器就向CPU发一次中断,使CPU执行一次中断服务。中断次数过多,耗时多,只适用于中慢速外设。

DMA控制方式

具有4个特点:
1.数据是在内存和设备之间直接传送的,不需要CPU干预。
2.仅在一个数据块传送结束后,DMA才向CPU发中断请求。
3.数据的传送控制完全由DMA控制器完成,速度快。
4.在传送过程中,CPU与外设并行工作,提高系统效率。

通道控制方式

(1)通道的引入
为使CPU摆脱繁忙的I/O事务,现代大、中型计算机都设置了专门处理I/O操作的机构,这就是通道。
通道相当于一台小型处理机,它接受主机的委托,独立执行通道程序,对外部设备的I/O操作进行控制,实现内存与外设之间的成批数据传输。
当主机委托的I/O任务完成后,通道发出中断信号,请求CPU处理。

(2)通道类型
① 字节多路通道。以字节作为信息输送单位,服务于多台低速I/O设备。
② 选择通道。在同一时间里只能为一台设备服务,主要用于连接高速外部设备。
③ 成组多路通道。结合了字节多路通道分时操作和选择通道高速传送的优点,广泛用于连接高速和中速设备。

操作系统的I/O子系统分四层,各层功能。

用户级I/O软件、设备无关软件、设备驱动程序、中断处理程序

用户级I/O软件

  • 多数I/O软件都在操作系统中,用户空间中也有一小部分。通常,它们以库函数形式出现。
    用户空间中另一个重要的I/O软件是SPOOLing系统。

设备无关软件

  • 设备驱动程序的统一接口
  • 缓冲技术
  • 缓冲区的设置
  • 出错报告
  • 分配和释放独占设备
  • 提供与设备无关的块大小

设备驱动程序主要功能

  • 接受来自上层、与设备无关软件的抽象读写请求,并且将该I/O请求排在请求队列的队尾,同时还要检查I/O请求的合法性。
    取出请求队列中队首请求,且将相应设备分配给它。
    向该设备控制器发送命令,启动该设备工作,完成指定的I/O操作。
    处理来自设备的中断。
  • 每个连接到计算机的I/O设备都需要某些设备特定的代码来对其进行控制,这样的代码就是设备驱动程序。

中断处理程序

  • 在I/O软件系统的底层
    当输入就绪、输出完成或发生错误时,I/O控制器便向CPU发送一个中断信号。
    CPU接收到中断信号后,将控制传送给中断处理程序。
    中断处理程序确定中断原因,执行相应的处理工作。如,磁盘I/O中断调用磁盘驱动程序处理。

磁盘寻道算法FCFS算法、最短寻道时间优先法SSTF算法、扫描法SCAN算法、巡回(循环)扫描法C-SCAN算法。

FCFS是最简单的磁盘调度算法。

基本思想:按进程请求访问磁盘的先后次序进行调度。

最短寻道时间优先法SSTF

基本思想:选择与当前磁头所在磁道距离最近的请求作为下一次服务的对象。

扫描法(SCAN)

基本思想:磁头从磁盘的一端出发,向另一端移动,遇到所需的磁道时就进行服务,直至到达磁盘的另一端。在另一端上,磁头移动方向反过来,继续下面的服务。

巡回(循环)扫描法(C-SCAN)

基本思想:规定磁头移动时,服务方向是单向的。例如,自里向外移动,边移动边服务,当磁头移到最外磁道时立即返回到最里磁道,如此循环进行扫描。

答题规范:
② SSTF
磁头移动顺序:143→147→150→130→102→94→91→86→175→177
磁头移动总量:4+3+20+28+8+3+5+89+2=162
③ 扫描法SCAN
磁头移动顺序:143→147→150→175→177→199→130→102→94→91→86
磁头移动总量:4+3+25+2+22+69+28+8+3+5=169

与设备无关的操作系统I/O软件包括什么功能。

9

引入缓冲的主要目的三点?(见PPT)

(1)引入缓冲技术的主要目的
① 缓解CPU与I/O设备间速度不匹配的矛盾。
② 提高它们之间的并行性。
③ 减少对CPU的中断次数,放宽CPU对中断响应时间的要求。

IP分类

1

主机1:192.168.127.129

主机1docker:172.16.200.1/24

docker1的emqx:172.16.111.11

主机2:192.168.127.130

主机2docker2:172.16.201.1/24

docker2emqx:172.16.222.22

修改docker的ip

主机1

修改daemon.json文件

1
vim /ect/docker/daemon.json

内容大致如下↓

1
2
3
4
5
6
7
{
"bip":"172.16.201.1/24", # docker的ip
"registry-mirrors": ["https://hub-mirror.c.163.com",
"https://ghcr.io",
"https://mirror.baidubce.com"], # docker源
"insecure-registries":["192.168.127.129:5000"]
}

主机2

修改daemon.json文件

1
vim /ect/docker/daemon.json

内容大致如下↓

1
2
3
4
5
6
7
{
"bip":"172.16.201.1/24",
"registry-mirrors": ["https://hub-mirror.c.163.com",
"https://ghcr.io",
"https://mirror.baidubce.com"],
"insecure-registries":["192.168.127.129:5000"]
}

然后重启docker

ubuntu开启路由,加入路由规则

开启IP转发

执行以下指令

1
sysctl net.ipv4.ip_forward=1

或者进入 /etc/sysctl.conf

添加↓

1
net.ipv4.ip_forward=1

下载相关工具

安装 iptables-persistent

1
apt install iptables-persistent

将ip规则追加到rules.v4中

1
iptables-save > /etc/iptables/rules.v4

还原iptables配置。

1
iptables-restore < /etc/iptables/rules.v4

主机1

依次执行以下指令↓

1
2
3
iptables -F	//清除所有的iptables规则
iptables -P INPUT ACCEPT //允许接收
iptables -P FORWARD ACCEPT //允许发送数据包

添加路由↓

1
iptables -t nat -A POSTROUTING -s 172.16.111.0/24 -o ens33 -j MASQUERADE

另一个容器的路由

1
ip route add 172.172.222.0/24 via 192.168.127.130

主机2

依次执行以下指令↓

1
2
3
iptables -F	//清除所有的iptables规则
iptables -P INPUT ACCEPT //允许接收
iptables -P FORWARD ACCEPT //允许发送数据包

添加路由↓

1
iptables -t nat -A POSTROUTING -s 172.16.222.0/24 -o ens33 -j MASQUERADE

另一个容器的路由

1
ip route add 172.172.111.0/24 via 192.168.127.129

docker配置

主机1

创建相应网关

1
docker network create --subnet=172.16.111.0/24  emqxnet

启动容器

1
docker run  -itd  --name emqx11  --ip 172.16.111.11  --network=emqxnet -p 4369:4369 -p 18083:18083 -p 1883:1883 -p 8084:8084 -p 8883:8883 -p 8083:8083  -p 4370:4370 -p 5368:5368 -e  EMQX_NAME=emqx -e EMQX_HOST=172.16.111.11  -e EMQX_CLUSTER__DISCOVERY_STRATEGY=static emqx/emqx:5.0.4

主机2

创建相应网关

1
docker network create --subnet=172.16.222.0/24  emqxnet

启动容器

1
docker run  -itd  --name emqx22  --ip 172.16.222.22  --network=emqxnet -p 4369:4369 -p 18083:18083 -p 1883:1883 -p 8084:8084 -p 8883:8883 -p 8083:8083  -p 4370:4370 -p 5368:5368 -e  EMQX_NAME=emqx -e EMQX_HOST=172.16.222.22  -e EMQX_CLUSTER__DISCOVERY_STRATEGY=static emqx/emqx:5.0.4

节点2加入集群

进入容器

1
docker exec -it emqx22 /bin/bash

加入节点

1
./bin/emqx_ctl cluster join emqx@172.16.111.11

2