操作系统学习——何为虚拟

参考链接:

《现代操作系统(第三版)》
《计算机硬件基础》
《开机启动过程》
《Motherboard Chipsets and the Memory Map》
《什么是操作系统?》
《分段 分页 虚拟内存空间 逻辑地址 物理地址》

什么是操作系统

我们都知道电脑是由硬件和软件组成的,我们可以使用软件来操作和控制硬件资源,但是硬件是一直在更新迭代并且变化的,怎么来保证应用软件可以不随着硬件的变化而被淘汰呢?

计算机抽象结构

操作系统是一种运行在内核态的软件,可以直接操作硬件并管理内存和进程。而应用程序属于用户态,不直接操作硬件,操作系统暴露给用户态的是一种抽象的硬件接口。

如何做到这种抽象呢?一个进程可以让一个程序感觉自己独立运行在一个环境中,一个程序运行大致需要做:

  1. CPU要执行程序指定的CPU指令
  2. 执行的过程中必然要访问内存
  3. 执行的过程可能要与人交互

所以一个进程也围绕着虚拟出一个CPU, 一份内存展开。

怎么虚拟出一个CPU呢? 好办, CPU运行速度比人能察觉的快得多. 它可以在两个进程中间切换, 这一个瞬间给你显示网页, 下一个瞬间给你播放音乐. 对,事实就是这样的, 你的CPU在负责给你唱歌的时候,只是在数十个任务中时不时唱一句, 但是你会觉得音乐是连续的。

另外随着CPU的处理速度的提高,出现了频率太高存在散热问题和电传输速度的限制,所以多核处理器得以出现,双核、四核、16核等。

关于如何去虚拟内存下面介绍。

计算机硬件

一台计算机由下列五部分组成:运算器、控制器、存储器、输入设备和输出设备。

计算机组成

其中运算器和控制器组成了CPU;存储器包括内存、硬盘等等。而输入设备是人向计算机输送命令的设备,主要有鼠标和键盘。输出设备主要有显示屏。而通常我们的软件是放在硬盘里的,因为断电后不会丢失,当点击某个软件后,此时系统将软件运行数据放入内存中,而CPU会从内存中取得数据,之后软件启动。

计算机主板

芯片组是构成主板电路的核心,Intel芯片组是专门为英特尔处理器设计的,用来连接CPU与其他设备如内存、显卡等。

芯片组是 南桥(管理低速设备)和 北桥(管理高速设备)的统称,就是把以前复杂的电路和元件最大限度的集成在几颗芯片内的芯片组。如果说中央处理器(CPU)是整个电脑系统的大脑,那么芯片组将是整个身体的心脏。

CPU

CPU 是电脑的大脑,任何命令的执行都需要经过CPU。由于CPU访问内存的时间比CPU执行指令的时间慢的多,所以CPU内部有寄存器来保存一些常用到的数据,如变量和临时数据等。

寄存器分为以下几类:

  1. 通用寄存器:用来用来保存变量和临时结果。
  2. 程序计数器:由于CPU在一个时刻只能执行一个命令。且CPU是分时去执行任务的,在用户看来,CPU可以在同一个时间并行处理多个任务。当CPU处理完一个任务时,需要在切换到下一个任务,程序计数器就记录着下一个任务的内存地址。
  3. 堆栈指针:堆栈(先进后出),队列(先进先出),由于CPU在同一时间只能处理一个任务,所以当多个任务到来时,将这些任务放在堆栈中,CPU每次运行完任务时,都会从堆栈中取出下一个任务继续运行。
  4. 程序状态寄存器PSW:这个寄存器包含了条码位(由比较指令设置)、CPU优先级、模式(用户态或内核态),以及各种其他控制位。用户通常读入整个PSW,但是只对其中少量的字段写入。用户在运行程序时只能调用系统层面的接口,应用程序不会操作硬件,这个模式为用户态,操作系统操作硬件为内核态。所以软件在运行时要在用户态和内核态之间切换。用户程序必须使用系统调用(system call),系统调用陷入内核并调用操作系统,TRAP指令把用户态切换成内核态,并启用操作系统从而获得服务。

CPU实物图

CPU的针脚连接到主板的前端总线(Front-end Bus)上,进而与北桥芯片组相连。不同的针脚用途不同:有传送物理内存地址的,有传送数据和指令的,也有用来传送中断信号的。

CPU运行有三个模式,实模式,32位保护模式以及64位保护模式。实模式下CPU支持最大1MB内存寻址,32位CPU支持最大4GB内存寻址。

