河曲智叟 发表于 2025-3-23 06:27:50

Linux笔记---文件系统软件部门

1. 前置概念

1.1 块

硬盘是典型的“块”设备,操作系统读取硬盘数据的时候,实在是不会一个扇区一个扇区地读取的,而是一次性一连读取多个扇区,以提升服从,即一次性读取一个”块”(block)。
一个”块”的大小是由格式化的时候确定的,并且不可以更改,最常见的是4KB,即一连八个扇区组成一个 ”块”。”块”是文件存取的最小单位。
https://i-blog.csdnimg.cn/direct/80135a6e8a4847d5b9114c6f2004633f.png
https://i-blog.csdnimg.cn/direct/774c83bf513a45769d20b3d65207b944.png
块号是从零开始一连递增的,结合我们在硬件部门学习的LBA地址,我们可以得出以下结论:
   块号 = LBA / 8;
LBA = 块号 * 8 + n(块内的第几个扇区); 
1.2 分区

一个磁盘现实上可以被划分为多个分区。以Windows为例,我们的电脑通常只有一块固态硬盘,但是我们可以在逻辑上将其划分为C、D、E、……等多个磁盘。C盘、D盘、E盘等,即为物理硬盘的多个分区。
在操作系统层面上,这些盘作为外设,都是以文件的形式被管理起来的,但他们是一种逻辑上的分区,没有物理参考,如何界定分区范围呢?
柱面是分区的最小单位,以是我们可以使用柱面号码的方式来进行分区,其本质就是设置每个分区的起始柱面和结束柱面号码。 
https://i-blog.csdnimg.cn/direct/1d1a8f88d02a4739834037df5b743058.png
上图中启动块(Boot Block/Sector)的大小是确定的,为1KB,由PC标准规定,用于存储磁盘分区信息和启动信息,任何文件系统都不能修改启动块。启动块之后才是ext2文件系统的开始。
2. 文件系统

我们想要在硬盘上存储文件,必须先把硬盘格式化为某种格式的文件系统,才气存储文件。文件系统的目的就是构造和管理硬盘中的文件。在 Linux 系统中,最常见的是 ext2 系列的文件系统。
https://i-blog.csdnimg.cn/direct/c72e8b031d124efab923d31719f170c8.png
从上图就可以看出,一个分区现实上就对应一个文件系统。为了模拟物理意义上的多个独立磁盘,以是各个分区的文件由自己的文件系统进行管理。
2.1 块组(Block Group)

https://i-blog.csdnimg.cn/direct/4b6f5d48305747e09bc964cb6a08c7fe.png
ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着雷同的结构组成,各个块组可以应用雷同的管理计谋。
一个块组会被划分为6个部门:超等块(Super Block)、块组形貌符表(GDT)、块位图(Block Bitmap)、inode位图(inode Bitmap)、i节点表(inode Table)、数据区(Data Blocks)。
其中,前四个部门是存储的是管理信息,后两个部门存储的才是文件的数据。
 2.1.1 数据区(Data Blocks)

从命名上就可以看出,这部门是由一个一个的块(Block)构成的,用于存储文件的内容数据。
上图中块组各部门的比例并不是很准确,现实上数据块部门会占到一个块组的绝大部门空间。
每一个块都有自己对应的编号,一个分区的块同一编号(跨组不跨分区)。
2.1.2 i节点表(inode Table)

