读书笔记-《操作系统真象还原》-第0章 一些你可能正感到迷惑的问题

我非常推荐大家阅读 《操作系统真象还原》。
因为知识量大,我的阅读方法是 笔记做的非常完整…

操作系统开发全记录

学习开发操作系统的意义

  1. 希望能获得 迁移的能力.

对于操作系统 实际上很多地方都使用到,比如手机端.

  • 进线程管理

  • 内存,硬盘等资源管理

  1. 希望能 倒逼输入.

对于磁盘管理(B树),线程调度,调试机制等更深入理解 和 实践.

  1. 大型框架项目的开发经验

组织代码等.

学前疑问

  • 内核和用户 地址划分访问映射 是如何 做的?

这里面涉及了CPU硬件

  • 内存是如何收集的?在保护模式

  • 线程是如何切换的?

注册 时钟中断

  • 建立一套联机调试机制

代码参考

https://github.com/seaswalker/tiny-os

第0章 一些你可能正感到迷惑的问题

3 写操作系统,哪些需要我来做

分层概念.

每个部分是 一层功能模块.

将自己的工作结果 交给下一层的 模块.这里的 模块 指的是 各种外设,硬件

操作系统 能做什么 ,取决于 主机 上硬件的功能.

所以写 OS 需要了解 硬件,其提供了接口. 通过指令 控制 硬件.参考 硬件手册少不了.

4 软件是如何访问硬件的?

IO接口: 就是硬件适配标准.其他硬件要按照标准来才能被大家使用.

硬件的两类通信方式

硬件在 输入 和 输出 上大体分为:串行并行.分别为 串行接口 并行接口 来和CPU通信.

网线是怎么通信的?要实现以太网协议?

我觉得或许可以直接将 tcp的通信数据 以串口方式 打包给其他机器,由其他机器进行代理.或许更简单.

访问外部硬件的两个方式

    1. 将某个外设的内存映射到 一定 范围的 地址空间中.

CPU通过 地址总线 访问 该 内存区域 会落到 外设的内存中。

eg: 显卡是显示器的 适配器. CPU不直接和 显示器交互. 直接和显卡通信.

显卡上 有片 内存 叫 显存,它被映射到 物理内存上的 0xB8000 - 0xBFFFF,

访问这片内存 就是 访问显存,往 这片 内存写字节 就是 向 屏幕打印.

怎么高大上实现的,不关心,要相信上层的工作.

那么原来的这个区域的内存呢?

    1. 外设 是 通过IO接口 与 CPU通信的.

CPU 访问 外设,就是访问IO接口. 由IO接口将信息传递给 另一端的 外设.

CPU从来不知道这些设备的存在,它只知道 操作自己的IO接口(体现了分层)

如何访问IO接口呢? IO接口 上 有一些寄存器(端口),访问接口就是访问这些寄存器(常说的:端口).它内部电路会自己处理 我们的需求.

总共有多少寄存器呢? out in 指令?

前面的映射是不是 也是由out in指令在底层执行的呢?不是的 参见:<20/BIOS中断/BIOS依赖谁

5 应用程序是什么 和 操作系统是 如何配合到一起的?

CPU 只知道去cs:ip寄存器中指向的内存 取指令并执行.

它并不区分 谁是程序 谁是系统.

操作系统 是 人们 为了 管理计算机 方便而创造出来 的一套管理办法.

应用程序要用某种语言编写.

这让我想到了协程,线程切换代价太大.所以…或许可以提供一种特别的线程?来解决10W问题?

其实并没有什么语言 , 程序根据某种 规则 编译 成 某种 机器指令.

编译器 提供了 一套 库函数,库函数中 封装了系统调用,这样的代码集合 称为:运行时库.

C语言 的运行库, 就是 所谓的 CRT(C Runtime Library).

应用程序 + 操作系统提供的功能 = 完整的程序.

这些功能是 以 操作系统的函数 的方式提供的.即 系统调用.

用户态内核态 是 对CPU来说的.指:CPU运行在 用户态 还是 内核态.

而不是对用户进程来讲的.

用户进程陷入内核态是指: 由于 中断 发生,当前进程被 暂停执行,其上下文被内核中断程序保存起来后, 开始执行 一段 内核的代码.

