更多优质内容
请关注公众号

浅谈操作系统原理(五) 存储管理之内存管理和文件管理-阿沛IT博客

正文内容

浅谈操作系统原理(五) 存储管理之内存管理和文件管理

栏目:其他内容 系列:浅谈操作系统原理系列 发布时间:2020-03-24 11:29 浏览量:4372

存储管理之内存分配和回收

存储管理的目的:
确保计算机有足够内存来保存和处理数据
确保程序可以从可用内存中获取一部分内存来使用
确保程序可以归还使用后的内存以供其他程序用


内存分配的过程
固定分区分配
将内存划分为若干个固定大小的区域,每个分区只提供给一个程序使用,互不干扰。
固定分区分配是支持多道程序的最简单的内存分配方式

动态分区分配
根据进程实际需要,动态分配相应大小内存空间

首先一个内存会划分为一个个小的分区。
这些内存分区有些是空闲的未被使用的,有些是正在被程序们使用的。

系统会用两种数据结构存放分区,分别是表和双向链表

对于表结构
表存放的是分区的分区id和该分区是否被使用(0或1)。
如:
分区  1 2 3 4 5 6 7 8
标记  0 1 1 1 0 0 1 0 


对于双向链表

双向链表只存放空闲的分区,连续的空闲分区作为链表的一个节点
假设内存有10块分区,空闲分区为1,3,4,6,9,10,非空闲区为2,5,7,8
双向链表的结构和节点为:
1<--->3,4<--->6<--->9,10

相邻的空闲区会被当成整体作为一个节点(如3,4是一个节点,6是一个节点,9,10是一个节点),因此链表中的每个节点可能包含一个空闲区,也可能包含多个空闲区
所以链表中的每个节点的容量不同。
所以每个节点需要记录该节点存储的容量


动态分区分配算法
首次适应算法(FF算法)
分配内存是,要从链表头部开始遍历,如果找到了适合某进程所需内存容量的节点就会分配内存给该进程。如果遍历了整个链表都找不到合适的节点则分配失败。

例如:
空闲链表:1<--->3,4,5<--->7<--->9,10
有一个进程需要2个空闲区大小的内存空间
当遍历到第二个节点,发现合适,此时会从节点2将2个空闲区给进程使用,这两个空闲区变成非空闲区。
此时空闲链表变为 1<--->5<--->7<--->9,10

由于每次都是从头部开始遍历,导致靠近链表头部的空闲区被不断划分,形成很多内存碎片(例如刚刚节点2的空闲区从3,4,5变成5)。导致头部的空闲区是不连续的,之后的进程要遍历到链表尾部才能从合适的节点拿到内存空间

循环适应算法是首次适应算法的改进,其下一次遍历不是从头开始,而是从上一次找到合适节点的位置开始往下遍历。


最佳适应算法
该算法要求空闲区链表按照容量大小排序,再遍历空闲区链表找到合适的空闲区节点

例如
1<--->3,4<--->6<--->9,10
会先排序为
1<--->6<--->3,4<--->9,10

这种算法的好处是避免“大材小用”,避免形成过多的内存碎片。原因是所需内存少的进程在链表前半部分就能找到合适的空闲节点,就不会去分割大块内存


快速适应算法
将不同容量的空闲节点放到不同的链表。为进程分配内存的时候,只需找到进程所需内存对应大小的那条链表,在那条链表找获取内存分区即可。减少了遍历的过程。

例如: 
1<--->4<--->8
2,3<--->9,10<--->14,15
5,6,7<--->11,12,13

内存回收的过程
分为四种情况:
空闲区,回收区
回收区,空闲区
空闲区,回收区,空闲区
回收区 

第一种情况,空闲区和回收区相邻,且回收区在空闲区之后。
例如 1<--->4<--->8
此时分区5要回收到内存中,就变成了 1<--->4,5<--->8
此时节点2的空间容量扩大,但是节点2的地址不变,还是指向空闲区4的地址空间

第二种情况如
1<--->4<--->8
分区3要回收到内存中,就变成了 1<--->3,4<--->8
此时节点2的空间容量扩大,但是节点2的地址变成空闲区3的地址

