# 操作系统

定义

  • 操作系统是一组管理并控制计算机操作、运用和运行硬件、软件资源和提供公共服务来组织用户交互的互相关联的系统软件应用程序,同时也是计算机系统的内核和基石。

任务

  • 设备管理
  • 文件管理
  • 进程管理

# CPU 作用

读取指令、执行指令、控制硬件、传输数据。

  • 通过地址总线读取指令。
  • 通过控制总线控制硬件设备。
  • 通过数据总线传输数据。

# 启动

操作系统启动

开机 --CPU 执行 主板rom中的BIOS程序 ,进行硬件自检 --- 硬件自检后 cpu 会将 0号扇区调入内存 中执行 MBR 主引导记录(Bootloader 程序)--- 主引导记录(Bootloader 程序)根据 磁盘分区表选择操作系统 --- 将操作系统所在的 系统盘的第一个扇区调入内存 中并执行操作系统 ------ 操作系统控制调度cpu和硬件系统 分配资源给其他应用程序。

# BIOS 硬件自检

BIOS 是放在主板 rom 上的一段程序,rom 是只读的,不能写入,因此 BIOS 出厂时,就已经写死在 rom 里面,每次开机启动都会执行 BIOS 硬件自检。

  • rom(只读存储器,在出厂时就焊死在了主板上,不能写入,它存储的数据在断电之后不会丢失)

# 0 号扇区

0 号扇区是磁盘的一块区域,存放着 MBR 主引导记录(包含代码和磁盘分区表),磁盘分区表记录着磁盘分区的信息和大小。

# 系统盘

0 号扇区的程序会根据磁盘分区表找到操作系统软件所在的磁盘分区,称为系统盘,相比于普通磁盘分区,其第一个扇区具有主引导程序,可以启动操作系统。

# 详细结构

/images/硬件连接

# CPU 执行

cpu 存储着地址寄存器和指令寄存器,地址寄存器中保存着 cpu 需要执行的下一个指令的内存位置,执行完指令之后地址寄存器的位置会增加,指向下一个指令。

  • 程序记时器,为了防止恶意抢占 CPU 资源(如果一个程序一直不等待 IO,并执行死循环,就不会发生系统中断,会导致 CPU 资源被恶意抢占,因此现代 cpu 会有一个程序记时器,定时发生一个中断。)

多核 CPU:一个 CPU 中有多个内核,这样操作系统可以将不同的应用程序分配给不同的内核去处理,实现硬件层面真正得并行和同时执行。

  • CPU 和内存通过总线连接在一起,CPU 不断从内存中取出操作系统的指令进行执行。

# 操作系统执行

操作系统执行:操作系统本质上是一个软件,也需要被 cpu 执行,操作系统有一个调度器,里面存放着进程队列,当程序启动时,就会被放入到进程队列中,当 CPU 可用时,调度器就会执行进程队列中的应用程序,由于等待程序 IO 的时间远大于 CPU 执行的时间,因此每个程序都是并发执行的,当一个程序需要等待 IO 时,就会发生系统中断,CPU 就会从用户态陷入到内核态,交由操作系统处理,操作系统就会将进程执行状态保存到 PCB 进程控制块中,推入到进程队列中,转而去执行其他的进程。

# 进程内存布局

进程大致分为:静态代码,执行栈,堆,未初始化数据和初始化数据。
进程结构
设备控制器
cpu调度设备控制器

# 栈执行快的原因

栈的内存已知,并且行为可预测,因为栈所占用的内存已经事先分配好了,不用在运行时申请内存和解决内存碎片的问题(申请内存需要一个系统调用以及其他昂贵的开销)。执行栈满就会发生栈溢出。

#

与栈不同,堆的大小可以通过系统调用动态调整,由于内存是一页一页申请的(内存页,个人电脑一般 4kb),内存大小不足就会申请下一块内存,因此会出现内存外碎片,因为数据的内存占用释放和变化等问题会导致内碎片。对于这些碎片问题,js 采用新生代老生代分区来进行解决。

# 链表

链表的优点是解决了碎片化问题,易于插入和删除,缺点是内存遍布各个块,不利于 CPU 缓存命中,链表过长时,查询效率很慢。

# 动态数组

动态数组由于长度可变,因此附加了一些其他的方法如 push、delete 等,为了防止添加元素覆盖其他内存,动态数组每次添加时需要检查当前数组是否已满,如果数组已满则需要分配一个更大的数组并将每一项复制过去,再更新指针引用(这样性能开销很大,通常是首次就创建一个容量长度足够的动态数组,以减少分配次数)

# js 中的数组

js 中的数组是一种哈希映射,本质上是一个对象,在数组 10000 处插入一个数组,内存中并不会申请到 10000 个数据所需要的内存空间。python 中的数组存储的是值得引用。

# 并发和并行

并发是一段时间内交替执行,并行是同一时刻同时执行,单核 CPU 执行不同应用程序是并发执行,多核 CPU 直接是并行执行。

# 用户态和内核态

操作系统运行在内核态,可以完全访问硬件资源,也可以执行机器能够运行的任何指令。应用程序运行在用户态,只能执行一部分指令,对于影响硬件的指令(特权指令)无法执行。

  • 用户态是指:应用程序在运行时,CPU 所处的状态,此时 PCU 所处的状态的级别很低,不能直接访问某些机器指令,或者不能直接读写磁盘
  • 内核态是指:操作系统在运行时,CPU 所处的状态,此时 CPU 可以运行任何指令包括特权指令,去控制硬件和 CPU 的执行。

