操作系统——IO与文件系统

如何让外设工作

当敲击键盘时,会将对应字符存入键盘的寄存器,随后会向cpu发送中断,cpu取到寄存器的数据后向显卡的寄存器发送写命令,将该数据写入到显卡的寄存器中。最后显卡将该数据写入显存,屏幕上就会显示出结果。

让外设工作需要如下几点
1.操作系统要为用户提供一个简单的文件视图,用来使得不同硬件都有一个统一的接口
2.向对应的外设寄存器发送out指令
3.外设执行完任务后中断cpu
在这里插入图片描述
printf的故事

首先,先创建buf将格式化输出写到缓存区内,然后进行系统调用:
write(1,buf)
这里的1为标准输出的句柄。虚拟文件系统会根据fd索引到一个file文件对象,这个file里面包含了标准输出文件的inode索引节点对象,再根据这个inode即可直接找到磁盘文件(这个文件实际上是/dev/tty0,这个文件是在shell的whoami指令中打开并建立索引关系的。tty代表中断设备),下面会跟据inode中的信息判断所指向的是块文件还是字符文件,如果是字符文件,会在inode对象中找到相应的字符设备,再调用这个字符设备的相应函数(虚拟文件系统的精髓)向里面写数据,而显示器对应的函数所对应的操作是:先判断缓存区是否满了,如果满了,则sleep,否则,将数据写入缓存区。最终,将缓存区的数据写入显存

生磁盘的使用
最直接的使用:往控制器内写入柱面、磁头、扇区、以及缓存的位置,即可直接使用。
缺点:需要知道的参数太多,过于繁琐

操作系统的磁盘驱动为我们提供了一层抽象,我们只需提供一个盘块号,磁盘驱动会根据这个盘块号计算出各种参数,定位到磁盘的具体位置。盘块号是如何划分的呢?Linux0.11中一个盘块是两个扇区。盘块中的扇区越多,磁盘空间利用率越低(容易形成内部碎片),但读写速度越高(一次寻道可以读出更多数据)也就是说,操作系统为我们文件提供的第一层抽象是———盘块号

磁盘调度
如果只有一个进程,那么该进程理所当然的可以通过盘块号直接进行磁盘驱动,定位到自己需要操作的扇区。然而操作系统是多进程的,因此不同进程通过将各自请求的盘块号放入请求队列中来等待磁盘调度。而这就引出了磁盘调度问题,即按照什么样的方式进行寻道查找,以什么样的顺序处理队列中的请求才会使寻道时间短?
先来先服务、最短寻道、电梯算法(Linux0.11采用)

在这里插入图片描述
从生磁盘到文件
虽然操作系统为我们提供了盘块号的抽象,是我们不必纠缠于繁琐的扇区,柱面操作。然而,直接使用盘块号对于用户来说依旧过于生硬,因此,操作系统为我们提供了第二层抽象:文件对象

在用户眼中,文件并不是磁盘上的一个个盘块,而是一串字符流。那么,操作系统是如何建立从字符流到盘块的映射呢?比如,我们要将test.c这个文件的第200到第212字符删掉,我们该如何得知这几个字符位于哪些盘块号呢?

首先,第一种结构为连续结构
即,操作系统对于某一文件的存取是存放在连续的盘块上的。即该文件的第0-99个字节存放在第一个盘块,第100-199个字节存放在第二个盘块号…这样,我们只需要知道文件的起始位置在哪个盘块号,就可以很快根据字节流的起始和结束位置判断出所要操作的字节流在哪个盘块上。这种结构下,我们需要在文件的FCB上提供文件名,起始盘块号和每个盘块号大小的映射
在这里插入图片描述