当应用程序陷入内核后, 它对以后发生的事情完全不知道.

它的上下文环境 已经被 保存在自己的 R0特权栈中了.

应用程序 是 通过 系统调用 来和 操作系统 配合完成 某项功能的.

例如 Linux系统来说:

将功能号放在 eax中执行int 0x80即可.

666 以上的阅读,让我又明白了一些东西.我之前有些不清楚R3切换到内核到底发生了什么.现在有了一些答案: 触发一个约定的中断,由中断例程来处理.

我认为之前的疑惑,可能是由于 进程之类的信息混在一起使我们搞糊涂了.

6 为什么称为"陷入"内核

当 用户程序 欲 访问系统资源时(硬件或者内核数据结构),它需要进行系统调用.

CPU便进入内核态-也称管态.

陷入很形象,如图:

陷入内核

7 内存访问 为什么要 分段 ?

分段是给CPU访问内存的方式,

只有CPU才关注段,为什么CPU要用段呢? 分成一段一段才能访问呢?

行业中很多问题是 历史遗留问题.

分段始于8086: CPU和寄存器都是16位.意味着:65536字节=64KB.

由于当时,人们访问内存没有虚拟内存一说,只有绝对地址的物理内存.

出现一个问题:

编译器: 编译的程序到同一地址程序,无法同时启动.

重定位对段的需求

睿智的前辈 研究出用分段的解决方案:

让CPU采用 "段基址+段内偏移地址"的方式来访问任意内存.

好处是:程序可以重定位了…

尽管指令中给的是绝对物理地址,但可以同时运行多个程序了…

CPU 采用 "段基址+段内偏移地址"的形式访问内存.

就需要专门提供段基址寄存器:

cs,ds,es.

程序中 需要用哪块内存,只要加载合适的段到段基址寄存器中,再给出相对于该段基址的偏移地址便可.

CPU中的地址单元会将两个地址相加后的结果,用于内存访问,送上地址总线.

  • 段基址 不一定非得是65536的倍数.

段基址可以是任意的,这就是段可以重叠的原因.

eg: 段基为0xC00,想访问0xC01,就要用0xC00:0x01的方式访问才行.

若段基址改为0xc01,就要用:0xC01:0x00的方式来访问了.

总之想访问某个物理地址,只要凑出合适的段基地址和段内偏移地址,其和为该物理地址即可.

eg:

像访问0xc04,段基址和段内偏移组合可以是:


0xC01:0x03

0xB00:0x104

怎么拼都可以哈哈…

分了段后,段内地址相对于段基址是不变的,无论段基址是多少.

只要给出 段内偏移,CPU就能访问到正确的指令(CPU666)

所以 程序分段 首先是为了 重定位.

CPU为了重定位加了寄存器, 原来是干这用的啊

访问1MB的空间

0xffff

1MB是2^20.需要20位的地址才能访问到.

如何做到16位寄存器访问20位地址空间呢?

两个16位的寄存器怎么加也顶多到17位而已.不够用啊.

只有20位寄存器才方便访问20位空间.

一个大胆的想法:

CPU在地址处理单元中 动手脚.

段基址 + 段内偏移地址

自动将段基址 *16,即左移4位. 然后再和16位 的段内偏移地址相加,这下地址就变成了20位.

就能访问20位空间了。

8 代码中为什么分为代码段 ,数据段? 这和内存访问机制中的段是 一回事吗?

x86 CPU必须分段才能运行.

这里的分段是必然的,只是在平坦模型下,硬件段寄存器中 指向的 内存段为最大的4GB.

如果是在 平坦模式 下编程, 操作系统就整个 4GB 内存都放在了一个段中,我们就不需要来回切换段寄存器所指向的段. 代码是否需要分段,取决于 OS是否在平坦模式下。

数据段 和 代码段的意义

代码中的段 是 程序员为自己区分数据和代码而做的.

由于 处理器 支持了 具有分页机制的 虚拟内存,操作系统 也 采用了分页模型,因此 编译器 会将程序按内存划分成 代码段数据段.

eg: gcc会为程序划分为: 代码段, 数据段, 栈段, .bss段, 等部分.

