名词总结
名词 | 概念 |
---|---|
PCB | 进程控制块(PCB Process Control Block),系统中存放、管理和控制进程信息的数据结构称为 |
PC | 程序计数器 也就是下一条指令的地址 |
PID | 进程ID(Process ID) 进程标识符 |
TCB | 线程控制块 |
FCB | 文件控制块 |
PSW | 程序状态字寄存器,用于存放PC、IR等的信息 |
IR | 指令寄存器,存放到当前进行的指令 |
PID | 系统进程 |
POSIX | 可移植操作系统接口 |
IPC | 进程间的通信机制 |
系统调用 | 指的就是引起内核态和用户态切换的一种方式 |
半双工 | 半双工和全双工是计算机网络中的概念,意思是通讯同一时间只允许一方发送数据(对讲机) |
全双工 | 通信允许两方向上同时传输数据(电话) |
P操作 | 属于系统调用,来自荷兰语proveren,即test,检测,代表wait原语,通常使用P(S)代替wait(S) |
V操作 | 属于系统调用,来自荷兰语verhogen,即increment,增加,代表原语signal,通常使用V(S)代替signal(S) |
用户态 | 一般的操作系统对执行权限进行分级,分别为用保护态和内核态。用户态相较于内核态有较低的执行权限, 很多操作是不被操作系统允许的,从而保证操作系统和计算机的安全。 |
内核态 | 内核态相当于一个介于硬件与应用之间的层,可以进行硬件的调度、使用,可以执行任何cpu指令,也可以引用任何内存地址, 包括外围设备, 例如硬盘, 网卡,权限等级最高。 |
用户态内核态切换 | 三种情况下,用户态会转换到内核态,系统调用、程序异常(例如/0,内存资源耗尽等)、来自外围设备的中断 |
系统调用/程序接口 | 用户程序通过系统调用的方式才能对硬件进行使用,或者说操作系统将使用硬件的接口提供给用户程序 |
中断 | 中断是操作系统内核程序夺取cpu的唯一途径,或者说用户程序调用内核代码的唯一途径,因为在一般情况下,操作系统会将cpu使用权交给应用程序。 |
GDT | 操作系统对应的的段表 |
LDT | 每个进程对应的段表,在PCB中的一个表,LDT放在寄存器 LDTR 中 REGISTER |
TLB | 快表寄存器 |
JVM | Java Virtual Machine Java虚拟机 的缩写 |
secondary | 缓冲区 |
CHS | 磁盘块封装 |
CPU | CPU,Central Processing Unit ,也叫中央处理器 |
李志军老师网课
P4 操作系统的接口
什么是操作系统的接口?
系统调用。 操作系统提供了一些重要的函数,这就是操作系统的接口,接口表现为函数调用,有由于是系统提供的,所以称为系统调用。
什么是操作系统?
操作系统是执行系统调用的代码
POSIX ( IEEE制定的一个标准族 )
表头 | POSIX定义 | 表头 |
---|---|---|
任务管理 | fork | 创建一个进程 |
execl | 运行一个可执行程序 | |
pthread_create | 创建一个进程 | |
文件系统 | open | 打开一个文件或目录 |
EACCES | 返回值,表示没有权限 | |
mode_t_st_mode | 文件头部结构:文件属性 |
P5 系统调用的实现
CPL (CS) | 当前的特权级 |
RPL (DS) | |
DPL | 目标特权级 |
GDP表 | 存放 CS 与 DS |
IDP表 | 中断向量表 |
Q1:用户程序不能随意 jum
用户程序 调用 放在操作系统内存中的东西 时,因为安全性问题,是不能直接访问的,
Q2:分为 内核 / 用户 态,内核 / 用户 段
将内核程序和用户程序隔离,核心态也就是内核态为0,0S服务为 1和2,用户态为 3
每一条指令执行的时候,都有PC,PC由 CS 和 iP,CS包括的有本段程序处于的特权级信息,只有当前特权级大于等于目标特权级,才能执行这条指令,注意数值越小表示特权级越高。
Q3:硬件提供了 主动进入内核的方法
这是用户程序发起调用内核代码的唯一方式, int 中断指令
P6 操作系统的历史
第一个历史:计算机的发展
第一阶段:批处理 无法实现切换与调度。
第二阶段:多道程序交替执行,也就是多进程了,各个作业直接的切换和调度称为核心,既有 IO 调用,又有计算任务。
第三阶段:每个人启动一个作业,分时系统,各个作业之间快速切换,与前一阶段不同的是,定时的来切换,将每个作业占用 OS 的时间相等,核心仍然是任务切换
第四阶段:UNIX 出现了
第五阶段:Linux 出现了,Linux 由 UNIX 改造而来。
第一个历史总结:
- 多进程结构是操作系统基本图谱
- 用户通过执行程序使用计算机(吻合冯诺依曼的思想)
- 作为管理者,操作系统要让多个进程合理推进,就是进程管理
- 多进程(用户)推进时会有内存复用等问题。
第二段历史:操作系统的发展
第二段历史主要就是 基于文件的图像。
DOS就是命令行窗口
通过文件来使用计算,非常方便,又加上了一些图形化界面,文件就是第二个重要的图像。
第二个历史,对用户的使用感觉加倍重视,各种文件、编程环境、图形界面等
如何通过文件存储代码,执行代码,操作屏幕
如何让文件和操作变成图标,点击,或者触碰
任务:
1)掌握、实现操作系统的多进程图谱,多进程图像。覆盖两个部分
- 1、CPU,怎么从 CPU 出来内存。CPU 管理
- 2、内存也是一大块,还讲了进程。内存管理
2)掌握、实现操作系统的文件操作视图,文件操作图像。
- 文件是什么?其实文件就是 IO 设备
- IO 设备主要讲磁盘文件,IO 怎么驱动,磁盘怎么工作,
P7 我们的学习任务
操作系统要管理硬件——CPU管理、内存管理,这两个合起来也就是 进程视图
操作系统套管理设备管理——中断设备管理、磁盘管理,这两个合起来就是 文件视图
用户通过接口进入操作系统,进程的管理,就要用 fork,用 fork 管理CPU进行
内存是怎么管理呢?要通过地址,使用内存与 多进程图像是接在一起的
操作设备为什么就是操作文件呢?设备 对应的是 设备的文件,dev, 操作某个文件,就会操作对应的设备。
操作文件,open 一个普通文件与 open 一个设备文件 分别是怎么展开的
P8 CPU管理的直观想法
IO 工作的特别慢,一个IO指令执行起来非常慢的
P9 多进程图像
- 1、操作寄存器如何完成切换:
- 操作系统组织多个进程,用PCB放到不同队列中,用状态转移推进。
- 2、写调度程序
- DPL和CPL 是用来保护操作系统的一种机制,只有OS的DPL才等于0,
- 3、进程同步与合作
- 锁
- 4、地址映射:逻辑内存与物理内存不同,
- 通过映射表来实现,
- 多进程的地址空间分离,这是内存管理的主要内容。
- 进程管理和内存管理共同构成了多进程图像
P10 用户级线程
1、进程 = 资源 + 指令序列
-
资源 :包括寄存器值,PCB,内存映射表
-
线程: 指令序列
-
线程保留了并发和交替执行的优点,同时避免了切换进程的代价
-
实质就是映射表不变,而PC指针改变,体现了分治的思想
2、线程的实用性
- 多线程的用户交互性好
- 多线程的资源共享
3、总结:用户级线程切换的核心
- 一个栈变成两个栈
- 每个线程拥有自己的栈,自己的TCB
- 在切换的时候先切换TCB,在切换栈
- 在创建的时候就是把要切换的PC指针先放到自己的栈中,然后再创建TCB,在将来切回来的时候,首先通过TCB切换到对应的栈中,然后在栈中弹出对应的地址,也就是PC指针。
4、Yield 函数
- 要求能切出去再切回来
- esp
- 线程切换先切TCP,然后再切PC指针,
- 一个线程一个栈
- Yield 切出去的时候,需要先保存需要下一步执行的地址,也就是下一跳,为了返回时可以接着往下执行,
- Yield 切换回来的时候,不需要 jum 到下一跳,也就是就是不用切换PC,因为下一跳的地址已经在切出去的时刻压栈了
5、Create 函数
- 制造出第一次切换时应该的样子
6、用户级线程
- 调用Yield函数,自己主动让出cpu,内核看不见用户级线程的TCP,内核只能看见所属进程而看不见用户级线程,
- 所以一个用户级线程需要等待,内核会切到别的进程上,不会切到该进程下的其他用户级线程!!!
7、内核级线程
- 访问硬件(比如IO网卡等)相关的,一定要用到内核。
- 此时TCP在内核中,内核是可以看到TCP的
- 内核能看见内核级线程,一个线程需要等待,内核会切到所属进程下的其他内核级线程。
- 内核及线程就叫 Schedule,为了区别与用户级线程的Yield
P11 内核级线程
1、compare
- 切换进程实际上是切换内核级线程 + 切换资源
- 核 是由操作系统管理的,只有操作系统才能分配硬件资源,用户级线程不涉及资源的访问
- 一个系统中这三个都有:用户级线程,核心级线程,进程,这样才能充分分配
2、MMU
- memory management uint 内存映射表
3、多处理器 VS 多核
SS stack segment register | 栈段暂存器 |
SP stack pointer | 栈指针 |
ESP extra segment point | 附加段寄存器指针 |
EFLAGS | 你可以当作是保存当前运行状态 |
-
只有多核才能用多线程
-
这里按老师的意思的话 多核CPU是没法让多进程并行执行的… 但是实际上好像并不是这样,我上SO上搜了一下,发现在多核CPU中,应该每个核都有一个MMU(至少在core i7中是这样)
-
并行才能最大发挥多核优势,多进程因为只有一个mmu的原因不能并发只能并行,但多线程在只有一个mmu的情况下也能并行
-
几个核执行这个内存单元里的进程里的多线程,再用另外几个核执行另一个内存单元里的多线程
-
因为多进程MMU不是用的一套
-
通过查阅资料,老师此处说的多进程不能发挥多核价值,或许有误
-
1.linux下并未对进程线程分别做抽象,都是利用task_struct来描述具体调度的一个单元
-
2.也就是说创建进程、线程的时候其实都是调用的clone
-
3.如果clone传参共享资源则为创建线程反之则为进程
-
4.所以“多进程在多核上的情况”,其关键就在于MMU是否共享
-
5.直接给出回答:MMU通常不是共享的。
-
6.具体参考i7存储系统框图,每个core都有一个MMU
-
7.也就是说如果有两个进程A B,每个进程下有两个线程A:T1T2 B:T3T4
-
8.两core单CPU情况下,可以是core1:T1 core2:T3
4、用户级线程到核心级线程的区别
- 一个栈到一套栈,两个栈到两套栈
- 用户级线程切换是:先TCB切,然后根据TCB再切换用户栈
- 核心级线程切换是:先TCB切,然后根据TCB再切换一套栈,这一套栈包括用户栈和内核栈。
5、中断会自动转到内核栈
-
INT 访管中断,对应一个 IRET 刚好是反着的一个过程,弹栈返回到中断前的状态。
-
按下鼠标,启动磁盘等硬件中断
-
时钟中断
-
IRET :中断返回,根据中断返回切换到用户栈
-
read 是一个库函数,调用库函数会展开成一段 int 指令
-
使用call和ret才能自动压弹栈,这里糸统终端就需要手动记录cs,ip
-
内核栈就是记录糸统中断后恢复状态必须的cs,ip,及用户栈的信息
-
switch_to 仍然是通过 TCB 找到内核栈指针,然后通过 ret 切到内核栈指针,然后通过 ret切到某个内核程序,最后再用 CS : PC 切到用户程序
-
TCB在内核,从线程1中断进内核,在内核从TCB1切换到TCB2,然后进线程2用户态接着执行,所以要先用中断进入内核,才能进场TCB的切换
P12 内核级线程的实现
- 1、线程1中 从用户栈到内核栈
- 靠中断,现在以 系统中断为例,也就是 fork,fork本身是创建进程系统调用
- 2、线程1中 从内核栈切换到 TCB
- 3、TCB 调度完成从线程1到线程2的切换
- 4、线程2中从 TCB 切到内核栈
- 5、线程2中 从内核栈到用户栈
P15 schedule 函数 *
count 保证了响应时间的上界为 2p p为初始值
经过 IO 之后,count就会变大, IO 师姐越长,counter 越大,照顾了IO进程,也就是照顾了前台进程
后台进程一直按照counter轮转,近似了SJF
每个进程只用维护一个 counter 变量,简单高效
COUNT的复用,既作为优先级,又作为时间片,count会改变,优先级是动态的
首先是找到所有就绪态任务的最大counter,大于零则切过去,否则更新所有任务的counter,即右移一位再加priority
然后进入下一次的找最大counter大于零则切否则更新counter,
所以说那些没在就绪态的counter就一直在更新,数学证明出等的时间越长counter越大
等他们变成就绪态了,由于counter大,也就可以优先切过去了
P16 进程同步与信号量
程序和进程关系:
一个进程代表程序的一次执行,程序可以多次执行,也就对应了多个进程
等待是进程同步的核心,需要让进程 走走停停 来保证多进程合作的合理有序
等信号量期间是阻塞的
请求的信号量的时候会把自己阻塞起来,直到获取到信号后再激活变成就绪状态
信号不能满足要求,因为信号只是有或者没有,所以要引入信号量
信号量就是用来记录一些信息量,并根据这个信息来总结是 sleep还是 weakup,其实就是一个整形变量
信号只有两种状态,适用于单进程
信号量可以涵盖更多地信息,适用于多进程
信号量的 P操作和 V操作
信号量是一个结构体
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6VTKCWT4-1622192224112)(C:\Users\lh\AppData\Roaming\Typora\typora-user-images\image-20210526142514074.png)]
P17 信号量临界区保护
验证保护算法是否合理的标准:
- 互斥进入
- 有空让进
- 有限等待
进入临界区的保护方法
-
1、软件方法
- 1)轮换法 有空让进效果不好!!!
- 2)标记法 可能会造成无限制空转等待
- 3)非对称标记 结合了标记和轮转两种思想
- 两个进程:Peterson算法
- 多个进程:面包店算法
-
2、 硬件方法
- 关闭中断来关闭调度即可
- 但是注意,多CPU的时候不好使。这里涉及到多cpu如何schedule
-
3、 硬件原子指令
- 其实是用 mutex锁信号量 来保护信号量,为了解决mutex仍然需要保护的问题
使用硬件级原子操作,不能被打断不能切出去进行调度
- 其实是用 mutex锁信号量 来保护信号量,为了解决mutex仍然需要保护的问题
应用:
- 单核系统开关中断
- 多核系统原子变量自旋锁。
总结:
- 硬件原子指令法,不须换保护变量,
- 软件的锁,需要设置一个保护变量,会出现无休止的套娃。
P18 信号量的代码实现 *
P19 死锁处理
死锁即是形成了资源的等待环路,死锁的4个必要条件
- 1、互斥使用
- 2、不可抢占
- 3、请求和保持
- 4、循环等待
死锁处理方法:
- 1、死锁预防:破坏死锁出现的条件
- 1.1 在进程执行之前,一次性申请所有需要的资源,不会占用资源再去申请其他资源
- 1.2 对资源类型进行排序,自愿申请必须按顺序进行,不会出现环路等待。
- 总结:引入太多不合理因素
- 2、死锁避免:当请求出现时,先假装分配,然后用银行家算法,也就是在执行前检测每个资源的请求,如果会造成死锁就拒绝
- 2.1 银行家算法,缺点是复杂度太高,效率低
- 总结:每次申请都执行银行家算法 O(mn),效率太低
- 3、死锁检测+恢复
- 等出现问题了,有一些进程因为死锁而停住了,再处理,选择一个进程进行回滚,然后再用银行家算法来算是否能找到安全序列,如果不行,再回滚,直到所有程序都能执行。
但是回滚是个大问题!!!已经写入磁盘,还得退回来,那就很麻烦了。 - 恢复很不容易,进城造成的改变很难恢复
- 等出现问题了,有一些进程因为死锁而停住了,再处理,选择一个进程进行回滚,然后再用银行家算法来算是否能找到安全序列,如果不行,再回滚,直到所有程序都能执行。
- 4、死锁忽略
- 也就是重启
- windows,linux个人版 都不做死锁处理,直接忽略,大不了重启就好了,小概率事件,代价可以接受
- 死锁出现是不确定的低概率事件
P20 内存的使用与分段
一段内存,一段程序,这个程序被分为多个段,有代码段,变量段等,每个段在内存中找到一块空闲区域,每个段将找到地址的初始值记录在对应段的LDT中,然后将LDT表放在PCB中,又因为编译、链接后的装入模块的地址都是从0开始的,即可执行文件指令地址不变,也就是PC指针的初始值都是从0地址开始取值执行,在每次取址的时候都会查PCB中的LDT表,根据LDT表中对应某个段的基址来找对应的物理地址,也就是从程序中的逻辑地址找到对应在内存中的物理地址,然后进行相应的操作。
段可以有:
主程序段、变量集段、函数库段、动态数组(堆)段、栈段
通过 段号+段内偏移 进行取址
不是将整个程序,而是将各个段分别放入内存中,每个段都需要记录其基地址,存在一个表中,该表称为段表,段表保存到PCB中
每个进程对应一个PCB,PCB中有一个与之对应的 LDT段表,某个进程在运行时,会首先把自己的LDT表在寄存器 ldtr (register) 中更新,这个寄存器暂且理解成只有一个,所以每次取址时都是与 ldtr寄存器 中的基址进行相加的运算,所以当需要切换到另外一个进程时,ldtr寄存器也会跟着切换,变成另外一个进程的 LDT段表。
与之前讲过的地址映射表联想起来,这个LDT段表 就是 地址映射表
P21 内存的分区与分页
三步:
- 第一步:分段 (LDT段表),将一个程序分成多个段落,编译干的事儿
- 第二步:找到一段空闲分区,需要再内存中划分空闲区
- 第三步:通过一个表,将映射关系做好,需要通过磁盘读写,把程序载入进来,这是设备驱动的事儿,
- 这三步完成后就建立了一张映射表,和PCB关联在一起,这是前面讲的进程管理的事情。
这三步完成以后,在以后的运行过程中,就可以通过运行时重定位取址执行,程序执行起来,内存也跟着使用起来了。
物理内存需要分页
程序、用户是需要分段
32位操作系统,也就是有32位地址总线,32位地址总线可以有 2^32,也就是4G的内存空间,
就需要对这4G的内存空间来建立一个虚拟页到物理页的映射转换,
4G的内存空间,没一页是 4kB,那么就有 1M ( 也就是2^20 ) 的页,这个映射表就是这 1M 个页的映射关系
页表的存储也是有一个寄存器,叫 C23 寄存器,这个与段表非常类似,也是存在PCB中的。
通过页来找物理地址这件事,是通过硬件做的,CPU都有一套叫 MMU 的内存管理单元,里面有一套管理分页的电路
只要你把页表的起始地址给 C23 寄存器,寄存器就是硬件,MMU 就可以根据偏移地址等找出对应的物理地址,
一个程序是由多个段组成的,为了避免内存碎片带来的浪费,这每个段不能直接放在内存中,而是要先把内存分页,把这个段打散成多个页来放在内存中
MMU memory manager unit 内存管理单元
P22 多级页表与快表
一个常识:CPU在执行指令的时候,如果是做一些加减乘除类似的运算,那些都是在CPU内部做的,其速度是非常快的,CPU执行指令主要花费的时间不是在运算上,而是花费在通过地址总线来访问内存,这个速度是很慢的,也就是取址是慢的,
问题:
- 需要页表项 连续
- 也需要降低也变的存储空间
方法一:只存放用到的页
- 缺点:页表编号不连续
方法二:多级页表
- 相当于书的目录的作用。既减轻了存储压力,每个还都连续,空间效率比较高,但是浪费了时间
方法三:多级页表 + 快表
- 快表相当于一个cache,把频繁使用的地址映射记录下来
- TLB 是一组相连快速存储,是寄存器
- 在时间可空间上做到最优化。
目录表项和页号表项都是2^10个
offset是一共有2^12个么,每个offset的大小是4KB么?这块没理解,求解答
页面的大小是有offset所占位数决定,而2的12次方等于4KB
P23 段页结合的实际内存管理
程序分段
- 分配端、建段表 LDT,段不能分开,是需要合在一起的。
物理内存分页
- 分配页、建页表
基地址在PCB中,要把程序放入内存,才能开始使用内存
只要做好段表和页表,执行指令时MMU就能自动完成,MMU是用来计算逻辑地址到虚拟地址再到物理地址的转换的,这个过程是由硬件来完成,移位的操作很快的。所以需要先把段表和页表做好。
页段同时存在是重定位
逻辑地址 | 虚拟内存 | 虚拟地址 | 物理地址 |
---|---|---|---|
cs : ip 段号 : 段内偏移 |
根据段表,看段号找到基址,再根据偏移,产生出的地址就是虚拟内存的虚拟地址
这个虚拟地址不是真的地址,需要再次经过一个映射,这次的映射支持分页机制,这次根据页号和偏移找到对应的物理地址,
1)子进程的数据段和代码段映射到不同的虚拟内存地址,每个进程分配 64M 虚拟内存地址
2)父进程和子进程共用一套页目录表,因为虚拟地址不同,页目录号号不同,不打架
3)在页目录表中增加对应页框号表,即父进程和子进程中对应的虚拟地址不同,但是不同地址映射到了同一块页框,也即映射到同一块物理内存。
4)然后对子进程设置共享内存段属性为只读,这样在子进程往里写的时候,会发现只读,会修改映射,映射到其他空间!!!
达到对于用户来讲虽然是操作的同一个地址,但其实对应的是物理内存的两块区域,即子进程和父进程的内存区域是分隔的!!!
P24 内存换入-请求调页 swap in
段页同时存在,就需要用到虚拟内存
上层用户是看不到物理地址的,他们看到的就是0-4G的一个空间,(这个是在32位操作系统上虚拟内存的大小)
每个进程可以单独使用这4G内存,就像是单独拥有这4G内存一样,操作系统就是给用户提供了这样一个假象,好像用户可以在0-4G的内存上随意使用这个内存
后面的从虚拟内存到物理地址的映射,用户是全然不知的,操作系统是实现了从虚拟内存到物理内存的映射
物理内存一般是比较小的,有1G,有2G,但是在用户看来都是4G,
用户执行的程序都在磁盘上,需要将磁盘上的指令放在内存上,才能完成执行指令
所有指令都是一句一句执行的,所以当执行完一句后,再次换入换出到内存上,这样就实现了大的虚拟内存映射到了小的物理内存上。
请求的时候才从虚拟内存到物理内存映射,才换入到物理内存
虚拟内存相当于一个大仓库,
P25 内存换出
访问地址发现缺页,产生缺页中断,从磁盘中读取,换入
当从磁盘读入时,发现物理内存不足,也就是页框不足,就要用 clock 算法产生时钟中断,淘汰掉一个页面,写进磁盘
这个过程就是 swap in 和swap out 的swap分区管理
这样通过 内存,时钟中断,缺页中断,磁盘,磁盘管理,磁盘驱动,这样就形成了swap换入换出的一个样子,
而实现换入换出,是为了实现虚拟内存,这个就是实现虚拟内存的核心
实现虚拟内存,是为了实现段页
实现段页,是为了实现操作系统管理内存的思想
管理内存,是为了实现让程序能够载入内存,也就是让程序能够执行起来
让程序能够执行起来,这就是进程
内存管理和进程管理这两个部分是操作系统的核心,
在加上一些设备驱动,文件系统,系统初始化,系统引导,这些加起来就是一套完整的操作系统
P26 IO与显示器
一台计算机,有CPU,有内存,有显示器,有键盘,即使在没有磁盘的情况下,也就可以开始使用了,
I/O就是输入输出,I/O设备就是可以将数据输入到计算机或将计算机数据输出的设备,常见的:鼠标、键盘、音响、显示器、打印机、话筒、摄像头等等。
**I/O控制器:**CPU无法直接控制I/O设备,需要一个电子部件去充当中间人,这个部件就是I/O控制器,CPU控制I/O控制器,I/O控制器控制I/O设备。
假如我们的CPU能够控制I/O设备,那不同的厂商、不同型号的设备,都要对应进行编码,显然是不切实际的,所以CPU要采用通用调度方式调度I/O设备从而需要I/O控制器。
外设的控制器:
显示器的 I/O 控制器:对应显卡
键盘:输入 对应
磁盘:读写
核心是就三个:
- 1、CPU向外设发出一条 读或写 的指令,也就是
- 2、外设工作完成的时候,向CPU发出中断
- 3、CPU处理中断
但是OS为了统一,要想让外设实现驱动需要做三件事:
-
1、向外设的核心控制器的某些寄存器端口或者是某些存储端口,发出读写指令,所以第一个部分就是 通过 out 向外设发出指令,这个是计算机/CPU使用外设最核心的东西
-
2、但是要想写出上面的几条核心指令,必须要对硬件非常了解,要知道硬件对应的端口,硬件指令的格式等等,这些细节非常麻烦,因为不一样的外设制造厂商通常造出的设备也不一样,操作系统为了隐蔽这些细节,就做了一个统一的视图,也就是将所有的外设都做成文件,根据文件所对应的文件名,根据文件描述的结构中的信息,决定了走那条路,最终到达某个设备对应的 out 代码,
所以第二个部分就是把不同设备对应的 out 代码,采用文件的方式,向上封装起来,
-
3、进行中断处理
-
1、CPU 通过 out 指令向 外设 发出命令
-
2、通过文件形成统一的文件视图
-
3、进行中断处理
对于显示器来说:
从CPU开始out,也就是从用户指令开始,
P27 键盘
按下键盘就相当于中断,
对于键盘来说:
从键盘按下开始讲故事,
将键盘键入的字符 放入 缓冲队列
设备驱动的核心内容:
无非就是 out 、read 等这样的指令
要么是CPU 发出指令,要么是设备发出中断,
无非是最终和文件接在一起
其他的内容就是一些包装。
键盘的回显,和显示器显示一样
键盘将键入的字符放入缓冲区,显示器从缓冲区中读取
一个是 write_q, 显示器
一个read_q,键盘
P28 生磁盘的使用
怎么让磁盘驱动起来,也就是用起来
CPU中的磁盘控制器通过out写一些指令,磁盘收到指令后进行读或者写
磁盘工作完成之后,向CPU发出中断,做一些后续处理
part1:
三步:
移动磁头到相应的磁道上
旋转磁盘到相应的扇区上
最后与内存进行相应的读(磁生电)或者写(电生磁)
磁盘的IO过程:控制器 寻道 旋转 传输
控制器中要包含几个信息
柱面、磁头、扇区、缓存位置
out指令中要指定上面几个信息的具体数值,然后将 out 指令发出去
操作系统为了将整个磁盘抽象起来,把上面的几个信息整合封装起来,用唯一的表示 也就是盘块号 来标识,
这样就可以只需要知道 block号,操作系统就可以自己通过这个 block号 找到具体的位置
这就是使用操作系统的系统,可以将使用硬件变得简单且高效
应用程序通过盘块号来访问磁盘,效率得到了很大的提升
操作系统在使用抽象的时候,也要想办法将使用的过程变得高效,磁盘的寻道时间是相对来说很慢的,寻道时间也就是移动磁头的时间,而其他的启动时间、传输时间都很迅速,不是最主要的时间消耗。
因为相邻盘块通过是连续使用的,为了减少寻道时间,就把相邻盘块放在同一磁道上。
part2 四个抽象
第一个抽象
-
CHS 磁盘块封装
-
就是抽象出 block, chs 抽象为盘块 block
第二个抽象 核心就是多个磁盘访问请求出现在请求队列中时该怎么办? 其实就是调度问题
- 也就是 把 block 放入请求队列时产生了冲突该怎么办,也就是磁盘调度算法,实际中通常使用电梯算法
第三个抽象
- block的集合抽象为文件,根据 FCB 找到 inode ,根据 inode 再找到对应的 block
第四个抽象
- 文件的组织形式抽象为目录,根据给出的路径 找到 FCB
给我一个路径名,这个路径名就是一个树状结构,我能够得到这个文件的 FCB
根据这个 FCB ,我能得到 inode
根据 inode 和对应的映射,就能找到盘块号
根据盘块就能放到电梯队列上,
根据电梯队列,就能在磁盘中断的时候把对应的 盘块 读出来,算出 CHS,并把 CHS 发给控制器
控制器根据 CHS 就可以驱动马达,控制磁盘读写相应的盘块
最终就是有了路径,就找到了磁盘的某个盘块 的映射。
磁盘调度算法
1、先来先服务 FCFS, FCFS-First Come First Serve
根据进程请求房屋内磁盘的现后顺序进行调度。符合惯性思维,但在很多时候,效果很差。
2、最短寻找时间优先 SSTF,SSTF-Shortest Seek Time First
学过数据结构与算法的话,核心思想就是贪心算法,该算法会优先处理与当前磁头最近的磁道的需求。
3、扫描算法 SCAN
核心思想,只有磁头移动到最外侧磁道的时候才能往内侧移动,移动到最内侧的时候才能向外侧移动。
4、电梯算法 C-SCAN
总结
1、进程得到 盘块号,算出扇区号,这里埋下一个文件管理的伏笔
2、通过扇区号,得到缓冲内存,使用电梯算法,把缓冲内存放到请求队列中,
3、接下来的工作就是硬件干的,就不用占用CPU了,进程就可以 sleep_on 了
4、然后磁盘工作完成后,产生中断,将进程唤醒 weak_up
P29 从生磁盘到文件
实际上我们使用生磁盘,并不是直接通过盘块号,而是通过文件,也就是字符流,文件就是由字符流来标识的
本章节就是讲,从盘块号怎么抽象出 文件,反过来怎么从文件找到盘块号进行使用,进行映射的抽象。操作系统封装了这个映射,并进行维护。
盘块是逻辑的,实际上读取的是扇区,其读取效率交由下层解决,即电梯算法
引入文件后,那就不叫生磁盘了,那就是 cook_disk ,也就是熟磁盘 ,哈哈哈哈好有趣
FCB 记录起始块,文件控制块
映射有很多种:
顺序存储:类似于数组
链式存储:类似于链表
索引结构:inode ( i 对应的就是 index , node 就是相当于节点,一个结构体嘛,比如 FCB 就是一个结构体,通过结构体来查询,所以叫他 inode)
索引就是文件分成不同的物理块存入磁盘,对每个物理块都有一个索引与之对应,需要读写时就通过索引表查询其物理地址进行相关操作。
总结:
从文件找到盘块号,怎么找呢?通过 文件控制块TCB的映射关系,再加上一些小小的计算
P30 文件使用磁盘的实现 *
根据文件名找到 inode·,这个文件名不是但指文件的名字,还包括整个路径,所以是根据 路径名找到 inode
根据 inode 找到盘块号
根据盘块号往电梯队列中放,
根据电梯队列中的盘块号,算出 chs cfs
最后把 是 out 发在磁盘控制器中
最后磁盘控制器驱动马达,电生磁、磁生电,对磁盘产生读或写
P31 目录与文件系统
整个磁盘变成一棵 目录树
chs 抽象为盘块 block,block的集合抽象为文件,文件的组织形式抽象为目录
第四层抽象:整个磁盘被抽象为一个文件系统
给我一个路径名,这个路径名就是一个树状结构,我能够得到这个文件的 FCB
根据这个 FCB ,我能得到 inode
根据 inode 和对应的映射,就能找到盘块号
根据盘块就能放到电梯队列上,
根据电梯队列,就能在磁盘中断的时候把对应的 盘块 读出来,算出 CHS,并把 CHS 发给控制器
控制器根据 CHS 就可以驱动马达,控制磁盘读写相应的盘块
最终就是有了路径,就找到了磁盘的某个盘块 的映射。
P32 目录解析代码的实现 *
操作系统的全图:
操作系统是一个什么东西呢?
是一个管理硬件的软件,硬件比如 CPU、内存,外设等
OS 首先是管理 CPU,这就引入了OS 第一个重要的视图——多进程图像
在程序执行的过程中,为了让程序执行,需要从内存中不断地取址执行取址执行,这就需要把内存使用起来
内存就引入了 分段分页,段页合作,虚拟内存,还有内存的换入换出,这样就可以把程序放进内存中的某个位置,将指令运行起来,内存也使用起来了,
在程序执行时,对于一些 open,read 等文件指令,就是根据文件视图与外设 如磁盘、显示器、键盘等也联系起来了,将各种设备就业使用起来了
OS 的视图有两大视图
多进程视图是核心,多个进程转来转去,实现了 CPU、内存的运转,
然后再加上 文件的open、read等接口,就通过 IO 把各个外设也使用起来了,
多进程视图往往又是和 IO 、内存等并行的往前推进
第二大视图是 文件视图,用来访问 IO 端口,
将两大视图,再加上系统接口,系统启动,这个系统就能自己从磁盘上载入进来,打出 logol,然后在内存中展开,创建多个进程
用户敲命令来启动进程,用户就把 PPT,word、MP3 、后台编译程序 等应用程序工作起来,计算机就解决了各种有待解决的各种问题
完结撒花~
实验没有做,还是很空的。
leetbook 硬核操作系统指南
进程的系统调用
下面是 UNIX 操作系统和 Windows 操作系统系统调用的对比
UNIX | Win32 | 说明 |
---|---|---|
fork | CreateProcess | 创建一个新进程 |
waitpid | WaitForSingleObject | 等待一个进程退出 |
exit | ExitProcess | 终止执行 |
open | CreateFile | 创建一个文件或打开一个已有的文件 |
close | CloseHandle | 关闭文件 |
线程的系统调用
调用函数 | 功能 |
---|---|
thread_create | 创建新的线程 |
thread_exit | 退出线程 |
thread_join | 表示一个线程可以等待另一个线程退出 |
thread_yiel | 表示允许线程自动放弃 CPU 从而让另一个线程运行 |
为了使编写可移植线程程序,IEEE 在 IEEE 标准 1003.1c 中定义了线程标准。线程包被定义为 Pthreads
POSIX线程(通常称为pthreads)是一种独立于语言而存在的执行模型,以及并行执行模型
每个工作流程都称为一个线程,可以通过调用POSIX Threads API来实现对这些流程的创建和控制。可以把它理解为线程的标准。
POSIX Threads 的实现在许多类似且符合POSIX的操作系统上可用,例如 FreeBSD、NetBSD、OpenBSD、Linux、macOS、Android、Solaris,它在现有 Windows API 之上实现了pthread。
线程调用 | 描述 |
---|---|
pthread_create | 创建一个新线程 |
pthread_exit | 结束调用的线程 |
pthread_join | 等待一个特定的线程退出 |
pthread_yield | 释放 CPU 来运行另外一个线程 |
pthread_attr_init | 创建并初始化一个线程的属性结构 |
pthread_attr_destory | 删除一个线程的属性结构 |