上次讲了VFS层,这次说说文件系统层,文件系统层将不同的文件系统实现了VFS的这些函数,通过指针注册到VFS里面。所以,用户的操作通过VFS转到各种文件系统,linux用到最多的是ext4文件系统,我们就说这个吧。EXT4是第四代扩展文件系统(英语:Fourth extended filesystem,缩写为 ext4)是Linux系统下的日志文件系统,是ext2和ext3文件系统的后继版本。

ext4文件系统布局

一个Ext4文件系统被分成一系列块组。为减少磁盘碎片产生的性能瓶颈,块分配器尽量保持每个文件的数据块都在同一个块组中,从而减少寻道时间。以4KB的数据块为例,一个块组可以包含32768个数据块,也就是128MB。每个块组一般包括超级块、块组描述符表、预留块组描述符表、数据位图、inode位图、inode表、数据块。Ext4文件系统主要使用块组0中的超级块和块组描述符表,在特定的块组(譬如说0,3,5,7)才有超级块和块组描述符表的冗余备份。普通块组中不含冗余备份,那么块组就以数据块位图开始。如下图所示:

当格式化磁盘成为Ext4文件系统的时候,mkfs将在块组描述符表后面分配预留GDT表数据块(“Reserve GDT blocks”)以用于将来扩展文件系统。紧接在预留GDT表数据块后的是数据块位图与inode表位图,这两个位图分别表示本块组内的数据块与inode表的使用,inode表数据块之后就是存储文件的数据块了。在这些各种各样的块中,超级块、GDT、块位图、Inode位图都是整个文件系统的元数据,当然inode表也是文件系统的元数据,但是inode表是与文件一一对应的,我更倾向于将inode当做文件的元数据,因为在实际格式化文件系统的时候,除了已经使用的十来个inode表外,其他inode表中实际上是没有任何数据的,直到创建了相应的文件才会分配inode表,文件系统才会在inode表中写入与文件相关的inode信息。

0.引导块

系统初始时根据MBR的信息来识别硬盘,其中包括了一些执行文件就来载入系统,这些执行文件就是MBR里前面446字节里的boot loader 程式,而后面的16字节X4的空间就是存储分区表信息的位置,最后以0x55AA这两个字节结束,如下图:

分区表主要储存一下三种信息:

  1. 分区号
  2. 分区起始位置
  3. 分区大小

1.超级块

超级块用于存储文件系统全局的配置参数(譬如:块大小,总的块数和inode数)和动态信息(譬如:当前空闲块数和inode数),其处于文件系统开始位置的1k处,所占大小为1k。为了系统的健壮性,最初每个块组都有超级块和组描述符表(以下将用GDT)的一个拷贝,但是当文件系统很大时,这样浪费了很多块(尤其是GDT占用的块多),后来采用了一种稀疏的方式来存储这些拷贝,只有块组号是3, 5 ,7的幂的块组(譬如说0,3,5,7)才备份这个拷贝。通常情况下,只有主拷贝(第0块块组)的超级块信息被文件系统使用,其它拷贝只有在主拷贝被破坏的情况下才使用。其结构体是struct ext4_super_block,位于fs/ext4/ext4.h文件:

