linux内核篇-文件系统(硬盘、虚拟文件系统、文件缓存)

news/2024/4/26 10:57:33/文章来源:https://blog.csdn.net/weixin_53344209/article/details/130720625

文件系统的意义
之前说的都是在进程在物理内存保存的数据,内存就像一个纸箱子,仅仅是一个暂存数据的地方,而且空间有限。如果我们想要进程结束之后,数据依然能够保存下来,就不能只保存在内存里,而是应该保存在外部存储中。
我们最常用的外部存储就是硬盘,数据是以文件的形式保存在硬盘上的。为了管理这些文件,我们在规划文件系统的时候,需要考虑如下几点。

文件系统的几个要点:
1、严格的组织形式。以单位进行存储,比如图书馆的书架分成很多小格子;
2、要有索引,实现快速查找;
3、要有缓存,实现热点文件的快速应用;
4、要有文件夹的形式,方便查询和管理。相当于给书分类;

相关命令:
1、格式化
所谓格式化,就是将一块盘使用命令组织成一定格式的文件系统的过程。比如硬盘或者U盘,要先格式化才能放放文件。
使用 Windows 的时候,咱们常格式化的格式为NTFS(New Technology File System)。在 Linux 下面,常用的是 ext3 或者 ext4。
当一个 Linux 系统插入了一块没有格式化的硬盘的时候,我们可以通过命令fdisk -l,查看格式化和没有格式化的分区。
我们可以通过命令mkfs.ext3或者mkfs.ext4进行格式化。
mkfs.ext4 /dev/vdc
执行完这个命令后,vdc会建立一个分区,格式化为ext4文件系统的格式。
格式化后的硬盘,需要挂在某个目录下面,才能作为普通的文件系统进行访问。
mount /dev/vdc1 /根目录/用户A目录/目录1

文件系统相关系统调用(open,rw,lseek,close)
在内核中,要有一整套的数据结构来表示打开的文件。在用户态,每个打开的文件都有一个文件描述符,可以通过个这种文件相关的系统调用,操作这个文件描述符
当使用系统调用open打开一个文件时,操作系统会创建一些数据结构来表示这个被打开的文件。为了能够找到这些数据结构,在进程中,我们会为这个打开的文件分配一个文件描述符fd。

硬盘文件系统
硬盘分为盘片,磁道,扇区,一个扇区512字节。硬盘存储单元是块,一块的大小是扇区大小的整数倍,默认是4K

inode索引
inode就是对应的索引。主要包含两部分信息,
一个是元数据(比如文件权限、属主,属组,时间,大小),ls -l列出来就是这些信息;
“某个文件分成几块、每一块在哪里”,这些在 inode的 i_block 里面。
在ext2-3里面, i_block有15项。其中前 12 项直接保存了块的位置,也就是说,我们可以通过 i_block[0-11],直接得到保存文件内容的块,后面放的是间接块的位置,这就导致了,对于大文件来讲,我们要多次读取硬盘来能找到相应的块,这样访问速度就比较慢。

为了解决这个问题,ext4做了一定的改变。它引入了一个新的概念,叫做Extents比如,一个文件大小为128M,如果使用4K大小的块进行存储,需要32K个块。如果按照ext2或者ext3那样散着放,数量太大了。但是Extents可以用于存放连续的块,也就是说,我们可以把128M放在一个Extents里面。这样的话,对大文件的读写性能提高了,文件碎片也减少了
在这里插入图片描述

树的形式,每个节点都有一个头,ext4_extent_header 可以用来描述某个节点。包括节点的项数和类型;(节点大小12Btyes)
类型主要有叶子结点这一项会直接指向硬盘上的连续块的地址,我们称为数据节点 ext4_extent;
分支节点,这一项会指向下一层的分支节点或者叶子节点,我们称为索引节点 ext4_extent_idx。

inode位图和块位图
如果我要保存一个数据块,或者要保存一个inodee,我应该放在硬盘上的哪个位置呢?难道需要将所有的inode列表和块列表扫描一遍,找个空的地方随便放吗?
当然,这样效率太低了。所以在文件系统里面,我们专门弄了一个块来保存 inode 的位图。在这 4k 里面,每一位对应一个 inode。如果是 1,表示这个 inode 已经被用了;如果是 0,则表示没被用。同样,我们也弄了一个块保存 block 的位图。位图法大大减少了空间使用。比如redis的bitset也是位图结构,适合用于二值的类型。