只有操作系统才能控制硬件设备,应用程序需要通过系统调用使用操作系统的接口才能通过操作系统控制硬件资源。操作系统通过特权指令和中断机制来实现操作系统和硬件之间的交互。

# 陷入

  • 内核态可以访问所有硬件设备,也可以执行硬件上能够运行的各种指令
  • 用户态只能执行一部分机器指令,不可以运行 I/O 指令或者影响机器控制的指令 (特权指令)
  • 内核态 (由操作系统) 封装了系统函数,维护了系统调用映射表,作为接口给应用程序 (用户态), 用户态传入对应的系统调用号即可执行系统函数
  • 用户态的应用程序没有权限调用系统指令,此时需要去申请外部资源 (1、系统调用 2、syscall、3、中断和异常), 然后就陷入到了内核态,内核态通过用户态传入的系统调用号找出系统调用映射表中的系统函数进行执行

# 系统调用分类

系统调用可以在终端执行 man syscalls 查看

  • 进程:exit 和 fork 等
  • 文件:open、chown 等
  • 设备:read、write 等
  • 信息:getxxx、setxxx 等
  • 通信:mmap 和 sendfile 等

# 操作系统启动

操作系统启动时,会建立一张系统调用映射表,标识是系统调用号,传入系统调用号就可以执行对应的函数

  • 应用程序运行在用户态,当执行内核态提供的接口时,就会从用户态陷入到内核态,通过系统调用号去执行对应的内核态的接口
  • 发生陷入时,用户态部分首先将参数保存到寄存器中,然后根据系统调用名得到系统调用号并保存到寄存器中,调用内核态的接口执行对应的系统函数,内核态执行完毕之后,会将执行结果返回给 CPU 的寄存器中进行存储,CPU 拿到结果继续执行用户态剩下的内容

# 系统中断

由于 CPU 执行的效率很快,而 I/O 的操作很慢,为了使 CPU 不会长时间等待程序 I/O, 因此有了系统中断,程序中断时的状态存储在 PCB 进程控制块中

  • linux 操作系统中,32 位操作系统中断使用 int 0x80 中断,64 位操作系统通过调用 syscall 发生中断 (陷入)
  • 系统中断是为了提高 CPU 的执行效率
  • 操作系统会为每一个设备控制器分配一个内存地址,然后 CPU 会根据内存地址找到对应的设备控制器的寄存器 (有指令寄存器、数据寄存器和控制寄存器),CPU 调用控制寄存器执行指令
  • 当发生中断时,需要将 CPU 寄存器中的值复制到进程的内核中,这个过程称为保存现场
  • 当 CPU 继续执行时,就会从 PCB 进程控制块中获取到存储的状态,然后写入到 CPU 的寄存器中,继续执行

# 计算机缓存 Cache

# 什么是缓存

  • 由于 CPU 计算效率远大于 I/O 读取和写入的效率,因此有了计算机缓存结构
  • 计算机缓存是在 CPU 处理器和内存之间引入的缓存结构,当 CPU 加载的数据在缓存中时,就不用再去内存中读取,这称之为缓存命中。如果缓存中没有要加载的数据就需要从内存中读取,再通过数据总线返回给 CPU,这称之为缓存丢失。

# 缓存行

  • 缓存行是计算机缓存结构中存储数据的基本单位。
  • 缓存被划分为多个块,每个块可以存储一定量的数据,当数据从内存加载到缓存时,通常是以块为单位进行的。
  • 每个块都有固定的大小,常见的块大小包括 32 字节、64 字节等,这个大小也称为缓存的行大小或缓存的块大小。
  • 当 CPU 访问数据时,如果缓存未命中,则整个块都会从内存中加载到缓存中,以减少内存的访问次数
  • 当缓存已满时,需要决定哪些块被替换,常见的替换策略包括最近最少使用(LRU)、先进先出(FIFO)等。
  • 预取策略,现代 CPU 通常使用预取策略,根据程序的访问模式预测性地加载多个块到缓存中,以提高数据访问的速度。

# 内存块(内存页)

  • 内存块(内存页)是指在内存中存储数据的基本单位,在分页内存管理系统中,内存被划分为多个固定大小的页,每个页可以看作是一个内存块。内存块(分页)的大小通常是 4kB(4*1024 字节)。

# 缓存映射

缓存映射涉及到如何将住内存中的数据映射到 CPU 缓存中,缓存映射的不同方式会影响到缓存的效率和性能。主要有:全相联映射、直接映射和组相连映射。

  • 缓存映射的定义是:主存地址按照某种规律(函数)映射到缓存中,当 CPU 访问存储器时,它发出的内存地址会自动变换为缓存地址,这种映射通常有硬件实现。

# 缓存结构

计算机缓存结构

  • 每个存储器只和相邻一层的存储器设备打交道。
  • 计算机缓存结构用于解决 CPU 处理速度与主存储器(内存)访问速度之间的差异,通常包括:寄存器、一级缓存 L1 Cache、二级缓存 L2 Cache、三级缓存 L3 Cache 和主存储器。
  • L1 Cache 和 L2 Cache 在每个 CPU 核心之内,每个核独立,L3 Cache 是一个共享缓存层,存储量更大,可以被多个 CPU 共享。
  • 缓存结构的工作原理是:局部性原理(时间局部性和空间局部性)
  • 数据从内存加载到缓存中一次性加载大小通常是 64 字节,称为 缓存行 ,意味着即使需要一小部分数据,也会一次性加载一整行数据到缓存中。
  • 缓存行的设计利用了空间局部性原理,有利于减少内存访问次数,提高数据访问的效率。