struct ext4_super_block {/*00*/  __le32  s_inodes_count; //inode数量__le32 s_blocks_count_lo;//块数量__le32   s_r_blocks_count_lo;//保留块的数量__le32  s_free_blocks_count_lo;//空闲块的数量
/*10*/  __le32  s_free_inodes_count;//空闲inode的数量__le32  s_first_data_block;//第一块数据块__le32   s_log_block_size;//块大小__le32    s_log_cluster_size; /* Allocation cluster size */
/*20*/  __le32  s_blocks_per_group;//每个块组的块数量__le32 s_clusters_per_group;   /* # Clusters per group */__le32    s_inodes_per_group;//每个块组的索引数量__le32    s_mtime;//挂载时间
/*30*/  __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;//最小版本
/*40*/  __le32  s_lastcheck;//最近检查时间__le32  s_checkinterval;//最长检查时间,超过就回调检查__le32   s_creator_os;       /* 要创建文件系统的os */__le32  s_rev_level;//修订版本
/*50*/  __le16  s_def_resuid;//默认预留块的用户id__le16 s_def_resgid;//默认预留块的用户组id/** These fields are for EXT4_DYNAMIC_REV superblocks only.** Note: the difference between the compatible feature set and* the incompatible feature set is that if there is a bit set* in the incompatible feature set that the kernel doesn't* know about, it should refuse to mount the filesystem.** e2fsck's requirements are more strict; if it doesn't know* about a feature in either the compatible or incompatible* feature set, it must abort and not try to meddle with* things it doesn't understand...*/__le32   s_first_ino;        /* 第一个非保留的inode号码 */__le16  s_inode_size;       /* inode结构大小 */__le16   s_block_group_nr;   /* 该超级块所在的块组号 */__le32  s_feature_compat;   /* 兼容特性集 */
/*60*/  __le32  s_feature_incompat; /* 非兼容特性集 */__le32  s_feature_ro_compat;    /* 只读兼容特性集 */
/*68*/  __u8    s_uuid[16];     /* 128的卷uuid */
/*78*/  char    s_volume_name[16];  /* 卷名字 */
/*88*/  char    s_last_mounted[64] __nonstring; /* 最近一次的挂载目录 */
/*C8*/  __le32  s_algorithm_usage_bitmap; /* 用于压缩 *//** Performance hints.  Directory preallocation should only* happen if the EXT4_FEATURE_COMPAT_DIR_PREALLOC flag is on.*/__u8   s_prealloc_blocks;  /* 预分配的块数 */__u8    s_prealloc_dir_blocks;  /* 为目录预分配的块数 */__le16   s_reserved_gdt_blocks;  /* 因为数据增长为块组描述符保留的块数 *//** Journaling support valid if EXT4_FEATURE_COMPAT_HAS_JOURNAL set.*/
/*D0*/  __u8    s_journal_uuid[16]; /* 日志超级快的uuid */
/*E0*/  __le32  s_journal_inum;     /* 日志文件的索引号 */__le32    s_journal_dev;      /* 日志文件的设备号 */__le32    s_last_orphan;      /* 待删除的inode链表起始位置 */__le32 s_hash_seed[4];     /* HTREE散列表种子 */__u8    s_def_hash_version; /* 默认使用的哈希版本 */__u8 s_jnl_backup_type;__le16  s_desc_size;      /* 块组描述符大小 */
/*100*/ __le32  s_default_mount_opts;__le32 s_first_meta_bg;    /* 第一个块组 */__le32   s_mkfs_time;        /* 文件系统创建时间 */__le32    s_jnl_blocks[17];   /* 日志inode的备份 *//* 64bit support valid if EXT4_FEATURE_COMPAT_64BIT */
/*150*/ __le32  s_blocks_count_hi;  /* 块数量高位 */__le32   s_r_blocks_count_hi;    /* 保留块的数量高位 */__le32    s_free_blocks_count_hi; /* 空闲块的数量高位 */__le16    s_min_extra_isize;  /* inode最小大小,单位字节 */__le16   s_want_extra_isize;     /* 新的inode需要保留大小,单位字节 */__le32   s_flags;        /* 各种标志位 */__le16  s_raid_stride;       /* RAID stride */__le16  s_mmp_update_interval;  /* 多挂载检查等待时间,单位秒 */__le64  s_mmp_block;            /* 多挂载保护块 */__le32  s_raid_stripe_width;    /* blocks on all data disks (N*stride)*/__u8 s_log_groups_per_flex;  /* Flexible 块组大小 */__u8 s_checksum_type;    /* 元数据校验算法类型 */__u8 s_encryption_level; /* 加密的版本级别 */__u8   s_reserved_pad;     /* Padding to next 32bits */__le64  s_kbytes_written;   /* 写生命周期,单位千字节 */__le32  s_snapshot_inum;    /* 活动快照的Inode数 */__le32 s_snapshot_id;      /* 活动快照ID */__le64  s_snapshot_r_blocks_count; /* 供活动快照将来使用的保留块数量   */__le32    s_snapshot_list;    /* 磁盘上快照列表头的Inode号   */
#define EXT4_S_ERR_START offsetof(struct ext4_super_block, s_error_count)__le32 s_error_count;      /* fs错误个数 */__le32  s_first_error_time; /* fs第一个错误发生时间 */__le32 s_first_error_ino;  /* 第一个错误涉及的Inode */__le64   s_first_error_block;    /* 第一个错误涉及的块 */__u8 s_first_error_func[32] __nonstring; /* 第一个错误发生的函数 */__le32  s_first_error_line; /* 发生第一个错误的行号 */__le32  s_last_error_time;  /* 最近一次错误的时间 */__le32   s_last_error_ino;   /* 最近一次错误中涉及的inode */__le32 s_last_error_line;  /* 最近一次发生错误的行号 */__le64 s_last_error_block; /* 最近一次错误涉及的块 */__u8    s_last_error_func[32] __nonstring;  /*  最近一次错误发生的函数 */
#define EXT4_S_ERR_END offsetof(struct ext4_super_block, s_mount_opts)__u8  s_mount_opts[64];__le32 s_usr_quota_inum;   /* 用于跟踪用户配额的inode */__le32  s_grp_quota_inum;   /* 用于跟踪组配额的inode */__le32   s_overhead_clusters;    /* 文件系统的开销块/集群 */__le32 s_backup_bgs[2];    /* groups with sparse_super2 SBs */__u8 s_encrypt_algos[4]; /* 使用加密算法种类  */__u8 s_encrypt_pw_salt[16];  /* 用于string2key算法的Salt  */__le32    s_lpf_ino;      /* 索引节点的位置 */__le32 s_prj_quota_inum;   /* 用于跟踪项目配额的inode */__le32  s_checksum_seed;    /* crc32c(uuid) if csum_seed set */__u8 s_wtime_hi; //写入时间__u8  s_mtime_hi; //修改时间__u8  s_mkfs_time_hi;//简历文件系统时间__u8   s_lastcheck_hi;//最近一次检查__u8 s_first_error_time_hi;//第一次错误发生时间__u8   s_last_error_time_hi;//最近一次错误发生时间__u8   s_pad[2];__le32 s_reserved[96];     /* Padding to the end of the block */__le32 s_checksum;     /* crc32c(superblock) */
};