CPU在保护模式下执行程序的时候访问的内存地址实际上是逻辑地址(logical address),必须由MMU进行翻译成物理地址之后才能发往总线。

主板结构图

北桥会收到来自CPU的一系列物理内存地址请求,北桥需要根据内存映射(Memory Map)决定应该这些地址是应该发往RAM,Video Card,还是南桥芯片组。Linux系统下可以查看/proc/iomem文件来列出内存映射图。

内存映射图

具体物理内存地址和各区域范围根据不同芯片组和主板型号而定。棕色区域是从内存中划出的部分,注意到外设的地址也需要占用内存地址空间,这就是为什么32位操作系统安装了4GB内存后可用空间不到4GB的原因(除非使用Physical Address Extension技术)。
不过,CPU在64位保护模式下运行时,这些被硬件占用的内存地址空间可以通过映射到比实际RAM更大的内存地址这种方式来重新获得访问。这就是回收内存(reclaiming memory)技术。它需要主板芯片组的支持。

存储器

存储器包括:寄存器 缓存 内存 闪存(固态硬盘) 磁盘(机械硬盘) CMOS 磁带 虚拟内存。从左到又,速度变慢,容量变大。其中寄存器直接和CPU打交道,存储着一些CPU需要用到的数据,速度最快。

存储器分类

当一台机器有多个CPU时,每个CPU都需要知道对方的运行状态。此时缓存用来传输状态信息。内存通常称为随机访问存储RAM,就是我们通常所说的内存,容量一直在不断攀升,所有不能再高速缓存中找到的,都会到主存中找,主存是易失性存储,断电后数据全部消失。闪存的存储的速度比机械硬盘要快,且断电后数据不会消失,常常用在固态硬盘和数码相机的胶卷中。

还有一类存储器就是CMOS,它是易失性的,许多计算机利用CMOS存储器来保持当前时间和日期。CMOS存储器和递增时间的电路由一小块电池驱动,所以,即使计算机没有加电,时间也仍然可以正确地更新,除此之外CMOS还可以保存配置的参数,比如,哪一个是启动磁盘等,之所以采用CMOS是因为它耗电非常少,一块工厂原装电池往往能使用若干年,但是当电池失效时,相关的配置和时间等都将丢失。

虚拟内存

假设我们写了一段程序

1
2
3
4
5
6
7
int main() {
int a = 1;
int b = 2;
int c = a + b;
printf(%d, c);
return 0;
}

上面的程序经过编译大概变成了这样的CPU指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 初始化a和b , mov指令把后一个操作数的值复制给前一个操作数
Mov a, 1
Mov b, 2

// %开头的是CPU寄存器, 你可能还记得它是最快最小的存储设备, 我们要把a和b从内存里拿出来, 放进寄存器里
// 接下来Add指令才能工作
Mov %eax, a
Mov %ecx, b

// 加两个寄存器的值, 和写入前一个寄存器中, 在这里是写入eax寄存器
Add %eax, %ecx

// 把这个值写回内存
Mov c, %eax

//打印, 即把内存里的值送到显示器

这个时候我们试着想一下,你和小明两个人写了一个同样的程序,都需要访问内存的同一个地址,事实上这样是没问题的,这是为什么呢?

程序所要使用的内存会被映射为虚拟内存: 物理内存地址 = F (虚拟内存地址)

A和B都在访问自己的0x00, 但是由于他们的映射关系F不同, 这两个访问会访问不同的物理地址(内存的真正的地址)。

虚拟内存空间是系统的一种技术,当程序被载入内存时,运用虚拟内存空间技术让程序误认为自己目前独占电脑内存,能够占用电脑所有的内存,访问所有内存地址。

32位系统程序的指针为32位(4字节),2^32 = 4GB,也就是说指针可以取值的方法有2^32种,可以访问2^32地址。这也就为什么有种说法:32位系统支持装最高4g内存。当程序载入内存后,系统为程序赋予4GB虚拟空间。而程序理解在虚拟空间中的地址就是逻辑地址。但是逻辑地址只是一种假象,并不是指系统真的为程序分配了4GB内存。如下图,程序载入虚拟内存空间:

程序载入虚拟内存

上图中程序的.text段基址是0x08048000,0x08048000就是虚拟地址,每个被载入内存的程序都有可能基址是0x08048000。这是运用了虚拟内存空间技术,实际中是不可能存在的,如果每个程序都使用真实地址0x08048000内存,那两个程序就互相干预,导致数据的混乱。

