操作系统重要知识整理汇总

16 minute

IO 模型

  1. 同步阻塞 IO(BIO, Blocking IO):即传统的 IO 模型。
  2. 同步非阻塞 IO(NIO, Non-blocking IO):默认创建的 socket 都是阻塞的,非阻塞 IO 要求 socket 被设置为 NONBLOCK。注意这里所说的 NIO 并非 Java 的 NIO(New IO)库。
  3. 多路复用 IO(IO Multiplexing):一个服务端进程可以同时处理多个套接字描述符。经典的 Reactor 设计模式,有时也称为异步阻塞 IO,Java 中的 Selector 和 Linux 中的 epoll 都是这种模型(Redis 单线程为什么速度还那么快,就是因为用了多路复用 IO 和缓存操作的原因)。
  4. 异步 IO(AIO, Asynchronous IO):即经典的 Proactor 设计模式,也称为异步非阻塞 IO。

同步阻塞 IO

同步非阻塞 IO

多路复用 IO

异步 IO

其中多路复用 IO 可分为 select,poll,epoll 三种:

  1. select
  2. poll
  3. epoll

TODO

磁盘相关知识

磁盘格式

ExFAT: 通用, 但是测试发现在自家智能电视无法使用, 而电视一般基于安卓系统, 而调查知 Android 13 系统首次原生支持 exFAT, 所以新电视也许支持

MS-DOS(FAT32): 通用, 但无法传超过 4G 的单个文件

APFS: apple

NTFS: windows

附: 索尼电视通常支持 FAT32 和 exFAT,而三星和其他品牌通常支持 FAT32 和 NTFS。

磁盘调度算法

磁盘查找算法用于优化磁盘访问时间,主要有以下几种:

  1. 先来先服务(FCFS)

    • 按照请求到达的顺序进行处理。
    • 简单但可能导致长时间等待。
  2. 最短寻道时间优先(SSTF)

    • 优先处理离当前磁头位置最近的请求。
    • 减少平均寻道时间,但可能导致饥饿现象。
  3. 扫描算法(SCAN)

    • 磁头在磁盘上来回移动,像电梯一样处理请求。
    • 也称为电梯算法。
  4. 循环扫描(C-SCAN)

    • 类似于SCAN,但每次到达一端后直接返回起始端,不处理中间请求,提供更均匀的等待时间。
  5. LOOK 和 C-LOOK

    • 改进的 SCAN 和 C-SCAN 算法,只在有请求的范围内移动。

这些算法各有优缺点,选择时需要考虑系统负载和响应时间等因素。

进程、线程、协程

概念

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。

线程是进程的一个实体,是 CPU 调度和分派的基本单位。线程只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。

协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

进程和线程的区别

  1. 多进程中每个进程有自己的地址空间,线程则共享地址空间
  2. 线程是进程内的一个执行单元,一个进程可以包含多个线程
  3. 进程是系统进行资源分配和调度的一个独立单位,而线程是 CPU 调度和分派的基本单位

线程和协程的区别

  1. 一个线程可以包含多个协程
  2. 线程是同步机制,而协程则是异步
  3. 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态

线程调度

多级反馈队列(Multilevel Feedback Queue, MLFQ)是一种灵活的调度算法,旨在动态调整线程的优先级,以提高系统的响应性和公平性。以下是其主要特点和工作原理:

  1. 多级队列

    • 系统中存在多个队列,每个队列有不同的优先级。最高优先级的队列先被调度。
  2. 时间片分配

    • 优先级高的队列通常分配较短的时间片,以快速响应短任务。
    • 优先级低的队列分配较长的时间片,适合运行长任务。
  3. 优先级调整

    • 当一个线程使用完其时间片后,如果没有完成,会被移动到下一个较低优先级的队列。
    • 如果一个线程长时间未被调度(即处于低优先级队列),系统可能会提升其优先级,防止饥饿现象。
  4. 动态适应

    • MLFQ根据线程的执行行为动态调整其优先级,使得短任务快速完成,而长任务最终也能获得CPU时间。
  5. 抢占式调度

    • 如果有更高优先级的线程进入就绪状态,正在运行的低优先级线程可能会被抢占。
  6. 灵活性

    • MLFQ可以根据系统需求和任务特性调整队列数量、时间片长度和优先级调整策略。

这种调度策略适用于需要高响应性和灵活调度的环境,如交互式系统和多任务操作系统。它通过适应性调整,兼顾了不同类型任务的需求。

线程间通信方式

volatile 和 synchronized 关键字

volatile 关键字保证了共享变量的可见性,任何线程需要读取时都要到内存中读取(确保获得最新值)。