2.块组描述

GDT用于存储块组描述符,其占用一个或者多个数据块,具体取决于文件系统的大小。它主要包含块位图,inode位图和inode表位置,当前空闲块数,inode数以及使用的目录数(用于平衡各个块组目录数),每个块组都对应这样一个描述符,目前该结构占用32个字节,因此对于块大小为4k的文件系统来说,每个块可以存储128个块组描述符。由于GDT对于定位文件系统的元数据非常重要,因此和超级块一样,也对其进行了备份。其结构体是struct ext4_group_desc,位于fs/ext4/ext4.h文件:

struct ext4_group_desc
{__le32 bg_block_bitmap_lo; /* 数据块位图 */__le32   bg_inode_bitmap_lo; /* Inodes位图 */__le32    bg_inode_table_lo;  /* 块组中第一个Inodes表的数据块号 */__le16  bg_free_blocks_count_lo;/* 空闲数据块数量 */__le16 bg_free_inodes_count_lo;/* 空闲数据块数量 */__le16 bg_used_dirs_count_lo;  /* D块组中目录个数 */__le16    bg_flags;       /* EXT4_BG_flags (INODE_UNINIT, etc) */__le32  bg_exclude_bitmap_lo;   /* 排除快照的位图 */__le16  bg_block_bitmap_csum_lo;/* crc32c(s_uuid+grp_num+bbitmap) LE 数据块位图校验 */__le16  bg_inode_bitmap_csum_lo;/* crc32c(s_uuid+grp_num+ibitmap) LE Inodes位图校验 */__le16  bg_itable_unused_lo;   /* 未使用inodes数量 */__le16  bg_checksum;       /* crc16(sb_uuid+group+desc)校验 */__le32   bg_block_bitmap_hi; /* 数据块位图 MSB */__le32   bg_inode_bitmap_hi; /* Inodes位图 MSB */__le32    bg_inode_table_hi;  /* Inodes表块 MSB */__le16    bg_free_blocks_count_hi;/* 空闲块计数MSB */__le16    bg_free_inodes_count_hi;/* 空心啊节点数MSB */__le16   bg_used_dirs_count_hi;  /* 已经使用的目录数量MSB */__le16  bg_itable_unused_hi;    /* 未使用节点数MSB */__le32  bg_exclude_bitmap_hi;   /* 不包括位图块 MSB */__le16  bg_block_bitmap_csum_hi;/* crc32c(s_uuid+grp_num+bbitmap) BE */__le16  bg_inode_bitmap_csum_hi;/* crc32c(s_uuid+grp_num+ibitmap) BE */__u32   bg_reserved;
};

3.数据块位图

块位图用于描述该块组所管理的块的分配状态。如果某个块对应的位未置位,那么代表该块未分配,可以用于存储数据;否则,代表该块已经用于存储数据或者该块不能够使用(譬如该块物理上不存在)。由于块位图仅占一个块,因此这也就决定了块组的大小。如果一个数据块大小是4KB的话,那一个位图块可以表示410248个数据块的使用情况,这也是单个块组具有的最大数据块个数。这样可以算出一个块组大小是128MB。