第三种情况如
1<--->4<--->6<--->8
分区5要回收到内存中,就变成了 1<--->4,5,6<--->8
此时节点2的空间容量扩大,但是节点2的地址不变,总结点数变少,节点3的地址从空闲区6变成指向空闲区8

第四种情况
1<--->4<--->8
分区6要回收到内存中,就变成了 1<--->4<--->6<--->8
此时往链表插入新节点即可


当然系统中不可能只有一条空闲链表,而是有很多条。假如在某条空闲链表中找不到符合某进程所需的内存节点(例如进程需要10个分区的空间,但是这条链表里面的所有节点大小都小于10个分区),这个进程就会到其他空闲链表去找
===========================================

存储管理之段页式存储管理

这节主要是介绍进程怎么管理内存的。

页式存储管理
页面:页面和字块一样是内存中的一小块空间
字块是相对物理设备的定义
页面是相对逻辑空间的定义
但是其实字块和页面指的是大小一样的两块内存
页面是进程空间的定义

页式存储管理是将进程逻辑空间等分为若干页面
将物理内存空间分为和页面大小相等的物理块
以页面为单位将进程空间装进物理内存中分散的物理块

也就是说将进程分成很多页,将每一页装进内存的每一个物理块中。每一个物理块(字块)都存放着一页(进程空间的一部分)

这里的一页就是上面空闲链表中的一个分区。


*内存碎片
上一节说到内存的空闲区会被放到一个双向链表,并且相邻的空闲区会作为链表的一个节点。
当进程的页面大小要占3个分区的大小,但是链表中没有3个分区大小的节点却有4个分区的节点,所以就要找到链表中4个分区大小的节点用来存储这个页面。3个分区会从这个链表的这个节点被取走而剩下的1个分区就是内存碎片,会留在空闲链表中。

所以页面大小应该适中,过大难以分配,过小则内存碎片会很多。
页面大小通常为 512B~8K

通过页式管理,可以将进程的每个页面放到内存的字块中。

所以平时说的,系统给进程分配内存,其实具体就是为进程定义一个逻辑空间并将空间中页映射到物理内存块中。


但是怎么知道进程的页面存储在哪一个字块中?

页表
页表记录进程逻辑空间和物理空间的映射
一个进程有一个页表,该页表记录着这个进程中所有的页。

页表一个类似map的结构
页号      字块 
1           1  
2           3
3           5
4           10
5           12


页号相当于字块
页内偏移相当于字块中字的偏移(字的位置)

页地址 = 页号+页内偏移

页是逻辑空间是虚拟的,而字块是内存中的物理空间是真实的载体。

页式存储管理的问题
在一个32位系统,最多只有4G的内存上限,假设一个页的大小是4KB,那么页的总数上限=4G/4K=1M,所以有1M这么多个页(字块)。那么每个进程的页表中的页就有1M个。
如果一个页表中记录一个页表项要1byte,那么每个进程的页表要占1M*1byte=1M的内存。

注意这里不是说一个进程要占1M内存,而是一个进程的页表要占1M内存。


如果有一段连续的逻辑分布在多个页面,将大大降低执行效率


段式存储管理

相比于页式存储管理,页式是将进程的逻辑空间等分成若干段
而段式存储管理是将进程的逻辑空间不等分的划分为若干段

段的长度由连续逻辑的长度决定。一段连续的逻辑会存储在一个段中,而页式存储管理则是一段连续的逻辑会分散在多个页中。

段式存储管理也有一个段表记录每个段的信息。段表包括三个内容:
段号  基址  段长
1     10k    30k
2     40k    10k
3     50k    40k 
...

因为段的空间是不等的,所以除了要记录段长,还要记录每一段的起始位置,也就是基址,当然这个基址不是指这个段的地址。

段地址 = 段号+段内偏移


对比段式存储和页式存储:
共同点:段式存储和页式存储都离散的管理了进程的逻辑空间(都将进程空间分成若干块)

不同点:
页是物理单位,段式逻辑单位
分页是为了合理利用空间,分段是满足用户需求
页大小由硬件固定,段长度可动态变化(每个页的大小是相同的,每个段的大小可以是不同的)
页表信息是一维的,段表信息是二维的

