【树莓派不吃灰】命令篇⑨ 记录学习文件系统

在这里插入图片描述

  • ❤️ 博客主页 单片机菜鸟哥,一个野生非专业硬件IOT爱好者 ❤️
  • ❤️ 本篇创建记录 2022-11-24 ❤️
  • ❤️ 本篇更新记录 2022-11-24 ❤️
  • 🎉 欢迎关注 🔎点赞 👍收藏 ⭐️留言 📝
  • 🙏 此博客均由博主单独编写,不存在任何商业团队运营,如发现错误,请留言轰炸哦!及时修正!感谢支持!
  • 🔥 Arduino ESP8266教程累计帮助过超过1W+同学入门学习硬件网络编程,入选过选修课程,刊登过无线电杂志🔥

参考资料:

1. 外部存储设备

一般来说,我们的存储分为内存条和磁盘存储。

内存的优点在于读写极快,但是断电不保存。

磁盘的优点在于断电保存,存储容量大,缺点相比内存读写慢。

传统机械磁盘由多个盘片和磁头组成,每个盘片上有多个可以存储数据的磁道。如果读写的区域不连续,磁盘需要改变磁头位置来切换磁道,因此磁道切换会让读写效率降低。

而在树莓派中,SD卡的作用就对应着磁盘。

因为SD卡没有类似于传统机械磁盘的结构,随机读写和连续读写的速度差距不大。

Linux通过文件系统来管理外部存储器。而在Linux下常见的文件系统有ext2fsext3fsext4fs

备注:Linux支持很多文件系统,包括网络文件系统(NFS)、Windows的Fat文件系统。
运行命令:cat /proc/filesystems(查看Linux支持的文件系统)
在这里插入图片描述

而window系统采用FAT文件系统。

每种文件系统都有自己的一套数据管理策略。包括以下功能:

  • 通过文件名字路径层级来组织文件
  • 提供操作文件的接口,比如查找、新增、删除、读取和写入文件
  • 提供权限功能,比如文件保护和文件共享

同一个外部存储器可以划分为一个或者多个分区,每个分区使用一种文件系统来管理。以树莓派为例子,在SD卡上烧录Rasp系统之后,SD卡的存储空间会被划分为两个分区。一个分区叫做Boot启动分区,采用FAT32的文件系统,启动分区主要是用于开机启动(【树莓派不吃灰】命令篇⑥ 了解树莓派Boot分区,学习Linux启动流程),空间较小。剩下的空间就是主分区,采用了ext4fs的文件系统。

1.1 分区挂载

SD卡上的两个分区采用了两种文件系统。但是在我们Rasp系统中就只会看到了以根目录/为起点的文件系统。也就是说两个物理分区以某种形式合并到了Linux的文件树上。

我们把这个过程叫做挂载(Mounting),也就是让文件树上的某个目录和存储器的物理分区映射起来。这个目录就叫做挂载点

Linux系统的挂载信息保存在文件 /etc/fstab中。

pi@raspberrypi:~ $ sudo cat /etc/fstab
proc            /proc           proc    defaults          0       0
PARTUUID=81ab9192-01  /boot           vfat    defaults          0       2
PARTUUID=81ab9192-02  /               ext4    defaults,noatime  0       1
# a swapfile is not a swap partition, no line here
#   use  dphys-swapfile swap[on|off]  for that
pi@raspberrypi:~ $ 

这里需要配置6个参数(6列),<file system><mount point><type><options><dump><pass>;含义:

  • ① 指定了要挂载的设备,可以是一个 /dev 目录下的设备文件,例如 /dev/sdd1,也可以通过设备标签或者所谓的 UUID 来指定要挂载的设备,如 LABEL=某个设备标签 或 UUID=某个设备UUID或者PARTUUID=某个id。用设备标签或 UUID 更加安全和稳定。
pi@raspberrypi:/ $ ls -l /dev/disk/by-partuuid/
total 0
lrwxrwxrwx 1 root root 15 Nov 13 09:17 81ab9192-01 -> ../../mmcblk0p1
lrwxrwxrwx 1 root root 15 Nov 13 09:17 81ab9192-02 -> ../../mmcblk0p2
pi@raspberrypi:/ $ blkid
/dev/mmcblk0p1: LABEL_FATBOOT="boot" LABEL="boot" UUID="35B5-BC85" BLOCK_SIZE="512" TYPE="vfat" PARTUUID="81ab9192-01"
/dev/mmcblk0p2: LABEL="rootfs" UUID="c68b37be-930e-49c8-87e8-9005e8247103" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="81ab9192-02"
pi@raspberrypi:/ $ 

  • ② 挂载点。指定了挂载设备的目录。对于 swap 是没有挂载点,该处就显示 swap。这个路径必须先存在,提前创建好这个路径。
  • ③ 挂载分区文件系统类型。定义了该设备上的文件系统。相应的有ntfsext4fatvfat等等,这里要根据实际情况设置,同样的也可以通过指令blkid,查看对应的TYPE。
  • ④ 挂载的选项,用于设置挂载的参数。常见参数:
选项描述
sync磁盘和内存数据实时同步。
async磁盘和内存数据异步。
auto /noauto在开机时或键入 mount -a 命令时自动挂载/不自动挂载。
exec/noexec是否允许执行此分区的二进制文件。
defaults使用文件系统的默认挂载参数,例如 ext4 的默认参数为:rw, suid, dev, exec, auto, nouser, async。
ro以只读模式挂载文件系统。
rw以读写模式挂载文件系统。
user允许任意用户挂载此文件系统。
users允许所有 users 组中的用户挂载文件系统。
nouser只能被 root 挂载。
owner允许设备所有者挂载。
dev/nodev是否解析文件系统上的块特殊设备。
suid/nosuid是否允许 suid 操作,通常用于一些特殊任务,使一般用户运行程序时临时提升权限。
usrquota启动文件系统支持磁盘配额模式
grpquota启动文件系统对群组磁盘配额模式的支持
noatime不更新文件系统上 inode 访问记录,可以提升性能。
nodiratime不更新文件系统上的目录 inode 访问记录,可以提升性能。
relatime实时更新 inode access 记录。只有在记录中的访问时间早于当前访问才会被更新。(与 noatime 相似,但不会打断如 mutt 或其它程序探测文件在上次访问后是否被修改的进程),可以提升性能。
  • ⑤ 表示是否被dump备份。1备份,0不备份。
  • ⑥ 表示开机是否自检磁盘。根据数值来决定需要检查的文件系统的检查顺序。 根目录必须设置为最高的优先权 1, 其它所有需要被检查的设备设置为 2, 0 表示不检查。

