文件系统考古2:1984 - BSD Fast Filing System

这篇具有很好参考价值的文章主要介绍了文件系统考古2:1984 - BSD Fast Filing System。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

今天继续与大家分享系列文章《50 years in filesystems》,由 KRISTIAN KÖHNTOPP 撰写。

我们将进入文件系统的第二个十年,即1984年,计算机由微型计算机发展到了桌面和机柜工作站, BSD Fast Filing System 登场。

回看第一篇: 1974-Unix V7 File System

早期的 Unix 文件系统已经表现得很好,但也存在一些明显的问题。这些问题在操作系统 BSD(Berkeley Software Distribution)中进行了许多修复。 BSD 起源于 20 世纪 70 年代末和 80 年代初,由加州大学伯克利分校的计算机科学系开发和推广。在 Leffler、McKusick 等人撰写的的书中《The Design and Implementation of the 4.3BSD UNIX Operating System》有所记录。

1984 年发表的一篇经典论文《A Fast File System for UNIX》中,可以找到更简明、也更学术的讨论。该论文的作者包括 Marshall McKusick、Bill Joy(当时在Sun公司)、Samuel Leffler(当时在LucasFilm 公司)和 Robert Fabry。该论文提出了一个对 Unix 文件系统的重新实现方案,旨在提升文件系统的吞吐能力、优化存储空间的分配和增强数据访问的局部性

The hardware

在1984 年,4.3BSD 所针对的计算机是桌面和机柜工作站。这些机器具有 32 位数据寄存器和 32 位地址寄存器。

外部数据和地址总线的大小各不相同:早期的 68k 系列 CPU 总线尺寸较小。 但在 1984 年,Motorola 68020 诞生了。它是首款提供完整 32 位宽度总线的 68k 系列,集成了大约 200,000 个晶体管在芯片上。后来,68030 将原本独立的 MMU(内存管理单元)集成到了芯片上,而 68040 则将原本独立的 FPU(浮点运算单元)也集成到了芯片上。

早期的 Sun 工作站,如 Sun-3系列,采用了这些 CPU。但 Sun 公司从伯克利实验性的 RISC 系统中借鉴了设计思路,并于1986年发布了基于 SPARC 架构的 Sun-4 系列工作站。SPARC 架构采取了一些妥协的策略,但运行地很好,在 Sun 公司被 Oracle 收购之前持续得到改进与发展。然而,在之后的发展中 Oracle 先后终止了 SPARC和 Itanium CPU 架构的发展。

Curt Schimmel 在《UNIX Systems for Modern Architectures》一书中讨论了 SPARC 在 MMU、寄存器和内存访问设计上所做的权衡,以及为什么这样做是合理的。与此同时,在1985年,MIPS 架构首次亮相,这是另一系列的 RISC CPU 架构。它也是一个完全的 32位系统,被用于 SGI 工作站。

惠普公司也有另一种 RISC 类型的 CPU,即 PA-RISC,它是“Spectrum”研究计划的产物,在1986 年上市(后来被 Intel 的一款失败产品 Itanium 取代)。

计算机系统领域的先锋公司 DEC自己有 VAX,这是一种具有 CISC CPU 的 32 位机柜式计算机,从 1977 年开始就已经存在。直到 1992 年,他们才转向 RISC 架构,而后采用 Alpha AXP(“DEC Alpha”)架构,完全实现了 64 位。尽管这个架构很有趣,但它的存在时间不长:1998年被康柏公司收购后,该 CPU 停产,其知识产权于 2001 年出售给了英特尔。

总的来说,1984 年的工作站类型系统的主内存容量在几十 MB 左右,运行时的系统时钟频率在几十 MHz 左右

传统文件系统的短板

在 20 世纪 80 年代,32 位 VAX 系统被用于典型的工作站任务,包括图像处理和 VLSI 芯片设计等工作。当时使用的 Unix 文件系统在处理文件大小、 I/O 速度和文件数量方面出现了结构性问题。此外,只有 512 字节的 I/O 大小大大降低了磁盘子系统的性能。