由 操作系统 将 用户程序中的 各个段分配到不同的物理内存上.

之所以程序员不关心段:是因为

  • 编译器按平坦模型编译

  • 依赖的操作系统 采用 虚拟内存管理,即 CPU的分页机制

汇编 允许自己 分段.能够灵活编排 布局.这就属于人为 将程序分成段了,也就是 采用 多段模型编程.

将数据 和 代码分开的好处有三点:

  1. 可以为它们赋予不同的属性

代码段 只读,数据段可写.

  1. 为了提高CPU内存缓存的命令率

缓存起作用的原因是: 程序的局部性原理.

程序中的指令 和 数据 分离,有利于 增强程序的局部性.

CPU内部有针对 数据 和 针对 指令的两种缓存机制,因此将数据和代码 分开 存储 将使程序运行得更快。

  1. 节省内存

程序中存在一些只读的部分,比如代码.

当一个程序的多个副本同时运行时,每必要在内存中 同时 存在多个相同的代码段.

浪费 有限 物理内存资源. 只要把 这一个代码段共享就可以了.

之前不了解 CPU内存对 数据 和 指令 有两种 缓存机制

进一步思考:

数据段 和 代码段的属性 是谁添加上去的? 是谁又去根据 属性 保护程序的呢?

  • 编译器

将相同类型的数据放在一起,并写上标识.

readelf- e elf有很多段的类型.

  • CPU提供了原生支持

保护模式下 ,有全局描述符表(GDT)的数据结构. 这个表中每一项称为 段描述符.

  • 什么是描述符?

就是描述某种数据的 数据结构,是元信息,属于数据的数据. 就像身份证一样.

段描述符中有 段的属性位. 在以后的章节中可以看到.其实是有2个,一个是S字段,1bit,另一个是TYPE字段,4bit.

这两个字段配合起来组合出各种属性.

如只读,向下扩展,只执行等.

操作系统来 负责填写这张表.

看看 操作系统 为我们做了什么?

在操作系统 让 CPU 进入保护模式之前,要准备好GDP.填好段描述符.

填成什么样的属性,就具有什么样的属性.

  1. 编译器 负责 挑选 数据 并标记 以及合成

  2. 操作系统 通过 设置GDT,在GDT中指定 段的位置,大小及属性。

这是真正给段 添加属性的地方。OS认为它是属性它就是什么属性。

  1. CPU 的段寄存器,被OS赋予相关的选择子(理解为 段基址),从而确定了指向的段。在执行指令时,会根据该段的属性来判断指令的行为,若有违反 则发出异常。

这岂不是需要提前确定某内存的情况?

三者配合才能对 程序保护. 检查出 指令中的违规行为.

9 物理地址 逻辑地址 有效地址 线性地址 虚拟地址 的区别

物理地址 是真正的地址. 具有唯一性.

不管 什么 虚拟地址,线性地址,CPU最终都要以 物理地址 去 访问内存.

  • 在 实模式 下 :段基址+段内偏移地址经过 段部件处理,直接输出的就是物理地址.

  • 保护模式 下:段基址+段内偏移地址称为线性地址.

不过这里的段基址,并不是真正的地址了.而是称为选择子的东西.它本质上是一个索引,类似数组下标.

通过选择子这个索引,能在GDT中找到相应的段描述符,

在该描述符中记录了该段的起始,大小等消息,这样便能得到了段基址.

  • if没有开启地址分页功能,这个地址就是物理地址.直接可以访问物理内存.

  • if开启地址分页功能,这个线性地址 多了个名字叫 虚拟地址.

需要经过CPU的页部件转换 成 具体的 物理地址.才能上总线进行访问.

  • 无论何种情况:段内偏移地址(又称有效地址,逻辑地址).是程序员可见的地址.

  • 线性地址(又称虚拟地址)不是真实的内存地址,用来描述程序 或 任务 的地址空间。

分页功能需要在保护模式 下开启,32位系统保护模式下的寻址空间是4GB.所以虚拟地址或线性地址就是0-4GB的范围.
虚拟地址,物理地址

这里可以实际调试一下,加深印象

10 什么是 段重叠?

11 什么是 平坦模型?