可以看到,树莓派SD卡的主分区挂载在根目录上,而启动分区挂载在根目录下的/boot上。

以根目录为起点的文件树,包括了以/boot为起点的文件树。
在这里插入图片描述

1.2 查看磁盘信息(包括未挂载磁盘)

sudo fdisk -l

执行命令:

pi@raspberrypi:/ $ sudo fdisk -l
Disk /dev/ram0: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes

中间省略

Disk /dev/ram15: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/mmcblk0: 29.72 GiB, 31914983424 bytes, 62333952 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x81ab9192

Device         Boot  Start      End  Sectors  Size Id Type
/dev/mmcblk0p1        8192   532479   524288  256M  c W95 FAT32 (LBA)
/dev/mmcblk0p2      532480 62333951 61801472 29.5G 83 Linux
pi@raspberrypi:/ $ 

这里可以得到所有磁盘信息。

  • /dev/ramX ,将内存中的一块区域作为物理磁盘来使用

Linux中ramdisk的使用

  • /dev/mmcblk0,我们当前的sdk卡
1.2.1 mmcblk0p0
Disk /dev/mmcblk0: 29.72 GiB, 31914983424 bytes, 62333952 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x81ab9192

Device         Boot  Start      End  Sectors  Size Id Type
/dev/mmcblk0p1        8192   532479   524288  256M  c W95 FAT32 (LBA)
/dev/mmcblk0p2      532480 62333951 61801472 29.5G 83 Linux

这里是我们当前sd卡的信息。包括了一个块设备,两个分区。

  • mmc含义

SD/MMC 卡的设备构造差不多,MMC 应该是 SD 的前身,不过 MMC 当时的设计比 SD 小一半。所以,SD/MMC 的驱动通用,进一步的,Linux 的设备节点就延续了 MMC 的这个名字。

  • blk含义

blk 是块设备,后面的数字是设备的顺序编号

  • p含义

p表示分区,p0 就是第一个分区

1.2.2 sda1(额外了解)
  • sdxx

sd是scsi,SATA硬盘设备。

  • sda1

a代表是第一块硬盘 1代表是第一个主分区

  • sdb1

b代表是第二块硬盘 1代表是第一个主分区

1.3 查看UUID

挂载分区最保险的方法就是通过分区设备的 UUID 来挂载文件系统。每个分区都有一个唯一的 UUID。

使用 blkid 命令可以获取设备的 UUID:

blkid

pi@raspberrypi:/ $ blkid
/dev/mmcblk0p1: LABEL_FATBOOT="boot" LABEL="boot" UUID="35B5-BC85" BLOCK_SIZE="512" TYPE="vfat" PARTUUID="81ab9192-01"
/dev/mmcblk0p2: LABEL="rootfs" UUID="c68b37be-930e-49c8-87e8-9005e8247103" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="81ab9192-02"
pi@raspberrypi:/ $ blkid /dev/mmcblk0p1
/dev/mmcblk0p1: LABEL_FATBOOT="boot" LABEL="boot" UUID="35B5-BC85" BLOCK_SIZE="512" TYPE="vfat" PARTUUID="81ab9192-01"

1.4 查看文件系统挂载情况

sudo df

pi@raspberrypi:/ $ sudo df
Filesystem     1K-blocks     Used Available Use% Mounted on
/dev/root       30322460 11896804  17069144  42% /
devtmpfs         1800664        0   1800664   0% /dev
tmpfs            1965528        0   1965528   0% /dev/shm
tmpfs             786212     1360    784852   1% /run
tmpfs               5120        4      5116   1% /run/lock
/dev/mmcblk0p1    261108    50968    210140  20% /boot
tmpfs             393104       24    393080   1% /run/user/1000
overlay         30322460 11896804  17069144  42% /var/lib/docker/overlay2/6261e0bb63bf45226399a37b3f6ec924eaa3669dbb3d54c677a04acb7a91aa03/merged
overlay         30322460 11896804  17069144  42% /var/lib/docker/overlay2/288ce4b8ba487b3d7791987dcd71bcd6f371e40efc97e2a11ff1770fb3f1a758/merged
pi@raspberrypi:/ $ 

2. 文件系统

那么文件系统是如何运行的呢?这与操作系统的文件数据有关。较新的操作系统的文件数据除了文件实际内容外, 通常含有非常多的属性,例如 Linux 操作系统的文件权限(rwx)与文件属性(拥有者、群组、时间参数等)。

文件系统通常会将这两部份的数据分别存放在不同的区块,权限与属性放置到 inode 中,至于实际数据则放置到 data block 区块中。

另外,还有一个超级区块 (superblock) 会记录整个文件系统的整体信息,包括 inode 与 block 的总量、使用量、剩余量等。

每个 inode 与 block 都有编号,至于这三个数据的意义可以简略说明如下:

  • superblock

记录此 filesystem 的整体信息,包括inode/block的总量、使用量、剩余量, 以及文件系统的格式与相关信息等;

  • inode

记录文件的属性,一个文件占用一个inode,同时记录此文件的数据所在的 block 号码;

  • block

实际记录文件的内容,若文件太大时,会占用多个 block;

由于每个 inode 与 block 都有编号,而每个文件都会占用一个 inode ,inode 内则有文件数据放置的 block 号码。 因此,我们可以知道的是,如果能够找到文件的 inode 的话,那么自然就会知道这个文件所放置数据的 block 号码, 当然也就能够读出该文件的实际数据了。

玩过数据库sql的同学,应该知道索引这个东西。

这是个比较有效率的作法,因为如此一来我们的磁盘就能够在短时间内读取出全部的数据, 读写的效能比较好。

2.1 索引式文件系统

