Salle 2
L’arbre et l’uberblock
ZFS n’a pas de fsck. Après un plantage, le pool doit s’ouvrir soit dans l’état d’avant le dernier groupe de transactions, soit dans l’état d’après, jamais dans un mélange des deux, alors qu’un seul txg écrit des milliers de blocs sur plusieurs disques.
Tout le changement d’état doit donc se ramener à une seule petite écriture, qui a eu lieu ou non. Et cette écriture ne doit pas pouvoir détruire l’état précédent si le courant part en plein milieu. Le code y arrive avec un arbre suspendu à un seul pointeur, une règle qui interdit d’écraser un bloc vivant, et un ordre d’écriture à la fin de chaque txg.
1 Tout est suspendu à un pointeur
L’uberblock contient un seul pointeur de bloc, ub_rootbp. Il pointe vers le
meta objset du pool, le MOS, qui commence par un dnode, le meta-dnode, dont les blocs
sont des blocs de dnodes. Un dnode est la racine d’un objet : ses dn_blkptr[]
pointent vers dn_nlevels − 1 niveaux de blocs indirects, et le dernier niveau
pointe vers les données. Chaque pointeur est un blkptr_t de 128 octets qui contient
jusqu’à trois adresses et la somme de contrôle du bloc d’en dessous. Depuis l’uberblock, il
n’en faut pas plus pour trouver chaque bloc du pool. La figure dessine un fichier à trois niveaux et quatre blocs de
données sous l’objset.
- ancienne copie
- en mémoire, pas écrite
Vue 3D du même état. Faites glisser, ou utilisez les flèches sur la vue active, pour la tourner ; + et − pour zoomer. Toutes les commandes ci-dessus marchent de même en 3D.
Pas de WebGL dans ce navigateur : la figure reste en 2D, qui montre le même état.
La validation
Le txg 4211 est ouvert : 5 blocs à écrire. Étape suivante : spa_sync les écrit.
- suivant spa_sync écrit les nouveaux blocs du txg 4211, chacun à un nouvel endroit. code
- à faire Vidage : chaque disque écrit dans ce txg vide son cache d’écriture. code
- à faire Labels pairs : L0 et L2 reçoivent la configuration du txg 4211. code
- à faire Uberblocks : le txg 4211 va dans la case 115 des quatre anneaux. code
- à faire Labels impairs : L1 et L3 reçoivent la configuration du txg 4211. code
Dernier import
Pas encore d’import. Débranchez la prise à n’importe quelle étape, puis importez le pool.
Sur le disque
| Label | Début code | Configuration | Case 114 (+242 Kio) | Case 115 (+243 Kio) |
|---|---|---|---|---|
| L0 | 0 Mio | txg 4200 | txg 4210 | txg 4083 |
| L1 | 0,25 Mio | txg 4200 | txg 4210 | txg 4083 |
| L2 | 1023,5 Mio | txg 4200 | txg 4210 | txg 4083 |
| L3 | 1023,75 Mio | txg 4200 | txg 4210 | txg 4083 |
Un disque de 1 Gio, ashift 9 : chaque anneau a 128 cases de 1 Kio. Pas à l’échelle : un label fait 256 Kio, la zone de démarrage 3,5 Mio, le reste porte les blocs.
Version texte de cette figure
L’uberblock le plus récent sur le disque est celui du txg 4210, dans la case 114. Il pointe vers l’objset du txg 4210. L’arbre qu’il atteint : objset (txg 4210), dnodes (txg 4210), L2 n°0 (txg 4210), L1 n°0 (txg 4190), L1 n°1 (txg 4210), D0 (txg 4190), D1 (txg 4120), D2 (txg 4180), D3 (txg 4210).
Le txg 4211 a de nouvelles copies de objset, dnodes, L2 n°0, L1 n°1 et D2 ; Le txg 4211 est ouvert : 5 blocs à écrire. Étape suivante : spa_sync les écrit.
Labels : L0 configuration txg 4200, case 115 txg 4083 ; L1 configuration txg 4200, case 115 txg 4083 ; L2 configuration txg 4200, case 115 txg 4083 ; L3 configuration txg 4200, case 115 txg 4083.
2 Une écriture copie tout son chemin
Un bloc modifié n’est jamais réécrit sur place : il reçoit une nouvelle adresse. Son parent
contient son pointeur, adresse et somme de contrôle, donc le parent change aussi : dbuf_dirty
marque le parent comme modifié, et ainsi de suite jusqu’au dnode, marqué
à son tour dans son bloc de dnodes, jusqu’à l’objset. Un bloc de données modifié donne une nouvelle copie de
lui-même et de chaque bloc de son chemin vers la racine ; tous les autres blocs sont partagés par l’ancien arbre et
le nouveau. L’ancien arbre n’est pas touché : le dernier uberblock synchronisé l’atteint encore tout entier. Dans
la figure, écrivez D2, puis D3 dans le même txg : leurs ancêtres communs ne sont copiés qu’une fois. La
salle suivante suit les sommes de contrôle le long de ce chemin.
3 La validation, en quatre temps
À la fin d’un txg, une fois que spa_sync a écrit les nouveaux blocs, vdev_config_sync
enchaîne quatre étapes dans un ordre fixe. D’abord, il vide le cache d’écriture de chaque
disque écrit dans le txg, pour que le nouvel arbre soit sur un support stable avant que quoi que ce soit pointe
vers lui. Puis il écrit les labels pairs, L0 et L2, avec la nouvelle configuration. Puis
l’uberblock, dans la case ub_txg modulo le nombre de cases des quatre
labels, que l’uberblock du txg précédent n’occupe pas : cette écriture est la validation, l’instant où le
pointeur racine bascule vers le nouvel arbre. Enfin, les labels impairs, L1 et L3. Chaque
disque porte quatre labels de 256 Kio, deux à son début et deux à sa fin, et chaque label
se termine par un anneau de 128 Kio de cases d’uberblock : 128 cases sur les secteurs de
512 octets de la figure, 32 sur des disques à secteurs de 4 Kio.
Les labels ne sont écrits que pour les vdevs dont la configuration a changé dans ce txg. Dans la plupart des txg, aucune n’a changé : les temps 2 et 4 n’écrivent rien, et seul l’uberblock est nouveau. Désactivez « La configuration des vdevs change dans ce txg » et refaites les étapes.
4 Débrancher la prise, puis importer
À l’import, ZFS lit chaque case de chaque label. Une case ne compte que si
sa somme de contrôle et son nombre magique sont justes, donc une écriture déchirée est
ignorée ; parmi les autres, l’ub_txg le plus élevé gagne. Puis la
configuration est lue dans le label dont la configuration est la plus récente sans être
plus récente que cet uberblock. Débranchez la prise à chaque étape et importez. Avant le temps 3, aucun
uberblock du nouveau txg n’existe : l’ancien gagne, les labels pairs déjà écrits sont plus récents que lui et sont
écartés, les labels impairs lui correspondent encore. Dès qu’une copie du nouvel uberblock est arrivée, elle
gagne, et les labels pairs lui correspondent. L’arbre est l’ancien ou le nouveau, jamais un mélange. Réglez le
nombre d’écritures d’uberblock qui arrivent avant une coupure au temps 3 : zéro donne le cas (1) du commentaire du
code, une ou plus le cas (2).
L’idée, à trois profondeurs
En une phrase
Chaque bloc du pool est suspendu au seul pointeur de l’uberblock, une écriture copie son chemin au lieu de l’écraser, et le nouvel uberblock va dans une case que l’ancien n’occupe pas, une fois le nouvel arbre sur le disque : le pool est donc l’ancien arbre ou le nouveau, quel que soit l’instant où le courant part.
En un paragraphe
ub_rootbp pointe vers l’objset_phys_t du MOS ; les blocs de son meta-dnode contiennent
des dnodes ; les dn_blkptr[] d’un dnode mènent, à travers dn_nlevels − 1 niveaux de blocs
indirects, jusqu’aux données. dbuf_dirty marque le parent d’un bloc, récursivement, si bien qu’une
feuille modifiée donne à la synchronisation de nouvelles copies de la feuille et de tous ses ancêtres, chacune à une
nouvelle adresse. vdev_config_sync vide ensuite le cache des disques écrits dans le txg, écrit la
configuration des labels pairs, écrit l’uberblock dans la case ub_txg %
VDEV_UBERBLOCK_COUNT des quatre labels et le vide du cache, puis écrit les labels impairs. À l’import,
vdev_uberblock_load garde l’uberblock valide au txg le plus élevé, et
vdev_label_read_config prend le label dont le txg de configuration est le plus élevé sans dépasser le
sien. Une coupure avant l’arrivée de tout nouvel uberblock laisse l’ancien uberblock et les labels impairs ; une
coupure après l’arrivée d’un seul laisse le nouvel uberblock et les labels pairs. Chacune de ces paires est
cohérente.
En détail
Le sommet de l’arbre. L’uberblock désigne un txg et un pointeur de bloc.
struct uberblock
Extrait de include/sys/uberblock_impl.h, lignes
122–139, OpenZFS zfs-2.4.4.
Voir sur GitHub
struct uberblock {
uint64_t ub_magic; /* UBERBLOCK_MAGIC */
uint64_t ub_version; /* SPA_VERSION */
uint64_t ub_txg; /* txg of last sync */
uint64_t ub_guid_sum; /* sum of all vdev guids */
uint64_t ub_timestamp; /* UTC time of last sync */
blkptr_t ub_rootbp; /* MOS objset_phys_t */
/* highest SPA_VERSION supported by software that wrote this txg */
uint64_t ub_software_version;
/* Maybe missing in uberblocks we read, but always written */
uint64_t ub_mmp_magic; /* MMP_MAGIC */
/*
* If ub_mmp_delay == 0 and ub_mmp_magic is valid, MMP is off.
* Otherwise, nanosec since last MMP write.
*/
uint64_t ub_mmp_delay;-
L125
ub_txg: le txg que cet uberblock valide. À l’import, le plus élevé des valides gagne. -
L127
ub_timestamp: départage deux uberblocks du même txg. -
L128
ub_rootbp: le seul pointeur.dsl_pool_sync_mosle fixe une fois le MOS écrit.
objset_phys_t
Extrait de include/sys/dmu_objset.h, lignes
80–95, OpenZFS zfs-2.4.4.
Voir sur GitHub
typedef struct objset_phys {
dnode_phys_t os_meta_dnode;
zil_header_t os_zil_header;
uint64_t os_type;
uint64_t os_flags;
uint8_t os_portable_mac[ZIO_OBJSET_MAC_LEN];
uint8_t os_local_mac[ZIO_OBJSET_MAC_LEN];
char os_pad0[OBJSET_PHYS_SIZE_V2 - sizeof (dnode_phys_t)*3 -
sizeof (zil_header_t) - sizeof (uint64_t)*2 -
2*ZIO_OBJSET_MAC_LEN];
dnode_phys_t os_userused_dnode;
dnode_phys_t os_groupused_dnode;
dnode_phys_t os_projectused_dnode;
char os_pad1[OBJSET_PHYS_SIZE_V3 - OBJSET_PHYS_SIZE_V2 -
sizeof (dnode_phys_t)];
} objset_phys_t;-
L81
Un objset commence par un dnode entier, le meta-dnode. Ses blocs sont les blocs de dnodes de l’objset : l’objet n est le dnode n. L’objset propre d’un système de fichiers est suspendu au MOS de la même façon, par le
ds_bpde son dataset.
dnode_phys_t
Extrait de include/sys/dnode.h, lignes
213–229, OpenZFS zfs-2.4.4.
Voir sur GitHub
typedef struct dnode_phys {
uint8_t dn_type; /* dmu_object_type_t */
uint8_t dn_indblkshift; /* ln2(indirect block size) */
uint8_t dn_nlevels; /* 1=dn_blkptr->data blocks */
uint8_t dn_nblkptr; /* length of dn_blkptr */
uint8_t dn_bonustype; /* type of data in bonus buffer */
uint8_t dn_checksum; /* ZIO_CHECKSUM type */
uint8_t dn_compress; /* ZIO_COMPRESS type */
uint8_t dn_flags; /* DNODE_FLAG_* */
uint16_t dn_datablkszsec; /* data block size in 512b sectors */
uint16_t dn_bonuslen; /* length of dn_bonus */
uint8_t dn_extra_slots; /* # of subsequent slots consumed */
uint8_t dn_pad2[3];
/* accounting is protected by dn_dirty_mtx */
uint64_t dn_maxblkid; /* largest allocated block ID */
uint64_t dn_used; /* bytes (or sectors) of disk space */- L215 La taille des blocs indirects de l’objet, 128 Kio au plus : 1024 pointeurs de bloc de 128 octets.
- L216 La hauteur de l’arbre de l’objet ; 1 quand le dnode pointe directement vers les données.
-
L217
Combien des pointeurs racines,
dn_blkptr[], sont utilisés.
Une écriture remonte jusqu’à la racine.
dbuf_dirty
Extrait de module/zfs/dbuf.c, lignes
2537–2554, OpenZFS zfs-2.4.4.
Voir sur GitHub
if (db->db_level+1 < dn->dn_nlevels) {
dmu_buf_impl_t *parent = db->db_parent;
dbuf_dirty_record_t *di;
int parent_held = FALSE;
if (db->db_parent == NULL || db->db_parent == dn->dn_dbuf) {
int epbs = dn->dn_indblkshift - SPA_BLKPTRSHIFT;
parent = dbuf_hold_level(dn, db->db_level + 1,
db->db_blkid >> epbs, FTAG);
ASSERT(parent != NULL);
parent_held = TRUE;
}
if (drop_struct_rwlock)
rw_exit(&dn->dn_struct_rwlock);
ASSERT3U(db->db_level + 1, ==, parent->db_level);
di = dbuf_dirty(parent, tx);
if (parent_held)
dbuf_rele(parent, FTAG);- L2537 Un bloc sous le niveau le plus haut de son objet a un bloc indirect parent.
-
L2542–L2545
Le parent est au niveau + 1, bloc numéro
blkid >> epbs, avec 2epbs pointeurs par bloc indirect (1024 pour 128 Kio). La figure en met 2, pour rester petite. - L2552 Marquer un bloc marque son parent, et ainsi de suite ; le niveau le plus haut est tenu par le dnode, marqué à son tour. Chaque bloc marqué est écrit à un nouvel endroit à la synchronisation.
Les quatre temps. D’abord le vidage du cache.
vdev_config_sync
Extrait de module/zfs/vdev_label.c, lignes
2085–2098, OpenZFS zfs-2.4.4.
Voir sur GitHub
/*
* Flush the write cache of every disk that's been written to
* in this transaction group. This ensures that all blocks
* written in this txg will be committed to stable storage
* before any uberblock that references them.
*/
zio_t *zio = zio_root(spa, NULL, NULL, flags);
for (vdev_t *vd =
txg_list_head(&spa->spa_vdev_txg_list, TXG_CLEAN(txg)); vd != NULL;
vd = txg_list_next(&spa->spa_vdev_txg_list, vd, TXG_CLEAN(txg)))
zio_flush(zio, vd);
(void) zio_wait(zio);- L2085–L2090 La raison du vidage, dans les mots du code : les blocs de ce txg atteignent un support stable « before any uberblock that references them », avant tout uberblock qui les référence.
- L2093–L2098 Chaque vdev écrit dans ce txg est vidé ; l’attente les couvre tous.
vdev_config_sync
Extrait de module/zfs/vdev_label.c, lignes
2100–2139, OpenZFS zfs-2.4.4.
Voir sur GitHub
/*
* Sync out the even labels (L0, L2) for every dirty vdev. If the
* system dies in the middle of this process, that's OK: all of the
* even labels that made it to disk will be newer than any uberblock,
* and will therefore be considered invalid. The odd labels (L1, L3),
* which have not yet been touched, will still be valid. We flush
* the new labels to disk to ensure that all even-label updates
* are committed to stable storage before the uberblock update.
*/
if ((error = vdev_label_sync_list(spa, 0, txg, flags)) != 0) {
if ((flags & ZIO_FLAG_IO_RETRY) != 0) {
zfs_dbgmsg("vdev_label_sync_list() returned error %d "
"for pool '%s' when syncing out the even labels "
"of dirty vdevs", error, spa_name(spa));
}
goto retry;
}
/*
* Sync the uberblocks to all vdevs in svd[].
* If the system dies in the middle of this step, there are two cases
* to consider, and the on-disk state is consistent either way:
*
* (1) If none of the new uberblocks made it to disk, then the
* previous uberblock will be the newest, and the odd labels
* (which had not yet been touched) will be valid with respect
* to that uberblock.
*
* (2) If one or more new uberblocks made it to disk, then they
* will be the newest, and the even labels (which had all
* been successfully committed) will be valid with respect
* to the new uberblocks.
*/
if ((error = vdev_uberblock_sync_list(svd, svdcount, ub, flags)) != 0) {
if ((flags & ZIO_FLAG_IO_RETRY) != 0) {
zfs_dbgmsg("vdev_uberblock_sync_list() returned error "
"%d for pool '%s'", error, spa_name(spa));
}
goto retry;
}- L2100–L2108 Le temps 2, et ce que veut dire une coupure pendant celui-ci : les labels pairs plus récents que tout uberblock sont invalides, les impairs restent valides.
-
L2109
Les labels pairs :
vdev_label_syncécrit les labelsletl + 2, L0 et L2. - L2118–L2131 Le temps 3, et les deux cas d’une coupure pendant celui-ci : aucun nouvel uberblock n’est arrivé, ou au moins un. La figure rejoue les deux.
-
L2133
Les uberblocks ;
vdev_uberblock_sync_listles vide ensuite du cache, et réussit si au moins une écriture a réussi.
vdev_uberblock_sync
Extrait de module/zfs/vdev_label.c, lignes
1768–1797, OpenZFS zfs-2.4.4.
Voir sur GitHub
/*
* We chose a slot based on the txg. If this uberblock has a special
* RAIDZ expansion state, then it is essentially an update of the
* current uberblock (it has the same txg). However, the current
* state is committed, so we want to write it to a different slot. If
* we overwrote the same slot, and we lose power during the uberblock
* write, and the disk does not do single-sector overwrites
* atomically (even though it is required to - i.e. we should see
* either the old or the new uberblock), then we could lose this
* txg's uberblock. Rewinding to the previous txg's uberblock may not
* be possible because RAIDZ expansion may have already overwritten
* some of the data, so we need the progress indicator in the
* uberblock.
*/
int m = spa_multihost(vd->vdev_spa) ? MMP_BLOCKS_PER_LABEL : 0;
int n = (ub->ub_txg - (RRSS_GET_STATE(ub) == RRSS_SCRATCH_VALID)) %
(VDEV_UBERBLOCK_COUNT(vd) - m);
/* Copy the uberblock_t into the ABD */
abd_t *ub_abd = abd_alloc_for_io(VDEV_UBERBLOCK_SIZE(vd), B_TRUE);
abd_copy_from_buf(ub_abd, ub, sizeof (uberblock_t));
abd_zero_off(ub_abd, sizeof (uberblock_t),
VDEV_UBERBLOCK_SIZE(vd) - sizeof (uberblock_t));
for (int l = 0; l < VDEV_LABELS; l++)
vdev_label_write(zio, vd, l, ub_abd,
VDEV_UBERBLOCK_OFFSET(vd, n), VDEV_UBERBLOCK_SIZE(vd),
vdev_uberblock_sync_done, good_writes,
flags | ZIO_FLAG_DONT_PROPAGATE);
- L1768–L1781 Pourquoi la case doit différer de la case courante : un écrasement déchiré de l’uberblock validé pourrait le perdre. Le commentaire parle de l’extension RAID-Z, qui réutilise un txg ; la règle de la case, plus bas, vaut pour chaque txg.
-
L1782–L1784
La case :
ub_txgmodulo le nombre de cases (une de moins avec multihost). Des txg consécutifs prennent des cases consécutives ; le txg suivant ne tombe jamais sur la case du précédent. - L1792–L1796 La même case dans les quatre labels du disque.
vdev_config_sync
Extrait de module/zfs/vdev_label.c, lignes
2144–2161, OpenZFS zfs-2.4.4.
Voir sur GitHub
/*
* Sync out odd labels for every dirty vdev. If the system dies
* in the middle of this process, the even labels and the new
* uberblocks will suffice to open the pool. The next time
* the pool is opened, the first thing we'll do -- before any
* user data is modified -- is mark every vdev dirty so that
* all labels will be brought up to date. We flush the new labels
* to disk to ensure that all odd-label updates are committed to
* stable storage before the next transaction group begins.
*/
if ((error = vdev_label_sync_list(spa, 1, txg, flags)) != 0) {
if ((flags & ZIO_FLAG_IO_RETRY) != 0) {
zfs_dbgmsg("vdev_label_sync_list() returned error %d "
"for pool '%s' when syncing out the odd labels of "
"dirty vdevs", error, spa_name(spa));
}
goto retry;
}- L2145–L2152 Le temps 4 : les labels impairs en dernier. Une coupure ici laisse les labels pairs et les nouveaux uberblocks, qui suffisent.
-
L2154
La même fonction qu’au temps 2, avec
l = 1: L1 et L3.
Quels disques reçoivent labels et uberblocks : seuls les vdevs de la liste des modifiés reçoivent des labels ; si cette liste est vide, l’uberblock va à trois vdevs de premier niveau au plus.
spa_sync_rewrite_vdev_config
Extrait de module/zfs/spa.c, lignes
10792–10819, OpenZFS zfs-2.4.4.
Voir sur GitHub
if (list_is_empty(&spa->spa_config_dirty_list)) {
vdev_t *svd[SPA_SYNC_MIN_VDEVS] = { NULL };
int svdcount = 0;
int children = rvd->vdev_children;
int c0 = random_in_range(children);
for (int c = 0; c < children; c++) {
vdev_t *vd =
rvd->vdev_child[(c0 + c) % children];
/* Stop when revisiting the first vdev */
if (c > 0 && svd[0] == vd)
break;
if (vd->vdev_ms_array == 0 ||
vd->vdev_islog ||
!vdev_is_concrete(vd))
continue;
svd[svdcount++] = vd;
if (svdcount == SPA_SYNC_MIN_VDEVS)
break;
}
error = vdev_config_sync(svd, svdcount, txg);
} else {
error = vdev_config_sync(rvd->vdev_child,
rvd->vdev_children, txg);
}- L10792–L10815 Pas de changement de configuration : trois vdevs de premier niveau au plus, à partir d’un vdev tiré au hasard, sans les logs ni les vdevs sans metaslab. Un pool d’un seul disque reçoit chaque uberblock sur ce disque.
- L10816–L10818 Un changement de configuration : chaque vdev de premier niveau.
Où sont les labels.
vdev_label_offset
Extrait de module/zfs/vdev_label.c, lignes
158–170, OpenZFS zfs-2.4.4.
Voir sur GitHub
/*
* Basic routines to read and write from a vdev label.
* Used throughout the rest of this file.
*/
uint64_t
vdev_label_offset(uint64_t psize, int l, uint64_t offset)
{
ASSERT(offset < sizeof (vdev_label_t));
ASSERT0(P2PHASE_TYPED(psize, sizeof (vdev_label_t), uint64_t));
return (offset + l * sizeof (vdev_label_t) + (l < VDEV_LABELS / 2 ?
0 : psize - VDEV_LABELS * sizeof (vdev_label_t)));
}- L165–L166 Le décalage tombe dans un label, et la taille du périphérique est un multiple d’un label.
- L168–L169 Les labels 0 et 1 au début, 2 et 3 dans les 512 derniers Kio. Sur le disque de 1 Gio de la figure : 0, 256 Kio, 1023,5 Mio et 1023,75 Mio. Après les deux labels du début vient une zone de démarrage de 3,5 Mio.
L’import. Chaque case de chaque label est lue, et une case valide est gardée si elle bat la meilleure trouvée jusque-là.
vdev_uberblock_compare
Extrait de module/zfs/vdev_label.c, lignes
1495–1505, OpenZFS zfs-2.4.4.
Voir sur GitHub
int
vdev_uberblock_compare(const uberblock_t *ub1, const uberblock_t *ub2)
{
int cmp = TREE_CMP(ub1->ub_txg, ub2->ub_txg);
if (likely(cmp))
return (cmp);
cmp = TREE_CMP(ub1->ub_timestamp, ub2->ub_timestamp);
if (likely(cmp))
return (cmp);- L1498 Le txg le plus élevé gagne.
- L1503 À txg égal, l’horodatage le plus tardif ; puis la séquence multihost, que la figure laisse de côté.
vdev_label_read_config
Extrait de module/zfs/vdev_label.c, lignes
832–863, OpenZFS zfs-2.4.4.
Voir sur GitHub
for (int l = 0; l < VDEV_LABELS; l++) {
nvlist_t *label = NULL;
if (zio_wait(zio[l]) == 0 &&
nvlist_unpack(vp[l]->vp_nvlist, sizeof (vp[l]->vp_nvlist),
&label, 0) == 0) {
/*
* Auxiliary vdevs won't have txg values in their
* labels and newly added vdevs may not have been
* completely initialized so just return the
* configuration from the first valid label we
* encounter.
*/
error = nvlist_lookup_uint64(label,
ZPOOL_CONFIG_POOL_TXG, &label_txg);
if ((error || label_txg == 0) && !config) {
config = label;
for (l++; l < VDEV_LABELS; l++)
zio_wait(zio[l]);
break;
} else if (label_txg <= txg && label_txg > best_txg) {
best_txg = label_txg;
nvlist_free(config);
config = fnvlist_dup(label);
}
}
if (label != NULL) {
nvlist_free(label);
label = NULL;
}
}- L835–L837 Un label ne compte que si sa lecture réussit, et la lecture vérifie la somme de contrôle propre au label : un label déchiré est ignoré.
-
L852–L856
Le label au txg de configuration le plus élevé sans dépasser
txg, celui de l’uberblock gagnant (l’appel). C’est ainsi que les labels pairs d’un txg qui n’a pas été validé sont écartés.
Après l’import, le txg suivant est celui du gagnant + 1 : quand l’ancien arbre a gagné, le txg perdu est réécrit, et son uberblock va dans la même case, par-dessus la case déchirée. L’import demande aussi une mise à jour de la configuration, qui marque chaque vdev comme modifié pour ce txg : il réécrit les quatre labels, déchirés compris, quoi qu’il change d’autre.
Ce que la figure laisse de côté
Un seul dataset et très peu de blocs : deux pointeurs par bloc indirect au lieu de 1024 au plus, les niveaux
indirects propres au meta-dnode et le passage du MOS à l’objset d’un système de fichiers repliés en un seul bloc de
dnodes. Un seul disque, si bien que chaque uberblock et chaque label y arrivent ; ses 128 cases sont celles de
secteurs de 512 octets. Le code lance ensemble les écritures d’un même temps ; la figure les fait arriver dans
l’ordre (L0 puis L2, L1 puis L3, les uberblocks de L0 à L3) et déchire celle en cours quand la prise est débranchée.
Le contenu de la configuration, la nouvelle tentative avec ZIO_FLAG_IO_RETRY, multihost, l’extension
RAID-Z, les passes de synchronisation multiples d’un txg et la libération des anciens blocs (la
salle des instantanés) ne sont pas montrés. La vue 3D dessine le même état
que la vue 2D.