《Linux是怎么样工作的》读书笔记(一)

简介: 《Linux是怎么样工作的》读书笔记

引言

这本书是个人看过的讲操作系统底层里面讲的最通俗易懂的了,但是200多页的内容确实讲不了多深的内容,所以不要对这本书抱有过高期待,当一个入门书了解即可。

书籍介绍

  1. 原富士通一线Linux内核开发工程师基于十余年经验执笔,专业实用
  2. 聚焦Linux核心功能,简明易懂地讲解操作系统是怎样工作的
  3. 198张示意图帮助理解,详略得当,为读懂大部头开路
  4. 结合大量实验程序,边动手操作边学习,真实体验系统运行过程

个人评价

内容比较基础,但是有关Linux的内容都有涉及,另外作者用C语言程序对于操作系统的缓存,交换内存,CPU进程管理器,固体硬盘和机械硬盘随机读写和顺序读写做验证和测试的程序比较有意思。

但是不得不说能把操作系统这种抽象的东西讲的生动形象实属不易,作者在日本一线大厂(可以翻翻富士通相关资料)搞Linux内核开发专业程度也毋庸置疑,另外这本书的编排是由浅入深的挺不错。

总结:非常难以定位的一本书,建议可以参考里面的知识,根据相关内容深入即可。

资源

2022年3月出版的一本书所以没有找到相关资源。

下面内容为随书一些C语言模拟操作系统底层工作的一些程序,感兴趣可以下载来看一下。

链接: pan.baidu.com/s/1eU65e1OK… 提取码: pghr

笔记索引

注意笔记的索引并不是按照原书的结构组织,因为个人阅读这本书是“倒着”读的,结合目录发现从后往前读比较符合个人的理解习惯,也就是从外部存储器到内部的工作机制比较符合个人的思考。

可以点击副标题跳转到相关的节点

常规的认识是知识由浅入深,其实有时候用难易交叉学习的方式可能更加符合人的学习习惯

第一部分:Linux与外部结构介绍

主要介绍了机械磁盘和SSD硬盘的工作机制对比,顺序读写和随机读写之间的差别,这部分使用C语言模拟磁盘的读写性能比较有意思。

介绍了Linux和设备交互的文件系统设计,一共分为7层,当然书中只是简单归纳,如果要深入需要阅读更多资料。

讲述了IO调度器和预读机制的相关内容。

第二部分:Linux文件系统设计

这一节讲述如何快速的了解一个Linux文件系统的设计方式,文件系统的设计当然没有不是几页纸就能讲清楚的,但是对于我们大致了解Linux整体的设计思路入门还是不错的。

第三部分:计算机存储层次简析

如果你对笔记电脑或者台式机主板等等基本配置了解,或者对整个操作系统的工作进程有一个大致的了解,这一节的内容完全可以跳过。

计算机的存储器层级结构是越靠近CPU和CPU关系越密切价格越高容量越小,我们常见的存储器,速度从快到慢的排序是:寄存器 -> 高速缓存 -> 内存 -> 外部存储器,这一节则针对这几个存储层级进行介绍。

之后会介绍关于转译后备缓冲区,页面缓存,缓冲区缓存和Linux不常见也几乎不使用的缓存调优参数。

第四部分:Linux内存管理和优化

针对内存的管理是操作系统进程管理核心和关键所在,此部分介绍了关于内存管理的内容,内存管理是整本书介绍最为详细的部分,个人认为核心是掌握 请求分页写时复制,这两个特性被大量使用,除此之外理解内存的分配方式和分配的细节过程也是必要的。

另外这部分个人笔记在补充的同时也将内容拆分为上下部分:

第五部分:进程调度器 CPU进程调度目前主流的方式是两种,第一种是像window那样抢占式调度,每一个CPU可能会出现调度时间分配不等的情况,而另一种是时间分片的方式,时间分片是Linux 常见的进程调度器,特点是每一个进程有近似相等的CPU使用权,在使用完成之后立马交给下一个进程完成工作,使用分片的方式虽然可能导致一些重要任务延迟,但这样的处理和调度方式使得系统最为稳定。

进程调度器本身很复杂,为了减少复杂性作者没有做过多介绍,所以个人笔记内容也相对较少。

第零部分:计算机程序概览 理解操作系统运行我们需要了解有关计算机信息的基础概念,我想如果有想法去研究操作系统底层多少对于计算机的基础理念不会陌生,所以这一部分个人当作总结。

附录 此部分是对于第一个部分物理磁盘的分配方式资料扩展,感兴趣可以阅读。

注意⚠:最后个人的笔记组织形式将会是难-易-难混杂的组织方式。

Linux与外部结构介绍

HDD磁盘介绍