我们将 inode 与 block 区块用图解来说明一下,如下图所示,文件系统先格式化出 inode 与 block 的区块,假设某一个文件的属性与权限数据是放置到 inode 4 号(下图较小方格内),而这个 inode 记录了文件数据的实际放置点为 2, 7, 13, 15 这四个 block 号码,此时我们的操作系统就能够据此来排列磁盘的阅读顺序,可以一口气将四个 block 内容读出来! 那么数据的读取就如同下图中的箭头所指定的模样了。
在这里插入图片描述
这种数据存取的方法我们称为索引式文件系统(indexed allocation)

2.1.1 EXT2 文件系统(了解)

标准的 Linux 文件系统 Ext2 就是使用这种 inode 为基础的文件系统。

Linux 的文件除了原有的数据内容外,还含有非常多的权限与属性,这些权限与属性是为了保护每个用户所拥有数据的隐密性。含有 inode/block/superblock 等。inode 的内容在记录文件的权限与相关属性,至于 block 区块则是在记录文件的实际内容。 而且文件系统一开始就将 inode 与 block 规划好了,除非重新格式化(或者利用 resize2fs 等命令变更文件系统大小),否则 inode 与 block 固定后就不再变动。

Ext2 文件系统在格式化的时候基本上是区分为多个区块群组 (block group) 的,每个区块群组都有独立的 inode/block/superblock 系统。感觉上就好像我们在当兵时,一个营里面有分成数个连,每个连有自己的联络系统, 但最终都向营部回报连上最正确的信息一般!这样分成一群群的比较好管理啦!整个来说,Ext2 格式化后有点像底下这样:

在这里插入图片描述
在整体的规划当中,文件系统最前面有一个启动扇区(boot sector),这个启动扇区可以安装启动管理程序, 这是个非常重要的设计,因为如此一来我们就能够将不同的启动管理程序安装到个别的文件系统最前端,而不用覆盖整颗硬盘唯一的 MBR, 这样也才能够制作出多重引导的环境啊!至于每一个区块群组(block group)的六个主要内容说明如后:

2.1.1.1 data block (数据区块)

data block 是用来放置文件内容数据,在 Ext2 文件系统中所支持的 block 大小有 1K, 2K4K 三种而已。在格式化时 block 的大小就固定了,且每个 block 都有编号,以方便 inode 的记录啦。 不过要注意的是,由于 block 大小的差异,会导致该文件系统能够支持的最大磁盘容量与最大单一文件容量并不相同。 因为 block 大小而产生的 Ext2 文件系统限制如下:
在这里插入图片描述
基本限制如下:

  • 原则上,block 的大小与数量在格式化完就不能够再改变了(除非重新格式化)
  • 每个 block 内最多只能够放置一个文件的数据
  • 承上,如果文件大于 block 的大小,则一个文件会占用多个 block 数量
  • 承上,若文件小于 block ,则该 block 的剩余容量就不能够再被使用了(磁盘空间会浪费)

如上第四点所说,由于每个 block 仅能容纳一个文件的数据而已,因此如果你的文件都非常小,但是你的 block 在格式化时却选用最大的 4K 时,可能会产生一些容量的浪费喔!

2.1.1.2 inode table (inode 表格)

再来讨论一下 inode 这个玩意儿吧!如前所述 inode 的内容在记录文件的属性以及该文件实际数据是放置在哪几号 block 内! 基本上,inode 记录的文件数据至少有底下这些:

  • 该文件的存取模式(read/write/excute)
  • 该文件的拥有者与群组(owner/group)
  • 该文件的容量
  • 该文件创建或状态改变的时间(ctime)
  • 最近一次的读取时间(atime)
  • 最近修改的时间(mtime)
  • 定义文件特性的旗标(flag),如 SetUID...
  • 该文件真正内容的指向 (pointer)

inode 的数量与大小也是在格式化时就已经固定了,除此之外 inode 还有些什么特色呢?

  • 每个 inode 大小均固定为 128 bytes
  • 每个文件都仅会占用一个 inode 而已;
  • 承上,因此文件系统能够创建的文件数量与 inode 的数量有关
  • 系统读取文件时需要先找到 inode,并分析 inode 所记录的权限与用户是否符合,若符合才能够开始实际读取 block 的内容。

高能内容来了!

我们约略来分析一下 inode / block 与文件大小的关系好了。inode 要记录的数据非常多,但偏偏又只有 128bytes 而已, 而 inode 记录一个 block 号码要花掉 4byte ,假设我一个文件有 400MB 且每个 block 为 4K 时, 那么至少也要十万笔 block 号码的记录呢!inode 哪有这么多可记录的信息?为此我们的系统很聪明的将 inode 记录 block 号码的区域定义为12个直接,一个间接, 一个双间接与一个三间接记录区。这是啥?我们将 inode 的结构画一下好了。
在这里插入图片描述
上图最左边为 inode 本身 (128 bytes),里面有 12 个直接指向 block 号码的对照,这 12 笔记录就能够直接取得 block 号码啦! 至于所谓的间接就是再拿一个 block 来当作记录 block 号码的记录区,如果文件太大时, 就会使用间接的 block 来记录编号。如上图当中间接只是拿一个 block 来记录额外的号码而已。 同理,如果文件持续长大,那么就会利用所谓的双间接,第一个 block 仅再指出下一个记录编号的 block 在哪里, 实际记录的在第二个 block 当中。依此类推,三间接就是利用第三层 block 来记录编号啦!

这样子 inode 能够指定多少个 block 呢?我们以较小的 1K block 来说明好了,可以指定的情况如下:

  • 12 个直接指向: 12*1K=12K
  • 间接: 256*1K=256K
    每笔 block 号码的记录会花去 4bytes,因此 1K 的大小能够记录 256 笔记录。
  • 双间接: 2562561K
    第一层 block 会指定 256 个第二层,每个第二层可以指定 256 个号码。
  • 三间接: 256256256*1K
    第一层 block 会指定 256 个第二层,每个第二层可以指定 256 个第三层,每个第三层可以指定 256 个号码
  • 总额:将直接、间接、双间接、三间接加总,得到 12 + 256 + 256256 + 256256*256 (K) = 16GB

当文件系统将 block 格式化为 1K 大小时,能够容纳的最大文件为 16GB,比较一下文件系统限制表的结果可发现是一致的!但这个方法不能用在 2K 及 4K block 大小的计算中, 因为大于 2K 的 block 将会受到 Ext2 文件系统本身的限制,所以计算的结果会不太符合。