这种结构查找起来很快,因为都是连续的地址空间,但是有一个显而易见的缺点,就是当我们的文件大小不断增加,由于要保证连续性,因此我们很有可能会覆盖到之前已经写在盘块上的其他文件的内容。
因此引出了第二种结构:链式结构
在这里插入图片描述
这种情况下,每个盘块的末尾都有该文件对应的下一个盘块的地址,而我们要查找200-212范围的字符流,就需要不断地读取盘块,查找地址,读取下一个盘块…第一种结构和第二种结构的关系实际就是数组和链表的关系。
第三种结构:索引结构
在这里插入图片描述

文件控制块中会存有该文件的索引表所在的盘块号,比如上图,19盘块中记录了该文件的索引表。而表中记录了文件第零个盘块大小的数据存放在哪个盘块,第一个盘块大小的数据存放在哪个盘块…

在这里插入图片描述
而这个索引表所在的盘块号其实就是我们常说的inode
sys_write系统调用
实际代码中,我们读写文件通常是传入文件的句柄,以及需要读出或写入磁盘的数据所在的缓冲区buf,以及读取的数量。那么,句柄这一抽象是如何与我们上面讲的inode这一抽象产生关联的呢?
在这里插入图片描述
首先,根据句柄fd在进程的文件描述符表中找到相关的文件对象,文件对象描述进程怎样与一个打开的文件进行交互。文件对象是在文件被打开的时候创建的。文件对象中存有当前文件的目录项对象,
在这里插入图片描述
VFS把每个目录看作由若干子目录和文件组成的一个普通文件,而目录项对象将每个分量与其对应的节点相联系,因此目录项对象存有与文件名相关联的索引节点对象-----inode

在这里插入图片描述
而索引节点对象如上文所说,包含文件系统处理文件所需要的所有信息。文件名可以随意更改,但索引节点对象对应的文件是唯一的。而索引节点对象的数据结构中包含了一个超级块对象
在这里插入图片描述
超级块对象就是我们常说的数据盘块。因此,我们可以通过 inode查找并操作文件的一个个盘块对象,并且可以得知文件的块大小,文件的长度,文件系统的类型等底信信息。

而最后调用的file_write 分别传入了inode,file对象和要写入的buffer和写入长度count。操作系统首先根据file对象找到文件在字节流的起始位置,然后用该起始位置和count相加得到一个要操作的字节流的范围,最后通过文件的inode索引这个范围定位到相应的数据盘块,即文件的这个范围对应哪个磁盘块。

综上,只要我们定位到了某一文件对象,我们就可以通过句柄->文件对象->目录项对象->inode->盘块->磁盘对应的扇区,柱面。

那么,我们怎么找到这个文件呢?操作系统中的文件数很多,难道我们要挨个查找嘛?因此,我们引出操作系统为我们提供的最后一层抽象:目录树。 我们如何使用目录呢?众所周知,我们可以通过/my/data/a来定位到a文件。那么,诸如/目录,my目录,等目录中应该存放哪些信息呢?我们想通过my找到data,my目录中应存放data目录的FCB信息,而data目录应存放a目录的FCB信息,是这样的吗?这样当然可行,只不过每次都要加载子文件的FCB信息到内存进行比对会使得文件查找变得十分缓慢。
因此,我们对其进行优化:my目录中不必存放data目录的FCB,仅需存放一个键值对<data: data的FCB指针>这样,我们可以比对键来确定是否为要查找的文件,如果比对正确,就把指针所指向的FCB加载进内存,进行下一步比较。

总结

下面我们来看一下,操作系统如何基于文件树的结构和他提供的多层抽象来进行文件的访问:
首先,用户会根据传进来的路径名进行open函数的调用。open会根据路径名是否以/开头,如果以/开头,会将inode引用赋给根节点,否则,赋给pwd的节点。随后,根据这个初始的inode,假如我们open("/etc/init/test.c"),系统会去查找这个文件inode的子目录项链表
在这里插入图片描述
去遍历链表,查找名字为etc的子目录项。得到这个目录项对象后,在拿到这个目录项对象的inode
在这里插入图片描述
并将其赋给系统的inode引用。然后再查找这个inode的子目录项链表,遍历链表查找名为init的子目录项…最终,系统找到test.c的inode

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值