4.inode位图

Inode位图用于描述该块组所管理的inode的分配状态。我们知道inode是用于描述文件的元数据,每个inode对应文件系统中唯一的一个号,如果inode位图中相应位置位,那么代表该inode已经分配出去;否则可以使用。由于其仅占用一个块,因此这也限制了一个块组中所能够使用的最大inode数量。

5.inode表

Inode表用于存储inode信息。它占用一个或多个块(为了有效的利用空间,多个inode存储在一个块中),其大小取决于文件系统创建时的参数,由于inode位图的限制,决定了其最大所占用的空间。以上这几个构成元素所处的磁盘块成为文件系统的元数据块,剩余的部分则用来存储真正的文件内容,称为数据块,而数据块其实也包含数据和目录。其结构体是struct ext4_inode,位于fs/ext4/ext4.h文件:

struct ext4_inode {__le16    i_mode;     /* 文件类型和访问权限 */__le16   i_uid;      /* 文件所有者ID */__le32 i_size_lo;  /* 文件大小,单位字节 */__le32    i_atime;    /* 访问时间 */__le32    i_ctime;    /* 索引修改时间 */__le32  i_mtime;    /* 文件内容修改时间 */__le32    i_dtime;    /* 删除时间 */__le16    i_gid;      /* 用户组ID */__le16   i_links_count;  /* 连接数量 */__le32    i_blocks_lo;    /* 块数量 */__le32 i_flags;    /* 文件类型 */union {struct {__le32  l_i_version;} linux1;struct {__u32  h_i_translator;} hurd1;struct {__u32  m_i_reserved1;} masix1;} osd1;               /* 特定的os信息1 */__le32    i_block[EXT4_N_BLOCKS];/* 文件内容块号码 */__le32  i_generation;   /* 文件版本 */__le32    i_file_acl_lo;  /* File ACL */__le32    i_size_high;    //文件大小的高位__le32 i_obso_faddr;   /* Obsoleted fragment address */union {struct {__le16   l_i_blocks_high; /* 数据块数高16位 */__le16   l_i_file_acl_high;//高16位的文件ACL__le16    l_i_uid_high;   /* 所有者id的高16位 */__le16  l_i_gid_high;   /* 组ID的高16位 */__le16    l_i_checksum_lo;/* crc32c(uuid+inum+inode) LE */__le16    l_i_reserved;} linux2;struct {__le16    h_i_reserved1;  /* Obsoleted fragment number/size which are removed in ext4 */__u16 h_i_mode_high;__u16 h_i_uid_high;__u16  h_i_gid_high;__u32  h_i_author;} hurd2;struct {__le16   h_i_reserved1;  /* Obsoleted fragment number/size which are removed in ext4 */__le16    m_i_file_acl_high;__u32 m_i_reserved2[2];} masix2;} osd2;               /* 特定的os信息2 */__le16    i_extra_isize;//extra大小__le16   i_checksum_hi;  /* crc32c(uuid+inum+inode) BE */__le32  i_ctime_extra;  /* extra修改inode时间(nsec << 2 | epoch) */__le32  i_mtime_extra;  /* extra修改文件时间(nsec << 2 | epoch) */__le32  i_atime_extra;  /* extra访问时间(nsec << 2 | epoch) */__le32  i_crtime;       /* 文件创建时间(nsec << 2 | epoch) */__le32  i_crtime_extra; /* extra 文件创建时间 (nsec << 2 | epoch) */__le32  i_version_hi; /* 64位版本号高32位 */__le32  i_projid;   /* 项目ID */
};

Ext4预留了一些inode做特殊特性使用

Inode号 用途
0 不存在0号inode
1 损坏数据块链表
2 根目录
3 ACL索引
4 ACL数据
5 Boot loader
6 未删除的目录
7 预留的块组描述符inode
8 日志inode
11 第一个非预留的inode,通常是lost+found目录

6.数据块

首先,我们要知道每个inode结构体的 __le32 i_block[EXT4_N_BLOCKS] 参数是文件内容,他有多大呢?看下面:

/** Constants relative to the data blocks*/
#define EXT4_NDIR_BLOCKS        12
#define EXT4_IND_BLOCK          EXT4_NDIR_BLOCKS
#define EXT4_DIND_BLOCK         (EXT4_IND_BLOCK + 1)
#define EXT4_TIND_BLOCK         (EXT4_DIND_BLOCK + 1)
#define EXT4_N_BLOCKS           (EXT4_TIND_BLOCK + 1)