2.1.1.3 Superblock (超级区块)

Superblock 是记录整个 filesystem 相关信息的地方, 没有 Superblock ,就没有这个 filesystem 了。他记录的信息主要有:

  • block 与 inode 的总量
  • 未使用与已使用的 inode / block 数量
  • block 与 inode 的大小 (block 为 1, 2, 4K,inode 为 128 bytes)
  • filesystem 的挂载时间、最近一次写入数据的时间、最近一次检验磁盘 (fsck) 的时间等文件系统的相关信息;
  • 一个 valid bit 数值,若此文件系统已被挂载,则 valid bit 为 0 ,若未被挂载,则 valid bit 为 1 。

Superblock 是非常重要的,因为我们这个文件系统的基本信息都写在这里,因此,如果 superblock 死掉了, 你的文件系统可能就需要花费很多时间去挽救啦!一般来说, superblock 的大小为 1024bytes。相关的 superblock 信息我们等一下会以 dumpe2fs 命令来呼叫出来观察喔!

此外,每个 block group 都可能含有 superblock 喔!但是我们也说一个文件系统应该仅有一个 superblock 而已,那是怎么回事啊? 事实上除了第一个 block group 内会含有 superblock 之外,后续的 block group 不一定含有 superblock , 而若含有 superblock 则该 superblock 主要是做为第一个 block group 内 superblock 的备份咯,这样可以进行 superblock 的救援呢!

2.1.1.4 Filesystem Description (文件系统描述说明)

这个区段可以描述每个 block group 的开始结束的 block 号码,以及说明每个区段 (superblock, bitmap, inodemap, data block) 分别介于哪一个 block 号码之间。这部份也能够用 dumpe2fs 来观察的。

2.1.1.5 block bitmap (区块对照表)

如果你想要新增文件时总会用到 block 吧!那你要使用哪个 block 来记录呢?当然是选择『空的 block 』来记录新文件的数据啰。 那你怎么知道哪个 block 是空的?这就得要透过 block bitmap 的辅助了。从 block bitmap 当中可以知道哪些 block 是空的,因此我们的系统就能够很快速的找到可使用的空间来处置文件啰。

同样的,如果你删除某些文件时,那么那些文件原本占用的 block 号码就得要释放出来, 此时在 block bitmap 当中相对应到该 block 号码的标志就得要修改成为『未使用中』啰!这就是 bitmap 的功能。

2.1.1.6 inode bitmap (inode 对照表)

这个其实与 block bitmap 是类似的功能,只是 block bitmap 记录的是使用与未使用的 block 号码, 至于 inode bitmap 则是记录使用与未使用的 inode 号码啰!

2.1.2 查看文件系统

主要依赖于dfdumpe2fs命令。

Linux df 命令

  • df 找到当前挂载的磁盘信息
    在这里插入图片描述
    我们这里的文件系统是ext4,待会再说。
  • dumpe2fs 显示ext2、ext3、ext4文件系统的超级快和块组信息

在这里插入图片描述
分别输入:

  • sudo dumpe2fs /dev/mmcblk0p2
  • sudo dumpe2fs -h /dev/mmcblk0p2
pi@raspberrypi:~ $ sudo dumpe2fs /dev/mmcblk0p2
dumpe2fs 1.46.2 (28-Feb-2021)
Filesystem volume name:   rootfs ========》 文件系统的名称(Label)
Last mounted on:          /
Filesystem UUID:          c68b37be-930e-49c8-87e8-9005e8247103
Filesystem magic number:  0xEF53
Filesystem revision #:    1 (dynamic)
Filesystem features:      has_journal ext_attr resize_inode dir_index filetype needs_recovery extent flex_bg sparse_super large_file dir_nlink extra_isize metadata_csum
Filesystem flags:         unsigned_directory_hash 
Default mount options:    user_xattr acl ===》默认挂载参数
Filesystem state:         clean ===》这个文件系统时没问题的
Errors behavior:          Continue
Filesystem OS type:       Linux
Inode count:              1929536 ===》inode的总数
Block count:              7725184 ===》block的总数
Reserved block count:     335032  
Overhead clusters:        144569
Free blocks:              4630986 ===》还有多少个block可用
Free inodes:              1608828 ===》还有多少个inode可用
First block:              0
Block size:               4096 ===》每个block的大小
Fragment size:            4096
Reserved GDT blocks:      644
Blocks per group:         32768 ===》每个分组的block数量
Fragments per group:      32768
Inodes per group:         8176 ===》每个分组的inode数量
Inode blocks per group:   511 ===》每个分组inode块数量
Flex block group size:    16
Filesystem created:       Thu Sep 22 08:59:07 2022
Last mount time:          Sun Nov 13 09:17:01 2022
Last write time:          Thu Sep 22 09:01:52 2022
Mount count:              11
Maximum mount count:      -1
Last checked:             Thu Sep 22 08:59:07 2022
Check interval:           0 (<none>)
Lifetime writes:          13 GB
Reserved blocks uid:      0 (user root)
Reserved blocks gid:      0 (group root)
First inode:              11
Inode size:               256 ===》每个inode的大小,注意不是128
Required extra isize:     32
Desired extra isize:      32
Journal inode:            8  ===》Journal 跟日志式文件系统有关
Default directory hash:   half_md4
Directory Hash Seed:      946d5d24-dd69-46a7-a1a5-2b4915a21f4e
Journal backup:           inode blocks
Checksum type:            crc32c
Checksum:                 0x3e3e34f6
Journal features:         journal_incompat_revoke journal_checksum_v3
Total journal size:       64M
Total journal blocks:     16384
Max transaction length:   16384
Fast commit length:       0
Journal sequence:         0x00008c2f
Journal start:            12934
Journal checksum type:    crc32c
Journal checksum:         0xc181219e