至此综合一下:open函数调用链。
open 是一个系统调用,在内核里面会调用 sys_open
先要根据路径找到文件夹。如果发现文件夹下面没有这个文件,同时又设置了 O_CREAT,就说明我们要在这个文件夹下面创建一个文件,那我们就需要一个新的 inode。调用 dir_inode,也就是文件夹的 inode 的 create 函数;这里面的一个重要逻辑是,从文件系统里面读取inode位图,然后找到下一个为0的inode,就是空闲的inode。

文件系统的格式
光有块和索引还不够。还得有文件夹的形式。为什么呢?
想想,如果一个块4K的位图,最多是410248个数据块,然后每个数据块是4K,再乘以410248.一共可以表示的大小只有128MB,现在很多文件都比这个大。
所以,我们先把这个结构称为一个块组。有 N 多的块组,就能够表示 N 大的文件。这样一个个块组,就基本构成了我们整个文件系统的结构。因为块组有多个,块组描述符也同样组成一个列表,我们把这些称为块组描述符表。以及还有一个超级块记录所有的全局信息。
对于整个文件系统,别忘了咱们讲系统启动的时候说的。如果是一个启动盘,我们需要预留一块区域作为引导区,所以第一个块组的前面要留 1K,用于启动引导区。
在这里插入图片描述

注意,**超级块和块组描述符表都是全局信息,而且这些数据很重要。**如果这些数据丢失了,整个文件系统都打不开了,这比一个文件的一个块损坏更严重。所以,这两部分我们都需要备份,但是采取不同的策略。
默认情况下,超级块和块组描述符表都有副本保存在每一个块组里面。
对于超级块来讲,由于超级块不是很大,所以就算我们备份多了也没有太多问题。但是,对于块组描述符表来讲,如果每个块组里面都保存一份完整的块组描述符表,一方面很浪费空间;另一个方面,由于一个块组最大 128M,而块组描述符表里面有多少项,这就限制了有多少个块组,128M * 块组的总数目是整个文件系统的大小,就被限制住了。我们的改进的思路就是引入Meta Block Groups 特性。
首先,块组描述符表不会保存所有块组的描述符了,而是将块组分成多个组,我们称为元块组(Meta Block Group)。每个元块组里面的块组描述符表仅仅包括自己的,一个元块组包含 64 个块组,这样一个元块组中的块组描述符表最多 64 项。我们假设一共有 256 个块组,原来是一个整的块组描述符表,里面有 256 项,要备份就全备份,现在分成 4 个元块组,每个元块组里面的块组描述符表就只有 64 项了,这就小多了,而且四个元块组自己备份自己的。

目录的存储格式
其实目录本身也是个文件,也有 inode。inode 里面也是指向一些块。和普通文件不同的是,普通文件的块里面保存的是文件数据,而目录文件的块里面保存的是目录里面一项一项的文件信息。在目录文件的块中,最简单的保存格式是列表,就是一项一项地将 ext4_dir_entry_2 列在哪里。
每一项都会保存这个目录的下一级的文件的文件名和对应的 inode,通过这个 inode,就能找到真正的文件。第一项是“.”,表示当前目录,第二项是“…”,表示上一级目录,接下来就是一项一项的文件名和 inode
按照列表一个个去找,太慢了,于是我们就添加了索引的模式。当然,首先出现的还是差不多的,第一项是“.”,表示当前目录;第二项是“…”,表示上一级目录,这两个不变。接下来就开始发生改变了。是一个 dx_root_info 的结构,其中最重要的成员变量是 indirect_levels,表示间接索引的层数。如果我们要查找一个目录下面的文件名,可以通过名称取哈希。如果哈希能够匹配上,就说明这个文件的信息在相应的块里面。

软链接和硬链接的存储格式
ln [参数] [源文件或目录][目标文件或目录]

ln -s 创建的是软链接,不带 -s 创建的是硬链接
在这里插入图片描述
如图所示,硬链接与原始文件共用一个 inode 的,但是 inode 是不跨文件系统的,每个文件系统都有自己的 inode 列表,因而硬链接是没有办法跨文件系统的。
目录也不能创建硬链接,因为容易造成目录循环。
在这里插入图片描述
软链接不同,软链接相当于重新创建了一个文件,快捷方式相当于。这个文件也有独立的 inode,只不过打开这个文件看里面内容的时候,内容指向另外的一个文件。这就很灵活了。我们可以跨文件系统,甚至目标文件被删除了,链接文件还是在的,只不过指向的文件找不到了而已。

**

虚拟文件系统

