ZFS源代码之旅——ZAP模块分析(一)

来源:互联网 发布:韩国网络作家 编辑:程序博客网 时间:2024/06/07 02:50

0. 背景介绍

该内容主要来自于《ZFS On-Disk Specification》一书的第5章,其中部分内容依据ZFS代码有所更新。


ZAP(ZFS Attribute Process)模块位于DMU模块之上,用来对ZAP对象进行操作。ZFS通过ZAP对象来存储名字值对(name-value pairs)形式的属性,该属性的名字部分为一个以空字符('\0')结尾的字符串(最长为256字节),而属性的值部分为一组整数,该值的大小仅仅受限于ZAP数据块的大小。

ZAP对象可以用来保存数据集(dataset)的属性,用来查找文件系统中的对象,用来保存存储池的属性等等,它在ZFS整个系统中有十分重要的作用。下面的这张表包含了ZFS中ZAP类型的对象。(更多的ZAP类型的对象可以在dmu.h中定义的dmu_object_type枚举中查看)

ZAP对象类型DMU_OT_OBJECT_DIRECTORYDMU_OT_DSL_DIR_CHILD_MAPDMU_OT_DSL_DS_SNAP_MAP
DMU_OT_DSL_PROPS
DMU_OT_DIRECTORY_CONTENTS
DMU_OT_MASTER_NODE
DMU_OT_DELETE_QUEUE
DMU_OT_ZVOL_PROP

ZAP对象有两种形式:microzap对象和fatzap对象。microzap对象可以认为是轻量级的fatzap对象,它为较小的属性(数量少,名字和值的长度都有限)提供了一种快速存取和查找机制。而fatzap用来存储包含大量名字值对(且名字和值的长度较大)的属性。

ZFS根据下面3个条件决定应该是否可以用microzap对象来存储某一组属性的名字值对

1. 所有的名字值对都可以存入一个数据块中。对于ZFS来说,最大的数据块是128KB,即总共可以存放2047个microzap条目。(下面会详细介绍microzap的结构)

2. 所有属性的值都为uint64_t类型

3. 每个属性的名字部分为少于50个字节的字符串(包括结尾的空字符)

如果以上有一个条件未满足,则采用fatzap存放。


每个ZAP对象的数据块的头64个字节用来表明该数据块中包含的ZAP数据的类型,一共有3种类型的ZAP数据块,如下表所示:

ZAP Object Block Types标识符描述值ZBT_MICRO该数据块中存放microzap条目(1ULL<<63)+3ZBT_HEADER该块属于一个fatzap对象,该标识符只出现在fatzap对象的第一个数据块中(1ULL<<63)+1ZBT_LEAF该块属于一个fatzap对象,该标识符出现在除了第一块之外所有的fatzap对象的数据块中(1ULL<<63)+0


1. microzap磁盘结构

microzap实现了存取少量属性的一种简单方法。一个microzap对象只包含一个数据块,该数据块上存放microzap的条目(由mzap_ent_phys 结构体表示),每个microzap条目结构存放一个属性(名字值对)。

一个microzap数据块的结构如下:

1. 数据块开始的128个字节为一个microzap头部结构体(由mzap_phys结构体表示),该结构体定义如下:

typedef struct mzap_phys {uint64_t mz_block_type;/* ZBT_MICRO */uint64_t mz_salt;uint64_t mz_normflags;uint64_t mz_pad[5];mzap_ent_phys_t mz_chunk[1];} mzap_phys_t;

该结构体包含了一个64比特的ZBT_MICRO值,用来表示该数据块为一个microzap的数据块。

mz_salt为一个hash函数的参数,它可以使每个zap结构的hash函数不一样。

mz_normflags目前还不了解它的作用是什么。==toChange==

mz_pad为填充,以便下一个结构体从64字节的位置开始。

下面是一个mzep_ent_phys的结构体。

2. 在microzap头部结构体之后,便是一组microzap条目,即mzap_ent_phys结构体,从64字节位置开始,直到数据块结束,条目数量和数据块大小相关,最大可以在128KB的数据块上存放2047个条目。mzap_ent_phys结构体定义如下:

#define MZAP_ENT_LEN    64#define MZAP_NAME_LEN   (MZAP_ENT_LEN - 8 - 4 - 2)typedef struct mzap_ent_phys {uint64_t mze_value;uint32_t mze_cd;uint16_t mze_pad;/* in case we want to chain them someday */char mze_name[MZAP_NAME_LEN];} mzap_ent_phys_t;
mze_value中存放该名字值对的值,为uint_64类型。

mze_cd用来在两个键hash值相同时对它们进行区分(在之后介绍)。

mze_name用来表示名字值对中的名字,最大不超过50字节。


这样,一个microzap的磁盘结构如下图所示:



2. fatzap磁盘结构

fatzap结构用一种灵活的方式来存储大量的属性,或者是表示属性的名字值对有较长的名字或较长的值(不是uint64_t类型)的情况。该节首先对fatzap的总体进行描述,再详细介绍fatzap中的各个数据结构。