<==第一个 data group 内容, 包含 block 的启始/结束号码
Group 0: (Blocks 0-32767) csum 0x8410 [ITABLE_ZEROED]
  Primary superblock at 0, Group descriptors at 1-2 ==》超级区块在 0 号 block
  Reserved GDT blocks at 3-646
  Block bitmap at 647 (+647), csum 0x00007d0f
  Inode bitmap at 663 (+663), csum 0x00006fb2
  Inode table at 679-1189 (+679)  <==inode table 所在的 block
  4207 free blocks, 0 free inodes, 868 directories<==所有 inode 都用完了!
  Free blocks: 11600-11671, 11751-11768, 11774-11779, 11847-11914, 12029, 12815-12895, 13027-13061, 13158-13178, 13293-13298, 13501-13650, 13742-13746, 13758-13767, 14303-14381, 14424-14434, 14552-14557, 14834, 15336-15413, 15495-15532, 15623-15642, 15740-15745, 15940-16092, 16347, 17035-17111, 17320-17355, 17448-17466, 17504-17509, 17701-17819, 18099-18199, 18412-18423, 18498-18533, 18757-18851, 18966-18974, 18984-18996, 19079-19112, 19150-19231, 19243, 19463-19498, 19661-19666, 19704-19806, 19833-19834, 19841, 19940, 20629-20705, 20859-20894, 20967-20986, 21099-21104, 21230-21382, 21441-21443, 21451-21472, 21609-21683, 21779-21790, 21825-21838, 23048-23146, 23412-23460, 23607-23632, 23745-23751, 24036-24229, 24300-24301, 24358-24363, 24371-24384, 24399-24407, 24576-24728, 25049-25128, 25225-25230, 25265-25270, 25356-25457, 26812-26908, 27009-27056, 27198-27223, 27590-27781, 27865-27866, 27875, 27882, 27884, 27890, 28008-28023, 28100, 28105-28169, 28209, 28215, 28647-28724, 28763-28785, 28886-28899, 28957, 29013-29168, 29547-29623, 29676-29688, 29772-29776, 29871-30017, 30055, 30141-30187, 30266, 30275-30305, 30983-31078, 31289-31313, 31350-31356, 31556-31637, 31724-31749, 31751-31753, 31964-32035, 32157-32173, 32178-32182, 32516  <==剩余未使用的 block 号码
  Free inodes: 
....(底下省略)....
# 由于数据量非常的庞大,因此鸟哥将一些信息省略输出了!上表与你的屏幕会有点差异。
# 前半部在秀出 supberblock 的内容,包括标头名称(Label)以及inode/block的相关信息
# 后面则是每个 block group 的个别信息了!您可以看到各区段数据所在的号码!
# 也就是说,基本上所有的数据还是与 block 的号码有关就是了!很重要!

利用 dumpe2fs 可以查询到非常多的信息,不过依内容主要可以区分为上半部是 superblock 内容, 下半部则是每个 block group 的信息了。

从上面的表格中我们可以观察到这个 /dev/mmcblk0p2 规划的 block 为 4K, 第一个 block 号码为 0 号,且 block group 内的所有信息都以 block 的号码来表示的。 然后在 superblock 中还有谈到目前这个文件系统的可用 block 与 inode 数量喔!

至于 block group 的内容我们单纯看 Group0 信息好了。

Group 0: (Blocks 0-32767) csum 0x8410 [ITABLE_ZEROED]
  Primary superblock at 0, Group descriptors at 1-2 ==》超级区块在 0 号 block
  Reserved GDT blocks at 3-646
  Block bitmap at 647 (+647), csum 0x00007d0f
  Inode bitmap at 663 (+663), csum 0x00006fb2
  Inode table at 679-1189 (+679)  <==inode table 所在的 block
  4207 free blocks, 0 free inodes, 868 directories<==所有 inode 都用完了!
  Free blocks: 11600-11671, 11751-11768, 11774-11779,  
  省略部分
  31350-31356, 31556-31637, 31724-31749, 31751-31753, 31964-32035, 32157-32173, 32178-32182, 32516  <==剩余未使用的 block 号码
  Free inodes:

从上表中我们可以发现:

  • Group0 所占用的 block 号码由 0 到 32767 号,superblock 则在第 0 号的 block 区块内
  • 文件系统描述说明在第 1-2 号 block 中;
  • block bitmap 与 inode bitmap 则在 647 及 663 的 block 号码上。
  • 至于 inode table 分布于 679-1189 的 block 号码中!
  • 由于 (1)一个 inode 占用 256 bytes ,(2)总共有 1189 - 679+ 1(679本身) = 511 个 block 花在 inode table 上, (3)每个 block 的大小为 4096 bytes(4K)。由这些数据可以算出 inode 的数量共有 511 * 4096 / 256 = 8176 个 inode 啦!这个group0目前没有剩余可用的inode。
  • 剩余4207个block没用。
2.1.3 与linux目录树关系

在 Linux 系统下,每个文件(不管是一般文件还是目录文件)都会占用一个 inode , 且可依据文件内容的大小来分配多个 block 给该文件使用。

2.1.3.1 目录

当我们在 Linux 下的 ext2 文件系统创建一个目录时, ext2 会分配一个 inode 与至少一块 block 给该目录。其中,inode 记录该目录的相关权限与属性,并可记录分配到的那块 block 号码; 而 block 则是记录在这个目录下的文件名与该文件名占用的 inode 号码数据。也就是说目录所占用的 block 内容在记录如下的信息:
在这里插入图片描述
使用 ls -i 这个选项来处理:
在这里插入图片描述

pi@raspberrypi:~ $ cd /
pi@raspberrypi:/ $ ls -il
total 68
    12 lrwxrwxrwx   1 root root     7 Sep 22 08:02 bin -> usr/bin
     1 drwxr-xr-x   4 root root  4096 Jan  1  1970 boot ==14K block
     1 drwxr-xr-x  16 root root  3900 Nov 13 09:45 dev
392449 drwxr-xr-x 134 root root 12288 Nov 24 13:56 etc ==34K block
523265 drwxr-xr-x   3 root root  4096 Sep 22 09:02 home
    13 lrwxrwxrwx   1 root root     7 Sep 22 08:02 lib -> usr/lib
    11 drwx------   2 root root 16384 Sep 22 08:59 lost+found ===44K block
130818 drwxr-xr-x   3 root root  4096 Nov  5 11:00 media
261634 drwxr-xr-x   2 root root  4096 Sep 22 08:02 mnt
523266 drwxr-xr-x   6 root root  4096 Nov  9 23:03 opt
     1 dr-xr-xr-x 261 root root     0 Jan  1  1970 proc ==》此目录不占硬盘空间
