Chinaunix首页 | 论坛 | 博客
  • 博客访问: 164308
  • 博文数量: 17
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 342
  • 用 户 组: 普通用户
  • 注册时间: 2014-03-19 11:38
个人简介

A ZFS fan

文章分类
文章存档

2014年(17)

分类: 服务器与存储

2014-04-02 19:59:20

前一篇文章中我们说明了ZFSLabel在磁盘上的存储形式,这篇文章中,我们将详细说明一下Vdev在内存中的组织形式以及相关的实现细节。

 

1. vdev label的内存结构

上一篇中我们介绍过,vdevLabel在磁盘上的存储备份成了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;


2. vl_pad1vl_pad2不用特别说明


3. 读取Label信息(配置信息以及uberblock信息)


3.1.  vdev Label中读取vl_dev_phys结构体

  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. }

3.2. 加载uberblock

加载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. /* 读取最佳的uberblock,后文给出此函数的详细说明*/
  21.          vdev_uberblock_load_impl(zio, rvd, flags, &cb);
  22.          (void) zio_wait(zio);

  23. /* 找到设备之后,从该设备上获取配置信息 */
  24.          if (cb.ubl_vd != NULL)
  25.                  *config = vdev_label_read_config(cb.ubl_vd, ub->ub_txg);
  26.          spa_config_exit(spa, SCL_ALL, FTAG);
  27. }
  28.  
  29. static void vdev_uberblock_load_impl(zio_t *zio, vdev_t *vd, int flags,
  30.     struct ubl_cbdata *cbp)
  31. {
  32.          /* 递归查找所有的子设备 */
  33.          for (int c = 0; c < vd->vdev_children; c++)
  34.                  vdev_uberblock_load_impl(zio, vd->vdev_child[c], flags, cbp);
  35.          /* 只针对可读且是叶虚拟设备读取 */
  36.          if (vd->vdev_ops->vdev_op_leaf && vdev_readable(vd)) {
  37.                  for (int l = 0; l < VDEV_LABELS; l++) {
  38.                           for (int n = 0; n < VDEV_UBERBLOCK_COUNT(vd); n++) {
  39.                           /* 从偏移可以看出,值读取Label的uberblock部分 vdev_uberblock_load_done为回调函数,见后文说明*/
  40.                                    vdev_label_read(zio, vd, l,
  41.                                        zio_buf_alloc(VDEV_UBERBLOCK_SIZE(vd)),
  42.                                        VDEV_UBERBLOCK_OFFSET(vd, n),
  43.                                        VDEV_UBERBLOCK_SIZE(vd),
  44.                                        vdev_uberblock_load_done, zio, flags);
  45.                           }
  46.                  }
  47.          }
  48. }
  49.  
  50. /* 作为vdev_label_read的回调函数, 确保一直持有最佳的uberblock */
  51. static void vdev_uberblock_load_done(zio_t *zio)
  52. {
  53.          vdev_t *vd = zio->io_vd;
  54.          spa_t *spa = zio->io_spa;
  55.          zio_t *rio = zio->io_private;
  56.          uberblock_t *ub = zio->io_data;
  57.          struct ubl_cbdata *cbp = rio->io_private;
  58.  
  59.          ASSERT3U(zio->io_size, ==, VDEV_UBERBLOCK_SIZE(vd));
  60.  
  61.          if (zio->io_error == 0 && uberblock_verify(ub) == 0) {
  62.                  mutex_enter(&rio->io_lock);
  63.                  if (ub->ub_txg <= spa->spa_load_max_txg &&
  64.                      vdev_uberblock_compare(ub, cbp->ubl_ubbest) > 0) {
  65.                           /* 通过比较,记录最新的uberblock */
  66.                           *cbp->ubl_ubbest = *ub;
  67.                           cbp->ubl_vd = vd;
  68.                  }
  69.                  mutex_exit(&rio->io_lock);
  70.          }
  71.  
  72.          zio_buf_free(zio->io_data, zio->io_size);
  73. }


3.3. 同步Label信息

  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. }

关于具体的配置信息以及uberblock写入操作,可以查看ZFS源码 vdev_label.c
 

本文乃nnusun原创文章,请勿转载。如须转载请详细标明转载出处。

阅读(2278) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~