**
由于linux可以支持多达数十种不同的文件系统,它们的实现各不相同,因此linux内核向用户空间提供了虚拟文件系统这个统一的接口,来对文件系统进行操作。它提供了常见的文件系统对象模型,比如inode、directory entry、mount等,以及操作这些对象的方法,比如inode operations、directory operations、file operations等。
在这里插入图片描述

open函数过程,再一次说一下:
要打开一个文件,首先要通过get_unused_fd_flags得到一个没有用的文件描述符。在每一个进程的task_struct中,有一个指针files,类型是files_struct。files_struct 里面最重要的是一个文件描述符列表。对于任何一个进程,默认情况下,文件描述符0表示stdin标准输入,文件描述符1表示stdout标准输出,文件描述符2表示stderr标准错误输出。另外,再打开的文件,都会从这个列表中找一个空闲位置分配给它。do_sys_open中调用do_filp_open,就是创建这个struct file结构,然后fd_install(fd, f);是将文件描述符和这个结构关联起来。

准备开始节点路径查找。如果缓存中没有找到,就需要真的到文件系统里面去找了,对于ext4来说,调用的是ext4_lookup,会到物理文件系统中找inode。

linux为了提高目录项对象的处理效率,设计与实现了目录项高速缓存dentry cache,简称dcache。它主要由两个数据结构组成:
哈希表dentry_hashtable:dcache中的所有dentry对象都通过d_hash执行链到相应的dentry哈希链表中
未使用的dentry对象链表s_dentry_lru:dentry对象通过其d_lru指针链入LRU链表中。LRU 的意思是最近最少使用。只要有它,就说明长时间不使用,就应该释放了。

do_last获取文件对应的inode对象,并且初始化file对象。vfs_open里面最终要做的事情是,调用f_op->open,也就是调用ext4_file_open。另外一件重要的事情是将打开文件的所有信息,填写到struct file这个结构里面。

总结一下:解封装-陷入内核系统调用-分配文件描述符和文件结构绑定-路径查找(目录项缓存)-找到后打开并填充文件信息-返回到用户态。(陷入内核需要把CPU上下文寄存到内核栈的ptg结构里,内核一系列调用都是内核栈进行的,返回时恢复)

总结:虚拟文件系统就是为不同的文件系统提高统一的接口,包括文件操作,inode操作,目录查找操作。

文件缓存

文件系统的读写,其实就是调用系统函数read和write。对于 read 来讲,里面调用 vfs_read->__vfs_read。对于 write 来讲,里面调用 vfs_write->__vfs_write。在 ext4 层调用的是 ext4_file_read_iter 和 ext4_file_write_iter。

缓存其实就是内存中的一块空间,因为内存比硬盘快得多,linux为了改进性能,有时候会选择不直接操作硬盘,而是将读写都在内存中,然后批量读取或者写入硬盘。一旦能够命中内存,读写效率就会大幅度提高
因此,根据是否使用内存作为缓存,我们可以把文件的IO操作分为两种类型。
第一种类型是缓存IO。
大部分文件系统的默认IO操作都是缓存IO。
对于读操作来讲,操作系统会先检查内核中的缓冲区有没有需要的数据。如果已经缓存了,那就直接从缓存中返回;否则从磁盘中读取,然后缓存在操作系统的缓存中
对于写操作来讲,操作系统会先将数据从用户空间复制到内核空间的缓存中。这时对用户程序来说,写操作就已经完成。至于什么时候再写到磁盘中由操作系统决定,除非显示地调用了sync同步命令

第二种类型是直接IO。
如果在读的逻辑generic_file_read_iter 里面,发现设置了IOCB_DIRECT,则会调用address_space的direct_IO的函数,将数据直接读取硬盘。跨过了缓存层,直接到了文件系统的设备驱动层。由于文件系统是块文件,所以这个调用的是blockdev相关的函数。

我们主要学习缓存IO如何读写的。
缓存读:(预读+拷贝)
1generic_file_buffered_read函数。先找出page cache里面是否有缓存页。如果没有找到,不但读取这一页,还要进行预读;这需要在page_cache_sync_readahead函数中实现。预读完了以后,再试一把查找缓存页,应该能找到了;
如果第一次找缓存页就找到了,我们还是要判断,是不是应该继续预读;如果需要,就调用page_cache_async_readahead 发起一个异步预读。
最后,copy_page_to_iter 会将内容从内核缓存页拷贝到用户内存空间