那是不是这 4GB 的虚拟地址空间应用程序可以随意使用呢? Windows 系统中,这个虚拟地址空间被分成了 4 部分: NULL 指针区、用户区、64KB 禁入区、内核区等。

  1. NULL指针分区是NULL指针的地址范围。对该区域的读写将引发访问违规。
  2. 用户分区是应用程序能使用的,大约 2GB 左右。 用户分区又进行了详细的分区。PE文件结构对程序进行了分区
  3. 禁止访问分区只有在win2000中有。这个分区是用户分区和内核分区之间的一个隔离带,目的是为了防止用户程序违规访问内核分区。
  4. 内核方式分区对用户的程序来说是禁止访问的,操作系统的代码在此。内核对象也驻留在此。

实际上程序载入内存时并不是一次性全部载入内存。程序运行时存在局部性现象,就是说程序在短时间内只会运行某一局部代码。因此仅需将那些当前需要的少数页面或段载入内存即可运行,但系统继续往下运行,发现缺页或缺段就会触发中断请求,由操作系统将程序请求的页或段载入内存,继续运行。

段式管理

段式管理是比较容易想到的方案. 在这个方案基于一个简单的映射关系:物理地址 = 虚拟地址 + 基地址偏移

每个进程访问的虚拟地址只要加上基地址偏移就能得到数据在内存中物理地址。

为了高效实现, 我们需要借助两个特殊硬件的帮助。

  • 段基址寄存器: 寄存器里保存一个进程的基地址偏移量, 每次CPU执行到访问内存的CPU指令的时候, CPU自动加上基地址偏移, 这样就实现了虚拟地址到物理地址的转换。
  • 段长度寄存器:这个寄存器是保证进程独立运行的, 每个进程都要申请好自己要使用的内存最大值, 保存在这个寄存器里, 接下来如果CPU在执行一条访问内存的CPU指令的时候发现该指令在访问的地址超过最大值, CPU拒绝执行这条指令。

段式管理

页式管理

段式管理是建立在运行前申请和分配内存机制之上的.如果你想在进程运行的过程中申请内存, 段式管理就会变得很困难。

页式管理把每4KB的内存空间划分为一个页, 从内存0x00处开始给页编号. 即0x00000000–0x00000FFF是第0页, 0x00001000-0x00001FFF是第1页。

操作系统维护着一个链表, 表上的是还空闲的物理页(每一个节点代表一个物理页), 每一次进程申请内存(无论是运行前还是运行中) , 操作系统计算进程需要几个页, 从空闲链表上取下相应数目的物理页, 把映射关系保存到对应进程里。

当进程访问某个逻辑地址中数据,分页系统地址变换机构,用页号检索页表,如果页号大于或等于页表长度,则产生地址越界中断。否则将页表初始地址与页号和页表项长度乘积相加得到页表物理号的地址,获取到物理块号。再将物理块得到的地址与页内地址组合得到物理地址。

页式管理

如果选择的页面太小,虽然可以提高内存利用率,但是每个进程使用过多页面,导致页表过长。降低页面换入换出效率。

进程和线程

一个进程是一个应用程序独立运行需要的虚拟环境。

比较术语的说法是: 进程是操作系统资源分配的最小单位. (什么是资源呢? CPU时间, 内存空间, 一个程序运行需要的所有硬件都叫做资源, 你会发现操作系统就是做资源管理的) 在一段时间内, 可能有多个应用程序要求运行, 这个时候, 操作系统决定什么时候谁应该运行这个过程叫做进程调度。

线程就是共享虚拟内存映射的进程, 实际上, 多个线程从属于一个进程, 这些线程都共享进程的虚拟内存映射。 由于线程共享内存,所以线程之间存在线程安全问题。

进程和线程关系

每一个进程至少有一个主执行线程,它无需由用户去主动创建,是由系统自动创建的。用户根据需要在应用程序中创建其它线程,多个线程并发地运行于同一个进程中。

中断

计算机处理器的处理速度要远远大于硬盘数据的读写速度,所以就会造成处理器空闲状态,如果去利用这些空闲状态就是引入中断的原因。

中断的分类:

名称说明
程序中断某些条件下指令执行结果产生 如 除数为0,算术溢出
时钟中断由处理器内部的计时器产生,允许操作系统以一定的规律执行函数
I/O中断由I/O控制器产生,用于发信号通知一个操作的正常完成或者各种错误条件
硬件失效中断由诸如掉电或存储器校验错误等故障产生