机械磁盘从俯瞰的逻辑结构理解为类似一个同心圆的多个圈,从外层到内层进行编号,磁盘通过顺时针顺序编号,逆时针转动磁盘,这样处理是考虑查找磁盘的时候可以直接按照顺序扫描过去,磁头前进方向就是编号递增方向。

在下面的结构图中,磁道是每一个同心圆,而扇区指的是切割磁道所形成的“扇面”,因为切割之后的样子很像扇子的样子所以被称为扇区,扫描数据需要磁头在磁道上滑动,同时扇区会从0开始编号,一个编号对应一个扇区。

注意这是磁盘的俯视图,也就是说线的部分是物理磁盘上的“沟壑”,而扇区就是编号内的块。

image.png

image.png

下面是磁盘的侧面切面图,垂直叠加的盘通过一个磁盘一个磁头的组合,通过多磁头加快数据的处理速度。

image.png

注意在HDD磁盘中一个扇区读写最小单位为512字节,而且每一个扇区都是512字节,不管扇区是在外层还是内层。

⚠️注意:很多框架或者数据库会把一次读写的大小设置为512字节,因为512是最小读写单位所以可以不需要额外的维护可以保证读写的原子性。

磁盘大小计算

最早期磁盘可以通过下面的公式计算出整个磁盘的大小,因为磁道和扇区的数量是一一对应的:

存储容量=磁头数磁道(柱面)数每道扇区数*每扇区字节数

这样的设计有一个显而易见的问题那就是无论扇区面积大还是小都是固定大小,很显然外层的扇区数据被白白浪费了。

针对这样的问题后续的机械磁盘出现了改进,这项技术叫做ZBR(Zoned Bit Recording,区位记录)技术,这项技术根据每一圈的扇区来划分大小,同一个磁道圈内的扇区分布和大小相同。

这意味着越是外层的扇区数量越多,而内圈则较少,在划分之后密度均匀分布。

由于磁盘扇区存储形式的改进,寻址模式自然也要跟着进步,如今的硬盘大多使用 、LBA(Logical Block Addressing)逻辑块寻址模式,了解这个寻址模式才能了解磁盘的大小的计算方式。

然而现在HDD的磁盘读写受限在随机读写速度上,过去HDD磁盘比较流行的转速为7200转和5400转等等,区别的话是跑的快和跑的慢一点的蚂蚁。

虽然有SAS硬盘能突破15000转,并且现在还有研究团队研究寻找不同材料或者其他方式突破磁盘物理转速的限制(比如双磁盘转动的方式加快旋转),然而始终无法突破机械磁盘的设计的物理限制。

市面上为什么主流贩卖7200转的磁盘和5400的转速的磁盘而不是别的磁盘?

一方面是7200的随机读写性能经过测试是最佳的,同时讨论一块磁盘的性能不能看顺序读写的速度而是要看随机读写的速度。

针对机械磁盘存在一些物理壁垒,自东芝公司在1984年研究出闪存之后,闪存技术不断进步,又经过了5年之后的1989年,SSD磁盘逐渐走进历史舞台。

⚠️注意:为什么是7200转和5400转等等奇怪数字?

这两个数字都要从3600说起,计算机的前十年几乎所有的硬盘都是3600转的,这个3600又是从哪里来的呢?因为美国的交流电是60Hz的!于是就有了下面的公式:

  • 60Hz × 1转/Hz ×60秒/分钟 = 3600转/分钟
  • 5400 RPM = 3600 RPM × 1.5
  • 7200 RPM = 3600 RPM × 2 另外还有一个原因是专利争夺,你会发现转速有15000却没有10000,9000,8000这种数字,其实都是因为整数和500的倍数转速都被专利注册了,但是专利注册者估计没想到转速能破万吧。

SSD磁盘介绍

SSD 的硬盘分为两种,一种是基于闪存颗粒的闪存固态硬盘,另一种是DRAM 硬盘

闪存颗粒的硬盘也就是我们现代笔记本电脑以及移动固态多数使用的硬盘,这种磁盘的最大优点是可以移动,同时数据的保护不依赖电源就可以存储,在闪存颗粒中通常被分为 QLC,MLC,TLC,哪怕是寿命最短的QLC硬盘也有5-6年的寿命,而MLC寿命最长,保护得当往往可以十几年正常工作。

⚠️注意:固态硬盘过去成本非常高所以机械磁盘是主流,广泛普及也就这几年时间,所以上面说的内容都是理想状态。

在企业级的服务器使用的固态中通常以MLC为主,SSD磁盘的最大特点是不像是HDD一样受到物理冲击有可能造成整块磁盘不可用,但是SSD一旦损坏数据的修复成本很高或者说根本无法修复。

现在来看 SSD已经非常便宜了,但是HDD的大数据低成本存储依然很受一些用户欢迎。