缓存写:麻烦一点
1、 generic_perform_write 这个函数里面,是一个while循环。我们需要找出这次写入影响的所有的页,然后依次写入。 对于每一个循环,主要做四件事情:
第一步,对于ext4来讲,调用的是ext4_write_begin。(主要做一些日志相关的工作,是为了防止突然断电的时候数据丢失,order模式。这种模式不记录数据的日志,只记录元数据的日志,但是在写元数据的日志前,必须先确保数据已经落盘。这个折中,是默认模式)
第二步,调用iov_iter_copy_from_user_atomic。先将分配好的页面调用kmap_atomic 映射到内核里面的一个虚拟地址,然后将用户态的数据拷贝到内核态的页面的虚拟地址中,调用kunmap_atomic 把内核里面的元素删除。(前面说过内存映射到文件,也需要kmap_atomic 建立内存到文件的关联)
第三步 调用 ext4_write_end 完成写入。可以看出,其实所谓的完成写入,并没有真正写入硬盘,仅仅是写入缓存后,标记为脏页。
但是这里会有一个问题,数据很危险,一旦宕机就没有了,所以需要一种机制,将写入的页面真正写到硬盘中,我们称为回写(Write Back)。
第四步,调用 balance_dirty_pages_ratelimited,是回写脏页的一个很好的时机。如果脏页超过一定数目就回写。
另外还有几种场景也会触发回写:用户主动调用 sync;当内存十分紧张,以至于无法分配页面的时候,会调用free_more_memory,最终会调用wakeup_flusher_threads,释放脏页;脏页已经更新了较长时间,时间上超过了timer,需要及时回写。

总结:写缓存几个步骤:第一先做一些日志相关的工作;第二 kmap_atomic 映射到内核里面的一个虚拟地址。把用户数据写到内核的页面;第三完成写入标记脏页,第四回写落盘。

(注意文件缓存是在内核空间的。读缓存需要把数据从内核拷贝到用户空间,写缓存是把用户数据写到内核的page cache中)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_302314.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux环境基础开发工具

目录 Linux软件包管理器yum Linux开发工具 文本编辑器vi、vim vim的基本概念 vim操作 Linux编译器-gcc\g使用 函数库分为动态库和静态库 Linux调试器gdb使用 在gdb模式下的命令 Linux软件包管理器yum yum怎么说呢?就相当我们手机里的应用商店。我们需要安…

本地服务器与云服务器哪个好?

本地服务器和云服务器是企业可以使用的两种不同的服务器设置。主要区别在于本地服务器托管,第三方提供商托管云服务器。那么,本地服务器和云服务器哪个更好呢? 接下来,将带大家讨论本地服务器和云服务器的优缺点,并帮…

系统设计基础-大型网站通用架构模式

文章目录 一.何谓模式二.通用架构模式1.分层2.分割3.分布式4.集群5.缓存6.异步处理7.冗余备份8.自动化9.安全 本文主要参考自《大型网站技术架构:核心原理与案例分析》一书第二章节和其他网络文章,如有遗漏或错误,还望海涵并指出。谢谢&#…

​ NISP一级备考知识总结之信息安全概述、信息安全基础

参加每年的大学生网络安全精英赛通过初赛就可以嫖一张 nisp(国家信息安全水平考试) 一级证书,nisp 一级本身没啥考的价值,能白嫖自然很香 1.信息安全概述 信息与信息技术 信息概述 信息奠基人香农认为:信息是用来消…

在Ubuntu 22.04 LTS Jammy Linux 系统上安装MySQL

在Ubuntu 22.04 LTS Jammy Linux 系统上安装MySQL 1. Update Apt Package Index2. Install MySQL Server & client on Ubuntu 22.043. To Check the version4. Run the Security script to secure MySQL5. Login Database Server as the root user6. Manage MySQL service7…

mysql数据库的表约束

表的约束 5.1:表的约束的概念 定义: 数据库表约束是用于定义和实施数据完整性的规则或条件。它们被应用于数据库表中的列,以确保数据的一致性、有效性和准确性。表约束可以强制执行特定的规则,限制数据的插入、更新或删除操作&…

ESP32-C2系列开发板简介

C2是一个芯片采用4毫米x 4毫米封装,与272 kB内存。它运行框架,例如ESP-Jumpstart和ESP造雨者,同时它也运行ESP-IDF。ESP-IDF是Espressif面向嵌入式物联网设备的开源实时操作系统,受到了全球用户的信赖。它由支持Espressif以及所有…

python3 爬虫相关学习3:response= requests.get(url)的各种属性