缓存行和内存块(内存页)

  • 缓存行是指在 CPU 缓存中存储数据的基本单位,内存块(内存页)是指在内存中存储数据的基本单位。

# 计算机主存(内存)

每个程序员都应该了解的内存知识
内存中的程序

# 局部性原理

# 空间局部性

程序倾向于访问当前数据相邻的数据。

  • 计算机通过缓存来利用空间局部性原理。
# 时间局部性

程序倾向于访问最近刚访问过的数据。

  • 预取策略:计算机通过监控 CPU 的访问模式,自动提前加载频繁访问的数据,以实现时间局部性原理。

# 内存和缓存

  • 内存中存储着缓存映射,具体到每一个内存相对应的缓存的地址,当缓存命中时,CPU 直接访问缓存中的数据。当内存中的数据发生变化时,就要根据该内存的缓存映射更新缓存中的数据。
  • 内存到缓存的映射方式有:直接映射、组相联映射和全相连映射。

# 零拷贝技术

普通IO

  • 一次普通的文件读写需要发生四次拷贝
  • 例如当读写文件时:用户态首先陷入到内核态,调用对应的系统指令,读取磁盘文件的内容,拷贝到内核态的内存页缓存中 (DMA copy), 然后拷贝到用户态的缓存中 (CPU copy), 然后操作完文件之后,将新的文件内容拷贝到内核态的 Socket 缓冲区中 (CPU copy), 最后拷贝到磁盘 (或者通过网络发送)(DMA copy)

零拷贝是指:在用户态到内核态之间状态共享时,没有发生拷贝

# mmap 通信

mmap通信

mmap 省略从内核态到用户态的拷贝过程,将这一过程变为共享,到需要拷贝时,直接从内核态的页缓存拷贝到内核态的 Socket 缓冲区中 (用户态到内核态 0 拷贝)

  • mmap 减少了一次拷贝过程 (变为 3 次), 上下文切换次数仍然是四次

# sendfile 通信

sendfile通信

sendfile 优化只进行两次拷贝过程,上下文切换变为两次,文件从磁盘拷贝到内核态的页缓存后直接 DMA copy 拷贝到网络 (或磁盘) 中,并传输给内核态 socket 缓存一个传输文件描述符 (而不是拷贝)

# 总结

sendfile 通信效率更高,例如 kafka 就是使用 sendfile 通信,使得并发量更高,但是 sendfile 通信时,用户态获取不到文件内容
mmap 通信通信效率略低,但是用户态可以获取到文件内容,进一步处理和操作,例如 Rocket MQ 就使用 mmap 通信,进行更多的处理
mmap 适合小数据量读写,sendfile 适合大文件传输

# 硬件结构

# cpu 执行

冯诺依曼模型
键盘输入原理

运算器 控制器 存储器 输入设备 输出设备

存储单元

CPU 和硬件资源之间交互通过设备控制器,设备控制器中有命令寄存器,状态寄存器,数据寄存器和电路控制系统,CPU 通过轮询的方式监听状态寄存器,如果变化就根据中断请求号(IRQ)获取到操作系统中的中断服务程序的内存基地址。中断服务程序会保存之前程序的状态,从键盘控制器中读取输入,执行相应指令。

# 内存

内存是程序和数据存储的地方,存储的区域是线性的,内存中存放的是一块地址,用于找到对应引脚所在的位置,从而获取二进制数据。

  • 内存是
  • 计算机数据存储中,存储数据的基本单位是字节,1 字节等于 8 比特,每一个字节都对应一个内存地址。
  • 内存地址从 0 开始编号,自增排列,最后一个地址为内存总字节数 - 1,内存读写任何一个数据的速度都是一样的。
# 引用

引用本质上就是一块内存地址,不同的数据类型具有不同的内存大小。

# 字节序
  • 有时候大的数据需要使用多个字节进行存储,多个字节之间的序号就叫字节序。分为:大端序(字节顺序从高地址到低地址顺序存储,人易于理解)和小端序(字节顺序从低地址到高地址顺序存储,计算机易于执行)。

  • bit 比特 = 8 字节 = 10 ^ 3kb = ......

# 程序执行流程

以命令行工具为例
终端输入命令 --- 操作系统内核态识别到使用 node 运行 ----- 在环境变量中找到 node 应用程序 ---- 操作系统为应用内程序创建进程并放入到程序队列 -----cpu 执行应用程序,将二进制数据加载到内存 ----- 发生中断会保存进程状态到进程控制块中,转而执行其他进程 ------ 执行完毕或遇到错误,清理进程所使用的资源并释放内存。

# cpu

CPU 执行流程

  • 程序计数器中找到内存地址,根据内存地址读取指令
  • 解码指令为具体要执行的操作
  • 执行指令操作
  • 程序计数器自增,执行下一条指令,不断地读取指令 --- 解码指令 --- 执行指令。

CPU 位宽越大,可以计算的数值也就越大