fatzap对象中所有的条目(名字值对)都通过属性名的64比特hash值来组织。fatzap用该hash值在一个hash表中进行索引,以得到存放属性名字值对的数据块。hash值中用来进行索引的比特数(prefix)由hash表的大小决定,hash表会随着fatzap中存放的条目增多而变大。每一个hash表项指向一个fatzap数据块(叶数据块),用zap_leaf_phys结构表示,每一个叶数据块又包含了很多个子块(chunk),用zap_leaf_chunk结构表示。每一个属性的名字和值便存在多个子块之中。下图简要表示了fatzap中数据块的组织方式,详细的结构再之后说明。


2.1 zap_phys_t

fatzap对象的第一个数据块上保存了一个zap_phys_t结构体,根据hash表的大小,会决定是否将hash表直接存放在zap_phys_t结构体中(hash表要占用第一个数据块一半的空间,若数据块为128KB,则其中可存储8192个索引条目。当索引表大于这个数时,需要将它放在zap_phys_t之外)

zap_phys_t结构体的定义如下:

typedef struct zap_phys {uint64_t zap_block_type;/* ZBT_HEADER */uint64_t zap_magic;/* ZAP_MAGIC */struct zap_table_phys {uint64_t zt_blk;/* starting block number */ uint64_t zt_numblks;/* number of blocks */uint64_t zt_shift;/* bits to index it */uint64_t zt_nextblk;/* next (larger) copy start block */uint64_t zt_blks_copied; /* number source blocks copied */} zap_ptrtbl;uint64_t zap_freeblk;/* the next free block */uint64_t zap_num_leafs;/* number of leafs */uint64_t zap_num_entries;/* number of entries */uint64_t zap_salt;/* salt to stir into hash function */uint64_t zap_normflags;/* flags for u8_textprep_str() */uint64_t zap_flags;/* zap_flags_t *//* * This structure is followed by padding, and then the embedded * pointer table.  The embedded pointer table takes up second * half of the block.  It is accessed using the * ZAP_EMBEDDED_PTRTBL_ENT() macro. */} zap_phys_t;

下面依次介绍结构体中的各个字段:

zap_block_type,ZAP数据块类型的标识符。对于fatzap对象的第一个数据块来说,该字段设为ZBT_HEADER;

zap_magic,ZAP的magic number,该字段为0x2F52AB2AB(zfs-zap-zap)

zap_table_phys,用来表示hash表的结构体:

        zt_blk,hash表的第一个数据块号。当hash表不在zap_phys_t结构体内部时该字段有效,否则设为0;

        zt_numblks,hash表占用的数据块数。当hash表不在zap_phys_t结构体内部时该字段有效,否则设为0;

        zt_shift,hash值中用来索引hash表的比特数。对于128KB的数据块,当hash表在zap_phys_t结构体内部时,该值为13;

        zt_nextblk和zt_blks_copied在hash表改变大小时采用;

zap_freeblk,该字段用来记录第一个可以分配一个新的zap_leaf的空闲ZAP数据块;

zap_num_leafs,该字段记录该ZAP对象中zap_leaf_phys_t结构体的数量;

zap_salt,该值作为hash函数的参数,可是各个ZAP对象的hash函数不同;

zap_num_entries,该ZAP对象中存放的属性(名字值对)数;

zap_normflags和zap_flags还未搞懂。==toChange==


2.2 hash表

hash表中存放uint64类型的数,表示它指向的一个zap_leaf_phys_t结构体所在的数据块号。通过一个属性名字的hash值,我们首先得到该属性位于哪个数据块上,之后再去该数据块上查找该属性的值。

提到hash表,首先要问的是它如何处理冲突。通常的处理方法是将多个hash值相同的元素通过链表依次链接。(这也是ZFS最早采用的方法,“The pointer table is a hash table which uses a chaining method to handle collisions.” (ZFS On-Disk Specification)),但是新的ZFS采用了一种新的方法来处理冲突,即当发生冲突时,则将发生冲突的叶数据块分裂为两个,并改变zap_leaf_phys_t结构中的prefix值,具体的算法之后再详细介绍。


2.3 zap_leaf_phys_t结构体

hash表中的元素指向一个数据块号,该数据块为一个zap_leaf_phys_t类型的结构体。zap_leaf_phys_t结构体包含一个头部,一个hash表(叶数据块内部hash表)和多个子块(chunk)。

zap_leaf_phys_t结构体定义如下:

typedef struct zap_leaf_phys {struct zap_leaf_header {uint64_t lh_block_type;/* ZBT_LEAF */uint64_t lh_pad1;uint64_t lh_prefix;/* hash prefix of this leaf */uint32_t lh_magic;/* ZAP_LEAF_MAGIC */uint16_t lh_nfree;/* number free chunks */uint16_t lh_nentries;/* number of entries */uint16_t lh_prefix_len;/* num bits used to id this */                /* above is accessable to zap, below is zap_leaf private */uint16_t lh_freelist;/* chunk head of free list */uint8_t lh_flags;/* ZLF_* flags */uint8_t lh_pad2[11];} l_hdr; /* 2 24-byte chunks *//* * The header is followed by a hash table with * ZAP_LEAF_HASH_NUMENTRIES(zap) entries.  The hash table is * followed by an array of ZAP_LEAF_NUMCHUNKS(zap) * zap_leaf_chunk structures.  These structures are accessed * with the ZAP_LEAF_CHUNK() macro. */uint16_t l_hash[1];}zap_leaf_phys_t;