显然,inode Table就是用于存储inode的表格,我们只需要把inode搞清楚,这部门就迎刃而解了。
我们说过,文件 = 属性 + 数据。在Linux中,文件的属性和数据是分离存储的。
inode现实上就是存储文件属性(元信息)的结构体,中文译名“索引结点”。每一个文件都有对应的inode,每个inode都有其唯一标识符(inode号,同样跨组不跨分区,分区内同一编号)。
/*
* 磁盘上的inode结构
*/
struct ext2_inode {
    __le16 i_mode; /* 文件模式 */
    __le16 i_uid; /* 所有者UID的低16位 */
    __le32 i_size; /* 以字节为单位的文件大小 */
    __le32 i_atime; /* 访问时间 */
    __le32 i_ctime; /* 创建时间 */
    __le32 i_mtime; /* 修改时间 */
    __le32 i_dtime; /* 删除时间 */
    __le16 i_gid; /* 组ID的低16位 */
    __le16 i_links_count; /* 链接计数 */
    __le32 i_blocks; /* 块计数 */
    __le32 i_flags; /* 文件标志 */
    union {
      struct {
            __le32 l_i_reserved1;
      } linux1;
      struct {
            __le32 h_i_translator;
      } hurd1;
      struct {
            __le32 m_i_reserved1;
      } masix1;
    } osd1; /* 操作系统相关字段1 */
    __le32 i_block;/* 指向数据块的指针 */
    __le32 i_generation; /* 文件版本(用于NFS) */
    __le32 i_file_acl; /* 文件访问控制列表 */
    __le32 i_dir_acl; /* 目录访问控制列表 */
    __le32 i_faddr; /* 片段地址 */
    union {
      struct {
            __u8 l_i_frag; /* 片段编号 */
            __u8 l_i_fsize; /* 片段大小 */
            __u16 i_pad1;
            __le16 l_i_uid_high; /* 这两个字段 */
            __le16 l_i_gid_high; /* 原为reserved2 */
            __u32 l_i_reserved2;
      } linux2;
      struct {
            __u8 h_i_frag; /* 片段编号 */
            __u8 h_i_fsize; /* 片段大小 */
            __le16 h_i_mode_high;
            __le16 h_i_uid_high;
            __le16 h_i_gid_high;
            __le32 h_i_author;
      } hurd2;
      struct {
            __u8 m_i_frag; /* 片段编号 */
            __u8 m_i_fsize; /* 片段大小 */
            __u16 m_pad1;
            __u32 m_i_reserved2;
      } masix2;
    } osd2; /* 操作系统相关字段2 */
};

/*
* 与数据块相关的常量
*/
#define EXT2_NDIR_BLOCKS 12       /* 直接数据块数量 */
#define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS/* 一级间接块索引 */
#define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1)/* 二级间接块索引 */
#define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1)/* 三级间接块索引 */
#define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1)/* 总块指针数量 */
// 注意:EXT2_N_BLOCKS 的值为15   

[*]文件名属性并未纳入到inode数据结构内部。
[*]inode的大小一样平常是128字节大概256。
[*]任何文件的内容大小可以不同,但是属性大小肯定是雷同的。 
" ls " 指令带上 " -i " 选项即可检察文件对应的inode号。
https://i-blog.csdnimg.cn/direct/2d704ef8bd274b34b53755d75fcfb10b.png
以是,inode用于存储一个文件的属性信息,而inode Table由于同一管理一个分组当中的文件的inode。 
从 inode 到 block

inode 中存在能够找到其对应数据块的字段:
__le32 i_block;/* 指向数据块的指针 */

#define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1)/* 总块指针数量 */
// 注意:EXT2_N_BLOCKS 的值为15 总共15个指针,直觉上来说,一个文件最多对应15个数据块,最多只能存储 15 * 8 * 512 字节。
但很明显这样的容量完全不敷某些大文件塞牙缝的,如何确保文件被完全存放?
https://i-blog.csdnimg.cn/direct/19a352fba953466d956bc2c68a79d719.png
现实上,只有前12个指针是与数据块逐一对应的,背面的3个指针依次为一级、二级、三级间接指针,每级间接意味着中间存在着一个索引表(指针数组)。根据需要扩充索引表的内容,就可以不停增加一个文件对应的数据块。
2.1.3 inode位图(inode Bitmap)

inode位图用于记录inode Tbale中哪些inode结点是正在被使用的(存放了某个文件的属性),被使用则对应位置1,未被使用则对应位置0。
2.1.4 块位图(Block Bitmap)

同理,块位图用于记录Data Blocks中哪些数据块是正在被使用的。
2.1.5 快组形貌符(GDT)