从上面的代码可以知道EXT4_N_BLOCKS为15,就是说这个参数可以存放15*4=60字节。所以文件的大小决定着他的存放方式。

如果一个文件的大小小于60字节,文件的内容是直接放在inode中,没有对应的数据块。
如果一个文件的大小大于60字节,小于60KB,为什么是60KB,因为15个数组,每个数组存放一个数据块编号,每个数据块为4K(当然如果格式化时不是4K,自己计算一下),4KB*15=60KB。这时候文件内容存放在数据块。
如果一个文件的大小大于60KB,就需要使用到Extent 树结构体了。用extent树代替了逻辑块映射,使用extent,用一个struct ext4_extent结构就可以映射多个数据块。
下面看看Extent 树的数据结构:

/** This is the extent on-disk structure.* It's used at the bottom of the tree.*/
struct ext4_extent {__le32  ee_block;   /* exient叶子的第一个数据块号 */__le16    ee_len;     /* exient叶子的数据块数量 */__le16  ee_start_hi;    /* 物理数据块的高16位 */__le32  ee_start_lo;    /* 物理数据块的低32位 */
};/** This is index on-disk structure.* It's used at all the levels except the bottom.*/
struct ext4_extent_idx {__le32  ei_block;   /* 索引包含的逻辑数据块 */__le32  ei_leaf_lo; /* 指向下一级的数据块,可以是下一个索引或者叶子节点  */__le16    ei_leaf_hi; /* 物理数据块的高16位 */__u16   ei_unused;  //预留项,实际没有用到
};/** Each block (leaves and indexes), even inode-stored has header.*/
struct ext4_extent_header {__le16   eh_magic;   /* 可以支持不同的格式 */__le16   eh_entries; /* 有效项的个数 */__le16  eh_max;     /* 项的存储容量 */__le16  eh_depth;   /* 树的深度 */__le32    eh_generation;  /* 树的代数 */
};

Extents是以树的方式安排的,Extent树的每个节点都以一个ext4_extent_header开头,如果节点是内部节点(ext4_extent_header.eh_depth>0),ext4_extent_header后面紧跟的是ext4_extent_header .eh_entries个索引项struct ext4_extent_idx,每个索引项指向该extent树中一个包含更多的节点的数据块。如果节点是叶子节点(ext4_extent_header.eh_depth==0),ext4_extent_header后面紧跟的是ext4_extent_header .eh_entries个struct ext4_extent数据结构。这些ext4_extent结构指向文件数据块。Extent树的根结点存储在inode.i_blocks中,可以存储文件的前4个extents而不需额外的元数据块。如图所示:

事实上,系统会根据文件大小定义树的深度,也就是ext4_extent_header.eh_depth,上图就是深度为2的示意图,其中“存放extent索引的数据块”这一层可以没有也可以有多层。最终都会找到“存放extent节点的数据块”,并且通过“存放extent节点的数据块”找到存放文件内容的数据块。

总结:上面的数据结构都是硬件设备定好的,ext4只是把这些数据结构一个个读出来再分析哪些是目录哪些是文件哪些是文件内容而已。在ext4文件系统挂载的第一步是读取前512字节的MBR数据结构,确定是ext4格式的,并且分析有几个分区。然后根据分区的信息(分区类型,起始地址,长度)去到块组0中读取超级块,读取超级块后紧接着就是块组描述符表,通过块组描述符表就可以知道数据块位图,inode位图,inode表所在的数据块,位图是用来确定数据块和inode的使用情况,inode表记录着数据,前面的10个inode都有着特殊作用,其中inode2是根目录,里面记录着根目录的各种信息,从此就散发出来一连串的inode,他们可以代表着一个文件或者目录,通过他们这些inode可以找到对应的数据块。