DRAM是介于机械磁盘和固态硬盘中间的形式,其采用DRAM作为存储单元,它效仿传统硬盘的设计,可被绝大部分操作系统的文件系统工具进行卷设置和管理,并提供工业标准的PCI和FC接口用于连接主机或者服务器,但是最大问题应用范围相对较窄

HDD数据读取方式

HDD的磁盘读取数据顺序如下:

  • 设备将需要读写的扇区和设备号码以及扫描多少个扇区告诉磁盘
  • 移动磁头和转动盘片找到对应扇区。
  • 读取数据,把数据写入缓冲。
  • 如果所有的扇区扫描完成,读取的操作则算是完成。

HDD磁盘读写的要点

从逻辑上来看计算扇区扫描位置和扫描数量计算处理速度是很快的,将扇区内的数据读取或者写入的数据也是相对较快的。

然而我们知道因为转速限制的和磁头和盘片物理扫描是非常慢的,整个读写的性能瓶颈是磁头扇区寻址和扫描磁盘所需的物理磁盘开销以及最后带来的随机读写性能的权衡。

读写方式

磁盘扫描的几种情况磁盘扫描的方式直接决定了数据处理读写速度:

  • 顺序扫描:顺序扫描就是在一个磁道上直接划过连续的几个扇区,一次扫描就可以获取数据所以非常快。
  • 多次连续顺序扫描:连续顺序扫描是针对连续的几个扇区进行多次扫描,这个时间开销主要是在盘片的转动上,虽然依然比较快但是盘片转动依然产生一定延迟。
  • 随机读写:随机读写的开销主要在磁道来回寻址上,此时不但可能会产生磁盘转动,磁头还需要寻找分散的扇区,随机读写的效率是非常低的

⚠️注意:对于单次IO的访问如果获取的数据量超过磁盘请求数据量的上限,则会把请求由单次的顺序读写,拆分为多次的顺序扫描

影响硬盘性能的因素

  1. 磁盘寻道时间:磁盘的平均寻道时间一般在3-15ms。
  2. 磁盘转速:转速越快
  3. 磁盘本身的读写性能:和磁盘的设计厂商也有关系,随机读写强的HDD硬盘通常具备更好的IO性能,同时磁盘数据传输的越快传输量越大效果越好(废话)。

机械磁盘需要关注寻道时间旋转延迟,当然HDD的羸弱的读写性能实际上大同小异。

通用块层

通用块层:指的是在Linux系统对于HDD和SSD的抽象。

HDD和SDD它们被称为块设备。块设备的访问方式有两种,第一种是直接通过挂载的方式通过设备文件直接读写,第二种是根据文件系统对于磁盘进行封装以及提供引导入口,当然大部分的软件使用第二种方式。

由于不同类型的块设备处理方式不同,这些设备的处理的需要依赖驱动程序的控制才能实现访问,但是以我们日常使用Windows系统的经验,不可能是一个块设备一个驱动程序,不然我们每一次加入新硬盘都要装一遍驱动,这样也太麻烦了。

那么操作系统如何解决这个问题?这也就是通用块层的作用了:

image.png

Linux和设备的交互流程

这一个流程图有很多细节可以了解,抽出任何一个层都能写一篇长文出来,这里我们以简单了解IO调度器和磁盘预读机机制为主。

image.png

IO调度器和预读机制

在HDD中还有两个十分影响性能的机制,IO调度器预读机制,但是注意针对HDD的预读要比 SSD的预读效果要好很多,因为有时候因为排序和预读容易导致SSD的负面优化。

⚠️注意:有时候我们升级老设备将机械磁盘换固态的时候重装系统有可能出现黑屏,这是因为部分旧主板会通过BIOS对机械磁盘调优,类似对于磁盘“预热”,然而这种预热会影响固态的启动,所以出现类似情况可以检查BIOS是否有勾选类似的加速机械磁盘启动的选项。

IO调度器

IO调度器:是针对块设备访问的时候将请求积攒到一定的时间之后在进行一次请求。

所以针对IO调度器主要有下面两个重要的工作:

  • 合并:把对于多个扇区的访问IO请求合并为一个请求。
  • 排序:因为每个扇区都有编号,IO调度器会把连续的扇区访问的IO进行排序之后再进行访问,使得磁盘扫描更加趋近顺序扫描。

⚠️注意:IO调度器一般针对并发线程读写或者异步IO过程中等待IO结果的时候使用IO调度器,

预读机制

磁盘的磁头在扫描数据的时候,不会只扫描设备要求的几个扇区,而是会多扫描周边的扇区,注意这个预读只有在顺序扫描的时候发挥作用,当预读机制生效的时候如果发现预读的扇区在下一次访问不会用到,直接丢弃即可。

Linux文件系统设计

简单的文件系统如何设计