块组形貌符表(Group Descriptor Table),形貌块组属性信息,整个分区分成多个块组就对应有多少个块组形貌符。
每个块组形貌符存储一个块组的形貌信息,如在这个块组中从哪里开始是inode Table,从哪里开始是Data Blocks,空闲的inode和数据块还有多少个等等。
// 磁盘级blockgroup的数据结构
/*
* Structure of a blocks group descriptor
*/
struct ext2_group_desc
{
    __le32 bg_block_bitmap; /* Blocks bitmap block */
    __le32 bg_inode_bitmap; /* Inodes bitmap */
    __le32 bg_inode_table; /* Inodes table block*/
    __le16 bg_free_blocks_count; /* Free blocks count */
    __le16 bg_free_inodes_count; /* Free inodes count */
    __le16 bg_used_dirs_count; /* Directories count */
    __le16 bg_pad;
    __le32 bg_reserved;
}; 2.1.6 超等块(Super Block)

存放文件系统自己的结构信息,形貌整个分区的文件系统信息。
记录的信息重要有:bolck 和 inode的总量,未使用的 block 和 inode 的数量,一个 block 和 inode 的大小,近来一次挂载的时间,近来一次写入数据的时间,近来一次检验磁盘的时间等其他文件系统的相关信息。
注意,我们说的是超等块存放文件系统自己(即一个分区)的结构信息,并不是块组的结构信息,因此超等块的数据是极其重要的。超等块的数据被破坏则整个文件系统的结构就被破坏了。
   超等块在每个块组的开头都有一份拷贝(第一个块组必须有,背面的块组可以没有)。 为了包管文件系统在磁盘部门扇区出现物理标题的情况下还能正常工作,就必须包管文件系统的super block信息在这种情况下也能正常访问。以是一个文件系统的super block会在多个block group中进行备份,这些super block区域的数据保持同等。
/*
* 超级块的结构
*/
struct ext2_super_block {
    __le32 s_inodes_count; /* inode总数 */
    __le32 s_blocks_count; /* 块总数 */
    __le32 s_r_blocks_count; /* 保留块数 */
    __le32 s_free_blocks_count; /* 空闲块数 */
    __le32 s_free_inodes_count; /* 空闲inode数 */
    __le32 s_first_data_block; /* 第一个数据块位置 */
    __le32 s_log_block_size; /* 块大小(对数形式) */
    __le32 s_log_frag_size; /* 片段大小(对数形式) */
    __le32 s_blocks_per_group; /* 每个组的块数 */
    __le32 s_frags_per_group; /* 每个组的片段数 */
    __le32 s_inodes_per_group; /* 每个组的inode数 */
    __le32 s_mtime; /* 挂载时间 */
    __le32 s_wtime; /* 写入时间 */
    __le16 s_mnt_count; /* 挂载次数 */
    __le16 s_max_mnt_count; /* 最大挂载次数 */
    __le16 s_magic; /* 魔数签名(标识文件系统类型) */
    __le16 s_state; /* 文件系统状态 */
    __le16 s_errors; /* 错误检测时的行为 */
    __le16 s_minor_rev_level; /* 次修订版本号 */
    __le32 s_lastcheck; /* 最后检查时间 */
    __le32 s_checkinterval; /* 两次检查的最大间隔时间 */
    __le32 s_creator_os; /* 创建操作系统 */
    __le32 s_rev_level; /* 主修订版本号 */
    __le16 s_def_resuid; /* 保留块的默认用户ID */
    __le16 s_def_resgid; /* 保留块的默认组ID */
    /*
    * 以下字段仅适用于EXT2_DYNAMIC_REV版本的超级块
    *
    * 注意:兼容特性集与不兼容特性集的区别在于:
    * 如果内核无法识别不兼容特性集中设置的某个特性位,
    * 则应拒绝挂载该文件系统。
    *
    * e2fsck的要求更为严格:如果它无法识别兼容特性集
    * 或不兼容特性集中的任何特性,必须终止操作,
    * 不会尝试修改其不理解的内容...
    */
    __le32 s_first_ino; /* 第一个非保留inode编号 */
    __le16 s_inode_size; /* inode结构大小 */
    __le16 s_block_group_nr; /* 本超级块所在的块组号 */
    __le32 s_feature_compat; /* 兼容特性集 */
    __le32 s_feature_incompat; /* 不兼容特性集 */
    __le32 s_feature_ro_compat; /* 只读兼容特性集 */
    __u8 s_uuid; /* 128位卷UUID */
    char s_volume_name; /* 卷名称 */
    char s_last_mounted; /* 最后挂载目录路径 */
    __le32 s_algorithm_usage_bitmap; /* 压缩算法位图 */
    /*
    * 性能提示:仅当EXT2_COMPAT_PREALLOC标志启用时,
    * 目录预分配功能才会生效
    */
    __u8 s_prealloc_blocks; /* 尝试预分配的块数 */
    __u8 s_prealloc_dir_blocks; /* 为目录预分配的块数 */
    __u16 s_padding1;
    /*
    * 日志支持(当设置EXT3_FEATURE_COMPAT_HAS_JOURNAL时有效)
    */
    __u8 s_journal_uuid; /* 日志超级块的UUID */
    __u32 s_journal_inum; /* 日志文件的inode编号 */
    __u32 s_journal_dev; /* 日志文件的设备号 */
    __u32 s_last_orphan; /* 待删除inode链表的起始位置 */
    __u32 s_hash_seed; /* HTREE哈希种子 */
    __u8 s_def_hash_version; /* 默认使用的哈希版本 */
    __u8 s_reserved_char_pad;
    __u16 s_reserved_word_pad;
    __le32 s_default_mount_opts; /* 默认挂载选项 */
    __le32 s_first_meta_bg; /* 第一个元块组 */
    __u32 s_reserved; /* 填充至块末尾 */
};  2.1.7 总结