261635 drwx------   5 root root  4096 Nov  8 22:41 root
     1 drwxr-xr-x  31 root root   940 Nov 25 08:55 run
    14 lrwxrwxrwx   1 root root     8 Sep 22 08:02 sbin -> usr/sbin
392451 drwxr-xr-x   2 root root  4096 Sep 22 08:02 srv
     1 dr-xr-xr-x  12 root root     0 Jan  1  1970 sys
130819 drwxrwxrwt  16 root root  4096 Nov 25 12:46 tmp
523268 drwxr-xr-x  11 root root  4096 Sep 22 08:02 usr
    16 drwxr-xr-x  11 root root  4096 Sep 22 09:02 var
pi@raspberrypi:/ $ 

注意:

在目录底下的文件数如果太多而导致一个 block 无法容纳的下所有的档名与 inode 对照表时,Linux 会给予该目录多一个 block 来继续记录相关的数据;

2.1.3.2 文件

当我们在 Linux 下的 ext2 创建一个一般文件时, ext2 会分配一个 inode 与相对于该文件大小的 block 数量给该文件。例如:假设我的一个 block 为 4 Kbytes ,而我要创建一个 100 KBytes 的文件,那么 linux 将分配一个 inode 与 25 个 block 来储存该文件! 但同时请注意,由于 inode 仅有 12 个直接指向,因此还要多一个 block 来作为区块号码的记录喔!

2.1.3.3 目录树读取

inode 本身并不记录文件名,文件名的记录是在目录的 block 当中

那么因为文件名是记录在目录的 block 当中, 因此当我们要读取某个文件时,就务必会经过目录的 inode 与 block ,然后才能够找到那个待读取文件的 inode 号码, 最终才会读到正确的文件的 block 内的数据。

由于目录树是由根目录开始读起,因此系统透过挂载的信息可以找到挂载点的 inode 号码(通常一个 filesystem 的最顶层 inode 号码会由 2 号开始喔!),此时就能够得到根目录的 inode 内容,并依据该 inode 读取根目录的 block 内的文件名数据,再一层一层的往下读到正确的档名。

举例来说,如果我想要读取 /etc/passwd 这个文件时,系统是如何读取的呢?

查看文件和文件夹的inode号命令:

ls -l -di xxxx

pi@raspberrypi:/ $ ls -l -di / /etc /etc/passwd
     2 drwxr-xr-x  18 root root  4096 Nov 11 13:33 /
392449 drwxr-xr-x 134 root root 12288 Nov 24 13:56 /etc
395146 -rw-r--r--   1 root root  2147 Nov 12 14:31 /etc/passwd

该文件的读取流程为(假设读取者身份为 pi 这个一般身份使用者):

    1. / 的 inode
      透过挂载点的信息找到 /dev/mmcblk0p2 的 inode 号码为 2 的根目录 inode,且 inode 规范的权限让我们可以读取该 block 的内容(有 r 与 x) ;
    1. / 的 block
      经过上个步骤取得 block 的号码,并找到该内容有 etc/ 目录的 inode 号码 (392449);
    1. etc/ 的 inode
      读取 392449 号 inode 得知 pi 具有 rx 的权限,因此可以读取 etc/ 的 block 内容;
    1. etc/ 的 block
      经过上个步骤取得 block 号码,并找到该内容有 passwd 文件的 inode 号码 (395146);
    1. passwd 的 inode
      读取 395146 号 inode 得知 pi 具有 r 的权限,因此可以读取 passwd 的 block 内容;
    1. passwd 的 block
      最后将该 block 内容的数据读出来。
2.1.1.4 filesystem 大小与磁盘读取效能

关于文件系统的使用效率上,当你的一个文件系统规划的很大时,例如 100GB 这么大时, 由于硬盘上面的数据总是来来去去的,所以,整个文件系统上面的文件通常无法连续写在一起(block 号码不会连续的意思), 而是填入式的将数据填入没有被使用的 block 当中。如果文件写入的 block 真的分的很散, 此时就会有所谓的文件数据离散的问题发生了。

如前所述,虽然我们的 ext2 在 inode 处已经将该文件所记录的 block 号码都记上了, 所以数据可以一次性读取,但是如果文件真的太过离散,确实还是会发生读取效率低落的问题。 因为磁盘读取头还是得要在整个文件系统中来来去去的频繁读取! 果真如此,那么可以将整个 filesystme 内的数据全部复制出来,将该 filesystem 重新格式化, 再将数据给他复制回去即可解决这个问题

此外,如果 filesystem 真的太大了,那么当一个文件分别记录在这个文件系统的最前面与最后面的 block 号码中, 此时会造成硬盘的机械手臂移动幅度过大,也会造成数据读取效能的低落。而且读取头在搜寻整个 filesystem 时, 也会花费比较多的时间去搜寻!

2.2 Ext3日志式文件系统(首次升级)

2.2.1 Ext2文件系统的不足(了解)

新建一个文件或目录时,我们的 Ext2 是如何处理的呢? 这个时候就得要 block bitmapinode bitmap 的帮忙了!假设我们想要新增一个文件,此时文件系统的行为是:

1、先确定用户对于欲新增文件的目录是否具有 w 与 x 的权限,若有的话才能新增;
2、根据 inode bitmap 找到没有使用的 inode 号码,并将新文件的权限/属性写入;
3、根据 block bitmap 找到没有使用中的 block 号码,并将实际的数据写入 block 中,且升级 inode 的 block 指向数据;
4、将刚刚写入的 inode 与 block 数据同步升级 inode bitmap 与 block bitmap,并升级 superblock 的内容。

一般来说,我们将 inode table 与 data block 称为数据存放区域,至于其他例如 superblockblock bitmapinode bitmap 等区段就被称为 metadata (中介数据) 啰,因为 superblock, inode bitmap 及 block bitmap 的数据是经常变动的,每次新增、移除、编辑时都可能会影响到这三个部分的数据,因此才被称为中介数据的啦。

