操作系统相关知识整理

11 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。

磁盘调度算法

进程、线程、协程

概念

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

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

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

进程和线程的区别

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

线程和协程的区别

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

线程间通信方式

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 协议的通信方式,一个是本地进程间通信方式。