分页可以有效提高内存利用率(虽然会产生内存碎片)
分段可以更好的满足用户需求


段页式存储管理
先将逻辑空间按段式管理分成若干段
再把段内空间按页式管理分为若干页

段页地址 = 段号+段内页号+页地址


页式存储,段式存储和段页式存储都是为了管理进程中的逻辑空间,而将逻辑空间分割并映射到内存中。


========================================================


内存管理之虚拟内存 

问题:一个游戏有十几个G,但是计算机内存只有4G,请问这个游戏是怎么跑起来的?


为什么要有虚拟内存?因为:
1.有些进程实际需要的内存很大,超过了物理内存的容量
2.多道程序设计使得每个进程可用物理内存更加稀缺(意思是,由于多个进程可以并发或者并行,每个进程都需要内存,这样物理内存就更紧缺了)
3.不可能无限制的在电脑上增加物理内存

也就是说,大程序(消耗很大内存的程序)的运行以及多进程并发会使得物理内存总是不够这些进程运行所需的内存


虚拟内存是操作系统内存管理的关键技术
使得多道程序运行和大程序运行成为现实

那么虚拟内存是怎么实现“在物理内存小于所有进程所需内存的情况下”还能让进程们正常运行呢?
是通过将程序所使用的内存划分,将每个进程部分暂时不使用的内存放到辅存(磁盘)中。如果是进程正在使用的内存那么这部分内存不能放在磁盘中,这部分使用的内存还是消耗的物理内存的。

程序的局部性原理
局部性原理是指CPU访问存储器时,无论是存取指令还是存储数据,所访问的存储单元都趋于聚集在一个较小的连续空间。


局部性原理是虚拟内存实现的重要原因。

程序运行的时候,无需将进程的所有逻辑空间都装入内存,而只装部分即可(进程中的一些页映射到内存,一些页映射到了辅存)
如果访问页(页式管理中的页)不在内存里,则发出却页中断,发起页面置换(简单的说是从辅存中获取进程暂存到辅存的数据,并将数据写入内存)
从用户层面看,程序拥有很大空间,就是因为虚拟内存

虚拟内存实际是对物理内存的补充,速度接近于内存,成本接近于辅存。


虚拟内存置换算法(即辅存和内存交换数据的算法)
FIFO    先进先出算法
LFU     最不经常使用算法
LRU     最近最少使用算法

这里和之前说的,主存和高速缓存的置换算法基本一致,这里不再赘述。

接下来对比主存-辅存置换与高速缓存-主存置换

回顾一下,高速缓存和主存什么情况下才会置换或者说高速缓存什么时候要写入主存
当CPU处理程序时生成的数据在缓存中放不下时缓存中的数据就会写入主存。当CPU要获取的数据不在高速缓存中时,高速缓存就会从主存获取再供CPU使用。

主存和辅存置换的时机则在于当出现主存缺页的时候,主存就会读取辅存的内容。

两者的不同点:
Cache-主存的置换是为了解决速度不匹配的问题
主存-辅存的置换是为了解决容量问题,因为物理内存远远小于程序运行所需内存。

两者相同点:
置换的算法相同


总结:
物理内存 = 计算机实际的内存空间
虚拟内存 = 物理内存+辅存

==============================================


Linux的存储管理

Buddy内存管理算法
我们知道,内存中的空闲内存是一串连续的小空间隔着一些已用的空间又连着一串连续的小空间。在链表中表现为连续的空闲分区作为一个节点。
而进程使用内存的时候,也是使用连续的内存分区,比如某个进程要用到10个分区,它不会再内存中东取一块西取一块,而是在节点中获取连续的10个分区。但即便这样,这个节点也会出现内存碎片,例如这个节点有12个连续空闲分区,被进程使用10个分区就会剩余2个空闲分区,这两个空闲分区就是内存碎片。

内存碎片造成的问题是:一些需要使用稍微较多的内存的程序会用不上这些内存碎片,比如进程都是用8块10块空闲分区,但是作为空闲链表的某个节点只有2个分区,那么这些内存就不会被这些进程用到导致内存浪费。