论文中提到,文件系统的元数据和数据严格分离,即元数据位于文件系统的前部,而实际数据则位于文件系统的后部。这种分离设计有助于提高文件系统的性能和扩展性。

一个150MB 的传统 Unix 文件系统由4MB 的inode(索引节点)和146MB 的数据组成。这种组织方式将 inode 信息与数据分隔开来,因此访问文件通常需要从文件的 inode 到其数据之间进行一次长距离寻道。在一个目录中,文件通常不会被分配到 4MB 的 inode 连续槽位中,这就导致在对目录中多个文件的 inode 执行操作时,需要访问许多非连续的 inode 块。

正是因为这个元数据和数据分离的设计带来的问题,BSD FFS (BSD Fast Filing System) 的一个主要目标是改善文件系统的布局,将元数据和数据更加靠近,将单个目录中的文件存储得更加紧凑,避免文件被分散成小碎片,从而提高加载效率

碎片化:首先,创建四个文件,每个文件使用两个块。然后删除了文件 B 和 D。接着,空闲的空间被一个占用三个块大小的文件E回收,但是文件 E 被存储在不连续的块中。这导致了小的磁盘寻道和较慢的 I/O 操作。

另一个明确的目标是增加磁盘块的大小。较大的磁盘块在两个方面有助于提高吞吐量

  1. 较大的磁盘块提供了更大的 I/O 单元,因此可以在单个 I/O 操作中传输更多的数据;
  2. 较大的磁盘块还允许文件系统在一个间接块中存储更多的文件指针,大大减少了对间接块的访问次数。

该论文引用了一个 Unix 文件系统经过优化后的吞吐量,大约是理论最大值的4%,这是非常低效的。这主要归因于文件的碎片化,即文件中相邻块的非连续存储。对于碎片整理,虽然在 1976 年已经提出,但被认为不可行而被放弃。作者们希望通过在文件的初始存储位置上合理地放置文件来解决这个问题。

BSD FFS 的创新之处

理解柱面组和扇区

BSD FFS 的设计基于对硬盘的物理布局的理解,包括柱面、磁头和扇区(CHS)。它将硬盘分成柱面组,相邻的磁道属于同一个柱面组。

tu

当硬盘旋转时,不同的磁头进入盘片堆中,就像一个梳子。每个磁头在磁盘上标记一个磁道,控制器硬件将该磁道细分为物理磁盘块。所有磁头标记的磁道组成一个柱面。柱面组是一组连续的柱面。(图像来源:OSTEP,第3页)

每个柱面组都是一个传统 Unix 文件系统的迷你版本,包括超级块的副本、自己的本地索引节点区域以及本地索引节点和块使用位图。位图的使用也是新颖的,它们取代了传统文件系统中使用的空闲列表。由于文件系统知道 CHS 布局的信息,它能够确保每个副本的超级块不总是放置在同一盘片上,以提高文件系统对硬盘故障的容错性。

在 RAID(冗余磁盘阵列)论文发表之前几年,根据 Katz 的说法,RAID也是在伯克利开发的,时间为1983/1984年。

Katz 还提到,在那个时候,Stonebraker 一直在开发 Ingres(Postgres的前身),并提到他对低提交延迟的要求推动了改善 FFS 和后来 RAID 磁盘带宽的尝试。然而,对于RAID 分类的正统的研究直到1987年才开始。

许多初创公司和存储公司都将 RAID 论文作为他们开发的基础,其中包括 NetApp 和 EMC(通过Data General的Clariion 磁盘阵列)。

BSD FFS 不仅了解磁盘的 CHS 几何结构,还了解处理器速度和磁盘的旋转速度。这使得它能够配置并在超级块中记录交错因子,以优化磁盘 I/O 吞吐量。

硬盘持续不断地旋转,但是 CPU 需要时间来设置下一次传输。在此期间,磁头可能已经超过了下一个块的起始边界,现在系统需要等待一次完整的旋转才能进行写入。使用适当的交错因子,相邻的块号不会被连续地存储在磁盘上,而是在它们之间交错插入其他块。这给了 CPU 足够的时间来思考和设置下一个块的传输。

CPU速度越快,所需的交错因子就越低。