从这部门我们可以看出为什么 数据写入很慢,而删除却很快。由于删除数据只需要修改对应的标志信息置即可(如将inode Bitmap 和 Block Bitmap对应位置为0)。
由此我们又能大概猜到接纳站或数据规复技能的原理了,只要赶在数据被覆盖之前,找到被删除数据对应的原inode和数据块即可规复。但如果新的数据被写入到了这些存储空间那就无能为力了。
以是,当重要的数据被误删时,尽可能什么都不做,赶在数据被覆盖之前交由专业人士进行规复。
所谓的格式化,实在就是对分区进行分组,在每个分组中写入SB、GDT、Block Bitmap、Inode Bitmap等管理信息,这些管理信息统称:文件系统。
2.2 目次

2.2.1 标题提出

   
[*]文件名被存放在哪里呢?
前面我们提到过,inode的大小是固定的(即文件属性信息的大小是固定的),这重要是为了方便根据inode节点号来查找inode。但是这会带来一个标题:文件的名称不能存储在inode中(在前文代码中也有体现,由于名称大小随长度而变化)。
[*]文件名与inode的联系?
我们已经知道,根据inode号可以在一个分区当中准确的找到一个文件的属性和数据,但是我们从未关心过一个文件的inode号,我们使用的一直是"路径+文件名"的方式对文件进行访问、操作。也就是说,文件名与inode之间肯定创建了某种映射关系。
[*]目次文件的内容?
目次文件也是文件,它的内容是什么?
现实上,这三个标题存在着本质的联系。
2.2.2 目次文件的内容

目次文件存放的现实上是该目次下 "文件名 - indoe号" 的映射关系。
https://i-blog.csdnimg.cn/direct/6faf51d9df6c4b999cbb62ca11db94aa.png
当我们通过文件名访问一个文件时,OS 会在上级目次中的文件内容中查找该文件名对应的inode号。但是,OS 如何知道一个文件名对应的文件在哪个目次下呢?
2.2.2.1 路径剖析

从上面的分析可以看出,我们访问文件是肯定要有路径的,给出路径的方式有两种:
   

[*]绝对路径:用户显式给出从根目次到目标文件的路径。
[*]相对路径:历程的CWD + 用户以当前目次为起点给出到目标文件的路径。
在得到目标文件的绝对路径之后,OS 从根目次开始将路径中的目次一级一级地睁开,直到在目标文件的上一级目次文件中找到目标文件的inode。
当然,根目次拥有固定的文件名和 inode号,无需查找,系统开机之后就必须知道。
2.2.2.2 路径缓存

