前言
记录小林的图解系统部分重点知识,方便日后翻阅回看。
不一定面面俱到,也不会尽数记录,多半将需要辨析和对比的内容采用自己理解的方式来记录。
来源:小林coding
名词科普
MMU:MemoryManagementUnit内存管理单元,负责虚拟地址和物理地址的转换。
OOM:Out of Memory内存溢出。OOM Killer机制是一种保护进程手段
PCB:process control block 进程控制块,包括进程标识符、优先级、进程状态和虚拟地址信息等。
IPC:Inter-Process Communication,进程间通信
VFS:Virtual File System,虚拟文件系统,Linux在用户层和文件系统之间的中间层
DMA:Direct Memory Access 直接存储器访问。不需要依赖大量中断,使得设备在CPU不参与的情况下自行把I/O数据放入内存
前置一、硬件结构
1.1 计算机基本结构
名字 | 特点 |
---|---|
寄存器 | 存储计算时的数据,因为内存离CPU太远了,寄存器会快很多 |
逻辑运算单元 | 负责计算 |
控制单元 | 负责控制CPU工作 |
总线 | 负责CPU和内存及其他设备的通信 |
内存 | 基本单位是字节,线性存储 |
输入输出设备 | 控制总线与CPU连接 |
硬件的 64 位和 32 位指的是 CPU
的位宽
32 位CPU | 64 位CPU |
---|---|
寻址范围:最大操作4 GB内存 | 寻址范围:2^64 |
最好搭配32位宽的线路,一次只能计算不超过32位数字 | 只有当一次计算超过 32 位数字才有优势 |
软件的 64 位和 32 位指的是指令
的位宽
32位指令 + 兼容机制 -->可在64位机器上执行
64位指令不可再32位及其执行,因为32位寄存器放不下64位指令
操作系统也是一种程序,XX位操作系统即XX位软件
1.2 存储器层次结构
层级 | 特点 |
---|---|
寄存器 | |
L1 Cache | CPU独有,分为指令和数据,SRAM |
L2 Cache | CPU独有,SRAM |
L3 Cache | 多CPU共用,SRAM |
内存 | DRAM(动态存储芯片) |
SSD&HDD | 固态硬盘和机械硬盘 |
1.3 提升缓存命中率,提高CPU速度
读取过程
Cache Line
(缓存块) = Index
索引 + Valid bit
有效位 + Tag
(头标志) + Data Block
(数据块)
CPU访问内存地址
- 已知内存地址索引,得到CPU Cache Line 的 Index
- 判断有效位
Valid
,无效则访问内存 - 将内存和Cacheline的组标记
Tag
比较,不同则访问内存 - 根据内存的偏移量
Offset
访问数据块
总结: 读取数据的时候先访问Cache;有数据就读取;无数据则把内存的读入到Cache再读取
提升缓存命中率
数据缓存
:按内存布局顺序访问,如for(i)外for(j)内指令缓存
:CPU分支预测器预测未来的指令,提前放入指令缓存;所以先排序后遍历会更快,或者用likely
包裹if表达式手动预测多核CPU
:把线程绑在一个CPU核心上
1.4 缓存一致性
CPU的数据
写
入方式
方式 | 特点 |
---|---|
写直达 | 数据写入Cache Block后,并写入内存 (性能较差) |
写回 | 数据存在Cache时,写入Cache并标记为脏,不写入内存; 如果存放的是“别的内存地址的数据”则写入并标记为脏,如果此前这个数据是脏的,还需要将它写回到内存 |
只有 缓存不命中 && Cache为脏 才写入内存中;如果大量操作命中缓存则基本不需要读写内存,性能比写直达高很多
缓存一致性(多核心)
场景:A号CPU处理了变量i,但只是在Cache标记为脏;B号核心从内存读取了错误的i变量。
要想实现缓存一致性,关键是要满足 2 点:
机制 | 特点 | 具体实现 | |
---|---|---|---|
写传播 | 某个 CPU 核心发生写入操作时,需要把该事件广播通知给其他核心 | 总线嗅探 | CPU监听总线的活动并广播 |
事物的串行化 | 各核心看到的数据变化顺序是相同的 | 基于总线嗅探的MESI协议 | 已修改 / 独占 / 共享 / 已失效 |
MESI流程:
- A读取
i
的值,其他CPU没有缓存,标记为独占
- B读取
i
的值,广播给其他CPU核心,A返回读取信息,Cache Line状态为共享
- A修改
i
,首先广播令其他核心标记已失效
,A标记已修改
,此时Cache和内存数据不一致 - A再修改
i
,直接修改即可 - A的Cache里的
i
要被替换,是已修改状态,需要首先同步到内存
1.5 伪共享和CPU线程调度
伪共享
问题: 多个线程同时读写同一个 Cache Line 的不同变量时,而导致 CPU Cache 失效的现象。
解决: 内存对齐,通过空间换时间。
CPU选择线程
调度类 | 调度器 | 调度策略 | 原则 |
---|---|---|---|
Deadline(实时) | Deadline调度器 | SCHED_DEADLINE | 按deadline有限 |
Realtime(实时) | RT调度器 | SCHED_FIFO SCHED_RR | 对相同优先级的按FIFO / RR |
Fair(普通) | CFS调度器(完全公平) | SCHED_NORMAL SCHED_BATCH | 为每个任务安排虚拟时间vruntime,运行越久就越大; 调度的时候优先vruntime少的任务 |
1.6 中断
为了避免中断处理时间过长,影响进程调度,Linux 将中断处理分为上半部和下半部:
名称 | 特点 |
---|---|
上半部 | 对应硬中断,由硬件触发中断,用来快速处理中断 |
下半部 | 对应软中断,由内核触发中断,用来异步处理上半部未完成的工作 |
1.7 0.1 + 0.2 != 0.3
负数的二进制表示方式
补码:把正数的二进制全部取反再 + 1
用了补码的表示方式,对于负数的加减法操作,实际上是和正数加减法操作一样的;否则还要将加减法反转。
计算机存小数
符号位 | 指数位 | 尾数 |
---|---|---|
0 / 1 | 小数点移到第一个有效数字后;移动位数+偏移量(float里是127) | 小数点右侧的数字 |
二进制浮点数的小数点左侧只能有 1 位,并且还只能是 1,既然这一位永远都是 1,那就可以不用存起来了,尾数还能多存一位小数。
前置二、操作系统结构
什么是内核? 内核作为应用连接硬件设备的桥梁,应用程序只需关心与内核交互,不用关心硬件的细节。
内核程序执行在内核态,用户程序执行在用户态。当应用程序使用系统调用时,会产生一个中断。发生中断后, CPU 会中断当前在执行的用户程序,转而跳转到中断处理程序,也就是开始执行内核程序。内核处理完后,主动触发中断,把 CPU 执行权限交回给用户程序,回到用户态继续工作。
OS | 内核架构 | 可执行文件 |
---|---|---|
Linux | 宏内核;内核是一个完整的可执行程序,且拥有最高权限。系统内核的所有模块(内存、进程、文件、设备)都运行在内核态 | ELF 可执行文件链接格式 |
Windows | 混合内核 = 宏内核 + 微内核 微内核架构的内核只保留基本能力,驱动和文件等放在用户空间,服务之间隔离,但频繁切换有损性能 | PE 可移植执行文件 |
前置三、网络系统
一、内存管理
1.1 虚拟内存
直接操作物理地址行不通 ×
,因为同地址写入新值会擦除内容。
操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来。程序要访问虚拟地址的时候,由操作系统转换成不同的物理地址,这样不同的进程运行的时候,写入的是不同的物理地址,这样就不会冲突了
进程的虚拟地址----------------MMU
---------->硬件物理地址
内存分段
如何映射?
虚拟地址
包括段号
(段表的索引)和段内偏移量
| /
| /
指向 +
| /
↓ /
段表
----->1. 段的基地址
= 物理内存地址
------>2. 段界限
- 代码段
- 堆段
- 数据段
- 栈段
内存分页
虚拟地址
包括页号
(页表的索引)和页内偏移量
| /
| /
指向 +
| /
↓ /
页表
----------> 物理页号
= 物理内存地址
分段 | 分页 | |
---|---|---|
优点 | 产生连续内存空间 | 无外部碎片 |
缺点 | 1. 内存碎片 2. 内存交换效率低(交换的程序占内存很大) | 页内部内存碎片(最少分配一页) 32位4GB虚拟地址空间,如果每个页表项4字节,则需要 4MB 空间存储页表,(多级页表解决) |
多级页表
单级页表:页号----->页表中的物理页号
多级页表:一级页号—>二级页表(页号)地址—>二级页表的物理页号
在需要时才创建二级页表,单级页表4MB
,假如只有20%被用到,那么页表占用的内存空间只有4KB
(一级页表) + 20%*4MB =0.804MB
。
TLB
(Translation Lookaside Buffer):页表缓存 / 快表
段页式
:
- 先访问段表,得到页表的起始地址;
- 访问页表,得到物理页号
- 物理页号+页内位移 = 物理地址
虚拟内存作用
- 可以使运行内存超过物理内存大小。 因为CPU 访问内存的重复访问性,对于不经常用的内存,可以换到磁盘
- 解决多进程之间地址冲突的问题。 每个进程都有自己的页表且私有,虚拟内存空间相互独立。进程也没有办法访问其他进程的页表
- 提供了更好的安全性。页表项中除了物理地址之外,还有一些页的读写权限和存在等信息
1.2 Linux内存管理
Linux 内存主要采用的是页式内存管理,但同时也不可避免地涉及了段机制
虽然每个进程都各自有独立的虚拟内存,但是每个虚拟内存中的内核地址,其实关联的都是相同的物理内存。这样,进程切换到内核态后,就可以很方便地访问内核空间内存。
Linux的虚拟地址空间中的用户空间:
高
内存段 | 内容 |
---|---|
栈段 | 局部变量 和函数调用上下文 ,一般8MB |
文件映射段 | 动态库 、共享内存 ;动态分配 |
堆段 | 动态分配 的内存,向上增长 |
未初始化数据 .bss | 未初始化的静态变量 |
已初始化数据 .data | 静态常量 ,全局变量 |
程序文件 .text | 二进制可执行代码 |
低
1.3 malloc原理
malloc
不是系统调用而是C库的函数,用于动态分配内存。
系统调用 | 原理 | 阈值 | free 内存 | 缺点 |
---|---|---|---|---|
brk() | 堆顶指针向高地址移动 | 分配内存小于 128KB | 不归还,保留在内存池中下次直接复用 | 频繁malloc和free产生内存碎片,导致内存泄漏 |
mmap() | 在文件映射区分配内存 | 大于 128KB | 释放内存 |
- malloc分配的是
虚拟内存
,如果没有被访问就不会映射和占用物理内存 预分配更大
内存,1字节实际申请了132K字节。free多大内存?
malloc返回给用户态的地址比堆空间起始地址多了16字节,用于保存描述信息以便free知道内存块大小
1.4 内存回收机制
内存分配
访问分配的虚拟内存时没有映射到物理内存,会缺页中断,进入内核态调用缺页中断函数处理
- 有空闲物理内存,直接分配
- 无空闲内存,会进行回收内存
回收机制 | 特点 |
---|---|
后台内存回收 | 异步 ,不阻塞线程 |
直接内存回收 | 如果异步回收跟不上申请内存的速度,就直接回收,同步 且阻塞 进程 |
- 还无法满足,则触发OOM(Out of Memory)机制,一直杀死物理内存占用高的进程直到空间足够
回收范围
- 文件页。包括内核缓存的磁盘数据和文件数据;干净页直接是否,脏页写回磁盘
- 匿名页。如堆、栈数据等,没有实际载体,所以swap写到磁盘中再释放
均基于LRU
算法
性能影响
回收操作频繁–>磁盘IO次数多,会很卡,优化方式如下:
- 调整文件页和匿名页回收倾向。文件页影响小,因为干净页回收不发生磁盘IO
- 尽早触发异步回收。页高阈值、页低阈值和最小阈值。如果超过页低阈值,触发异步内存回收,但不阻塞;如果超过最小阈值则直接内存回收。调整阈值(三个阈值同时调整),要性能好就增大(提前触发异步),要内存使用大就调小。
如何不被OOM杀掉
对进程打分,打分高的会被杀掉。页面数越多、或校准值oom_score_adj
越高,得分越高,可以将oom_score_adj
调低(默认0,最低-1000),不容易被杀掉。
1.5 4GB物理内存机器申请8G内存
32
位操作系统 +4
GB物理内存。因为进程最大只能申请 3 GB 大小的虚拟内存,申请 8G 内存失败64
位操作系统 +4
GB物理内存。可申请128TB的虚拟内存。可以申请8
GB的虚拟内存,如果没有被使用,完全没有问题,如果被使用的话,要看系统有没有swap
分区(把磁盘当成内存来使用,负责换入换出):- 有
swap
分区:正常运行 - 无
swap
分区:物理空间不够,发生OOM
- 有
二、进程调度
2.1 进程线程基础知识
进程:
运行中的可执行文件
PCB
:进程控制块。把相同状态的进程链在一起,例如就绪队列、阻塞队列,可以灵活插入和删除。
CPU上下文切换包括如下:
- 进程上下文切换
- 线程上下文切换
- 中断上下文切换
切换 | 内容 | 位置 |
---|---|---|
CPU上下文切换 | CPU寄存器 和程序计数器 前者存储计算时的数据,后者存储指令位置 | |
进程 的上下文切换 | 保存在PCB ,包括虚拟内存、栈、全局变量等用户空间资源也包括内核堆栈和寄存器等内核空间资源 | 内核管理,所以发生在内核态 |
线程 的上下文切换 | 不同进程:和进程上下文切换一样 同一进程:虚拟内存不动,只切换栈和寄存器等私有数据 | 内核态 |
线程:
进程当中的一条执行流程,并发运行且共享地址空间
分类:
用户线程 | 内核线程 | 轻量级线程LWP | |
---|---|---|---|
位置 | TCB在用户态 的线程管理库实现,操作系统看不到TCB,只看得到PCB | TCB放在操作系统里 | 内核支持的用户线程 |
优点 | 没有内核态切换,速度快 | 系统调用阻塞不影响其他内核线程 多线程的进程获得更多CPU运行时间 | |
缺点 | 如果线程因系统调用阻塞,那么其他线程不能执行 用户态线程没法打断运行的线程,只能主动交出CPU使用权 | 线程的创建切换和终止由系统调用,系统开销大 |
进程和线程的区别:
《进程和线程的探讨》
进程 | 线程 | |
---|---|---|
概念 | 运行中的可执行文件 | 进程当中的一条执行流程,并发运行且共享地址空间 |
基本单位 | 资源分配的单位 | CPU调度的单位 |
资源分配 | 拥有完整资源平台 | 只独享寄存器和栈 |
开销 | 上下文切换开销大 | 减少并发执行的时间和空间开销,具体体现在 创建和终止时间快;同进程内切换时间快;通信效率更高 |
优点 | 编程容易 一个进程崩溃不影响其他进程 | 创建速度快 开销小 |
缺点 | 开销大 | 一个线程崩溃时会导致所有线程崩溃 |
2.2 进程调度
系统调度需要考虑的因素(原因):
CPU利用率
:IO请求阻塞时,CPU要从就绪队列运行一个进程吞吐率
:单位时间完成进程数等待时间
:就绪队列中进程的等待时间响应时间
:对于交互性应用(鼠标键盘)所考虑
名称 | 算法 | 适用范围 |
---|---|---|
先来先服务FCFS | 先来后到 | 对长作业 有利,适用于CPU繁忙型,不适用IO繁忙型 |
最短作业优先SJF | 优先短作业 | 对短作业 有利 |
高响应比优先HRRN | 优先权 = 等待时间 + 要求时间 要求时间 {等待时间 + 要求时间\over 要求时间} 要求时间等待时间+要求时间 | 无法预知要求时间,是理想 的 |
时间片轮转RR | 20ms-50ms | 最简单公平 |
多级队列反馈Multilevel Feedback Queue | 每个队列不同优先级,第一级按照FCFS ,没完成转入第二级队尾,有高优先级的立马响应 | 兼顾 长短作业 |
2.3 进程间通信
方式 | 特点 | 创建 | 优劣 |
---|---|---|---|
匿名管道 | 本质文件但没有文件实体;限父子 / 兄弟进程;半双工;内核态 切换;单向字节流;生命周期和进程同在 | |、int pipe(int fd[2]) | 效率低,不适合频繁交换 |
有名管道 | 有文件实体;无需亲缘关系;内核态 切换;生命周期和进程同在 | 命令:mkfifo 函数: int mkfifo() | 不相关进程也可以通信 |
共享内存 | 多进程共享物理内存同一块区域 | shmget() | 无内核态 切换;但可能会有冲突 |
信号量 | 为了防止多进程竞争共享资源互斥 :同一时刻只有一方访问临界区同步 :存在前后依赖关系 | 信号量类型sem_t 相关函数 sem_... | 主要用于互斥&同步 |
信号 | 事件发生时对进程的通知机制,类似于中断,是异步通信 | ctrl + C SIGINT 终止进程ctrl + Z SIGSTOP 进程被挂起kill -9 PID SIGKILL 杀死任何进程SIGCHLD 子进程结束时发出的信号SIGKILL 和SIGSTOP 不能被捕捉 | 唯一异步通信机制 |
Socket | 可基于TCP、UDP和本地进程通信 | int socket(协议族,通信协议,0) | 跨网络通信 |
消息队列 | 内核中的消息链表。虽然解决了管道的效率问题,但通信不及时、大小有限制;生命周期和内核同在,除非手动关闭 | 效率比管道高 | |
内存映射 | 磁盘文件映射到内存 | void* mmap() int munmap() |
共享内存和内存映射的区别:
- 共享内存可以直接创建;后者需要磁盘文件
- 进程操作同一块共享内存;后者在虚拟空间有独立内存
- 数据安全:进程突然退出:共享内存还在,后者消失
电脑死机:共享内存没了,由于磁盘还在所以后者还在 - 生命周期:进程退出,共享内存还在,要标记删除;后者在进程退出后销毁
2.4 线程安全问题
临界区:访问共享资源的代码片段,不能给多线程同时执行
互斥:一个线程执行时,其他线程禁止进入临界区。好比「A 和B 不能在同一时刻执行」
同步:相互制约的等待与互通信息。好比「A应该在B之前执行」
互斥锁 | 信号量 | 条件变量 | |
---|---|---|---|
功能 | 互斥 | 互斥、同步 | 满足条件时阻塞或解除阻塞,配合互斥量使用 |
函数 | 锁类型pthread_mutex_t 函数 pthread_mutex_... | 信号量类型sem_t 函数 sem_... | 条件变量类型pthread_cond_t 函数 pthread_cond_... |
条件变量cont_t
的必要性:如果没有条件变量的话,在没有物品时消费者循环查询是否有物品剩余,不停加锁和解锁,是一种资源浪费;条件变量设置wait
时包含了锁mutex
,调用pthread_cond_wait()
时会先解锁并阻塞,生产生产一个后,用signal()
通知这边解除阻塞,这边重新加锁并操作。《互斥锁为什么还要和条件变量配合使用》
悲观锁 | 互斥锁 | 自旋锁 | 读写锁 |
---|---|---|---|
类型 | pthread_mutex_t | pthread_spinlock_t | pthread_rwlock_t |
底层 | 内核 实现,失败时内核将线程置为“睡眠”,释放后唤醒 | 用户态,开销小 | 写锁 - 独占锁 读锁 - 共享锁 |
加锁失败 | 失败时被阻塞,且线程切换;堵塞线程从用户->内核,存在两次线程上下文切换的开销 | 一直忙等循环 | |
适用场景 | 锁住代码较长 | 锁住代码短; 异步、协程等用户态编程方式 单核CPU必须要抢占式调度器,因为自旋的线程不会放弃CPU | 读多写少 |
乐观锁(无锁编程)
先修改完共享资源,再验证这段时间内有没有发生冲突,如果没有其他线程在修改资源,那么操作完成,如果发现有其他线程已经修改过这个资源,就放弃本次操作。
生产者消费者问题
描述:
- 缓冲区空时,消费者必须等待生产者生成数据;缓冲区满时,生产者必须等待消费者取出数据。(
同步
) - 任何时刻,只能有一个生产者或消费者可以访问缓冲区;(
互斥
)
解决:
创建一个互斥量mutex
创建一个生产者信号量psem
,表示还有多少空位可生产,初始化为n
。(如果没有上限,那么一个信号量就够了)
创建一个消费者信号量csem
,表示库存数,初始化为0
生产者:
producer() {
sem_wait(&psem); 生产前先生产额度-1,如果为0就阻塞
pthread_mutex_lock(&mutex);
...
pthread_mutex_unlock(&mutex);
sem_post(&csem) 库存+1
}
消费者:
customer() {
sem_wait(&csem); 库存-1,如果为0阻塞
pthread_mutex_lock(&mutex);
...
pthread_mutex_unlock(&mutex);
sem_post(&psem) 生产额度+1
}
2.5 死锁
死锁的四个条件:
- 互斥
- 持有并等待:等待访问资源2的时候并不会释放持有的资源1
- 不可剥夺:释放后才能获取
- 环路等待
避免:破坏一个条件即可,通常是资源有序分配法
2.6 页面置换和磁盘调度算法
页面置换
将页面从磁盘调入物理内存
名称 | 算法 | 适用范围 |
---|---|---|
最佳页面置换算法OPT | 置换未来最长时间不访问的 | 理想 算法,衡量效率 |
先进先出~FIFO | 置换驻留时间长的 | 对短作业 有利 |
最近最久未使用LRU | 置换最久没访问的 | 效率高但开销大,需要每次更新频率链表,因此较少使用 |
最不常用LFU | 置换访问次数最少的 | 计数器成本也不低;只考虑频率没考虑时间 |
磁盘调度(寻道)
名称 | 算法 | 适用范围 |
---|---|---|
先来先服务FCFS | 按请求序列顺序 | 分散、性能差 |
最短寻道时间优先SSF | 优先最近的磁道 | 小区域来回移动 |
扫描scan 循环扫描 Cscan | 左右来回(到头); 单向(到头); | 中间的占便宜; 会更平均; |
LOOK CLOOK | 左右来回(不到头); 单向(不到头); |
三、文件系统
3.1 文件的存储
名称 | 优点 | 缺点 | 备注 |
---|---|---|---|
连续空间存放 | 读写效率高 | 磁盘空间碎片、文件长度不易扩展 | 需提前知道文件大小 |
隐式链表(非连续) | 无法直接访问数据块;稳定性较差 | 文件头包含第一块和最后一块数据块位置,每个数据块都有指向下一块的指针, | |
显式链表(非连续) | 因为表保存在内存中,减少了磁盘访问次数,提高了检索速度 | 不适用大磁盘,表太占空间 | 内存中 ,每个磁盘块的指针放在一张表中,整个磁盘一张表 |
索引(非连续) | 无碎片;增删方便;支持随机读写 | 开销大 | 每个文件都有「索引数据块」,里面是指向文件数据块的指针列表 |
Unix文件存储:
对于小文件直接查找;对于大文件采用多级索引,需要大量查询。
3.2 空闲空间管理
名称 | 优点 | 缺点 | 备注 |
---|---|---|---|
空闲表法 | 少量空闲区效果好 | 大量小的空闲区查询效率低 | 表内容包括第一个块号和块个数 |
空闲链表法 | 不能随机访问,不适用大型文件系统 | ||
位图法 | 二进制数字表示空闲 |
3.3 软链接和硬链接
给文件取别名 | 内容 |
---|---|
软连接 | 多个目录项索引指向一个文件,不可跨文件系统,删除所有硬链接及源文件后才彻底删除该文件 |
硬链接 | 重新创建一个文件,具有inode,文件存储的是另一个文件的路径,所以可以跨文件系统 |
3.4 文件I/O
分类 | 区别 | 具体 |
---|---|---|
缓冲与费缓冲I/O | 是否利用标准库缓冲 | 缓冲 :通过标准库缓存实现加速访问,到了输入或输出时标准库再系统调用,减少次数非缓冲 :直接系统调用 |
直接与非直接I/O | 是否利用操作系统的缓存(默认非直接) | 直接 :无内核缓存复制,直接经过文件系统访问磁盘非直接 :读时,数据从内核缓存拷贝给程序;写时,数据从程序拷贝给内核,再由内核决定什么时候写入磁盘 |
阻塞与非阻塞I/O | 是否等待 内核数据准备好&&数据从内核态拷贝到用户态 | 阻塞 :read时等待 内核数据准备好 && 从内核缓冲区拷贝到程序缓冲区,才返回非阻塞 :read立即返回,程序不断轮询内核,直到上述条件满足才获取结果 |
同步与异步I/O | 同步调用 :阻塞、非阻塞、多路复用都属于同步,因为都要等数据从内核态拷贝到用户态异步 :发起aio_read立即返回,内核自动拷贝到程序空间 |
四、设备管理
CPU通过读写设备控制器中的寄存器,来控制设备,相当于一个小CPU
寄存器:
数据寄存器
:CPU 向 I/O 设备写入需要传输的数据前,先发送一个 H 字符给到对应的 I/O 设备。命令寄存器
:CPU 发送一个命令,告诉 I/O 设备,要进行输入/输出操作,于是就会交给 I/O 设备去工作,任务完成后,会把状态寄存器里面的状态标记为完成。状态寄存器
:目的是告诉 CPU ,现在是否在工作,直到状态寄存标记成已完成,CPU 才能发送下一个字符和命令。
设备可分为块设备
:数据存储在固定大小的块中,如硬盘、USB
和字符设备
:以字符为单位接发字符流,不可寻址。
数据缓冲区:减少对设备的频繁操作
CPU 写入数据到控制器的缓冲区时,当缓冲区的数据囤够了一部分,才会发给设备。
CPU 从控制器的缓冲区读取数据时,也需要缓冲区囤够了一部分,才拷贝到内存。
I/O控制方式
轮询
:CPU一直查寄存器的状态标记位中断
:设备完成任务后触发中断到中断控制器,通知CPU停下来处理。
(1)软中断,例如调用INT
指令触发
(2)硬件中断,硬件通过中断控制器触发
中断对于磁盘频繁读写数据,不友好
,解决方法是DMA
DMA:Direct Memory Access 直接存储器访问。不需要依赖大量中断,使得设备在CPU不参与的情况下自行把I/O数据放入内存
- CPU对DMA控制器下指令,告诉它读多少、读完放在内存哪里
- DMA控制器向磁盘控制器发出命令;磁盘传输到内存后通知DMA
- DMA控制器中断通知CPU指令完成,CPU可以用内存里的数据了
设备驱动程序
设备控制器
属于硬件;为了减少设备控制器的差异,引入设备驱动程序
,属于操作系统的一部分,会提供接口给操作系统,方便OS通过调用设备驱动程序
的接口来控制设备控制器
另外,设备驱动程序
也会调用中断处理程序
的中断处理函数
来处理中断
通用块层
通用块层
用来管理不同块设备,具有以下功能:
- 向上为文件系统和应用程序,提供访问块设备的标准接口,向下把各种不同的磁盘设备抽象为统一的块设备,并在内核层面,提供一个框架来管理这些设备的驱动程序
- I/O调度
存储系统的I/O软件分层
Linux存储系统层次 | 内容 | 功能 |
---|---|---|
文件系统层 | 虚拟文件系统 | 向上为程序提供文件访问接口 向下管理磁盘数据 |
通用块层 | 块设备的I/O队列和调度器 | 调度I/O发给设备层 |
设备层 | 驱动程序、设备控制器、硬件设备 | 物理设备的I/O操作 |
键盘敲入字母,发生了什么
- 键盘控制器扫描数据,缓冲在其寄存器中,并给CPU发送中断请求。
- CPU收到中断后,OS保存上下文,并调用中断处理程序(属于键盘驱动程序)
- 中断处理函数从键盘控制器寄存器缓冲区找到字符,翻译成ASCII码(显示字符),并放入读缓冲队列
- 显示驱动程序把读缓冲队列数据放入写缓冲队列,最后写入到显示控制器的寄存器缓冲区中,并显示在屏幕上
- CPU回复上下文