随着硬盘开始配备集成控制器,并开始隐藏 CHS几何结构,并最终被线性块地址(LBA)取代,所有这些优化相对变得无关紧要。然而,在过去的十到十五年间,这些优化为系统提供了显著的性能优势。

大分块、小片段和尾部合并

在内部,FFS 使用至少4 KB大小的逻辑块。这些逻辑块,通过最多不超过两级间接块可以创建出最大 4GB 的文件。

较大的块可以提高 I/O 速度,但它们也会带来存储开销,因为文件的大小会按块递增。由于 FFS中的逻辑块由多个物理块组成,因此 FFS引入了片段(fragment)的概念,以公开较小的内部物理块。片段表示逻辑块内部的更小存储单位。通过引入片段的概念,FFS 可以更细粒度地管理和利用存储空间。尾部打包(tail packing)是一种技术,可以将多个文件的末尾存储在同一个逻辑块中。在传统的文件系统中,当文件的末尾部分不足以填满一个完整的物理块时,会导致空间浪费。因此,尾部打包的方法可以减少空间浪费。同时,通过利用片段的概念,尾部打包可以尽可能提升存储空间利用率。

为了防止进入片段逐渐增长和不断需要重新布局的阶段,此处系统采用的设计是:系统预先分配空间以填满逻辑块,并且尾部打包仅在文件关闭(即取消预分配)时才会发生。

长距离寻址分配策略

BSD FFS 引入了一系列布局策略,用于控制新目录、新文件的放置以及大文件的处理。全局策略主要关注选择适合的柱面组来存放数据,而本地策略则负责柱面组内的具体放置。
新的文件系统布局采用柱面组。每个柱面组都有自己的 inode 表,以及用于 inode 和块的空闲空间位图。文件系统旨在防止碎片化。

在某些情况下,是无法实现的:例如,如果一个柱面组的大小为 512 MB,并且要写入一个大于512 MB的文件,它将使用该柱面组中的一个 inode,但所有可用的空闲块已经用完。如果要将第二个文件放置到该柱面组中,inode可以被使用,但是该文件的数据块需要放置在其他地方,这是不理想的。

对于大文件,最好强制进行长距离寻道,从一个柱面组切换到下一个柱面组。文件系统可以从每一兆字节文件大小开始强制执行这样的长距离寻道。这将均匀地使用相邻柱面组之间的空闲块,同时在每个柱面组中保留一定数量的空闲块供其他文件使用。

这会有意地使文件产生碎片,但同时也确保碎片足够大以支持大文件的 I/O。碎片化(文件中块的非相邻放置)只有在碎片太小以至于无法高效读取时才会真正成为性能问题。

目录分配策略

相同目录中的文件通常会一起使用。将同一目录中的所有文件放置在同一个柱面组中是很有效的做法。

当然,这样做时还需要将不同的目录放置在不同的柱面组中,以确保文件系统空间的均匀使用。这意味着一个像这样的 Shell 脚本:

这个脚本将创建名为 fileXX 的十个文件,并将它们全部放置在与当前目录相同的柱面组中。

它还会在当前目录下创建十个名为 dirXX 的子目录。条件允许的话,每个子目录都会被放置在不同的柱面组中。FFS 会选择那些空闲 inode 数量高于平均水平且已有目录数量最少的柱面组。在柱面组中选择 inode 的方式是“下一个可用的”,因为整个柱面组的 inode 表只占用 8-16 个块。

为了放置数据块,考虑到这台机器所需的交错因子,FFS 投入了很多精力来寻找旋转最优的块。

BSD FFS 要求文件系统始终保持一定的可用空间。如果文件系统填满超过90%,许多算法将退化为传统文件系统的性能水平。

BSD FFS 其他改进

更大 Inode 和分块地址

例如,inode 号现在是 32 位数字。这个改变使得文件系统中可能的文件数量从 64K 增加到 42亿。

Inode 的大小已经翻倍:它现在被强制为 128 字节的大小(其中有 20 个未使用的字节)。此外,磁盘块地址现在是 4 个字节。在 4KB 块大小的情况下,这足以支持 42 亿个块,或者最大 16TB 的文件系统大小。

