操作系统基础
1 进程和线程
- 进程:是资源分配的最小单位,一个进程可以有多个线程,多个线程共享进程的堆和方法区资源,不共享栈、程序计数器
- 线程:是任务调度和执行的最小单位,线程并行执行存在资源竞争和上下文切换的问题
- 协程:是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。
2 进程间通信方式IPC
2.1 管道pipe
亲缘关系使用匿名管道,非亲缘关系使用命名管道,管道遵循FIFO,半双工,数据只能单向通信;
2.2 信号
信号是一种比较复杂的通信方式,用户调用kill命令将信号发送给其他进程。
2.3 消息队列
消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点。
2.4 共享内存(share memory)
- 使得多个进程可以可以直接读写同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。
- 由于多个进程共享一段内存,因此需要依靠某种同步机制(如信号量)来达到进程间的同步及互斥。
2.5 信号量(Semaphores)
信号量是⼀个计数器,⽤于多进程对共享数据的访问,这种通信⽅式主要⽤于解决与同步相关的问题并避免竞争条件。
2.6 套接字(Sockets)
简单的说就是通信的两⽅的⼀种约定,⽤套接字中的相关函数来完成通信过程。
3 用户态和核心态
3.1 用户态
只能受限的访问内存,运行所有的应用程序
3.2 核心态
运行操作系统程序,cpu可以访问内存的所有数据,包括外围设备
3.3 为什么要有用户态和内核态
由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络
3.4 用户态切换到内核态的3种方式
- 系统调用:主动调用,系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。
- 异常:当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,比如缺页异常,这时会触发切换内核态处理异常。
- 外围设备的中断:当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会由用户态到内核态的切换。
4 操作系统的进程空间
- 栈区(stack)— 由编译器自动分配释放,存放函数的参数值,局部变量的值等。
- 堆区(heap)— 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收 。
- 静态区(static)—存放全局变量和静态变量的存储
- 代码区(text)—存放函数体的二进制代码。
线程共享堆区、静态区
5 操作系统内存管理
存管理方式:页式管理、段式管理、段页式管理
5.1 分段管理:
将程序的地址空间划分为若干段(segment),如代码段,数据段,堆栈段;这样每个进程有一个二维地址空间,相互独立,互不干扰。段式管理的优点是:没有内碎片(因为段大小可变,改变段大小来消除内碎片)。但段换入换出时,会产生外碎片(比如4k的段换5k的段,会产生1k的外碎片)
5.2 分页管理:
在页式存储管理中,将程序的逻辑地址划分为固定大小的页(page),而物理内存划分为同样大小的页框,程序加载时,可以将任意一页放入内存中任意一个页框,这些页框不必连续,从而实现了离散分离。页式存储管理的优点是:没有外碎片(因为页的大小固定),但会产生内碎片(一个页可能填充不满)
5.3 段页式管理:
段⻚式管理机制结合了段式管理和⻚式管理的优点。简单来说段⻚式管理机制就是把主存先分成若⼲段,每个段⼜分成若⼲⻚,也就是说段⻚式管理机制 中段与段之间以及段的内部的都是离散的
6 页面置换算法FIFO、LRU
置换算法:先进先出FIFO、最近最久未使用LRU、最佳置换算法OPT
6.1 先进先出FIFO
- 缺点:没有考虑到实际的页面使用频率,性能差、与通常页面使用的规则不符合,实际应用较少
6.2 最近最久未使用LRU
- 原理:选择最近且最久未使用的页面进行淘汰
- 优点:考虑到了程序访问的时间局部性,有较好的性能,实际应用也比较多
- 缺点:没有合适的算法,只有适合的算法,lFU、random都可以
/**
* @program: Java
* @description: LRU最近最久未使用置换算法,通过LinkedHashMap实现
* @author: Mr.Li
* @create: 2020-07-17 10:29
**/
public class LRUCache {
private LinkedHashMap<Integer,Integer> cache;
private int capacity; //容量大小
/**
*初始化构造函数
* @param capacity
*/
public LRUCache(int capacity) {
cache = new LinkedHashMap<>(capacity);
this.capacity = capacity;
}
public int get(int key) {
//缓存中不存在此key,直接返回
if(!cache.containsKey(key)) {
return -1;
}
int res = cache.get(key);
cache.remove(key); //先从链表中删除
cache.put(key,res); //再把该节点放到链表末尾处
return res;
}
public void put(int key,int value) {
if(cache.containsKey(key)) {
cache.remove(key); //已经存在,在当前链表移除
}
if(capacity == cache.size()) {
//cache已满,删除链表头位置
Set<Integer> keySet = cache.keySet();
Iterator<Integer> iterator = keySet.iterator();
cache.remove(iterator.next());
}
cache.put(key,value); //插入到链表末尾
}
}
6.3 最佳置换算法OPT
- 原理:每次选择当前物理块中的页面在未来长时间不被访问的或未来不再使用的页面进行淘汰
- 优点:具有较好的性能,可以保证获得最低的缺页率
- 缺点:过于理想化,但是实际上无法实现(没办法预知未来的页面)
7 死锁条件、解决方式
死锁是指两个或两个以上进程在执行过程中,因争夺资源而造成的下相互等待的现象;
7.1 死锁的条件
- 互斥条件:进程对所分配到的资源不允许其他进程访问,若其他进程访问该资源,只能等待至占有该资源的进程释放该资源;
- 请求与保持条件:进程获得一定的资源后,又对其他资源发出请求,阻塞过程中不会释放自己已经占有的资源
- 非剥夺条件:进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用后自己释放
- 循环等待条件:系统中若干进程组成环路,环路中每个进程都在等待相邻进程占用的资源
7.2 解决方法:破坏死锁的任意一条件
- 乐观锁:破坏资源互斥条件,CAS
- 资源一次性分配:从而剥夺请求和保持条件、tryLock
- 可剥夺资源:即当进程新的资源未得到满足时,释放已占有的资源,从而破坏不可剥夺的条件,数据库deadlock超时
- 资源有序分配法:系统给每类资源赋予一个序号,每个进程按编号递增的请求资源,从而破坏环路等待的条件,转账场景