CPU 内部还有一些组件,比如:控制器、运算器和寄存器。

  • 控制器负责将内存中的指令、数据读写进寄存器,并根据指令的执行结果来控制整个计算机。
  • 运算器负责运算从内存中读入寄存器的数据。
  • 寄存器用于暂存指令、数据等处理对象,可以看作内存的一种,读写很快。

CPU 内部的寄存器访问速度最快,用于临时存放数据和指令,CPU 外部的缓存有 L1 L2 L3 三个 cache, 也用于缓存数据

  • 32 位 CPU 一次可计算 32 比特,也就是 4 个字节,最大寻址空间为 2 ^ 32 ,也就是 4G(通常数据总线和地址总线是 32 条)
  • 64 位 CPU 一次可计算 64 比特,也就是 8 个字节,最大寻址空间为 2 ^ 64 ,也就是 2 ^ 64byte(通常数据总线和地址总线是 32 条)
  • CPU 位宽越大,可以计算的数值就越大。
  • 不同架构的 CPU 其指令集是不同的。
  • CPU 寄存器有:通用寄存器(存放运算数据)、程序计数器(存储下一条指令地址)和指令寄存器(存储当前指令)三种。

# 总线

总线连接了 CPU 和内存以及其他设备,用于它们之间进行通信。总线可分为:地址总线、数据总线和控制总线。

  • 地址总线:用于指定 CPU 将要操作的内存地址
  • 数据总线:用于读写内存的数据
  • 控制总线:用于发送和接收信号,比如中断、设备复位等信号,CPU 接收到信号后自然响应也需要控制总线。
  • 当 CPU 读写内存数据时,需要通过地址总线指定内存地址,通过控制总线控制是读、写命令,最后通过数据总线传输数据。

# 输入输出设备

输入设备向计算机输入数据,计算机经过计算后,将数据输出给输出设备,期间如果输入设备是键盘,按下按键时需要和 CPU 进行交互,此时就需要用到控制总线

# struct file

  • struct file 是一个非常重要的数据结构,在内核中表示打开文件的对象,这个结构体包含了与打开文件相关的所有信息,包括文件的状态、文件的访问模式、文件的读写位置等。
  • struct file 是内核中管理文件的核心数据结构,为内核提供了统一的方式处理不同类型的文件,包括:文件、目录、设备文件、管道等等。

# 文件描述符

  • 当创建进程时,会为当前进程在内核空间创建一个 struct file,同时给应用进程返回一个整数,叫做文件描述符,应用进程中通过文件描述符可以获取到 struct file 进而操作文件。
  • 文件描述符是一个非负整数,通过文件描述符可以打开对应的文件、设备、管道和套接字等,进行读写操作。
  • 在 Linux 中每个进程都有一个文件描述符,每个进程默认最大可打开文件描述符为 1024

# 指令

指令分为操作码和操作数,都是使用二进制进行表示的。

# 进程中的栈(函数调用栈)

执行应用程序时,其进程中的栈(函数调用栈)会记录程序执行的流程状态,每个函数的执行和结束都会伴随着函数入栈和出栈。

# 总线

总线是用于 CPU 和内存以及其他设备之间的通信,总线可分为:

  • 地址总线:指定 CPU 将要操作的内存地址
  • 数据总线:读写内存的数据
  • 控制总线:发送和接收信号

总线通常有一个位宽,是为了总线能够并行的传输数据,以提高传输效率

  • CPU 的位宽最好不要小于线路的位宽,这样工作起来很麻烦。
  • 计算数额不超过 32 位的情况下,32 位和 64 位 CPU 之间没有什么区别。
  • 32 位 CPU 最大只能操作 4GB 的内存 (2 ^ 32), 这样即使装了 8GB 的内存条也没用。

# 程序执行

程序的运行过程就是一条条指令一步一步执行的过程,负责执行指令的就是 CPU。

CPU 执行程序的过程:

  • CPU 读取程序计数器,也就是指令的内存地址 (地址总线传输)。
  • CPU 将指令数据存入到指令寄存器。
  • 程序计数器自增,指向下一条指令。
  • CPU 分析指令类型,计算类型交给逻辑运算单元,存储类型交给控制单元。

CPU 从程序计数器读取指令(地址总线,数据总线)、到执行指令(逻辑运算单元)、再到下一条指令(程序计数器), 这个过程不断循环的过程被称为 CPU 的指令周期。

# 指令

指令是一串二进制数字的机器码,每条指令都有对应的机器码,每个 CPU 有不同的指令集。

# 指令周期

  • CPU 通过程序计数器读取指令对应的内存地址 (Fetch 取得指令)
  • CPU 对指令进行解码 (Decode 指令译码)
  • CPU 执行指令 (Execution 执行指令)
  • CPU 将计算结果存入内存 (Store 数据回写)

# CPU Cache Line

CPU Cache Line 表示一次性加载的数据的大小

CPU Cache Line 通常是 64 字节,也就是一般会加载目标数据,如果目标数据不够 64 字节,就会连同目标数据之后的一同进行加载,可以利用这个特性提升缓存命中率,提升代码性能

# 指令缓存命中率

CPU 具有分支预测器,对于 if 条件语句,如果分支预测接下来还是 if 语句的话,就会提前将指令存放在缓存中,从而提高指令的执行速度

例如:先将数组排序,在遍历数组,里面进行 if (i < n) 的条件判断就会比最后在进行数组排序的相同过程要快,因为缓存了 if 之后的指令