从最简单的角度考虑设计基本的文件系统我们可以用一个常规的文件读写举例。

最简单的文件系统包含下面的处理流程:

  • 首先文件数据从0开始记录,每一 个文件在文件系统中有名称,大小和位置三条基本信息。
  • 如果没有文件系统的辅助我们需要自行考虑文件的磁盘存储位置,需要从磁盘区域1到区域10根据文件的大小存储到块设备的对应位置,并且需要记录当前块的文件写入开始结束和结束为止,记录存储的数据大小。

image.png

为什么会有“态”?

显然在早期的单进程单用户操作系统中,是不存在态这个概念的。然而随着进程和用户的出现,当时的计算机面临着一个重要问题,就是如何限制不同进程的操作的权限

并不是所有的进程都能允许所有的外部用户操作的,因为不知道未来会出现哪些新的进程运作,所以工程师为了让系统和用户的进程可以分开,就准备让一些危险的操作只允许操作系统的进程去做,用户进程如果要做一些危险操作必须经过操作系统的“盘问”,之后再由操作系统去做。

最后“态”被设计为下面的形式:

image.png

模式切换

下面是Linux中用户态内核态硬件三者的关系:

用户态

是用户看得见的操作,比如想要读取某一个文件或者想要某一个文件改几个字,用户发送的这些指令通过内核转为机器码命令,然后在内核态对于磁盘进行IO操作。

也就是说 用户模式:发送指令操作 -> 内核模式,翻译用户指令为块设备可以识别的命令 -> 硬件。

在上面设计的最简单的文件系统中,用户模式切换到内核模式进行文件管理只需要关心文件大小,文件位置和文件名称。

内核态

只有拥有系统操作权限的操作系统才有权进行操作和访问,一般都是执行一些系统的核心工作。

硬件

硬件又被称为外部存储,这些操作只会和内核交互。

Linux文件系统结构

Linux的文件系统是树状结构设计,文件系统可以支持不同格式,不同文件系统的差别主要在最大支持操作文件大小,文件系统本身大小以及各种文件操作速度差别上。

文件系统存在ext2、ext3、ext4,它们的文件大小和存储形式和存储的位置都有差别,那么Linux是如何处理的呢?

Linux文件系统将使用接口的形式将文件IO操作进行抽象,无论文件系统的结构形式如何变动,最终都是通过和下面提到相关的接口来完成交互的。

吐槽:有点像是设计模式的外观模式

  • 创建删除:create/unlink
  • 打开关闭:open/close
  • 打开文件读数据:read
  • 打开文件写数据:write
  • 打开文件移动到指定位置:lseek
  • 特殊文件系统特殊操作:....

这一点和Linux块设备管理类似,在Linux与外部结构介绍中提到了块设备对于文件系统提供了通用层块进行抽象,对于用户模式角度所看到的单块设备之间是没有差别的,而真正的驱动处理出现在内核态中。

image.png

读取文件数据流程

在Linux中读取文件的流程如下:

  • 各个文件系统通用处理。
  • 文件系统专用处理,请求系统调用对应处理指令。
  • 设备驱动执行读写数据操作。
  • 块设备驱动完成读写指令操作。

从逻辑结构来看整个交互流程很简单,然而实际上这都是Linux的工程师们不断努力的成果。

数据和元数据

在Linux数据的种类分为元数据数据,元数据和文件名称,文件大小,文件位置相关,这些参数用于内核态读取块设备作为参考,而数据则是我们日常使用的视频数据,文本数据。

元数据除了上面的信息种类之外还包含下面的内容:

  • 种类:判断文件是保存数据的普通文件还是目录还是其他文件,也就是文件类型。
  • 时间信息:文件的创建时间、最后一次访问时间、最后一次修改时间。
  • 权限信息:Linux权限控制用户访问。

我们可以通过df命令和相关参数可以详细的了解文件系统的参数和运行情况,df是重要的运维命令,可以通过它了解到磁盘的容量情况。