在一般正常的情况下,上述的新增动作当然可以顺利的完成。但是如果有个万一怎么办? 例如你的文件在写入文件系统时,因为不知名原因导致系统中断(例如突然的停电啊、 系统核心发生错误啊~等等的怪事发生时),所以写入的数据仅有 inode table 及 data block 而已, 最后一个同步升级中介数据的步骤并没有做完,此时就会发生 metadata 的内容与实际数据存放区产生不一致 (Inconsistent) 的情况了。

既然有不一致当然就得要克服!在早期的 Ext2 文件系统中,如果发生这个问题, 那么系统在重新启动的时候,就会藉由 Superblock 当中记录的 valid bit (是否有挂载)filesystem state (clean 与否) 等状态来判断是否强制进行数据一致性的检查!若有需要检查时则以 e2fsck 这支程序来进行的。

不过,这样的检查真的是很费时~因为要针对 metadata 区域与实际数据存放区来进行比对, 呵呵~得要搜寻整个 filesystem 呢~如果你的文件系统有 100GB 以上,而且里面的文件数量又多时, 哇!系统真忙碌~而且在对 Internet 提供服务的服务器主机上面, 这样的检查真的会造成主机复原时间的拉长~真是麻烦~这也就造成后来所谓日志式文件系统的兴起了。

2.2.2 日志式文件系统(Ext3)

为了避免上述提到的文件系统不一致的情况发生,因此我们的前辈们想到一个方式, 如果在我们的 filesystem 当中规划出一个区块,该区块专门在记录写入或修订文件时的步骤, 那不就可以简化一致性检查的步骤了?也就是说:

1、预备:当系统要写入一个文件时,会先在日志记录区块中纪录某个文件准备要写入的信息;
2、实际写入:开始写入文件的权限与数据;开始升级 metadata 的数据;
3、结束:完成数据与 metadata 的升级后,在日志记录区块当中完成该文件的纪录。

在这样的程序当中,万一数据的纪录过程当中发生了问题,那么我们的系统只要去检查日志记录区块, 就可以知道哪个文件发生了问题,针对该问题来做一致性的检查即可,而不必针对整块 filesystem 去检查, 这样就可以达到快速修复 filesystem 的能力了!这就是日志式文件最基础的功能啰~

那么我们的 ext2 可达到这样的功能吗?当然可以啊! 就透过 ext3 即可! ext3 是 ext2 的升级版本,并且可向下兼容 ext2 版本呢!

记得 dumpe2fs 输出的信息,可以发现 superblock 里面含有底下这样的信息:

Journal inode:            8  ===》Journal 跟日志式文件系统有关
Default directory hash:   half_md4
Directory Hash Seed:      946d5d24-dd69-46a7-a1a5-2b4915a21f4e
Journal backup:           inode blocks
Checksum type:            crc32c
Checksum:                 0x3e3e34f6
Journal features:         journal_incompat_revoke journal_checksum_v3
Total journal size:       64M
Total journal blocks:     16384
Max transaction length:   16384
Fast commit length:       0
Journal sequence:         0x00008c2f
Journal start:            12934
Journal checksum type:    crc32c
Journal checksum:         0xc181219e

透过 inode 8 号记录 journal 区块的 block 指向,而且具有 64MB 的容量在处理日志呢!

为什么你想要从ext2转换到ext3呢

ext3和ext2的主要区别在于,ext3引入Journal(日志)机制,Linux内核从2.4.15开始支持ext3,它是从文件系统过渡到日志式文件系统最为简单的一种选择,ext3提供了数据完整性和可用性保证

  • ext2和ext3的格式完全相同,只是在ext3硬盘最后面有一部分空间用来存放Journal的记录;
  • 在ext2中,写文件到硬盘中时,先将文件写入缓存中,当缓存写满时才会写入硬盘中;在ext3中,写文件到硬盘中时,先将文件写入缓存中,待缓存写满时系统先通知Journal,再将文件写入硬盘,完成后再通知Journal,资料已完成写入工作;
  • 在ext3中,也就是有Journal机制里,系统开机时检查Journal的内容,来查看是否有错误产生,这样就加快了开机速度;

2.3 Ext4再度升级(树莓派当前文件系统)

Linux内核从2.6.28开始支持ext4文件系统,相比于ext3提供了更佳的性能和可靠性。

  • 与 Ext3 兼容。 执行若干条命令,就能从 Ext3 在线迁移到 Ext4,而无须重新格式化磁盘或重新安装系统。原有 Ext3 数据结构照样保留,Ext4 作用于新数据,当然,整个文件系统因此也就获得了 Ext4 所支持的更大容量。
  • 更大的文件系统和更大的文件。 较之 Ext3 目前所支持的最大 16TB 文件系统和最大 2TB 文件,Ext4 分别支持 1EB(1,048,576TB, 1EB=1024PB, 1PB=1024TB)的文件系统,以及 16TB 的文件。
  • 无限数量的子目录。 Ext3 目前只支持 32,000 个子目录,而 Ext4 支持无限数量的子目录。
  • Extents。 Ext3 采用间接块映射,当操作大文件时,效率极其低下。比如一个 100MB 大小的文件,在 Ext3 中要建立 25,600 个数据块(每个数据块大小为 4KB)的映射表。而 Ext4 引入了现代文件系统中流行的 extents 概念,每个 extent 为一组连续的数据块,上述文件则表示为“该文件数据保存在接下来的 25,600 个数据块中”,提高了不少效率。
  • 多块分配。 当写入数据到 Ext3 文件系统中时,Ext3 的数据块分配器每次只能分配一个 4KB 的块,写一个 100MB 文件就要调用 25,600 次数据块分配器,而 Ext4 的多块分配器“multiblock allocator”(mballoc) 支持一次调用分配多个数据块。
  • 延迟分配。 Ext3 的数据块分配策略是尽快分配,而 Ext4 和其它现代文件操作系统的策略是尽可能地延迟分配,直到文件在 cache 中写完才开始分配数据块并写入磁盘,这样就能优化整个文件的数据块分配,与前两种特性搭配起来可以显著提升性能。
  • 快速 fsck。 以前执行 fsck 第一步就会很慢,因为它要检查所有的 inode,现在 Ext4 给每个组的 inode 表中都添加了一份未使用 inode 的列表,今后 fsck Ext4 文件系统就可以跳过它们而只去检查那些在用的 inode 了。
  • 日志校验。 日志是最常用的部分,也极易导致磁盘硬件故障,而从损坏的日志中恢复数据会导致更多的数据损坏。Ext4 的日志校验功能可以很方便地判断日志数据是否损坏,而且它将 Ext3 的两阶段日志机制合并成一个阶段,在增加安全性的同时提高了性能。
  • “无日志”(No Journaling)模式。 日志总归有一些开销,Ext4 允许关闭日志,以便某些有特殊需求的用户可以借此提升性能。
  • 在线碎片整理。 尽管延迟分配、多块分配和 extents 能有效减少文件系统碎片,但碎片还是不可避免会产生。Ext4 支持在线碎片整理,并将提供 e4defrag 工具进行个别文件或整个文件系统的碎片整理。
  • inode 相关特性。 Ext4 支持更大的 inode,较之 Ext3 默认的 inode 大小 128 字节,Ext4 为了在 inode 中容纳更多的扩展属性(如纳秒时间戳或 inode 版本),默认 inode 大小为 256 字节。Ext4 还支持快速扩展属性(fast extended attributes)和 inode 保留(inodes reservation)。
  • 持久预分配(Persistent preallocation)。 P2P 软件为了保证下载文件有足够的空间存放,常常会预先创建一个与所下载文件大小相同的空文件,以免未来的数小时或数天之内磁盘空间不足导致下载失败。 Ext4 在文件系统层面实现了持久预分配并提供相应的 API(libc 中的 posix_fallocate()),比应用软件自己实现更有效率。
  • 默认启用 barrier。 磁盘上配有内部缓存,以便重新调整批量数据的写操作顺序,优化写入性能,因此文件系统必须在日志数据写入磁盘之后才能写 commit 记录,若 commit 记录写入在先,而日志有可能损坏,那么就会影响数据完整性。Ext4 默认启用 barrier,只有当 barrier 之前的数据全部写入磁盘,才能写 barrier 之后的数据。(可通过 “mount -o barrier=0” 命令禁用该特性。)

