前一篇文章中咱們說明了ZFS的Label在磁盤上的存儲形式,這篇文章中,咱們將詳細說明一下Vdev在內存中的組織形式以及相關的實現細節。c++
上一篇中咱們介紹過,vdev的Label在磁盤上的存儲備份成了4部分,第一部分8KB,對應VTOC的卷標;第二部分8KB,對應Boot Header信息;第三部分112K,對應nvlist鍵值對;第四部分128K,對應uberblock數組。這四個在如下的結構體(vdev_label)中能夠很清楚地看出。數組
1 typedef struct vdev_label { 2 char vl_pad1[VDEV_PAD_SIZE]; /* 8K */ 3 char vl_pad2[VDEV_PAD_SIZE]; /* 8K */ 4 vdev_phys_t vl_vdev_phys; /* 112K */ 5 char vl_uberblock[VDEV_UBERBLOCK_RING]; /* 128K */ 6 } vdev_label_t; 7 8 typedef struct vdev_phys { 9 char vp_nvlist[VDEV_PHYS_SIZE - sizeof (zio_eck_t)]; 10 zio_eck_t vp_zbt; 11 } vdev_phys_t; 12 13 typedef struct zio_eck { 14 uint64_t zec_magic; /* for validation, endianness */ 15 zio_cksum_t zec_cksum; /* 256-bit checksum */ 16 } zio_eck_t; 17 18 typedef struct zio_cksum { 19 uint64_t zc_word[4]; 20 } zio_cksum_t;
1 /* 根據給定的虛擬設備,返回其Label中的配置星系,對於沒有Label中沒有存儲txg的設備(好比spares/cache) 2 或者沒有徹底初始化的設備(txg=0),返回找到的第一個合法的Label信息。不然,返回最新的Label(txg值最大的) */ 3 nvlist_t * vdev_label_read_config(vdev_t *vd, uint64_t txg) 4 { 5 spa_t *spa = vd->vdev_spa; 6 nvlist_t *config = NULL; 7 vdev_phys_t *vp; 8 zio_t *zio; 9 uint64_t best_txg = 0; 10 int error = 0; 11 int flags = ZIO_FLAG_CONFIG_WRITER | ZIO_FLAG_CANFAIL | 12 ZIO_FLAG_SPECULATIVE; 13 14 ASSERT(spa_config_held(spa, SCL_STATE_ALL, RW_WRITER) == SCL_STATE_ALL); 15 16 if (!vdev_readable(vd)) // 判斷該虛擬設備是否可讀 17 return (NULL); 18 19 vp = zio_buf_alloc(sizeof (vdev_phys_t)); 20 21 retry: 22 for (int l = 0; l < VDEV_LABELS; l++) { 23 nvlist_t *label = NULL; 24 25 zio = zio_root(spa, NULL, NULL, flags); 26 27 /* 使用zio讀取vd設備的label,從Label的16K位置開始讀,讀出的結果放到vp中 */ 28 vdev_label_read(zio, vd, l, vp, 29 offsetof(vdev_label_t, vl_vdev_phys), 30 sizeof (vdev_phys_t), NULL, NULL, flags); 31 /* 若是讀取成功,將讀到vp中的結果轉換到nvlist類型的label變量中 */ 32 if (zio_wait(zio) == 0 && 33 nvlist_unpack(vp->vp_nvlist, sizeof (vp->vp_nvlist), 34 &label, 0) == 0) { 35 uint64_t label_txg = 0; 36 37 /* 輔助設備(sparse/cache)的Label中沒有txg值,新添加的設備可能沒有初始化徹底,直接返回第一個合法的Label */ 38 error = nvlist_lookup_uint64(label, 39 ZPOOL_CONFIG_POOL_TXG, &label_txg); 40 if ((error || label_txg == 0) && !config) { 41 config = label; 42 break; 43 } else if (label_txg <= txg && label_txg > best_txg) { 44 /* 若是不是最佳的Label,繼續尋找(找txg值最大的Label) */ 45 best_txg = label_txg; 46 nvlist_free(config); 47 config = fnvlist_dup(label); 48 } 49 } 50 51 if (label != NULL) { 52 nvlist_free(label); 53 label = NULL; 54 } 55 } 56 57 if (config == NULL && !(flags & ZIO_FLAG_TRYHARD)) { 58 flags |= ZIO_FLAG_TRYHARD; 59 goto retry; 60 } 61 62 zio_buf_free(vp, sizeof (vdev_phys_t)); 63 64 return (config); 65 }
加載uberblock時,須要加載最佳的uberblock以及與之相關的配置信息。首先要讀取每一個設備的每一個Label,記錄每一個數組中txg值最大的uberblock,而後從相同的設備中讀取配置信息。
緩存
1 void vdev_uberblock_load(vdev_t *rvd, uberblock_t *ub, nvlist_t **config) 2 { 3 zio_t *zio; 4 spa_t *spa = rvd->vdev_spa; 5 struct ubl_cbdata cb; 6 int flags = ZIO_FLAG_CONFIG_WRITER | ZIO_FLAG_CANFAIL | 7 ZIO_FLAG_SPECULATIVE | ZIO_FLAG_TRYHARD; 8 9 ASSERT(ub); 10 ASSERT(config); 11 12 bzero(ub, sizeof (uberblock_t)); 13 *config = NULL; 14 15 cb.ubl_ubbest = ub; 16 cb.ubl_vd = NULL; 17 18 spa_config_enter(spa, SCL_ALL, FTAG, RW_WRITER); 19 zio = zio_root(spa, NULL, &cb, flags); 20 21 /* 讀取最佳的uberblock,後文給出此函數的詳細說明*/ 22 vdev_uberblock_load_impl(zio, rvd, flags, &cb); 23 (void) zio_wait(zio); 24 25 /* 找到設備以後,從該設備上獲取配置信息 */ 26 if (cb.ubl_vd != NULL) 27 *config = vdev_label_read_config(cb.ubl_vd, ub->ub_txg); 28 spa_config_exit(spa, SCL_ALL, FTAG); 29 } 30 31 static void vdev_uberblock_load_impl(zio_t *zio, vdev_t *vd, int flags, 32 struct ubl_cbdata *cbp) 33 { 34 /* 遞歸查找全部的子設備 */ 35 for (int c = 0; c < vd->vdev_children; c++) 36 vdev_uberblock_load_impl(zio, vd->vdev_child[c], flags, cbp); 37 /* 只針對可讀且是葉虛擬設備讀取 */ 38 if (vd->vdev_ops->vdev_op_leaf && vdev_readable(vd)) { 39 for (int l = 0; l < VDEV_LABELS; l++) { 40 for (int n = 0; n < VDEV_UBERBLOCK_COUNT(vd); n++) { 41 /* 從偏移能夠看出,值讀取Label的uberblock部分
vdev_uberblock_load_done爲回調函數,見後文說明*/ 42 vdev_label_read(zio, vd, l, 43 zio_buf_alloc(VDEV_UBERBLOCK_SIZE(vd)), 44 VDEV_UBERBLOCK_OFFSET(vd, n), 45 VDEV_UBERBLOCK_SIZE(vd), 46 vdev_uberblock_load_done, zio, flags); 47 } 48 } 49 } 50 } 51 52 /* 做爲vdev_label_read的回調函數, 確保一直持有最佳的uberblock */ 53 static void vdev_uberblock_load_done(zio_t *zio) 54 { 55 vdev_t *vd = zio->io_vd; 56 spa_t *spa = zio->io_spa; 57 zio_t *rio = zio->io_private; 58 uberblock_t *ub = zio->io_data; 59 struct ubl_cbdata *cbp = rio->io_private; 60 61 ASSERT3U(zio->io_size, ==, VDEV_UBERBLOCK_SIZE(vd)); 62 63 if (zio->io_error == 0 && uberblock_verify(ub) == 0) { 64 mutex_enter(&rio->io_lock); 65 if (ub->ub_txg <= spa->spa_load_max_txg && 66 vdev_uberblock_compare(ub, cbp->ubl_ubbest) > 0) { 67 /* 經過比較,記錄最新的uberblock */ 68 *cbp->ubl_ubbest = *ub; 69 cbp->ubl_vd = vd; 70 } 71 mutex_exit(&rio->io_lock); 72 } 73 74 zio_buf_free(zio->io_data, zio->io_size); 75 }
1 /* 同步uberblock和vdev配置信息的更改,操做的順序是經過精妙的設計來保證在經過過程當中發生系統panic或是斷電, 2 磁盤上的信息仍然是完整的。代碼行中的註釋詳細說明每一個階段的做用。並且本函數任何階段發生了錯誤,能夠直接再 3 次調用,它能夠從新完成未完成的工做 */ 4 int vdev_config_sync(vdev_t **svd, int svdcount, uint64_t txg, boolean_t tryhard) 5 { 6 spa_t *spa = svd[0]->vdev_spa; 7 uberblock_t *ub = &spa->spa_uberblock; 8 vdev_t *vd; 9 zio_t *zio; 10 int error; 11 int flags = ZIO_FLAG_CONFIG_WRITER | ZIO_FLAG_CANFAIL; 12 13 /* 通常來講,咱們並不辛苦地去寫入全部的Label以及uberblock */ 14 if (tryhard) 15 flags |= ZIO_FLAG_TRYHARD; 16 17 ASSERT(ub->ub_txg <= txg); 18 19 /* 若是這不是因爲I/O錯誤而須要從新同步,並且這個事務組並無什麼修改,
配置信息也沒有改變,那麼這裏就不須要作任何工做。 */ 20 if (ub->ub_txg < txg && 21 uberblock_update(ub, spa->spa_root_vdev, txg) == B_FALSE && 22 list_is_empty(&spa->spa_config_dirty_list)) 23 return (0); 24 25 if (txg > spa_freeze_txg(spa)) 26 return (0); 27 28 ASSERT(txg <= spa->spa_final_txg); 29 30 /* 將磁盤緩存中的數據修改全都寫入磁盤以後在更新uberblock信息 */ 31 zio = zio_root(spa, NULL, NULL, flags); 32 33 for (vd = txg_list_head(&spa->spa_vdev_txg_list, TXG_CLEAN(txg)); vd; 34 vd = txg_list_next(&spa->spa_vdev_txg_list, vd, TXG_CLEAN(txg))) 35 zio_flush(zio, vd); 36 37 (void) zio_wait(zio); 38 39 /* 將偶數Label(L0,L2)同步到每一個髒虛擬設備,若是這個過程當中系統掛掉了,不要緊,全部的奇數Label中信息是合法的. 40 這保證了全部的偶數Label在uberblock以前被寫入到磁盤中. */ 41 if ((error = vdev_label_sync_list(spa, 0, txg, flags)) != 0) 42 return (error); 43 44 /* 將uberblock同步到svd[]中全部的vdev,若是系統在這個過程當中掛掉,要考慮兩種狀況: 45 1) 若是沒有uberblock被寫入磁盤,那麼以前的uberblock就是最新的,
奇數Label(尚未被更新)上的上的uberblock是合法的。 46 2) 若是有一個或多個uberblocks已經被寫入磁盤,那麼它就是最新的,
那麼偶數Label(已經被成功更新了)上的數據與新的uberblocks一致 */ 47 if ((error = vdev_uberblock_sync_list(svd, svdcount, ub, flags)) != 0) 48 return (error); 49 50 /* 將奇數Label同步到每一個髒虛擬設備,若是系統在這個過程當中掛了,偶數Label和新的uberblock對與pool是有效的, 51 下一次pool被打開的時候,第一件是要作的就是將全部的磁盤置爲髒,這樣全部的label都會被更新,在下個事務組開 52 始以前就將奇數Label的信息同步到磁盤上 */ 53 return (vdev_label_sync_list(spa, 1, txg, flags)); 54 }