上面所说的是内存页外碎片


页外碎片和页内碎片
内部碎片是已经分配给程序(能明确之处属于哪个进程)的内存空间大于这个进程所需的内存空间,不能被利用的内存空间就是内部碎片。
例如:程序要用10.3个页的内存,但是内存会分配11个页给这个程序,因为内存是按整页整页的算的

外部碎片是还没有分配,但由于太小而无法分配给申请内存空间的进程的内存空闲块。


该算法主要是为了解决内存外碎片的问题。

具体如下:
系统会生成11个链表,每一个链表分别放着节点大小不同的节点。节点最小的链表中每个节点只有1个连续页,节点最大的链表中每个节点有1024个连续页,每条链表有多个节点,一页大概有4k。
11条链表链表分别包含节点大小为1,2,4,8,16,32,64,128,256,512和1024个连续页的页框块。
1024个连续页链表的一个节点约为4M

例如:每个节点只有1个页的链表,假如*是一页,-代表节点的连接(指针)
*-*-....-*

每个节点有4个连续页的链表
****-****-****-...-****

buddy算法中,分配内存给进程时,遵循2的幂次方大小的原则
例如进程要申请使用100k的内存。那么系统会分配128k给进程。

现在假如某进程要233个页的内存,于是系统会分配256页的内存给它。
所以,系统会先到256连续页的那条链表去找
a.如果这条链表不为空,就会从链表中取出一个节点(256个页)给进程。

b.如果这条链表为空,就会到512连续页的链表去找,如果512链表不为空,系统会取下里面的一个节点,并均分为两个256连续页的大页块。一个分配给应用,另外一个移到256个页框的链表中。如果512个页的链表中仍没有空闲块,继续向1024个页的链表查找,如果仍然没有,则返回错误。

内存在回收时,会先将256个页放回256的链表中,如果这256个页能在链表中找到另一个地址连续的256个页,就会合并为512个连续页并放到512那个链表,如果还能找到连续的就再合并,并发到1024的链表。

这个算法是基于计算机处理二进制的优势具有极高效率

这个算法解决了内存外碎片的问题,一个节点不会被用的只剩一点点残渣,而是会把分割的节点往下级链表中放。

但是会存在页内碎片,例如上面说到的,如果进程要用到100k内存,系统会分配128k,剩下的28k在进程中是没有被用到的

相当于将页外碎片问题转移为页内碎片问题,尽管如此,buddy算法依然大大提高了内存使用的效率


Linux的交换空间(Swap)
Swap是磁盘的一个分区。
当Linux的物理内存不足时,会将一些内存交换到swap空间
swap是安装系统时配置的

可以用top命令看到Swap的信息。

虽然Swap分区可以分担物理内存的压力,但是不推荐使用,因为swap空间还是在磁盘中,所以速度会比较慢。如果频繁使用Swap就会导致Linux会运行的很慢。

swap的作用:
冷启动内存依赖:一些程序在开机的时候会运行,开机后就不怎么运行了,所以开机完毕后这些程序的数据会从内存写到swap,使的内存得到释放。
系统睡眠依赖:当系统睡眠时(不是关机,是睡眠),会将内存数据放到swap,等下次系统启动时会从swap加载到内存。
大进程空间依赖:就是进程需要很多内存,但是该进程有很多内存是暂时不使用的,这部分的内存会放到swap中暂存。


Swap和虚拟内存很像,但不是一回事:
swap和虚拟内存都存在于磁盘
都会和主存发生置换

但:
swap是操作系统概念,虚拟内存是进程概念
swap解决系统物理内存不足问题,虚拟内存解决进程物理内存不足问题


=========================================

操作系统的文件管理

文件的逻辑结构

文件可以分为:
有结构文件:文本,文档,媒体文件(图片,音频,视频)

无结构文件:二进制文件,链接库

有结构的文件的文件内容由定长记录和可变长记录组成
定长记录存着文件格式,文件描述等结构化的数据项
可变长记录存着文件具体内容