# 多核 CPU 缓存命中率

多核 CPU 之前高速缓冲区只有 L3 是共享的,由于每一个线程在不同 CPU 核心之间来回进行切换,就会导致每个核心的缓存命中率收到影响,当有多个同时执行计算密集型的线程时,为了防止因切换到不同核心导致缓存命中率下降的问题,我们可以将线程绑定在某一个 CPU 核心上

代码中可以使用 api 指定同一个 CPU

# 多核 CPU 缓存一致性

多核 CPU 需要注意缓存一致性问题,目的是确保在多核系统中,所有核心都能看到内存中数据的最新状态,防止数据不一致和丢失更新的问题,通常使用 MESI 协议进行管理。

# 伪共享

如果两个线程 a 和 b 分别使用同一个数组,a 使用 0,b 使用 1, 由于 Cache Line 读取的原因,就会导致多核 CPU 下,同一时间,两个 CPU 对于这个数组是一种伪共享的状态,当修改 Cache Line 的时候,虽然没有影响到另一个 CPU 缓存中需要使用的数据,也要对其缓存行 Cache Line 进行同步,就会消耗性能

代码中可以使用 api 决定缓存行 Cache Line 的大小

# 概念

# 操作系统作用

  • 对下层,统一管理硬件的资源;对上层统一硬件接口,给应用程序提供各种服务。
  • 操作系统将硬件资源协调好,使得不同时刻不同的应用程序可以访问不同的硬件资源,所有的应用程序可以公平的共享一台计算机的不同硬件资源,同时提高计算机硬件资源的利用率。

操作系统是应用程序与硬件之间的中间层,也是一个软件,操作系统屏蔽了硬件接口的差异,统一了和硬件交互的接口,同时监管应用程序操作硬件的过程,保证安全性。用户不能信任应用程序,但是可以信任操作系统。

  • 操作系统保证不同的应用程序运行在不同的内存块(内存页)中,防止资源竞争
  • 如果内存不够时,操作系统可以将一部分应用程序放入磁盘中,当运行时才调回到内存中。

# 操作系统结构

操作系统结构分为外壳和内核,外壳是 shell 界面,内核提供一系列功能,如:

  • CPU 管理
  • 内存管理
  • 磁盘管理
  • 中断处理(I/O 设备驱动)

# 操作系统内核

计算机是由各种外部硬件设备组成的,比如内存、CPU、硬盘等,如果每个应用都要和这些硬件设备通信,就太繁琐了, 内核就充当应用连接硬件设备的桥梁

应用程序只需要关心与内核进行交互,而不用关心硬件的细节

# 内核的能力

  • 管理进程线程,决定哪个进程线程使用 CPU
  • 管理内存,决定内存的分配和回收
  • 管理硬件设备,为进程与硬件设备之间提供通信能力
  • 提供系统调用,它是用户程序与操作系统之间的接口。

# 操作系统和 CPU

操作系统是一个软件,被 CPU 调度执行。同时 CPU 是管理和控制计算机硬件资源的软件,包括 CPU、内存、输入输出设备等。两者协同工作,操作系统控制 CPU 的执行,CPU 同时执行操作系统。

# 协同工作

操作系统和 CPU 协同工作,以实现计算机系统的整体功能。操作系统负责协调和管理资源,而 CPU 则负责执行具体的计算任务。
在现代计算机系统中,操作系统和 CPU 之间会有频繁的交互,例如,操作系统会向 CPU 发送指令来管理进程的执行,CPU 则会向操作系统报告硬件状态和中断请求。

# 内存区域

内核具有很高的权限,可以控制 CPU、内存、硬盘等,但是应用程序只具有很小的权限,因此大多操作系统将内存分为两个区域:

  • 内核空间,这个空间只有内核程序可以访问
  • 用户空间,这个空间专门给应用程序使用

当程序使用用户空间时,就常说程序在用户态执行,当程序使用内核空间时,就说程序在内核态执行

# 进程

进程是操作系统进行资源分配的基本单位。

# 线程

线程是进行 CPU 调度的基本单位。

# 内存

内存是:计算机的随机访问存储器(RAM),是计算机的主要存储资源之一,用于存储计算机运行时正在使用或即将使用的数据和指令。
内存具有:易失性(计算机关闭时内存中的数据会丢失)、访问速度快等特点

# 虚拟内存(地址空间)

虚拟内存是:虚拟内存是操作系统对物理内存的抽象,是一种内存管理技术,允许操作系统将硬盘空间用作临时的 RAM 来使用
虚拟内存能够运行比物理内存更多的内存的程序,同时也为操作系统提供了更大的地址空间,虚拟内存的关键有:

  • 地址转换
  • 分页
  • 页面置换算法
  • 硬盘空间使用
  • 性能影响
  • 内存管理

# 文件

文件是:操作系统对物理磁盘的抽象,是一个基本的数据存储单元,用于在计算机系统或存储设备上保存数据。

# shell

shell 是:一个程序,可以从键盘中获取命令并将其提供给操作系统进行执行。

# GUI

GUI 是:一个用户界面,用户可以通过图形或图标指示符与电子设备进行交互

# 计算机架构

计算机架构是:计算机体系结构是描述计算机系统功能,组织和实现的一组规则和方法,主要包括:指令集、内存管理、I/O 和总线结构等。

# 多处理系统