目录 1 requests.get(url) 的各种属性,也就是response的各种属性 2 下面进行测试 2.1 response.text 1.2 response.content.decode() 1.2.1 response.content.decode() 或者 response.content.decode("utf-8") 1.2.2 response.content.decode(…

C++类和对象再探

文章目录 const成员再谈构造函数成员变量的定义函数体内赋值初始化列表 隐式类型转换explicitstatic成员 const成员 我们知道在调用类的成员函数时,会有一个默认的this指针且这个this指针时不可以被修改的,例如在日期类中,会有隐式的Date * const this;注意这里默认会在this前…

Flutter仿写微信导航栏快速实现页面导航

文章目录 前言使用TabBar实现TabBar介绍TabBar的重要属性说明TabBarView介绍TabBarView的重要属性TabBar总结TabBar实现底部导航的例子 BottomNavigationBar实现BottomNavigationBar介绍BottomNavigationBar实现底部导航栏的例子 总结BottomNavigationBarTabBar根据实际情况选择…

【Vue基础】Element案例学习-智能学习辅助系统

一、效果展示 初步设计一个系统&#xff0c;有目录、搜索栏、表格操作等。 二、参考代码 主要关注上图“App.vue”和“BtestView.vue”两个文件的代码 1、App.vue <template><div ><!-- <h1>{{ message }}</h1> --><!-- <element-view&…

暴涨700w播放,星穹铁道恰饭频频登上B站爆款热榜!

B站作为现在年轻一代聚集的多元化社区&#xff0c;游戏内容则是社区内受众较为广泛的存在&#xff0c;而星铁作为面向年轻群体的回合制游戏&#xff0c;自然是赢得B站核心用户群体的青睐。 4月26日&#xff0c;暌违已久的手游《崩坏&#xff1a;星穹铁道》&#xff08;后文简称…

JavaEE(系列6) -- 多线程(解决线程不安全系列1-- 加锁(synchronized)与volatile)

首先我们回顾一下上一章节引起线程不安全的原因 本质原因:线程在系统中的调度是无序的/随机的(抢占式执行) 1.抢占式执行 2.多个线程修改同一个变量. 一个线程修改一个变量>安全 多个线程读取同一个变量>安全 多个线程修改不同的变量>安全 3.修改操作,不是原子的.(最…

Python带你实现批量自动点赞小程序

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 所用知识点: 动态数据抓包 requests发送请求 json数据解析 开发环境: python 3.8 运行代码 pycharm 2022.3 辅助敲代码 requests 请求模块 &#xff0c;第三方&#xff0c;需安装 win R 输入cmd 输入安装命令 pip inst…

初步认识性能测试和完成一次完整的性能测试

上一篇博文主要通过两个例子让测试新手了解一下测试思想&#xff0c;和在做测试之前应该了解人几点&#xff0c;那么我们在如何完成一次完整的性能测试呢&#xff1f; 测试报告是一次完整性能测试的体现&#xff0c;所以&#xff0c;这里我给出一个完整的性能测试报告&#xff…

springBoot中使用redis实现分布式锁实例demo

首先 RedisLockUtils工具类 package com.example.demo.utils;import org.junit.platform.commons.util.StringUtils; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.red…

SAP入门到放弃系列之需求管理的基本要素

需求管理目标&#xff1a; 一般而言&#xff0c;生产计划&#xff08;PP&#xff09;的总体目标&#xff0c;特别是需求管理的总体目标是通过减少以下内容来更好地为客户服务&#xff1a; 补货提前期存货成本 需求管理的要素&#xff1a; 需求管理工作的主要要素广义上可分…

❤ cannot read properties of null(reading appendChild)解决办法

❤ 操作元素报&#xff1a;cannot read properties of null(reading appendChild)解决办法 1、场景&#xff1a; 写的一个js渲染&#xff0c;但是出了个小问题&#xff0c;cannot read properties of null(reading appendChild)报错。 <div id"divps" class&qu…

机器学习项目实战-能源利用率 Part-1(数据清洗)

1. 项目背景 2009年的《当地法案84号》&#xff0c;或纽约市基准法案&#xff0c;要求对能源和用水量进行年度基准测试和披露信息。被覆盖的财产包括单个建筑物的税收地块&#xff0c;其总建筑面积大于50,000平方英尺&#xff08;平方英尺&#xff09;&#xff0c;以及具有超过…

OpenAI新作Shap-e算法使用教程

一、知识点 Shap-e是基于nerf的开源生成3d模型方案。它是由如今热火朝天的Open AI公司&#xff08;chatgpt&#xff0c;Dell-E2&#xff09;开发、开源的。Shap-e生成的速度非常快&#xff0c;输入关键词即可生成简单模型&#xff08;限于简单单体模型&#xff09;。 二、环境…