无结构文件又称为流式文件,内容长度以字节为单位
exe,dll,so 文件都是流式文件


顺序文件
是按照顺序存在存储介质中的文件
顺序文件是所有逻辑文件当中存储效率最高的
但是如果想再顺序文件的中间加内容或者修改删除是非常没有效率的
可变长文件不适合使用顺序文件格式存储


索引文件
索引文件是为了解决可变长文件存储而发明的文件格式
索引文件比顺序文件多一个索引表来完成存储的操作,索引表包括两个字段,key和value,一个key指向一段文件内容(索引文件会将所有的文件内容分割为多段)。


辅存的空间分配

分配方式:连续分配,链接分配,索引分配

一个文件的存储需要存到磁盘的多个扇区(或者说盘块)。

连续分配是为一个文件分配多个扇区,而这多个扇区是连续的。
例如有1~10000个扇区,某一个文件被分配到的是1~6号扇区

连续分配优劣:顺序读取文件很快;对存储要求高,要求满足容量的空闲连续存储空间(例如某个文件要分配1000个扇区,但是没有1000个连在一起的扇区,只有1000个离散的扇区)


链接分配是将文件存在离散的盘块,但是要额外的空间存文件的盘块链接顺序

链接分配又包括显式链接 隐式链接

隐式分配
隐式分配的下一个“链接指向”是存在当前盘块中。
例如,某个文件的内容映射到了5个扇区,按顺序分别是第2,9,7,18,16号盘块。2号盘块存着一个链接指向9号,9号存着链接指向7号以此类推。
根据链接可以找到下一部分文件内容存在哪个盘块中。

隐式分配适合顺序访问,随机访问效率很低(例如我要访问第16号盘块的内容要先2号->9号->7号->18号->16号,无论要找哪个盘块,我们都要从第一个盘块开始找)

显式分配
显式链接分配会单独将链接的指向放到一个表中,这个表存放着一个磁盘所有文件的盘块和链接指向。这个表叫做FAT表,即FAT文件系统
例如,某个文件被分配到了5个扇区,按顺序分别是第2,9,7,18,16号盘块。
FAT表为:

物理块     指向下一盘块的链接
...         ...
2           9
...         ...
9           7
...         ...

FAT不支持高效直接存储访问(因为FAT记录的条数很多,磁盘越大,FAT表的记录数就越多)。
当读取某一个文件的内容时,为了获取该文件的所在盘块,要检索整张FAT表,一个是慢,一个是要将整个FAT表加载到内存,会占用内存空间。


索引分配
会将一个文件所占有的盘块的索引单独放到一个盘块(索引块)中。当访问这个文件时,只需获取这个放索引的盘块(索引块)并将其内容加载到内存就能读取到任意一个存文件内容的盘块。

例如,某个文件被分配了5个盘块,按顺序分别是第2,9,7,18,16号盘块。将2,9,7,18,16这几个索引放到第12号盘块存放(存放的可能是一张索引表,类似与之前的FAT表,记录这盘块和下一盘块的链接)。

读取该文件的9号内容时,就会先读取12号盘块,获取索引表,从表中获取9号盘块链接,再去9号盘块取内容

每一个文件都有一个索引块

索引分配是现在的主流磁盘存储空间分配方式


辅存的空间管理
空闲表,空闲链表,位示图

空闲表:
序号      第一个空闲盘块号    空闲盘块数
1           2                   4       #2号~5号是连续的空闲盘块
2           9                   1      #只有9号是空闲的 
3           11                  32  
4           56                  20 

空闲链表: 
一个链表节点存着空闲盘块号和空闲盘块的数目

和空闲内存链表优点类似


位示图
记录每个盘块是否被使用(0/1来标记)

目录管理
目录是使用树型结构来管理的。

由于是树型结构,所以任何一个文件或者目录都有唯一的路径

Linux的重要目录
/bin    命令文件目录
/etc    配置文件目录
/home   家目录
/usr    系统应用目录 /usr/local
/proc   系统内存的隐射,存放内存,进程和CPU的信息(Linux中一切皆文件所以进程也是命令)
/dev     设备目录
/boot   开机引导目录
/root    超级管理员家目录