g@192 ~ % df
Filesystem                                                   512-blocks       Used Available Capacity  iused      ifree %iused  Mounted on
/dev/disk3s1s1                                                965595304   29663992  73597064    29%   500637  367985320    0%   /
devfs                                                               711        711         0   100%     1233          0  100%   /dev
/dev/disk3s6                                                  965595304         48  73597064     1%        0  367985320    0%   /System/Volumes/VM
/dev/disk3s2                                                  965595304    1034480  73597064     2%     2011  367985320    0%   /System/Volumes/Preboot
/dev/disk3s4                                                  965595304      31352  73597064     1%       48  367985320    0%   /System/Volumes/Update
/dev/disk1s2                                                    1024000      12328    985672     2%        3    4928360    0%   /System/Volumes/xarts
/dev/disk1s1                                                    1024000      15040    985672     2%       27    4928360    0%   /System/Volumes/iSCPreboot
/dev/disk1s3                                                    1024000       1240    985672     1%       39    4928360    0%   /System/Volumes/Hardware
/dev/disk3s5                                                  965595304  859395304  73597064    93%  1212174  367985320    0%   /System/Volumes/Data
/dev/disk6s1                                                 1000179712  807402240 192777472    81%  3153915     753037   81%   /Volumes/Untitled
/dev/disk7s1                                                 1953443840 1019557888 933885952    53%   497831     455999   52%   /Volumes/Extreme SSD
map auto_home                                                         0          0         0   100%        0          0  100%   /System/Volumes/Data/home
//GUEST:@Windows%2011._smb._tcp.local/%5BC%5D%20Windows%2011  535375864  212045648 323330216    40% 26505704   40416277   40%   /Volumes/[C] Windows 11.hidden
/dev/disk5s2                                                     462144     424224     37920    92%      596 4294966683    0%   /private/var/folders/wn/dvqxx9sx4y9dt1mr9lt_v4400000gn/T/zdKbGy

磁盘配额

容量配额是磁盘管理中的核心,对于Linux文件管理系统来说有下面几种磁盘的配额方式:

  • 用户配额:用户配额通常指的是/home,通常每一个用户家目录有固定的额度配比
  • 子卷配额:限制名字为子卷的单元可用容量。
  • 目录配额:可以通过特定目录的可用容量,比如共享目录的用户可用容量,ext4和xfs可以设置目录配额。

除了对于针对不同类型的配额之外,还需要考虑系统正常运行运作的系统配额,也就是说如果给用户和系统按照一刀切的方式配比100%的方式划分磁盘是一件危险的操作,对于一块磁盘保持不超过80%是比较常见设置。

文件系统意外恢复

数据管理最常见的问题是数据不一致,诸如在没有完成写入的时候突然断电,这种情况并不算特别少见,Linux提供了下面的两种方式解决断电数据状态不一致的问题:

  • 日志:通常出现在ext4和xfs的文件系统。
  • 写时复制:通常为btrfs的实现。

日志方式

使用日志处理情况要多一些,因为日志的方式具备一定的可读性也方便恢复,操作主要分为下面两个步骤:

  • 数据修改之前把原子操作记录到日志当中。
  • 宕机恢复的时候根据日志记录内容还原文件状态。

如果异常情况发生在日志记录之前,可以直接丢弃写入一部分的日志并且回滚,当作文件状态没有更改过。而如果异常状态发生在原子操作的过程之后,则根据日志的记录把操作重新执行一遍即可。

现代文件系统更多的是来自于系统的BUG产生的数据不一致问题,现代多使用SSD硬盘,SSD硬盘写入都非常快基本不会出现写入时数据不一致问题。

写时复制方式

不同文件系统对于写时复制的实现不同,介绍写时复制需要了解一些传统的比如ext4和xfs的文件系统工作机制。

这些文件系统在文件创建之后就会固定存放到磁盘的某个位置,哪怕删除或者更新文件内容也只是在原有空间上进行操作。

btrfs的写时复制的文件系统管理方案则比较特殊,创建之后的文件每次更新都会放到不一样的位置。

写时复制就是说更新和写入都是一次类似“复制”的操作,当新数据写入完成再把引用更新即可,原有的内容只要不被新文件覆盖还是可以被找到。

如果在写入的时候突然断电怎么办?

这时候数据是在另一个地方操作的,数据写入到一半也不会对旧数据有影响,如果是其他操作情况下比如写入刚完成没有更新引用的情况,此时只需要把引用更新一下即可。总之就是怎么样都不会影响原来的数据。

⚠️注意:其实磁盘本质上是没有删除这个概念的,计算机所谓的删除只是用户进程无法通过常规操作访问被删除的文件所在地址而已,但是通过一些特殊处理还是有办法通过文件碎片恢复原始文件的。

无法恢复的意外

如果是文件系统的BUG无法恢复的意外,对于不同的文件系统来说处理方案也不同。

几乎所有的文件系统都有通用的fsck命令进行恢复,但是这个命令定义是有可能恢复数据状态。

下面是关于这个命令的介绍:

fsck 命令

Linux fsck(英文全拼:file system check)命令用于检查与修复 Linux 档案系统,可以同时检查一个或多个 Linux 档案系统。

语法 fsck [-sACVRP] [-t fstype] [--] [fsck-options] filesys [...]