在32位环境下用一个段 就 能够访问到 硬件 所支持的所有4GB内存.

而不用像多段模型为了1MB地址空间还需要 额外 给硬件打补丁 打开A20地址线.

12 cs,ds这类sreg段寄存器,位宽是多少?

16位的实模式 和 32位的保护模式,用的段寄存器都是同一组. 长度都是16位宽.

20 BIOS中断,DOS中断,Linux中断的区别

实模式和保护模式下,随时都会有来自 外部和内部的事件发生.

事件按其来源分两类:

  • 异常Exception:

也称: 内中断,例外或 陷入(Trap)

事件来自CPU内部.

一般会依赖 程序的现场.

  • 中断Interruption:

事件来自于外部,由外部设备法出并通知了CPU.

BIOS 和 DOS 都在存在于实模式下的程序,

由它们建立的中断调用都是建立在中断向量表(Interrupt Vector Table).

它们都可以通过 软中断指令: int+中断号来调用的.

中断向量表(IVT)

向量表 中的每个中断向量大小是4字节.

4字节描述了 中断处理程序(例程) 的 段基址段内偏移地址.

该表总长1024字节,所以最多容纳256个 中断处理程序.

计算机 刚启动时,IVT 中指向的程序 是由BIOS建立的,

IVT从 物理内存地址0x0000处 开始 初始化,并向其中添加各种处理例程.

BIOS中断调用的 主要功能 是提供硬件访问的方法. 这让硬件操作简单起来.

你可以绕过BIOS来直接访问硬件.

因为BIOS也是通过 in/out 指令来 读写 外设的端口.

绕过BIOS访问硬件听起来很有趣

我现在也搞清楚一些BIOS中断的模糊印象了,它本身也是靠in/out工作的.

BIOS依赖谁?

BIOS 也要依赖 其他写的例程:

每个外设(显卡,键盘,各种控制器等),都有自己的内存, 不过是只读存储器ROM,

硬件 自己的 功能调用例程 及 初始化代码 都存放在 各个ROM中.

ROM规范:


第1个内存单元的内容是 0x55,

第2个内存单元是 0xAA,

第3个内存单元是该rom中以512字节为单位的代码长度.?这里是长度吗?还是校验合?

第4个内存单元起就是 具体代码.直到所示的长度为止.

这样岂不是可以 在外设中 写个死循环,让电脑开不了机?

电脑开不了机,拔外设是有道理的…

保护模式下 是直接通过 in/out访问吗,还能通过中断访问吗?

CPU 如何访问 到外设的ROM呢?

访问外设有两种方式:

  1. 内存映射

通过地址总线将外设自己的 内存映射到 某个内存区域.

(并不是映射到主板上插的内存条中!)

  1. 端口操作

外设都有自己的 控制器,控制器上有寄存器, 这些寄存器 就是所谓的 端口,通过 in/out 指令 读写端口来访问硬件的内存.

控制显卡 用的 便是 内存映射+端口操作的方式.

从内存的物理地址 0xA0000-0xFFFFF 这部分内存(~384kb),一部分是专门用来做映射的.

如果硬件存在, 硬件自己的ROM会被 自动映射 到 这片内存中的 某处.

如何映射的?是硬件完成的工作.这是早期硬件工程师的大胆天才做法,在很久之前就解决了.暂不深究.

如何检查这些内存?

BIOS会在运行期间 扫描 0xC0000-0xE0000之间的内存,检查按照ROM规范做检查:0x55,0xAA,

并对该区域进行 累加 和 检查.若结果与第3个字节的值相符,说明代码无误.Jmp到第4个字节 执行 硬件自带的例程 来初始化 硬件自身.

最后, BIOS填写 IVT 中相关项,使它们指向 硬件自带的例程。

BIOS有哪些中断例程

IVT 中 第0h-1Fh的项是BIOS中断.

BIOS在IVT中建立了哪些 中断例程?如下图

BIOS中断例程-1

BIOS中断例程-2

DOS的中断调用

DOS的 中断调用 也建立在IVT中,因为也是在实模式,所以IV号 和BIOS不能冲突。

0x20-0x27是DOS选择的中断号范围.