linux虚拟文件系统(二)-ext4文件系统结构相关推荐

  1. Linux虚拟文件系统vfs与文件操作始末

    虚拟文件系统(vfs)是Linux内核的子系统,其设计目的是对用户层(系统调用)屏蔽底层(各特定文件系统)实现的复杂性,对底层提供统一的接口与数据结构,使得能衔接各个特定文件系统(如ext2.ext3 ...

  2. Linux系统介绍(二)文件系统结构

    Table of Contents 概述 根目录/ 二进制目录 配置文件目录 数据目录 内存目录 /dev目录 物理设备文件 /dev/tty跟/dev/pts /dev/null /proc目录 / ...

  3. linux 目录防篡改,一种基于Linux虚拟文件系统的防篡改方法及系统的制作方法

    一种基于Linux虚拟文件系统的防篡改方法及系统的制作方法 [技术领域] [0001]本发明涉及文件防护技术领域,特别涉及一种基于Linux虚拟文件系统的防篡改方法及系统. [背景技术] [0002] ...

  4. Linux虚拟文件系统:数据结构与文件系统注册、文件打开读写

    数据结构 超级块 - super_block 索引节点 - inode 目录项 - dentry 文件结构 - file 虚拟文件系统实现 注册文件系统 - register_filesystem 打 ...

  5. Linux 虚拟文件系统四大对象:超级块、inode、dentry、file之间关系

    一:文件系统 1. 什么是文件系统? 操作系统中负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统. 通常文件系统是用于存储和组织文件的一种机制,便于对文件进行方便的查找与访问. 文件系统 ...

  6. linux 文件系统 vfs,linux虚拟文件系统vfs

    <操作系统>课程设计报告课程设计题目:操作系统课程设计 设计时间:2016/1/10一. 课程设计目的与要求需要完成的内容:(1) 安装虚拟机:Vmware.Vmware palyer ( ...

  7. Linux虚拟文件系统

    从文件 I/O 看 Linux 的虚拟文件系统 1 引言 Linux 中允许众多不同的文件系统共存,如 ext2, ext3, vfat 等.通过使用同一套文件 I/O 系统 调用即可对 Linux ...

  8. linux虚拟文件系统浅析

    linux虚拟文件系统浅析 虚拟文件系统(VFS) 在我看来, "虚拟"二字主要有两层含义: 1, 在同一个目录结构中, 可以挂载着若干种不同的文件系统. VFS隐藏了它们的实现细 ...

  9. Linux 虚拟文件系统(一)概述

    Linux 虚拟文件系统(一)概述 tags: Linux源码 Linux 虚拟文件系统一概述 文章梗概 正文 文件系统 虚拟文件系统架构 虚拟文件系统如何知道可用的文件系统有哪些的 不太喜欢的环节 ...

  10. linux var mqm权限,Linux MQ中间件/var/mqm文件系统结构与错误日志分析

    Linux MQ中间件/var/mqm文件系统结构与错误日志分析 Linux MQ中间件/var/mqm文件系统结构与错误日志分析 本文转自 http://www.wo81.com/tec/mid/m ...

最新文章

  1. 【c语言】计算三角形面面积
  2. 分享Silverlight/WPF/Windows Phone一周学习导读(10月1日-10月15日)
  3. Handlebars的基本用法
  4. 开源分布式数据库RadonDB的核心技术与实现
  5. C语言指针和链表的体会
  6. mysql获取时间_时间类型_时间格式化
  7. SAP Spartacus如何创建自定义route页面
  8. Spring框架中的单例Bean是线程安全的吗
  9. python isalpha函数用法_python中string模块各属性以及函数的用法
  10. 计算机背板知识,你知道背板的选购技巧吗?
  11. Android笔记 对话框demo大全
  12. C语言内存的动态分配
  13. (转)大公司里怎样开发和部署前端代码?
  14. 标准表达式中数据类型不匹配怎么解决_关于Inventor驱动尺寸中表达式的使用问题...
  15. 【HDU5008】Boring String Problem(后缀数组+二分查找+st表)
  16. CSP2019滚粗记
  17. 怎么将PDF文件分成若干个小文件呢
  18. win10桌面计算机打不开,win10桌面此电脑右键管理打不开处理方法
  19. JAVA根据PDF文件生成图片
  20. Eclipes配置代码模糊匹配(部分匹配)

热门文章

  1. 解决lay out无法使用闪退问题,SketchUp 2022 MAC中文 (草图大师) 支持M1intel芯片,支持monterey最新系统
  2. sketch up rbs/rbz/rb插件安装方法
  3. codeblocks安装包和主题及中文语言包百度云下载
  4. SDPT3 4.0版:半正定二次线性规划的MATLAB软件包
  5. mouse without borders其他选择中英对照说明
  6. 【Web前端】笔试题含解析
  7. css 六角形_创建数字六角形瓷砖图(第1部分)
  8. 公司财务记账软件免费版下载地址
  9. Qt第一章:pyside6安装与配置
  10. 筱筱看博客(uni-app、mumu模拟器、真机测试)