Linux的文件类型:
普通文件(-)
目录文件(d)
软连接(l)
设备文件(b/c)
套接字(s)
FIFO(p)


Linux的文件系统
文件系统有三种:FAT、EXT2/3/4,NTFS

FAT:用一张表保存所有盘块信息

NTFS:是Windows7,8,10的文件系统,是FAT的改进

EXT:又称扩展文件系统,一般是Linux常用的文件系统

Windows可以使用FAT,NTFS,但是无法使用EXT文件系统。
Linux则三个都可以使用。

如果讲一个EXT格式的U盘插入Windows的计算机中,计算机是无法识别的。

一个计算机可以接入多块磁盘,每一块磁盘都有自己的文件系统。比如你Linux系统机器里有两块磁盘,再接入一个U盘,磁盘1可以是NTFS系统,磁盘2是EXT系统,U盘可以是FAT32系统

EXT文件系统

ext文件系统下,文件系统会把磁盘逻辑上分为一个启动扇区(boot sector)和多个块组(block group)

启动扇区用于安装开机管理程序
块组用于存放文件系统的数据

每一个块组都包含以下内容:superblock/文件系统描述/inode bitmap/block bitmap/inode table/data block 


inode table (索引节点)
存放文件的iNode的地方
每一个文件或者目录都有一个inode
inode就是索引节点
所以ext文件系统是使用索引分配的方式给文件分配磁盘空间的。

inode(索引节点)具体存了:文件编号(唯一id),文件类型,文件权限,文件物理地址,文件长度,文件连接计数,文件状态,访问计数(当前那几个进程访问了这个文件),文件存取时间,链接指针,等等

文件名不是存放在inode节点上的,而是存放在目录的inode节点中。目的是,列出目录内所有文件的时无需加载文件的inode


inode bitmap(inode的位示图)
用于记录已分配的inode和未分配的inode(0/1标记)


data block
存放文件内容的地方。就是我们上面所说的盘块

block bitmap 
记录block的分配和未分配的情况


PS:块组不是存放一个文件的内容的,而是这很多inode和block的内容和信息的容器。
一个块组里面包含了很多block(盘块)和很多的inode(索引节点)。一个文件会使用一个inode记录文件的相关信息,使用多个block存储文件内容。


接下来具体看一下块组里面放了些什么:

#执行 
df -T

#得到
Filesystem     Type     1K-blocks     Used Available Use% Mounted on
/dev/vda1      ext4      51473888 18996804  29839336  39% /
devtmpfs       devtmpfs    930668        0    930668   0% /dev
tmpfs          tmpfs       941444       24    941420   1% /dev/shm
tmpfs          tmpfs       941444      752    940692   1% /run
tmpfs          tmpfs       941444        0    941444   0% /sys/fs/cgroup
tmpfs          tmpfs       188292        0    188292   0% /run/user/0


# 可以看到,系统将磁盘 /dev/vda1 挂载到了 根目录/ 上面。
# /dev/vda1这个磁盘是ext4的文件系统
# 执行 
dumpe2fs /dev/vda1  > vda1.log      # 查看/dev/vda1磁盘的信息


# 下面是磁盘信息的部分内容
Filesystem OS type:       Linux
Inode count:              3276800       # inode节点的总数
Block count:              13106944      # 盘块的总数,一个文件可能会被分配多个盘块,所以block数量比inode多
Reserved block count:     655341    
Free blocks:              11692441      # 空闲盘块数
Free inodes:              3168592       # 空闲inode数
First block:              0
Block size:               4096          # 每个盘块可存4096bit的内容(0.5k)
Fragment size:            4096
Group descriptor size:    64
Reserved GDT blocks:      1017
Blocks per group:         32768         # 一个块组包含多少盘块    
Fragments per group:      32768
Inodes per group:         8192          # 一个块组包含多少inode
Inode blocks per group:   512
Inode size:               256           # 一个inode的大小为256字节