多处理系统是:指计算机同时运行多个程序的能力。

# 程序计数器

程序计数器是:一个 CPU 中的寄存器,用于指示计算机在其程序序列中的位置。

# 多线程

多线程是:指一个软件或硬件上实现多线程并发执行的技术。

# CPU 核心(core)

CPU 核心是:CPU 的大脑,它接收指令,并执行计算或运算以满足这些指令,一个 CPU 可以有多个内核。

# 图形处理器(视觉处理器)

图形处理器是:

# 缓存命中

当应用程序或软件请求数据时,会首先发生缓存命中

# RAM(随机存取存储器、主存)

RAM 是:与 CPU 直接交换数据的内存存储器

# ROM(只读存储器)

ROM 是:一助攻半导体存储器,特点是一旦存储数据就无法改变或删除

# 驱动程序

驱动程序是:一个允许高级别电脑软件与硬件交互的程序

# USB

USB 是:连接计算机系统与外部设备的一种串口总线标准,也是一种输入输出接口的技术规范

# 地址空间

地址空间是:内存中可供程序或进程使用的有效地址规范

# 进程间通信

进程间通信是:指至少两个进程或线程间传送数据或信号的一些技术或方法
进程间的通信方式有:管道、消息队列、共享内存、信号量、信号、套接字

  • 管道
    管道一种半双工的通信方式,数据只能单向流动,管道实质上是一个内核缓冲区,且以先进先出的方式存取数据。
    管道分为命名管道和匿名管道
    匿名管道它的优点是:简单方便;缺点是:
    因为管道局限于单向通信且缓冲区有限,所以它的通信效率低,不适合进程间频繁地交换数据
    只能在父子进程间使用
    命名管道,和匿名管道相比,它的优点是:可以实现任意关系的进程间的通信;缺点是:
    长期存在系统中,使用不当容易出错
  • 消息队列
    消息队列是保存在内核中的消息链表
    优点:可以实现任意进程间的通信,并且通过系统调用函数来实现消息发送和接收之间的同步,无需考虑同步问题
    缺点:
    消息队列不适合比较大数据的传输,因为每个消息体都有最大长度限制,同时全体消息也有总长度上限
    通信过程中,存在用户态与内核态之间的数据拷贝开销
  • 共享内存
    共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问
    优点:进程可以直接读写这块内存而不需要进行数据拷贝,提高效率
    缺点:
    多个进程同时修改同一个共享内存,会发生冲突
    共享内存只能在同一计算机系统中共享
  • 信号量
    信号量是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据
    优点:信号量解决了止多进程竞争共享资源,而造成数据的错乱的
    缺点:信号量有限
  • 信号
    信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生
  • 套接字
    套接字通信不仅可以跨网络与不同主机的进程通信,还可以在同主机进程通信
    优点:
    传输数据为字节级,传输数据可自定义
    适合客户端和服务端之间信息实时交互
    可以加密,数据安全性强
    缺点:需对传输的数据进行解析,转化成应用级的数据

# 目录(文件夹)

目录或文件夹是:指一个装有数字文件系统的虚拟容器

# 路径

路径是:一种电脑文件或目录的名称的通用表现形式,它指向文件系统上的一个唯一位置

# 根目录

根目录是:

# 文件描述符

文件描述符是一个用于表述指向文件的引用的抽象化概念

# 客户端

客户端是:访问服务器提供的服务的计算硬件或软件

# 服务端

服务端是:为其他程序或设备提供功能的计算机程序或设备

# 操作系统功能

操作系统分为:进程管理、内存管理、文件系统管理、设备管理、用户接口、网络通信、安全和保护、系统监控、作业管理、分布式系统管理、电源管理和系统恢复

# 内存管理

操作系统的内存管理是操作系统中的一个非常重要的功能,负责 内存分配内存管理内存回收 ,以确保程序能够高效运行。

# 虚拟内存

原因

  • 程序在初始化时会被 CPU 分配相应的内存到进程中,如果程序申请更多内存就会导致两块内存存放的地址相距很远,不利于数据的追踪,因此有了虚拟内存,它抽象并隐藏了实际的物理内存地址,暴露易于管理的虚拟内存地址。
    进程持有的虚拟地址会通过 CPU 芯片中的内存管理单元的映射关系,来转变为物理地址,然后通过物理地址访问内存

# 进程管理

进程是操作系统进行资源分配的基本单位,线程是操作系统进行 CPU 调度的基本单位

  • 一个进程的活动期间至少具备三种基本状态,即:运行态、就绪态和阻塞态

进程的初始化和执行

  • 当一个作业在硬盘上存储时,可被通过命令行或图形界面的方式加载到内存中(作业调度器会选择哪些作业要加载到内存中),这样一个作业就变成了一个或多个任务,每个任务都对应于作业中的一个程序或程序的一部分
  • 随后操作系统为每个任务创建一个进程,涉及到为进程分配一个唯一的进程标识符(PID),并为他分配必要的资源(内存空间、文件描述符等),任务变为了进程。
  • 一个进程包含多个线程,是 CPU 调度的基本单位,一个线程包含:程序计数器、寄存器和堆栈。进程中的多个线程共享进程资源(地址空间、全局变量和打开的文件等)
  • 被选中的进程状态由新建变为就绪,意味着准备好执行,并等待调度器分配 CPU 时间
  • 进程调度器将根据进程优先级、CPU 时间和 I/O 时间来负责决定哪个进程将获得 CPU 时间。
  • 被调度器调度的进程将获得 CPU 时间,由就绪状态变为运行状态
  • 执行过程中,进程可能会因为各种原因改变状态,如:等待 I/O 操作(变为阻塞状态)、等待其他进程(变为等待状态)等
  • 一旦进程执行完毕,操作系统就会释放该进程的所有资源,移除该进程的所有痕迹。