文件长度被记录在一个 quad 中,这样可以支持超过 4GB 的单个文件大小。
Inode 现在包含 12 个直接块和三种类型的间接块。在 4KB 块大小的情况下,每个间接块可以容纳 1024 个块地址,因此每个文件可以容纳 12 + 1024 + 1024^2 + 1024^3 = 1074791436 个块,或者最大文件大小略大于 4TB。

Unix 用户 ID 和组 ID 长度仍然限制为一个 short 类型,每个系统的用户和组数量限制为 64 K。

即使 inode 中的时间类型仍然限制为 4 字节,但已经为 8 字节的时间戳预先分配了空间。

长文件名

传统文件系统中,目录项具有固定的 16 字节长度,其中 2 字节用于存储 inode 号,14字节用于存储文件名。

BSD FFS 定义了更复杂的目录项结构。一个目录项包含一个 4 字节的 inode 号,一个 2 字节的记录长度和一个 2 字节的名称长度,然后是实际的文件名。路径中的每个文件或者目录名限制为 255 字节,目录项的长度向上取整到下一个 4 字节边界。

目录仍然基本上是一个链表,因此在大型目录中搜索名称是很慢的。而在目录中搜索可用空间则更加复杂:为了创建一个新的目录条目,我们需要从开头开始遍历目录,试图找到当前结构中足够大以容纳待创建名称的空隙。如果找不到空隙,则将新名称追加到末尾,从而增加目录的大小。

目录中的空闲空间不会通过压缩来回收,只有在新的文件名称恰好适合时才会最终重新使用,也就是说当系统需要在目录中创建新的目录项或文件时,它会首先尝试找到一个已有的空间,其大小足够容纳待创建的名称。如果找到这样的空间,系统将把新的名称插入到该空间中,利用已有的空闲空间,而无需增加目录的大小。然而,如果没有足够大的空间可用,系统将追加新的名称到目录的末尾,从而增加目录的大小。

符号链接

传统的文件系统允许一个文件拥有多个名称,使用link()系统调用和硬链接机制。硬链接有数量限制(一个 short 类型,最多 64K 个名称)。

硬链接(hardlink)可能会意外丢失,例如,通过使用某些编辑器保存一个有硬链接的文件时。如果编辑器将文件保存为 filename.new,然后取消链接旧的 filename 并将新文件移动到相应位置,那么文件的硬链接属性将会被修改。

硬链接(hardlink)是指在文件系统中创建的指向同一文件或目录的多个文件名。它们与原始文件(或目录)共享相同的 inode(索引节点),因此它们实际上是相同的文件,只是具有不同的文件名。硬链接允许多个文件名引用同一份数据,节省存储空间,并且对文件的更改会在所有硬链接之间保持同步。

硬链接还会多次引用原始文件的 inode,而 inode 是特定于文件系统的, 因此它们不能跨越文件系统。

BSD 引入了一种新的文件类型(l,符号链接),并在链接文件中放置一个“替换文件名”,用于确定链接目标位置。它可以是绝对路径或相对路径(相对于符号链接文件的位置)。

当尝试访问符号链接时,系统将在 namei() 函数中重新解析文件名,使用链接中的文件名,从而将 open() 系统调用重定向到链接指向的位置。简单来说,符号链接提供了一个文件名的替代方式,当访问符号链接时,实际上是在访问链接的目标文件。

由于重定向发生在 namei() 中,它可以跨文件系统,因此新的链接类型不受单个文件系统的限制。它也不计入任何链接计数限制。

重命名系统调用

BSD 引入了 rename() 系统调用。过去,则需要通过调用 unlink() 和 link() 实现。由于这涉及多个系统调用,该操作不是原子操作:它可能会部分执行,并且容易受到恶意干扰。

配额

BSD 引入了文件系统使用配额的概念:这是对用户或组可以使用的文件数量和磁盘空间量设置的软限制 (soft limit)和硬限制(hard limit)。