Group 0: (Blocks 0-32767) [ITABLE_ZEROED]       # 第一个块组的详细信息,它存放着0号-32767号盘块
  Checksum 0x77e2, unused inodes 0
  Primary superblock at 0, Group descriptors at 1-7
  Reserved GDT blocks at 8-1024
  Block bitmap at 1025 (+1025), Inode bitmap at 1041 (+1041)
  Inode table at 1057-1568 (+1057)
  20512 free blocks, 1 free inodes, 1275 directories
  Free blocks: 10597, 10793, 12190, 12192, 12208, 12250, 12262-32767
  Free inodes: 8075
  
Group 1: (Blocks 32768-65535) [ITABLE_ZEROED]
  Checksum 0xc009, unused inodes 2235
  Backup superblock at 32768, Group descriptors at 32769-32775
  Reserved GDT blocks at 32776-33792
  Block bitmap at 1026 (bg #0 + 1026), Inode bitmap at 1042 (bg #0 + 1042)
  Inode table at 1569-2080 (bg #0 + 1569)
  6041 free blocks, 2901 free inodes, 1424 directories, 2235 unused inodes
  Free blocks: 34464-34559, 34685, 34688-34815, 35394, 35396-35399, 35408-35420, 35424-35425, 35556, 35571-35572, 35574-35578, 35580-35581, 35583-35700, 35702-35703, 35705-35710, 35715, 35724-35738, 35741, 35746, 35750, 35752-35765, 35769-36162, 36171-36181, 36184, 36187-36193, 36195-36210, 36212-36215, 36217-36222, 36224-36225, 36227-36228, 36230-36241, 36243-36254, 36267-36556, 36560-36714, 36719, 36721-36750, 36752-36840, 36846-37059, 37064-37254, 37256-37261, 37265-37272, 37274-37735, 37737-37743, 37745-37750, 37752-37774, 37776-37778, 37780-37989, 37991-38030, 38032-38066, 38068-38084, 38101, 38103-38109, 38111, 38115-38283, 38285-38295, 38297-38301, 38304-38319, 38321-38322, 38324-38337, 38345-38359, 38362-38437, 38441-38458, 38460, 38462-38495, 38498-38888, 38892-38910, 38912-39412, 39414-39423, 39513-39935, 40978, 40987-40990, 40992, 40995, 40998-40999, 41006, 41008-41469, 41471-41619, 41621, 41624-41654, 41656-41658, 41660-41661, 41663-41665, 41667-42289, 42304-42495, 45432-45439, 47188-47222, 62223-62233, 62258-62273, 63050-63104, 63490-63532, 65237-65251
  Free inodes: 13483, 13485-16384

.....
# 查看随意的一个文件的inode和block信息
# 执行
stat vda1.log

#得到
  File: ‘vda1.log’
  Size: 685008          Blocks: 1344       IO Block: 4096   regular file
Device: fd01h/64769d    Inode: 465547      Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2020-03-14 15:02:46.669280468 +0800
Modify: 2020-03-14 15:02:46.753280598 +0800
Change: 2020-03-14 15:02:46.753280598 +0800

从上面的信息知道:
vda1.log这个文件大小为 685kb , 占用了1344个块,索引号为465547


索引号可以说是文件的唯一标识

修改文件名不会影响索引号


Linux的设备管理

广义的IO设备
对CPU而言,凡是对CPU进行数据输入的都是输入设备,凡是负责CPU数据输出的设备都是输出设备

例如,CPU进行运算后将数据写入内存或者写入高速缓存,那么高速缓存或者内存就是IO设备中的输出设备

CPU对高速缓存或者内存进行读写就是IO操作。

按使用特性,可以将IO设备分为
存储设备:U盘,磁盘,内存
交互IO设备:鼠标,键盘,显示器

按信息交换的单位可分为
块设备:磁盘,SD卡
字符设备:打印机,shell终端

块设备是以块(block)为单位进行交互的设备
字符设备是以字符为单位进行交互的

因为一个block包含很多字节,所以块设备的交互速度比字符设备快很多

按照传输速率分类
1)低速设备
传输速率为每秒钟几个字节至数百个字节的一类设备。属于低速设备的典型设备有键盘、鼠标器、语音的输出和输出等。
2)中速设备
这是指其传输速率在每秒钟数千个字节至数万个字节的一类设备,典型的中速设备有行式打印机、激光打印机等。
3)高速设备
这是指其传输速率在数百个千字节至千兆字节的一类设备。典型的高速设备有磁带机、磁盘机、光盘机等