# 线程

线程是 CPU 调度的基本单位,一个进程中可以有多个线程,他们之间共享资源,线程出现的原因是为了解决进程间共享状态麻烦的原因。

# 进程间通信

# 通信方式

# 管道

速度慢、容量有限,父子进程间通信

  • 管道是一个内核空间中的固定大小的缓冲区,用于连接读进程和写进程之间通信的一个共享文件,又称 pipe 文件。
  • 管道是一种特殊的文件,允许两个进程进行通信,管道有一个读端和一个写端,数据只能单向流动,即从写端流向读端。
  • 无名管道是最基本的管道形式,只能在有共同祖先的进程之间使用(如父子进程),无名管道在操作系统的内核缓冲区中临时存储数据。
  • 当数据被写入管道时,实际上是被写入到操作系统内核空间的一个缓冲区中,这个缓冲区是管道的核心,允许数据在写入进程和读取进程之间传递
  • 创建管道时,操作系统会返回两个文件描述符给应用空间,一个用于读取一个用于写入,当一个进程向管道的写端写入数据时,数据被复制到内核缓冲区,当另一个进程从管道的读端读取数据时,数据从内核缓冲区被复制到进程的用户空间内存。
  • Node.js 中通过 child_process 模块创建子进程获取文件描述符,文件描述符的 on 方法、stdout.on 方法和 stderr.on 方法监听即可利用管道进行通信
# 命名管道
  • 命名管道在内核态中有名字,因此可以被不同的、没有亲缘关系的进程使用。
  • 命名管道是一种特殊的文件系统对象,允许不相关的进程间双向通信
  • 命名管道可以在不相关的进程之间使用,甚至可以跨网络使用,命名管道在文件系统中有一个名字,看起来像一个普通的文件,用户空间中可以通过文件路径来引用这些命名管道。
  • Node.js 的 IPC 模型是基于网络 socket,可以指定一块相同的文件路径,通过 net 模块传入文件路径创建连接。
# 消息队列

容量收到系统的限制,第一次读时,要考虑上一次没有读完数据的问题,发送到消息队列的数据太大时,需要拷贝的时间也就越多。

  • 管道属于一股脑的输入,没有边界,其大小受限而且只能承载无格式字节流的方式,适合于需要连续数据流的场合。
  • 而消息队列按照一个个独立单元(消息体)进行发送,有明确的边界,其中每个消息体规定大小块并且是独立的,同时发送方和接收方约定好消息类型或者正文的格式,适合于需要发送离散消息的场景,如:事件通知或请求 / 响应模式。
  • 消息队列是持久的,管道通常在创建它的进程终止之后就会消失,除非设置了命名管道。
  • 内核态中创建一个消息队列,操作系统中多个进程都可以操作这个消息队列
  • Node.js 中通常使用 Redis 的消息队列库。
# 共享内存

能够很容易地控制容量、速度快、但是要保持同步

  • 在通信的进程之间创建不同虚拟地址指向同一个物理地址,通过共享的物理地址地址进行读写操作实现进程间的信息交换。
  • 每个进程访问内存时,都有一个虚拟内存地址和物理内存地址的映射,一般两个进程的虚拟内存地址可以是一样的,映射的物理内存地址是不一样的,共享内存就是让两个进程虚拟内存地址映射的物理地址相同。
  • Node.js 中 sharedArrayBuffer 用于共享内存
# 信号量

不能传递复杂的消息,只能用来同步

  • 多个进程共享内存时,如果同时都往里面写内容,就难免会出现冲突的现象,信号量实际上是一个计数器,主要是实现进程之间的同步和互斥,而不存储通信内容。
  • 信号量通常用于控制对共享资源的访问,以实现同步和互斥,而不是直接用于进程间通信。
  • 信号量是一种更为复杂的同步机制,用于控制对共享资源的访问,它通常用于多线程编程中,但也可以在进程间通信中使用,尤其是需要同步访问共享资源时。
  • Node.js 中可以使用三方库 semver,确保不同进程中使用相同的名称去创建信号量。
# socket 套接字

不同进程间通信

  • 基于连接的进程间通信,首先需要有一个进程在监听某个端口,也称为服务进程,套接字主要分为两种类型:TCP 套接字和 UDP 套接字。
  • 如果哪个进程想和这个服务进程通信,就要先和服务进程完成三次握手,握手完成后,服务进程会创建一个通信型 socket 和客户进程进行数据通信,服务进程继续监听。
  • TCP 套接字:使用 TCP 协议、提供可靠的、有序的和错误检测功能。
  • UDP 套接字:使用 UDP 协议、提供无连接的不可靠服务。
  • Node.js 种通过 net 模块创建 TCP 套接字、通过 dgram 创建 UDP 套接字。
# 信号