参数

  • filesys : device 名称(eg./dev/sda1),mount 点 (eg. / 或 /usr)
  • -t : 给定档案系统的型式,若在 /etc/fstab 中已有定义或 kernel 本身已支援的则不需加上此参数
  • -s : 依序一个一个地执行 fsck 的指令来检查
  • -A : 对/etc/fstab 中所有列出来的 partition 做检查
  • -C : 显示完整的检查进度
  • -d : 列印 e2fsck 的 debug 结果
  • -p : 同时有 -A 条件时,同时有多个 fsck 的检查一起执行
  • -R : 同时有 -A 条件时,省略 / 不检查
  • -V : 详细显示模式
  • -a : 如果检查有错则自动修复
  • -r : 如果检查有错则由使用者回答是否修复

案例

检查 msdos 档案系统的/dev/hda5是否正常,如果有异常便自动修复 :


fsck -t msdos -a /dev/hda5

fsck 命令存在的问题

然而这个命令看似很强大,但是存在一些很严重的性能问题:

  • 遍历文件系统并且检查文件系统的一致性同时还会修复不一致的地方,文件系统非常庞大恢复速度会长达几个小时或者几天。
  • 如果中途恢复失败,崩溃的不只是机器。
  • 修复成功不一定会恢复到期望状态,同时对于一切不一致的数据和元数据都会删除。

计算机存储层次简析

存储组件介绍

首先我们来看看不同存储层次的介绍,包括上面提到的寄存器,高速缓存,内存以及他们三者之间的关系。

我们从整体上看一下存储层次结构图:

image.png

注意⚠️:小字这些参数放到现在都是比较老的了,我们只需要简单理解从左到右速度由最快到快到慢到最慢的递进。

高速缓存

高速缓存是位于CPU与主内存间的一种容量较小但速度很高的存储器

内存的数据被读取之后,数据不会直接进入寄存器而是先在高速缓存进行存储,高速缓存通常分为三层,读取的大小取决于缓存块的大小,读取速度取决于不同层级高速缓存的容量。

高速缓存的执行步骤如下:

  1. 根据指令把数据读取到寄存器。
  2. 寄存器进行计算操作。
  3. 把运算结果传输给内存。

在上面的三个步骤中寄存器和高速缓存基本没有传输消耗,但是内存的传输就就慢不少了,所以整个流程运算的瓶颈也是内存的传输速度,这也是为什么使用高速缓存来解决寄存器和内存之间的巨大差异。

高速缓存分为L1,L2,L3,在讲述理论知识之前,这里先举一个形象一点的例子方便理解:

  • L1 cache:就好像需要工具在我们的腰带上可以随时取用,所以要获取它的步骤最简单也最快
  • L2 cache:就好像需要的工具放到工具箱里面,我们如果需要获取要先打开工具箱然后把工具箱的工具挂到腰上才能使用。为什么不能从工具箱取出来再放回去呢?其实思考一下如果你需要频繁使用那得多累呀。另外工具箱虽然比腰上的空间大不少,但是也没有大特别多,所以L2 cache 没有比L1 cache大多少。
  • L3 cache:L3相比L1和L2要大非常多,相当于一个仓库,我们获取数据需要自己走到仓库去找工具箱然后放到身边,然后再像是上面那样执行一次,虽然仓库容积很大,但是需要操作的步骤最多,时间开销也最大。

L1 cache下面是L2 cache,L2下面是 L3 cache,根据上面介绍L2和L3都有跟L1 cache一样的问题,要加锁,同步,并且L2比L1慢,L3比L2慢。

这里我们再举例简述高速缓存的内部操作:

假设需要读取缓存块是10个字节,高速缓存为50个字节,R0、R1的寄存器总计为20个字节,当R1需要读取某个地址的数据时,在第一次读取数据的时候将10字节先加载到高速缓存,然后再由高速缓存传输到寄存器,此时R0有10字节的数据,如果下次还需要读取10个字节,同样因为高速缓存发现缓存中有相同数据,则直接从高速缓存读取10个字节到R1中。

那么如果此时R0数据被改写会怎么办?首先CPU会先改写寄存器的值,改写寄存器值之后会同时改写高速缓存的值,此时如果存在从内存进来缓存块数据,在高速缓存中会先标记这些值,然后高速缓存会在某一个时刻把改写的数据同步到内存中。

如果高速缓存不足系统会发生什么情况?

首先高速缓存会根据缓存淘汰机制淘汰末端最少使用的高速缓存,但是如果高速缓存的“变脏”速度很快并且高速缓存的容量总是不足,此时就会发生内存频繁写入高速缓存并且不断变动高速缓存的情况,此时就有可能会出现可感知的系统抖动。

注意⚠️:本部分讨论的内容全部为回写,改写的方式分为直写回写,回写在高速缓存中存在一定的延迟,利用时间积累的方式定时改写的方式进行内存的同步刷新,而直写的方式则会在高速缓存改变的那一刻立刻改写内存的值。

如何衡量访问的局限性呢?