按设备的共享属性分类
这种分类方式可以将IO设备分为如下三类:
1)独占设备
这里是指在一段时间内只允许一个用户访问的设备,即临界资源。因而,对多个并发进程而言,应当互斥地访问这类设备。系统一旦把这类设备分配给某进程后,便由该进程独占,直到释放。
2)共享设备
这是指一段时间内允许多个进程同时访问的设备。当然,对于每一时刻而言,该类设备仍然只允许一个进程访问(并发而不是并行)。显然,共享设备必须是可寻址和可随机访问的设备。典型的共享设备是磁盘。
3)虚拟设备
这是指通过虚拟技术将一台独占设备变换为若干逻辑设备,供若干个用户(进程)同时使用。


IO设备的缓冲区

我们之前的说的CPU和IO设备的速率不匹配,除了可以通过添加高速缓存来解决之外,IO设备的缓冲区也能做到。

IO设备的缓冲区可以:
减少CPU处理IO请求的频率(这里不是说CPU进行IO操作,而是说CPU发送IO请求,CPU是不进行IO操作的。进程提出IO请求时,CPU将IO请求发给IO设备,让IO设备处理请求)
提高CPU和IO设备之间的并行性

两个例子:
1.一个程序和一个IO设备进行了四次交互,比如一个php脚本中对一个文件写入了四次(调用了四次fwrite())
那么它不是每调用一次fwrite(),就往文件里面写一次。
而是会将四次写入的内容放到缓冲区再一次性的写入磁盘中。(前提是四次写入的内容的大小没有超过缓冲区的大小)
这样的话,原本CPU要对磁盘发出4次IO操作的请求,现在变成只有1次请求。

PS:
专用缓冲区只使用于特定的IO进程
当这样的IO进程比较多的时候,内存消耗也很大(缓冲区本质是内存空间)
所以操作系统划分出可供多个进程使用的公共缓冲区,叫做缓冲池

一个缓冲池有多个缓冲区,当进程需要进行和IO设备交互的时候,就会从缓冲池取出一个合适大小的缓冲区使用。用完以后(比如关闭文件句柄或者进程运行结束)会归还这个缓冲区给缓冲池


SPOOLing技术
是关于慢速的字符设备如何与计算机主机交换信息的技术
利用高速共享设备(磁盘)将低速的独享设备模拟为高速的共享设备
逻辑上为每一个用户(或者说进程)都分配了一台独立的高速独享设备

所以SPOOLing技术是一种虚拟设备技术

举一个例子:
有3个进程都要使用打印机(打印机是输出设备,进程要将数据输出到打印机上),由于他是独享设备,所以每一个进程要使用完毕这个设备才能释放这个设备给其他进程用。而且他的速度比较慢,所以其他进程要等很久才能用到这个打印机。

如果使用spooling技术,则不会直接把打印机分配个某一个进程。而是将3个进程的数据输出到磁盘的文件中,因为磁盘是高速设备,而且是共享设备,所以3个进程可以同时(并发不是并行)写入磁盘放到文件中而且写入速度很快。然后3个进程就执行结束了。

这些文件会放到一个队列中,spooling进程会调用打印机一次次的将队列中的文件的内容输出。

spooling技术把同步调用低速设备改为异步调用(进程只要输出完自己的数据到磁盘就完事,不用管数据有没有输出到打印机)

那么这个队列叫做输出井或者输入井。spolling负责输出井和低速设备的调度
逻辑上,进程是直接和高速设备交互而不是和低速设备交互,减少了进程等待的时间,也加快的传输速度。




更多内容请关注微信公众号
zbpblog微信公众号

如果您需要转载,可以点击下方按钮可以进行复制粘贴;本站博客文章为原创,请转载时注明以下信息

张柏沛IT技术博客 > 浅谈操作系统原理(五) 存储管理之内存管理和文件管理

热门推荐
推荐新闻