中断过程

如上图由于完成I/O操作需要花费较长的时间,I/O程序需要挂起等待操作完成,因此用户程序会在WRITE调用处停留相当长的一段时间。
从应用程序的角度来看,并不需要添加额外的特殊代码来处理中断,处理器和操作系统来负责挂起用户程序,然后在同一个地方恢复中断。

中断执行流程

在中断处理过程中需要执行额外的指令来确定中断的性质,必然会造成一些额外的开销。但是相比较I/O的耗时,这些开销微不足道。
中断激活了很多事件,包括处理器硬件中的事件和软件中的事件。

评论

Ajax Android AndroidStudio Animation Anroid Studio AppBarLayout Babel Banner Buffer Bulma ByteBuffer C++ C11 C89 C99 CDN CMYK COM1 COM2 CSS Camera Raw, 直方图 Chrome Class ContentProvider CoordinatorLayout C语言 DML DOM Dagger Dagger2 Darktable Demo Document DownloadManage ES2015 ESLint Element Error Exception Extensions File FileProvider Flow Fresco GCC Git GitHub GitLab Gradle Groovy HTML5 Handler HandlerThread Hexo Hybrid I/O IDEA IO ImageMagick IntelliJ Intellij Interpolator JCenter JNI JS Java JavaScript JsBridge Kotlin Lab Lambda Lifecycle Lint Linux Looper MQTT MVC MVP Maven MessageQueue Modbus Momentum MySQL NDK NIO NexT Next Nodejs ObjectAnimator Oracle VM Permission PhotoShop Physics Python RGB RS-232 RTU Remote-SSH Retrofit Runnable RxAndroid RxJava SE0 SSH Spring SpringBoot Statubar Style Task Theme Thread Tkinter UI UIKit UML VM virtualBox VS Code VUE ValueAnimator ViewPropertyAnimator Vue Vue.js Web Web前端 Workbench api apk bookmark by关键字 cli compileOnly computed css c语言 databases demo hexo hotfix html iOS icarus implementation init jQuery javascript launchModel logo merge methods mvp offset photos pug query rxjava2 scss servlet shell svg tkinter tomcat transition unicode utf-8 vector virtual box vscode watch webpack 七牛 下载 中介者模式 串口 临潼石榴 主题 书签 事件 享元模式 仓库 代理模式 位运算 依赖注入 修改,tables 光和色 内存 内核 内部分享 函数 函数式编程 分支 分析 创建 删除 动画 单例模式 压缩图片 发布 可空性 合并 同向性 后期 启动模式 命令 命令模式 响应式 响应式编程 图层 图床 图片压缩 图片处理 图片轮播 地球 域名 基础 增加 备忘录模式 外观模式 多线程 大爆炸 天气APP 太白山 头文件 奇点 字符串 字符集 存储引擎 宇宙 宏定义 实践 属性 属性动画 岐山擀面皮 岐山肉臊子 岐山香醋 工具 工厂模式 年终总结 开发技巧 异常 弱引用 恒星 打包 技巧 指令 指针 插件 插值 摄影 操作系统 攻略 故事 数据库 数据类型 数组 文件 新功能 旅行 旋转木马 时序图 时空 时间简史 曲线 杂谈 权限 枚举 架构 查询 标准库 标签选择器 样式 核心 框架 案例 桥接模式 检测工具 模块化 模板 模板引擎 模板方法模式 油泼辣子 泛型 洛川苹果 浅色状态栏 渲染 源码 源码分析 瀑布流 热修复 版本 版本控制 状态栏 状态模式 生活 留言板 相册 相对论 眉县猕猴桃 知识点 码云 磁盘 科学 笔记 策略模式 类图 系统,发行版, GNU 索引 组件 组合模式 绑定 结构 结构体 编码 网易云信 网格布局 网站广播 网站通知 网络 美化 联合 脚手架 膨胀的宇宙 自定义 自定义View 自定义插件 蒙版 虚拟 虚拟机 补码 补齐 表单 表达式 装饰模式 西安 观察者模式 规范 视图 视频 解耦器模式 设计 设计原则 设计模式 访问者模式 语法 责任链模式 贪吃蛇 转换 软件工程 软引用 运算符 迭代子模式 适配器模式 选择器 通信 通道 配置 链表 锐化 错误 键盘 闭包 降噪 陕西地方特产 面向对象 项目优化 项目构建 黑洞
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×