几乎所有的程序都可以分为下面两种情况:

  • 时间局限性:在一定的时间内缓存可能被访问一次,但是可以隔一小段时间再一次访问,常见的情况是一个循环中不断取值。
  • 空间局限性:访问一段数据的同时还需要访问它周边的数据情况,有点类似磁盘的预读机制。

如果进程可以衡量并且把控好上面两个点,那么基本可以认为是一款优秀的程序,但是现实情况往往不是如此。

小结:

  • 高速缓存是利远远大于弊的一个设计。
  • 数据不一致和数据同步高速缓存性能影响的主要问题。
  • 高速缓存一旦被占满,则系统处理速度会出现一定延迟。

寄存器

寄存器包括指令寄存器(IR)和程序计数器(PC),它属于中央处理器的组成部分,寄存器中包含指令寄存器程序计数器以及累加器(数学运算)。

ARM走的是简单指令集,X86走复杂指令集,虽然X86从现在来看是走到了尽头,但是依然占据市场主导地位。

复杂指令集会包含非常多的寄存器完成复杂运算,比如下面一些寄存器:

  • 通用寄存器
  • 标志寄存器
  • 指令寄存器

如果有感兴趣可以将寄存器作为深入X86架构的入口。

内存

内存不仅仅是我们熟知的电脑内存,从广义上来说还包括只读存储,随机存储和高速缓存存储

这里可能会有疑问为什么内存使用的最多却不如寄存器和高速缓存呢?

因为内存不仅仅需要和CPU通信还需要和其他的控制器和硬件打交道,管的事情越多效率自然越低,同时如果内存吃紧CPU还需要等待内存传输,当然这也可以反过来解释为什么需要高速缓存和寄存器。

除了上面提到的原因之外,还有比较关键的原因是主板的总线带宽是有上限的并且需要共享给各路使用,比如南桥和其他的一些外接设备等等,同时总线也是需要抢占的,并不是分片使用。

其他补充

转译后备缓冲区

下面的内容来自百科的解释:

转译后备缓冲器(英语:Translation Lookaside Buffer,首字母缩略字:TLB),通常也被称为页表缓存转址旁路缓存,为CPU的一种缓存,由内存管理单元用于改进虚拟地址到物理地址的转译速度。

目前所有的桌面型及服务器型处理器(如 x86)皆使用TLB。TLB具有固定数目的空间槽,用于存放将虚拟地址映射至物理地址标签页表条目。为典型的结合存储(content-addressable memory,首字母缩略字:CAM)。

其搜索关键字为虚拟内存地址,其搜索结果为物理地址。如果请求的虚拟地址在TLB中存在,CAM 将给出一个非常快速的匹配结果,之后就可以使用得到的物理地址访问存储器。如果请求的虚拟地址不在 TLB 中,就会使用标签页表进行虚实地址转换,而标签页表的访问速度比TLB慢很多。

有些系统允许标签页表被交换到次级存储器,那么虚实地址转换可能要花非常长的时间。

进程如果想要访问特殊的数据,可以通过下面提到的方式访问逻辑地址:

  • 对照物理页表通过查表的方式把虚拟地址转物理地址。
  • 通过访问对应的物理地址寻找实际的物理地址。

注意⚠️:这里的操作类似二级指针的访问操作,如果想要高速缓存发挥作用必须是一级指针的查找才有意义,但是二级指针的查找是没有太大意义的。

转译后备缓冲器说白了就是用于加速虚拟地址到物理地址转化的一块特殊空间,目的是为了提高多级嵌套映射查找的速度。

页面缓存

注意上面提到的内容是页表缓存,这里是页面缓存

页面缓存的作用是什么呢?我们都知道外部的硬件存储速度是最为缓慢的,通常应用程序操作硬盘中的数据都是预先把数据加载到内存再进行操作,然而数据并不是直接从磁盘拷贝到内存的,而是在内存和外部存储设备之间多了一层页面缓存。

页面缓存的读取步骤如下:

  • 进程读取磁盘文本数据,寻找到相关数据之后将内容加载到页面缓存。
  • 把页面缓存的内容复制到内存中,此时物理数据和内存以及页面缓存数据一致。
  • 如果需要改写文件文本数据,首先会通知页面缓存标记自己为“脏页”。
  • 如果内存不足则空出空闲的页面缓存给内存使用。
  • 如果页面缓存和内存都不足就需要刷新“脏页”空出空间给内存继续使用。
  • 通常情况下页面缓存会定期刷新缓存回写到磁盘中保持数据同步。

另外需要注意如果页面缓存一直没有进程访问,页面缓存会一直“膨胀”,如果页面缓存和内存一直不够用,就会不断的回写脏页并且产生性能抖动问题。

缓冲区缓存