信号不用于传递数据,而是发送方和接收方约定的操作。

  • 信号可以在不同进程之间或一个进程的不同线程之间传递。
  • 一个进程可以向另一个进程发送一个信号,这个进程对不同信号进行不同的处理。(比如 shell 中 ctrl + c 就是向进程中发送一个信号)
  • 进程默认信号:SIGKILL:终止进程、SIGSTOP:停止进行执行、SIGCONT:继续被暂停的进程、等
  • 进程也可以自己监听信号,对不同信号进行不同的处理。
  • Node.js 中通过 process.on 来监听信号。

# 通信问题

互斥

  • 有时多个进程中的部分代码同时读写同一个全局变量时,就会因为竞争而产生一些问题,这部分代码被称为临界区,
  • 读写互斥法:(使用一个锁,进程 A 访问临界区时上锁,进程 B 不能访问、保持等待,等进程 A 出了临界区之后解锁,进程 B 才能进入),不过因为这个锁也是共享的,也会出现竞争现象。
  • 严格轮换法:

同步

# CPU 调度算法

CPU 调度以线程为基本单位,进程通常有两种:一是:计算密集型进程(长时间占用 CPU 进行计算),二是:I/O 密集型(访问外接设备的时间长、次数多)

# 先来先服务

CPU 先运行先到来的进程,直到它阻塞

  • 先来先服务会导致先执行的线程即使在等待 I/O 操作,也会阻塞后面的线程
# 短作业优先

哪个线程

# PCB

PCB 全称:进程控制块,是进程存在的唯一标识

# I/O

I/O 即输入输出,比较关心的有:磁盘 I/O(读写磁盘文件),网络 I/O(读写 socket 文件)和标准输入输出(读写控制台)

# I/O 设备控制器

每一个 I/O 设备都对应着一个设备控制器,设备控制器的目的是管理和控制输入 / 输出设备与计算机系统之间的数据交换,屏蔽了底层硬件之间的差异,为操作系统提供一致的调用接口。

  • I/O 设备控制器包含:指令寄存器、数据寄存器和控制寄存器
  • 控制寄存器用于控制设备的工作模式、配置参数、中断等。
  • 指令寄存器用于存放 CPU 发送的 I/O 指令。
  • 数据寄存器用于存储 CPU 发送给设备的数据信息。

当应用调用操作系统提供的硬件接口时,就会发生系统调用,CPU 在内核态会向该硬件的设备控制器发送指令(操作系统为每个硬件设备分配了内存地址),当设备控制器是 busy 状态就会发生系统中断,CPU 就去执行其他程序,当设备控制器变为 ready 状态时,就会通过控制寄存器执行指令,将 CPU 发送的指令存到指令寄存器,发送的数据存到数据寄存器。最后执行整个指令,完成 I/O 操作。

# socket 接口

# socket 套接字文件

套接字文件是一个特殊类型的文件,表示一个网络套接字,常用于本地通信,一个进程可以在文件系统中创建一个套接字文件,另一个进程可以访问这个套接字文件进行通信。

  • 套接字文件通常不会占用磁盘空间,是由操作系统管理的一种抽象,用于实现进行间通信,通常存储在内存中。
# socket 套接字接口

和 socket 文件进行进程间通信相似,网络请求会调用 socket 套接字接口,但与 socket 文件无关。

  • 应用层调用 socket 套接字相关 api,然后陷入到内核态。
  • 操作系统根据网络协议栈封装为相应的数据包,最后调用网卡发送请求。
  • 当数据请求过来之前,内核缓冲区是空的,应用进程会发生系统中断(system_call 函数),cpu 转而执行其他应用进程。
  • 直到数据请求到网卡,网卡将网络数据包封装成帧(添加帧首和帧尾,包含目的 Mac 地址和源 Mac 地址),接着交由内核态中网络协议栈
  • 网络协议栈先将帧封装为网络层的数据包(提供源 IP 地址和目的 IP 地址)再封装为应用层的数据报(提供端口指定应用程序),最后会将该数据从内核态拷贝到用户态应用进程中。
  • 应用进程获取数据进行执行。

# 阻塞 I/O 和非阻塞 I/O

阻塞 I/O 不会发生系统中断(system_call 函数),会一直等待 I/O 输入,而非阻塞 I/O 会发生系统中断 (system_call 函数),如果数据没准备好,内核缓冲区是空的,就会发生系统中断(system_call 函数),保存状态到 PCB 中,先执行其他进程,回头如果数据准备好了就会再次执行。

# 软链接和硬链接

  • 软链接和硬链接是操作系统中用于创建文件链接的两种方式
  • 硬链接直接指向原始文件的磁盘文件,软链接指向原始文件路径 (存储在文件系统的目录结构中)
  • 软链接是一个特殊类型的文件,包含了指向另一个文件或目录的路径
  • 删除原始文件对硬链接没有影响,因为硬链接直接链接到磁盘文件,但是删除原始文件对软链接有影响,因为软链接指向的是原始文件的文件路径

# 图解系统

# 计算机基本结构

运算器、控制器、存储器、输入设备、输出设备

# 如何写出高质量代码

  • 提高 CPU 缓存命中率
  • 避免多核 CPU 伪共享 Cache Line
  • 为线程指定 CPU 核心

# 学习过程

计算机科学速成课
计算机是怎么跑起来的
程序是怎么跑起来的
操作系统(哈工大李志军老师)
操作系统导论
深入理解计算机系统
数据库系统概论
计算机网络
TCP/IP

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

dmq 微信支付

微信支付

dmq 支付宝

支付宝