下面依次介绍zap_leaf_phys_t结构体中的各个字段。

lh_block_type, ZAP数据块类型,设为ZBT_LEAF;

lh_prefix和lh_prefixlen,每个叶数据块上存放那些hash值的前lh_prefixlen位比特和lh_prefix相等的属性。lh_prefixlen不能大于zt_shift,因此,有可能多个fatzap的hash表的表项会指向同一个叶数据块(如,当zt_shift = 13时, 1100011000xxx都会指向lh_prefix = 1100011000,lh_prefixlen为10的叶数据块);

lh_magic为叶数据块的magic number,为0x2AB1EAF(zap-leaf);

lh_nfree表示该叶数据块上的空闲子块数;

lh_nentries表示该叶数据块上存放的ZAP属性(名字值)对数;

lh_freelist表示空闲子块的头子块;

lh_flags待研究 ==toChange==

l_hash表示叶数据块的hash表,用来快速查找zap_leaf_entry型子块。该hash表的冲突通过将冲突子块组成链表的形式解决。

2.4 zap_leaf_chunk结构体

每个ZAP叶数据块包含了一个子块数组。共有3种类型的子块:zap_leaf_entry,zap_leaf_array和zap_leaf_free。每个存入fatzap的属性都通过一个zap_leaf_entry和多个zap_leaf_array共同表示,下图首先介绍了子块之间的链接关系,之后在详细介绍zap_leaf_chunk结构体。


zap_leaf_chunk联合的定义:

typedef union zap_leaf_chunk {struct zap_leaf_entry {uint8_t le_type; /* always ZAP_CHUNK_ENTRY */uint8_t le_value_intlen;/* size of value's ints */uint16_t le_next;/* next entry in hash chain */uint16_t le_name_chunk;/* first chunk of the name */uint16_t le_name_numints;/* ints in name (incl null) */uint16_t le_value_chunk;/* first chunk of the value */uint16_t le_value_numints;/* value length in ints */uint32_t le_cd;/* collision differentiator */uint64_t le_hash;/* hash value of the name */} l_entry;struct zap_leaf_array {uint8_t la_type;/* always ZAP_CHUNK_ARRAY */uint8_t la_array[ZAP_LEAF_ARRAY_BYTES];uint16_t la_next;/* next blk or CHAIN_END */} l_array;struct zap_leaf_free {uint8_t lf_type;/* always ZAP_CHUNK_FREE */uint8_t lf_pad[ZAP_LEAF_ARRAY_BYTES];uint16_t lf_next;/* next in free list, or CHAIN_END */} l_free;} zap_leaf_chunk_t;

下面详细介绍联合中的各个结构体的定义。

zap_leaf_entry结构体:叶hash表指向这种类型的子块,该类型的子块内有指向zap_leaf_array子块的指针,一个属性的名字和值都存放在多个zap_leaf_array中。

        le_type: ZAP_LEAF_ENTRY = 252;

        le_value_intlen: 该属性值的int占用多少字节;==toConfrim==

        le_next: 指向相同hash值的下一个zap_leaf_entry结构体的指针;

        le_namechunk: 指向存储属性名字的一系列zap_leaf_array类型子块的头子块的子块号;

        le_name_numints: 属性名字总共占用的字节数,包括结尾的空字符;

        le_valuechunk: 指向存储属性值的一系列zap_leaf_array类型子块的头子块的子块号;

        le_value_numints: 属性值总共占用的字节数(一个名字值对的值可能是一组整数);

        le_cd: 当多个属性的hash值相同时,通过不同的le_cd来进行区分;

        le_hash: 该属性对应的hash值;

zap_leaf_array结构体:用来保存属性名字或属性值的单位,多个该类型的chunk可以链接起来表示一个比较长的属性名字或比较大的属性值。

        la_type: ZAP_LEAF_ARRAY = 251;

        la_array: 21字节的数组用来表示一个属性的名字或值的一部分;

        la_next: 16比特整数,用来表示该名字或值的下一个部分所在的zap_leaf_array结构体所在的子块;

zap_leaf_free结构体:用来管理空闲的子块,通过链表的形式链接。

        lf_type: ZAP_LEAF_FREE = 253;

        lf_next: 16比特整数,用来表示下一个空闲子块的位置;

3. 结语

ZAP对象在ZFS中有非常重要的地位,这篇文章详细的描述了ZAP对象的两种类型,以及它们是如何在磁盘上存储的。在接下来的几篇文章中,我会详细分析ZAP的两种类型是如何实现名字值对的存储,查找和删除等操作。

       

原创粉丝点击