每次访问文件都从根目次开始将目次逐级睁开未免太过贫苦。现实上,OS 会将本次开机曾打开过的文件和目次维护到路径缓存中。这个路径缓存是内存级的一个树状结构。
Linux内核中维护树状路径结构的内核结构体叫做 struct dentry:
struct dentry {
    atomic_t d_count;      /* 引用计数器 */
    unsigned int d_flags;    /* 由d_lock保护的标志位 */
    spinlock_t d_lock;       /* 每个dentry独立的自旋锁 */
    struct inode* d_inode;   /* 关联的inode指针(NULL表示负向条目) */
   
    /*
    * 以下三个字段会被__d_lookup访问,将它们放在同一缓存行
    */
    struct hlist_node d_hash;/* 查找哈希链表节点 */
    struct dentry * d_parent;/* 父目录dentry指针 */
    struct qstr d_name;      /* 目录项名称结构体 */
   
    struct list_head d_lru;    /* LRU回收链表 */
   
    /*
    * d_child和d_rcu共享内存空间
    */
    union {
      struct list_head d_child;/* 父目录的子条目链表 */
      struct rcu_head d_rcu;   /* RCU回调结构体 */
    } d_u;
   
    struct list_head d_subdirs;/* 当前目录的子条目链表 */
    struct list_head d_alias;    /* inode别名链表 */
    unsigned long d_time;      /* 用于d_revalidate的时间戳 */
    struct dentry_operations* d_op;/* dentry操作函数表 */
    struct super_block* d_sb;   /* 所属的超级块指针 */
    void* d_fsdata;               /* 文件系统私有数据 */
   
#ifdef CONFIG_PROFILING
    struct dcookie_struct* d_cookie; /* 调试用的cookie指针(如果启用性能分析) */
#endif
   
    int d_mounted;                /* 挂载点标记 */
    unsigned char d_iname; /* 短文件名内联存储 */
}; 当然,内存资源是非常珍贵的,这颗树不能在内存中无限生长,他只会维护近来最少使用的数据,长时间未被访问的目次或文件会被剔除。
在使用操作系统的过程中,我们会发现当我们首次睁开大量文件目次(如使用find等指令时)时会有肉眼可见的耽误,但是之后再次执行时速度就会变得非常敏捷。
2.2.2.3 opendir 和 readdir

opendir和readdir函数是Linux操作系统的两个系统调用,用于目次操作。
opendir函数
   

[*]功能:打开一个目次并返回一个指向该目次的DIR*范例的指针,该指针可用于后续的目次读取操作。
[*]原型:DIR *opendir(const char *name);
[*]参数:name是要打开的目次的路径名。
[*]返回值:成功时返回一个DIR*范例的指针,指向打开的目次流;失败时返回NULL。
readdir函数
   

[*]功能:读取目次流中的下一个目次项,并返回一个指向struct dirent范例的指针,该结构体包罗了目次项的相关信息。
[*]原型:struct dirent *readdir(DIR *dir);
[*]参数:dir是由opendir函数返回的目次流指针。
[*]返回值:成功时返回一个struct dirent*范例的指针,指向目次流中的下一个目次项;到达目次末尾或堕落时返回NULL。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char* argv[]) {
    if (argc != 2) {
      fprintf(stderr, "Usage: %s <directory>\n", argv);
      exit(EXIT_FAILURE);
    }
   
    DIR* dir = opendir(argv); // 系统调⽤,⾃⾏查阅
    if (!dir) {
      perror("opendir");
      exit(EXIT_FAILURE);
    }
   
    struct dirent* entry;
    while ((entry = readdir(dir)) != NULL) { // 系统调⽤,⾃⾏查阅
      // Skip the "." and ".." directory entries
      if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..")
            == 0) {
            continue;
      }
      printf("Filename: %s, Inode: %lu\n", entry->d_name, (unsigned long)entry->d_ino);
    }
   
    closedir(dir);
    return 0;
} 注意事项
   

[*]遍历目次时,通常需要忽略.和..这两个特殊的目次项,以制止无限循环。
[*]当readdir函数返回NULL时,可能是到达目次末尾,也可能是发生了错误,需要根据errno的值来判定具体情况。
 2.3 挂载分区