缓冲区缓存很容易和页面缓存搞混,我们只需要简单理解是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,比如设备文件直连外部的存储设备,U盘读写和外接磁盘的读写等等,这些读写通过缓存区缓存进行管理。

需要注意缓冲区缓存通常不会特别大(20MB 左右),这样内核就可以把分散的写集中起来,统一优化磁盘的写入, 比如可以把多次小的写合并成单次大的写等等。

Linux中调优参数

了解上面各个组件的内容和细节之后,我们来看几个简单的Linux调优参数。

回写周期

回写周期可以通过sysctl 的vm.dirty_writeback_centisecs 参数调整,但是注意这个值的单位比较特殊,厘秒,这个参数默认设置为500,也就是5秒进行一次回写。

厘秒(英文:centisecond,符号cs),1厘秒 = 100分之1

当然除非为了实验了解否则不要把这个值设置为0。

除了这个参数之外,还有个百分比的参数,当脏页的数量超过百分比之后就会触发脏页回写的操作防止性能剧烈抖动,下面案例的10代表了10%。

下面是这个参数的内容:


vm.dirty_backgroud_ratio = 10

如果想要使用字节的形式控制这个阈值,可以通过参数vm.dirty_background_bytes指定,如果这个参数为0则代表不开启这个配置。

脏页不允许一直存在,如果脏页积攒到一定的量的时候会内核会触发回写操作,可以通过vm.dirty_ratio 控制,当到达此百分比内核会阻塞用户进程并且把所有的脏页回写。

vm.dirty_ratio除了设置百分比参数也可以通过字节限制,参数是vm.dirty_bytes

除了这些不太常用的参数之外,还有一些更为特殊的调优参数, 比如配置用于清空所有的页面缓存的操作方法是向/proc/sys/vm/drop_caches 写入3,为什么是写入3,这里留给读者自己寻找答案。

超线程

超线程(HT, Hyper-Threading)是英特尔研发的一种技术,于2002年发布。超线程的技术可以把一个核心伪装成两个核心看待,同时对于单核心的CPU,也可以享受模拟双核心的优惠,当然超线程技术不只是有好处,还有一个明显的缺点是多线程抢占以及线程上下文带来的开销,同时哪怕在最理想的情况下超线程的技术最多也只能提升 20% -30%的,但是这个优化对于当年技术实力有限的情况下的技术优化和性能提升效果是非常显著的。

从此牙膏厂走向了挤牙膏的不归路

小结

这部分内容更像是对于家用电脑常见的几个核心组建进行稍加深入的介绍,学习这些内容不仅对于计算机的深入理解是必要的,对于我们日常选配电脑一些商家说明也能有更深理解。

在其他的补充部分介绍了三个缓存,分别是转译后备缓冲,页面缓冲,缓冲区缓冲,这三者虽然名字相近但是内部负责的工作差别还是比较大,介绍三者之后介绍了一些关于Linux的调优参数。

对于深入X86架构来说,理解各个寄存器的核心工作机制比较关键,而对于战未来的ARM使用的精简指令集对于整个生态发展更为合适。


《Linux是怎么样工作的》读书笔记(二)https://developer.aliyun.com/article/1394972

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
4月前
|
缓存 IDE Linux
《Linux是怎么样工作的》读书笔记(二)
《Linux是怎么样工作的》读书笔记
44 0
《Linux是怎么样工作的》读书笔记(二)
|
11月前
|
消息中间件 存储 网络协议
《硬核linux攻略》读书笔记更新中
《硬核linux攻略》读书笔记更新中
|
Shell Linux Perl
《Linux Shell脚本攻略》读书笔记
《Linux Shell脚本攻略》读书笔记
176 0
|
存储 Shell Linux
《Linux命令行与shell脚本编程大全》读书笔记————第一章 初识Linux shell
本章内容 1、什么是Linux 2、Linux内核的组成   1、1 什么是Linux Linux课划分为以下四部分 a)Linux内核 b)GNU工具 c)图形化桌面环境 d)应用软件   1.1.1 深入探究Linux内核 内核主要负责以下四种功能 a)系统内存管理 b)软件程序管理 c)硬件设备管理 d)文件系统管理   1、系统内存管理 内核不仅管理服务器上的可用内存,还可以创建和管理虚拟内存(即实际上不存在的内存)。
1250 0
|
Linux 机器学习/深度学习 安全
|
关系型数据库 Linux
《Linux就该这么学》读书笔记
cat /proc/cpuinfo lsmod 安装VNC服务 重置root密码: image.png RPM操作: image.png 日期: image.
1258 0
《Linux内核设计与实现》读书笔记 - 目录 (完结)
《Linux内核设计与实现》读书笔记 - 目录 (完结) 读完这本书回过头才发现, 第一篇笔记居然是 2012年8月发的, 将近一年半的时间才看完这本书(汗!!!).