2.4 FAT文件系统

FAT 这种格式的文件系统并没有 inode 存在,所以 FAT 没有办法将这个文件的所有 block 在一开始就读取出来。每个 block 号码都记录在前一个 block 当中, 他的读取方式有点像底下这样:

在这里插入图片描述
上图中我们假设文件的数据依序写入1->7->4->15号这四个 block 号码中, 但这个文件系统没有办法一口气就知道四个 block 的号码,他得要一个一个的将 block 读出后,才会知道下一个 block 在何处。 如果同一个文件数据写入的 block 分散的太过厉害时,则我们的磁盘读取头将无法在磁盘转一圈就读到所有的数据, 因此磁盘就会多转好几圈才能完整的读取到这个文件的内容!

2.5 挂载点

每个 filesystem 都有独立的 inode / block / superblock 等信息,这个文件系统要能够链接到目录树才能被我们使用。 将文件系统与目录树结合的动作我们称为『挂载』。

挂载点一定是目录,该目录为进入该文件系统的入口。 因此并不是你有任何文件系统都能使用,必须要『挂载』到目录树的某个目录后,才能够使用该文件系统的。

另外一句话,同一个 filesystem 的某个 inode 只会对应到一个文件内容而已(因为一个文件占用一个 inode 之故), 因此我们可以透过判断 inode 号码来确认不同文件名是否为相同的文件。

pi@raspberrypi:~ $ ls -ild /  /.  /..
2 drwxr-xr-x 18 root root 4096 Nov 11 13:33 /
2 drwxr-xr-x 18 root root 4096 Nov 11 13:33 /.
2 drwxr-xr-x 18 root root 4096 Nov 11 13:33 /..
pi@raspberrypi:~ $ 

挂载点均为 / ,因此三个文件 (/, /., /..) 均在同一个 filesystem 内,而这三个文件的 inode 号码均为 2 号,因此这三个档名都指向同一个 inode 号码,当然这三个文件的内容也就完全一模一样了! 也就是说,根目录的上一级 (/…) 就是他自己!

之前说过,树莓派里面有两个分区,启动分区和主分区。启动分区的挂载点是/boot,主分区挂载点是/。

pi@raspberrypi:~ $ ls -lid / /boot
2 drwxr-xr-x 18 root root 4096 Nov 11 13:33 /
1 drwxr-xr-x  4 root root 4096 Jan  1  1970 /boot
pi@raspberrypi:~ $ ls -lid / /boot

filesystem 最顶层的目录之 inode 一般为 2 号,因此可以发现,/和/boot为两个不同的fileSystem。

2.7 Linux VFS (Virtual Filesystem Switch)

了解了我们使用的文件系统之后,再来则是要提到,那么 Linux 的核心又是如何管理这些认识的文件系统呢? 其实,整个 Linux 的系统都是透过一个名为 Virtual Filesystem Switch 的核心功能去读取 filesystem 的。 也就是说,整个 Linux 认识的 filesystem 其实都是 VFS 在进行管理,我们使用者并不需要知道每个 partition 上头的 filesystem 是什么~

VFS 会主动的帮我们做好读取的动作呢~

假设你的 / 使用的是 /dev/hda1 ,用 ext3 ,而 /home 使用 /dev/hda2 ,用 reiserfs , 那么你取用 /home/dmtsai/.bashrc 时,有特别指定要用的什么文件系统的模块来读取吗? 应该是没有吧!这个就是 VFS 的功能啦!透过这个 VFS 的功能来管理所有的 filesystem, 省去我们需要自行配置读取文件系统的定义啊~方便很多!整个 VFS 可以约略用下图来说明:

在这里插入图片描述

2.7 碎片整理

需要碎片整理的原因就是文件写入的 block 太过于离散了,此时文件读取的效能将会变的很差所致。 这个时候可以透过碎片整理将同一个文件所属的 blocks 汇整在一起,这样数据的读取会比较容易啊! 想当然尔,FAT 的文件系统需要经常的碎片整理一下,那么 Ext2 是否需要磁盘重整呢?

由于 Ext2 是索引式文件系统,基本上不太需要常常进行碎片整理的。但是如果文件系统使用太久, 常常删除/编辑/新增文件时,那么还是可能会造成文件数据太过于离散的问题,此时或许会需要进行重整一下的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

单片机菜鸟爱学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值