我们前面提到过,inode的编号是不能跨分区的,这意味着在不同的分区可能有雷同的inode号,OS 如何知道该去哪个分区进行查找呢?
2.3.1 挂载点

   
[*] 挂载点绑定: 当用户访问文件路径(如/home/user/file)时,系统通过路径剖析逐级确定文件地点的分区。比方,若/home是独立分区(如挂载到/dev/sdb1),则访问/home/user/file时,系统会直接操作/dev/sdb1分区下的文件系统,无需跨分区查找。​​​​​​​https://i-blog.csdnimg.cn/direct/fa68d5b1ac914546a5709b21eeaf3580.png
[*] 分区元数据隔离: 每个分区维护独立的文件系统结构(如超等块、inode表)。纵然不同分区的inode号雷同,系统通过分区挂载信息将查找范围限定在当前分区的元数据中,制止混淆。
2.3.2 关键数据结构支撑

   

[*] 目次项(dentry): 目次文件中存储的并非单纯的文件名→inode映射,而是隐含了分区上下文。比方,当访问/var/log时,若/var是独立分区,系统会直接读取该分区的inode表,无需关注其他分区。
[*] 捏造文件系统(VFS)抽象: VFS层通过struct super_block记录每个分区的挂载信息,当历程访问文件时,内核通过路径剖析确定目标文件地点的super_block,从而锁定对应的分区和inode表。
2.3.3 总结

操作系统通过路径剖析→挂载点定位→分区隔离查找的流程,确保纵然不同分区存在雷同inode号,也能精准定位目标文件。这一机制依靠文件系统的挂载信息隔离和VFS层的抽象管理,而非单纯依靠inode的全局唯一性。
3. 软硬链接

通过上面的介绍我们知道,文件名与文件自己实在并没有什么一对一的联系,乃至文件自己都不知道自己的文件名,而只知道自己的inode。
因此,文件名只是作为用户标识文件的一种本领。现实上,我们可以让多个不同的文件名指向同一个文件,即文件名与inode形成多对一的关系。
3.1 硬链接

如上所述,就是新界说一个文件名,让它指向某个文件的inode,可以理解为给文件起别名。
ln [原文件名] [新文件名] https://i-blog.csdnimg.cn/direct/9cf7fc1cb6e14d2293178d4e73fd7055.png
可以看到,两个文件的属性信息完全雷同,包括inode号。 
可以验证二者就是同一个文件:
https://i-blog.csdnimg.cn/direct/e871915cf8614b06a9057ad3277bc0ad.png
3.2 软链接

相比于硬链接,软链接更像是为文件创建快捷方式。
ln -s [原文件名] [新文件名] https://i-blog.csdnimg.cn/direct/21ba0bc0549541bfafab13483a81104a.png
 可以看到,软链接创建的链接文件的inode号与之前的不同,是一个独立的文件,就雷同于Windows当中的快捷方式文件。
在使用上来说,效果与硬链接完全一样:
https://i-blog.csdnimg.cn/direct/bcb66e5748ad4665b43308d0a4567ffb.png
3.3 硬链接数

我们知道,文件的属性当中有一个叫做硬链接数的东西,之前一直不理解是什么意思,但是信赖现在大家已经能够理解了。
https://i-blog.csdnimg.cn/direct/08836f2fef654b948017bc7668fbdb56.png
硬链接数就是指向这个文件的文件名的数量,也可以理解为别名数,大概 --- "引用计数" 。只有当硬链接数归零时,文件才会被现实删除。
这就为我们提供了一种文件备份的方式(防误删不防修改),即为一个重要文件创建硬链接。
目次文件的硬链接数

新创建的目次文件的硬链接数就是2,这是为什么呢?
https://i-blog.csdnimg.cn/direct/11540ca45ee3446ba575e8899337b856.png
 由于目次文件中会默认存在 " . " 和 " .. " 两个目次,其中 " . " 就是新创建的文件的一个硬链接。
当我们在目次中在创建一个目次时,我们会发现目次文件的硬链接数又会加一:
https://i-blog.csdnimg.cn/direct/fafbcadba38349aca0e44fceaeaaeb8f.png
这是由于test_1内部的 ".." 指向的就是test。
这也就分析了为什么rm在删除目次文件时需要加上选项 "-r" 以进行递归删除(即从内向外一级一级地删除)。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Linux笔记---文件系统软件部门