DOS中断 实际上 只 占用 0x21这个中断号,也就是DOS只有这一个中断例程。

它区分功能是通过 ah写子功能号 实现的.

Linux的中断

Linux是在进入 保护模式 后才建立中断例程的.

保护模式下, IVT已经不存在了. 取而代之的是中断描述符表(Interrupt Descriptor Table,IDT).

保护模式下执行 int会 自动访问 IDT

实模式下 执行 int会 自动访问 IVT

本书实现系统调用时,全是基于Linux思想的.

21 Section 和 Segment 的区别

它们是编译器 提供的 伪指令.

  • section(节),系统只关心它的属性, 涉及 全局描述符表 中的 段描述符 的 访问权限等属性

  • segment(段)

链接器 把 目标文件 链接成 可执行文件,将多个section 做了合并.

23 操作系统 是如何识别 文件系统 的?

各分区都有 超级块,一般位于 本分区的 第2个扇区.

超级块 记录了此分区的信息,其中就有文件系统的 魔数,一种文件系统对应一种魔数.

24 如何控制 CPU的 下一条指令?为什么不能用mov CS:IP

CS:IP寄存器.

x86体系结构的CPU中,程序计数器 PC 并不是单一的某种寄存器. 它是一种寄存器 组合: 指的是:

段寄存器CS指令寄存器IP

mov指令一次只能改变一个寄存器,不能同时改变cs和ip,改哪一个都会导致下一条指令出错哟.

故设计了 专门 改变执行流 的 指令.

jmp,call,int,ret

它们可以同时修改cs和ip,做到 硬件级别 上实现 原子操作.

ARM 程序计数器 有单独的PC寄存器,所以就方便使用mov了.

这里我去阅读了一些CPU制作的资料,感觉似乎深入理解一些了:<自己设计制作CPU与单片机>

总线的数据会被广播,同时只能有一元件写

而 控制命令是 使用单独的线路的

25 指令集,体系结构,微架构,编程语言

假设 我们 的指令格式 最大支持: 三个寄存器参数 和 一个立即数参数.


操作码 寄存器操作数1 寄存器操作数2 寄存器操作数3 立即数

1字节 1字节 1字节 1字节 4字节

各指令并不完全按照 以上 格式填充, 不同的指令有不同的参数,只有操作码部分是固定的. 其他操作数部分可选.

CPU 在 译码阶段 识别出 操作码 后,自然知道 该 指令 需要什么样的 操作数.

所以不同的指令 其机器码 长度 很可能不一致.

现实中的指令格式很复杂, 市面上的指令集都有哪些?

  • CISC 最早的指令集

复杂指令集计算机.

eg:x86指令集.

  • RISC 精简指令集

MIPS(做得最好,严格遵守该思想,公认优雅),ARM(手机处理器),Power(IBM用于服务器上的处理器),C6000(数字信号处理器,广泛用于视频处理)

  • CISC为什么复杂?

早期程序员 使用 汇编开发,当然希望汇编越强大越好。尽量一个指令多干几件事.

很多时候,程序员 并不会 用到 那些复杂的 指令 和 寻址方式,编译器也未必会 将其编译成 复杂的指令形式.

导致 大量指令 被浪费.

  • RISC:

处理器 及 指令集 被重新设计,

  • 保留了那些基本常用的指令,减少了 硬件电路的复杂性.

大部分指令 都能在一个 时钟周期内 完成, 有利于 提升流水线的 效率.

  • 指令采用了 定长编码

译码工作更容易了.

AMD的x86指令架构是Intel授权给他们的.

为区分,Intel在官方手册上称自己的指令集为IA32.

指令集是一套约定: 规定了

  • 有哪些指令,

  • 指令的二进制编码,

  • 指令格式等.

如何实现是硬件自己的事.它们各自实现的方式,就叫 微架构.

微架构是指令的物理实现方式.

x86由于效率低下,改进时在内部实现上 采取了RISC内核,

即一条CISC指令在译码时, 分解成 多条RISC指令,这样执行效率就提高了.

指令集 背后不仅是个计算机生态链, 更重要的是 全球经济链.

使用不认识的指令集,可能无编译器 ,操作系统,应用软件 也需要 重复编译.所以新指令集太难推广.用现有的反倒好.