synchronized 关键字确保只能同时有一个线程访问方法或者变量,保证了线程访问的可见性和排他性。

等待/通知机制

等待/通知机制,是指一个线程 A 调用了 wait() 方法进入等待状态,而另一个线程 B 调用了 notify() 或 notifyAll() 方法,线程 A 收到通知后从 wait() 方法返回,进而执行后续操作。

上述两个线程通过 Object 来完成交互,其中 wait() 和 notify/notifyAll() 的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

管道输入/输出流

管道输入/输出流和普通的文件输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存。

管道输入/输出流主要包括了如下 4 种具体实现:PipedOutputStream、PipedInputStream、PipedReader 和 PipedWriter,前两种面向字节,而后两种面向字符。

join 方法

如果一个线程 A 执行了 otherThread.join() 语句,那么线程 A 将等待 otherThread 线程终止之后才从 otherThread.join() 返回。

每个线程终止的前提是前驱线程的终止,每个线程等待前驱线程终止后,才从 join() 方法返回,这里涉及了等待/通知机制(等待前驱线程结束,接收前驱线程结束通知)。

ThreadLocal

ThreadLocal,即线程本地变量(每个线程都有自己唯一的一个),是一个以 ThreadLocal 对象为键、任意对象为值的存储结构。

底层是通过 ThreadLocalMap 来存储信息,key 是弱引用,value 是强引用,所以使用完毕后要及时清理(尤其使用线程池时)。

进程间通信方式

管道

“|” 表示的管道称为匿名管道,用完了就销毁。

还有命名管道,也被叫做 FIFO,因为数据是先进先出的传输方式。

所谓的管道,就是内核里面的一串缓存。从管道的一段写入的数据,实际上是缓存在内核中的,另一端读取,也就是从内核中读取这段数据。另外,管道传输的数据是无格式的流且大小受限。

消息队列

消息队列是保存在内核中的消息链表,在发送数据时,会分成一个一个独立的数据单元,也就是消息体(数据块),消息体是用户自定义的数据类型,消息的发送方和接收方要约定好消息体的数据类型,所以每个消息体都是固定大小的存储块,不像管道是无格式的字节流数据。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。

共享内存

共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西,另外一个进程马上就能看到了,都不需要拷贝来拷贝去,传来传去,大大提高了进程间通信的速度。

信号量

信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据。相关操作:P(-1)V(+1) 操作。

信号

信号是异步通信机制,信号可以在应用进程和内核之间直接交互,内核也可以利用信号来通知用户空间的进程发生了哪些系统事件,信号事件的来源主要有硬件来源(如键盘 Cltr+C )和软件来源(如 kill 命令),一旦有信号发生,进程有三种方式响应信号 1. 执行默认操作、2. 捕捉信号、3. 忽略信号。有两个信号是应用进程无法捕捉和忽略的,即 SIGKILL 和 SIGSTOP,这是为了方便我们能在任何时候结束或停止某个进程。

Socket

前面说到的通信机制,都是工作于同一台主机,如果要与不同主机的进程间通信,那么就需要 Socket 通信了。Socket 实际上不仅用于不同的主机进程间通信,还可以用于本地主机进程间通信,可根据创建 Socket 的类型不同,分为三种常见的通信方式,一个是基于 TCP 协议的通信方式,一个是基于 UDP 协议的通信方式,一个是本地进程间通信方式。

负载

负载平均值(Load Average)是一个反映系统负载的指标,表示在特定时间间隔内,系统上等待执行的进程数量的平均值。它通常以三个数字的形式显示(top 命令),分别表示最近 1 分钟、5 分钟和 15 分钟的平均负载。

如何理解负载平均值

  • 数值含义

    • 1.0:表示在一个单核CPU系统上,CPU正好满负荷运行。
    • 小于1.0:表示CPU有空闲时间。
    • 大于1.0:表示有进程在等待CPU资源。
  • 多核系统

    • 对于多核系统,负载平均值可以用总核心数来衡量。例如,一个 4 核 CPU 的系统,负载平均值为 4 表示满负荷。

负载过高的影响

  • 响应变慢:系统可能会变得不响应。
  • 进程排队:更多的进程需要等待资源。
  • 可能的瓶颈:需要识别是否是 CPU、内存、I/O 等资源的瓶颈。

优化建议

  • 优化应用程序:减少不必要的计算和资源消耗。
  • 增加硬件资源:增加 CPU 核心或内存。
  • 分布式处理:将任务分配到多个机器上。

通过监控和分析负载平均值,可以更好地理解系统的资源使用情况,并采取措施优化性能。