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

1. vdev label的内存结构

上一篇中我们介绍过,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; 

2. vl_pad1和vl_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
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 } 

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

转载于:https://www.cnblogs.com/SunChina/p/3643962.html

ZFS - vdev label 的加载与同步相关推荐

  1. JavaScript异步加载与同步加载

    关于同步加载与异步加载的区别 同步加载:同步模式,又称阻塞模式,会阻止浏览器的后续处理,停止了后续的解析,因此停止了后续的文件加载(如图像).渲染.代码执行. 异步加载:异步加载又叫非阻塞,浏览器在下 ...

  2. Kafka如何对Topic元数据进行细粒度的懒加载、同步等待?

    首先确保Topic的元数据可用,否则消息根本没法往外发.如果以前从没加载过Topic元数据,就会在doSend发送消息时调用waitOnMetadata方法在此同步阻塞住,等待连接Broker成功后拉 ...

  3. define 解析依赖,判断状态,初始化/触发加载 --------require 同步加载(直接返回)/异步加载(创建匿名模块,判断状态,初始化/触发加载)

    define (1)获取依赖 AMD/CMD (2)触发回调 onScriptLoad (3)解析依赖 已加载:触发回调 未加载:创建module,监听,触发加载 检查module状态 require ...

  4. 关于同步加载与异步加载的区别

    关于同步加载与异步加载的区别 同步加载:同步模式,又称阻塞模式,会阻止浏览器的后续处理,停止了后续的解析,因此停止了后续的文件加载(如图像).渲染.代码执行. 异步加载:异步加载又叫非阻塞,浏览器在下 ...

  5. ios html字符串 label,iOS UIlabel怎么加载html字符串 富文本的用法

    要加载html字符串,用人说,直接用webView啊!但是,有时候我们只需要显示2行文字,如此少的内容却要在复杂的UI排版中加入一个占用资源较多的webview,得不偿失.这里要说的是,我们其实可以用 ...

  6. 同步加载与异步加载的区别

      同步加载: 同步模式,又称阻塞模式,就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去:   即同步加载会阻止浏览器 ...

  7. ext ajax同步加载数据,ext 的loadmask 与ajax的同步请求水火不容

    因为ajax 的同步请求会有一段请求时间.有的短.有的长,对于短的咱们仍是能接受的,不过长的话就必须处理一下了,ajax 就好比处于ext 4.2.0的框架下,须要一个遮掩的样式,框架是有自带的,lo ...

  8. 【CEGUI】资源加载流程

    CEGUI资源加载流程 CEGUI版本 0.8.7 主要资源类型 Scheme scheme资源(包括图像集.字体资源.窗口外观信息.类型映射)等.可以通过".scheme"&qu ...

  9. 浏览器中Javascript的加载和执行

    在刚学习Javascript时曾对该问题在小组内做个一次StudyReport,发现其中的基础还是值得分析的. 从标题分析,可以加个Javascript的加载和执行分为两个阶段:加载.执行.而加载即浏 ...

最新文章

  1. 后台ajax调用中字符串到jquery中的json对象和数组对象转换问题
  2. linux中cat more less head tail 命令区别
  3. 使用Struts2防止表单重复提交
  4. 定时器里面的作用域问题
  5. 面试mysql中怎么创建索引_阿里面试:MySQL如何设计索引更高效?
  6. Linux中的用户和组
  7. 广义动量定理之速度V的应用分析
  8. OpenSSL库概述
  9. mysql中连接和断开数据库_robot framework——连接和断开mysql数据库
  10. [1]elasticsearch源码编译
  11. Linux安全模块(LSM)学习——简单的LSM demo(1)
  12. php7isapi,Windows7 IIS7下以FastCgi和ISAPI方法安装配置PHP5教程
  13. python random模块随机抽样专题
  14. android textview 英文 自动换行,TextView设置文字包含中英文时自动换行问题的终极解决方案...
  15. linux 查看java_opts_java_opts 参数与JVM内存调优
  16. vue 所有dom加载完毕后操作dom节点
  17. FFmpeg学习(音视频理论知识)
  18. oracle数据库报错1033,ORACLE出现错误1033和错误ORA-00600的解决方法
  19. 【POI2013】bzoj3426 Tower Defence Game
  20. java实现文本纠错功能_调用百度API进行文本纠错

热门文章

  1. MobileSubstrate 介绍
  2. 模仿支付宝芝麻信用 绘图 多种因素占比多边形
  3. Two Dimensional Convex Hull
  4. js、jquery、jsp的区别
  5. Windows安全日志分析
  6. 皮皮书屋页面链接修正
  7. 人工智能算法—决策树
  8. Tkinter 组件详解(四):Radiobutton
  9. 中国第一台台式计算机,中国第一台计算机诞生在什么时候?
  10. 惠普磁盘阵列如何进行逻辑分区【磁盘阵列分盘】