26 库函数 是用户进程 与 内核的 桥梁

库函数 帮助我们 写下 调用 系统调用 的代码.

直接用库函数 比较高效.

将详细的编译,连接 两个过程详细地打印出来.


gcc -v



编译阶段->汇编阶段->链接阶段

ccl as ld

.asm .o(待重定位文件,函数还没有地址) x和编译器的运行时.o等链接起来

.asm .lib x

27 转义字符 和 ASCII 码

可见性分为两类:

  • 不可见字符 : 属于控制字符or通信专用字符

33个: 0-31 和 127

  • 可见字符

其余各种符号.

使用 xxd 和xxd-r 可以 在Linux下快乐玩耍.

28 MBR,EBR,DBR和OBR 各是什么

这些都是围绕 计算机 控制权 的交接 而展开的.

BIOS

计算机接电后 运行的是 基本输入输出系统 - BIOS.

只是完成一些简单的检测 或 初始化 工作.

MBR

控制权 交给MBR.MBR在固定位置等待. 因此 MBR 位于 整个 硬盘 最开始的扇区.

MBR 是 主引导记录, Master 或 Main Boot Record.

存在磁盘最开始的扇区, – 0盘0道1扇区 --MBR引导扇区.

CHS 方式表示的MBR 引导扇区的地址.扇区从1开始.

LBA 方式表示的扇区是 以 0 为起始扇区编号.

一般情况下扇区大小是512字节.

MBR引导扇区内容结构是:

  1. 446 字节的引导程序及参数

  2. 64字节的分区表

  3. 2字节结束标记 0x550xaa

MBR按照约定 将这个位置的MBR 加载到 物理地址 0x7c00,然后跳过去执行.

这样就将处理器使用权交给MBR了.

MBR的作用是 众多可能的 次引导程序 中挑出最适合的人选,并交出系统控制权.

注意 64字节的分区表,每个分区表项占用 16字节.所以最多容纳4个分区.

MBR遍历 它们找到合适的次引导程序–操作系统提供的加载器

MBR 的目的是 将控制权交给 操作系统加载器,由其完成 操作系统的自举,最终使 控制权 交给 操作系统内核.

各个分区都可能存在操作系统哟. 操作系统到底在哪里呢?

为了能让MBR指出道哪里有操作系统, 在分区时,将分区设置为活动分区,即把 分区表中该分区对应 的 分区表项 中的活动(最开始的1个字节)标记为 0x80.

其他值均为0x00.

表示该分区上 有 引导程序.

MBR 如果找到 0x80标记,即将 控制权转交给 此 分区上的 引导程序–内核加载器.

按照约定: 内核加载器 必须放在 该分区的 最开始的扇区.又称–操作系统引导扇区操作系统引导记录(OBR-OS Boot Record).

OBR扇区 的 起始 命令一般是 3个字节的的跳转指令,指向操作系统引导程序.

计算机历史上 向来把 兼容性 放在首位,这才是计算机 蒸蒸日上的原因.

OBR 是从DBR遗留下来的.

DBR 和 OBR是同一扇区的名字

DOS Boot Record

DOS操作系统的引导记录(程序).

内容:

  1. 跳转指令,使MBR跳转到引导代码

  2. 厂商信息,DOS版本信息

  3. BIOS参数块BPB(BIOS Parameter Block)

  4. 操作系统引导程序

  5. 结束标记0x550xaa

为了兼容 分区最开始的扇区都作为DOS引导扇区。

UNIX系为了兼容MBR也传承了 这个习俗,都将各分区 最开始的扇区作为自己的引导扇区.

这个扇区 引导各种操作系统,所以它的新名字叫:OBR.

EBR

EBR扩展分区 是为了 解决分区数量限制 兼容 MBR才提出的概念。

MBR只能容纳4个主分区.

如果不是主分区 而是 扩展分区的话,其中就设计了一个逻辑分区表.

EBR扩展分区 , 中开头部分也有一个OBR哟。

也就是 总OBR数量 = EBR扩展分区数量+主分区数量.

更多详细内容

请参阅 以后的 跟踪分区 的章节.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值