为了有效地实现它们,需要做如下修改:

  • 现在,如果一个用户想要把自己文件的所有者改为其他用户,那么他必须拥有特权操作的权限。否则,他就只能创建一个只有自己能访问的目录,然后把目录里的所有文件都发送给目标用户。这样一来,这些文件就会占用另一个用户的配额,而不是自己的配额。
  • 类似地,不再允许将文件的组员身份更改为任意组。只允许设置为该用户所属的某个组。
  • 最后,新创建的目录和文件继承自它们的父目录,而不是用户的主要组。这样,项目目录中的文件将计入项目的配额,而不是用户主要组配额。

咨询式文件锁

4.2BSD 中已经引入了咨询式文件锁。为了实现这种机制,它引入了新的 flock() 系统调用。

  • 文件锁可以是共享的(读锁)或独占的(写锁);
  • 它们总是作用于整个文件,而不是字节范围;
  • 不尝试检测死锁;
  • 它们与文件描述符绑定。因此,当进程死亡时,其文件句柄会自动关闭,从而自动释放所有持有的锁。这非常健壮,直到 dup() 和 fork() 开始发挥作用。

后来,POSIX 试图改进这一点,引入了第二种完全不同的锁系统 fcntl()。它存在一些缺陷,但可以对字节范围进行操作,并实现了一些基本的死锁检测。

在这类实现了这两种文件锁机制的内核中如Linux系统,这两种锁机制互不兼容,也不知道对方的存在。

在 《Advisory File Locking – My take on POSIX and BSD locks》这篇文章中进一步讨论了所有这些内容,并提供了示例程序。

总体表现

在论文中,作者指出了以下优点:

  • Ls 和ls -l 命令的速度很快,因为单个目录中文件的 inode 位于同一个柱面组内。因此,读取和列出目录时,寻道次数非常少,寻道距离也很短(除了子目录,它们通常要保证彼此的距离很远)。测试发现,当检索一个没有包含子目录的目录时,速度提高了8倍;

  • 在传统文件系统中,理论最大带宽的利用率仅为3%,而在使用不同的控制器硬件的情况下,这一利用率增加到了22%甚至47%。作者对这些结果感到非常自豪,因为这些结果是在实际的生产系统上产生的。尽管文件的数量和规模可能会改变,但文件系统在其生命周期内能够持续相对稳定的吞吐量。

这些改进解决了主要的需求,即提高吞吐量和稳定的布局,使性能不会随时间降低。

此外,还进行了许多提升用户体验的改进,使得 BSD 在团队使用的过程中表现地更好;以及开启了一些新功能。

虽然 Linux 中并没有 BSD 代码,但 Ext2 文件系统基本上是对 BSD FFS 的重新实现。

无论是 BSD FFS 还是 Linux ext2,它们仍然是非日志文件系统,在发生崩溃后需要进行文件系统检查。它们在处理具有许多条目的目录方面也表现不佳,在处理深层次目录结构时稍好一些。为了跟上不断增长的存储容量,BSD FFS 和 Linux ext2 这两个文件系统需要进行额外的改进和优化,以便能够更好地支持处理大容量存储介质和大型文件系统。

此外,仍然存在其他一些不太明显的限制:文件系统代码中的几个位置受到锁的保护,使得在具有高并发性的系统上扩展某些操作变得困难。

直到1994年,SGI 的 XFS 才开始解决这些问题,经过了另外十年的时间。

未完待续。

如有帮助的话欢迎关注我们项目 Juicedata/JuiceFS 哟! (0ᴗ0✿)文章来源地址https://www.toymoban.com/news/detail-475465.html

到了这里,关于文件系统考古2:1984 - BSD Fast Filing System的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Node.js中的文件系统(file system)模块

    聚沙成塔·每天进步一点点 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发者,这里都将为你提供一个系统而

    2024年02月04日
    浏览(47)
  • 【视觉SLAM】ORB-SLAM2S: A Fast ORB-SLAM2 System with Sparse Optical Flow Tracking

    Citations: Y. Diao, R. Cen, F. Xue.ORB-SLAM2S: A Fast ORB-SLAM2 System with Sparse Optical Flow Tracking[C].2021 13th International Conference on Advanced Computational Intelligence (ICACI). Wanzhou,China.2021:160-165. Keywords: Visualization,Simultaneous localization and mapping,Cameras,Real-time systems,Aircraft navigation,Central Processing Unit,Traje

    2023年04月08日
    浏览(68)
  • VMware虚拟机装系统时识别不出系统镜像文件(system not found / vmtool)

    1:先排除IOS镜像是否有问题 我一开始是在阿里云镜像下载的,出现找不到镜像,因为新版VMware已经不能使用从其他地方下载的镜像文件,之后在官网下的问题解决了(官网: 获取Ubuntu服务器版 | Ubuntu )。 2:先创建一个空白磁盘 1:创建虚拟机的时候先创建一个空白磁盘,

    2024年02月12日
    浏览(47)
  • 安卓ROM定制 修改必备常识-----初步了解system系统分区文件夹的基本含义 【二】

    安卓修改rom 固件 修改GSI 移植rom 必备常识 lib--**so文件基本解析 一起来了解system目录相应文件的用途吧。(rom版本不同里面的app也会不一样) 给大家说下最简单的方法提取img里面的文件,对于后缀img格式的文件可以使用7zip.选择***.img使用7zip打开压缩包方式,可以用于简单提

    2024年02月07日
    浏览(36)
  • 从安卓系统USB升级包里提取system.img、boot.img和recovery.img在内的镜像文件

    如果你拿到一个USB升级包,你会发现升级包的结构基本相似。 但是里面并不是直接就有包括system.img、boot.img和recovery.img在内的镜像文件。 如果我们需要在Android手机上获取Magisk。提取内核(boot.img)就至关重要。当然其他镜像根据你的需要也有其他用处。 这时,如果你需要这

    2024年02月02日
    浏览(47)
  • 题目:1984.学生分数的最小差值

    ​ 题目来源:         leetcode题目,网址:1984. 学生分数的最小差值 - 力扣(LeetCode) 解题思路:        将数组排序后,计算当 i=0,1,2,3.... 时 nums[i+k-1] 与 nums[i] 之差并返回其中的最小值即可。   解题代码: 总结:         官方题解也是一样的思路,滑动窗口。

    2024年02月15日
    浏览(65)
  • 服务器挂载/dev/sdt1 is apparently in use by the system; will not make a 文件系统 here! 问题解决

    磁盘分区后设置文件系统失败  先查看占用 然后清除 类似于:这样的操作 如果有效的话到这里就可以啦 如果不可以,接下来提供一种思路。 我遇到这个问题找的大佬解决的,我根据操作旁观的操作步骤记录的,不一定全对,有问题再查一下资料 首先查看磁盘信息 然后生成

    2024年02月11日
    浏览(43)
  • 【华为OD机考 统一考试机试C卷】考古学家考古问题(C++ Java JavaScript Python)

    目前在考C卷,经过两个月的收集整理, C卷真题已基本整理完毕 抽到原题的概率为2/3到3/3, 也就是最少抽到两道原题。 请注意:大家刷完C卷真题,最好要把B卷的真题刷一下,因为C卷的部分真题来自B卷。 另外订阅专栏还可以联系笔者开通在线OJ进行刷题,提高刷题效率。

    2024年02月03日
    浏览(48)
  • 【华为OD机考 统一考试机试C卷】考古学家考古问题(C++ Java JavaScript Python C语言)

    目前在考C卷,经过两个月的收集整理, C卷真题已基本整理完毕 抽到原题的概率为2/3到3/3, 也就是最少抽到两道原题。 请注意:大家刷完C卷真题,最好要把B卷的真题刷一下,因为C卷的部分真题来自B卷。 另外订阅专栏还可以联系笔者开通在线OJ进行刷题,提高刷题效率。

    2024年02月01日
    浏览(39)
  • Java生态系统的进化:从JDK 1.0到今天

    目录 前言  JDK 1.0:开启Java时代 JDK 1.1:Swing和内部类  JDK 1.2:Collections框架和JIT编译器 JDK 1.5:引入泛型和枚举 JDK 1.8:Lambda表达式和流  JDK 11以后:模块化和新特性 未来展望  总结 作者简介:  懒大王敲代码,计算机专业应届生 今天给大家聊聊前Java生态系统的进化:从

    2024年02月04日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包