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

A ZFS fan

文章分类
文章存档

2014年(17)

分类: 服务器与存储

2014-06-07 19:56:19

前面一片博客说了,spa_config_load()只是将配置文件的信息加载到内存中,而非真正将pool导入。
这篇文章我们将详细说明一下存储池的导入、导出过程。


1. 根存储池的导入:
int spa_import_rootpool(char *devpath, char *devid)
根存储池所在的磁盘是操作系统的启动磁盘,它的导入是通过给定设备的物理地址以及磁盘的设备ID来实现的。
对于x86架构的机器,devpath_list中包含设备ID/设备的物理名称(比如:"id1,sd@SSEAGATE..." or "/pci@1f,0/ide@d/disk@0,0:a"),GRUB的findboot命令将会返回应该启动的设备信息。
对于Sparc架构的机器,不管系统采用的是单个磁盘构建的根存储池还是镜像磁盘构建的存储池,devpath_list中都回包含启动设备的物理路径(比如:"/pci@1f,0/ide@d/disk@0,0:a")。
根存储池的导入过程:
■  根据给定的设备路径或设备ID(两者只要一个就可以),读取设备的ZFS Label信息,根据读取的信息构建nvlist_t类型的config结构。这部分通过 spa_generate_rootconf(devpath, devid, &guid) 来实现。spa_generate_rootconf最终调用了底层驱动来读取ZFS的label信息。
■  如果读取成功,说明磁盘上存在ZFS信息,失败,给出错误“Cannot read the pool label from xxx”,然后直接返回。
■  解析读取出来的config信息,找到存储池名称:poolname;将既存的根存储池从SPA名空间中移除,将刚刚读取的新的存储池加入到SPA名空间中,同时将该spa_t结构体的spa_is_root变量设置位B_TRUE;
■  根据读取出来的Label中的Config信息,生成设备树。
■  遍历设备树中所有的叶节点(即遍历所有的物理磁盘。)查找看是否有更合适的启动磁盘(即查找ZPOOL_CONFIG_POOL_TXG值更大得磁盘),如果找到,则提示重新从该磁盘启动。


2. 导入普通存储池
int spa_import(const char *pool, nvlist_t *config, nvlist_t *props, uint64_t flags)
给定要导入的存储池的名称,配置信息,属性以及导入的类型。
普通存储池的导入过程:
■  首先查看是否有要导入的存储池同名的存储池已经存在。如果已经存在,则返回错误。
■  创建spa_t结构体,同时初始化该对象的相关数据。
■  根据flag给定的设定,如果包含ZFS_IMPORT_VERBATIM选项,则按照函数参数中给定的配置、属性原封不动地添加到spa中,同时同步到磁盘上,记录一条“import”类型的日志,之后即可以返回。
■  调用spa_active函数,将要导入的pool状态置为激活状态。(关于spa的状态转换,后面的博客中将详细说明)
■  在导入的过程中发现的新情况返回给调用者(比如说存在设备丢失,回滚信息等)。
■  将配置信息中的热备份(spare)设备信息清除,直接调用spa_load_spares(spa_t *spa)来加载spare信息,同样的方法调用spa_load_l2cache(spa_t *spa)加载二级缓存设备。
■  处理spare设备的配置信息以及来l2cache设备的配置信息。
■  更新config的cache文件(包含新导入的pool信息)。
■  记录一条“import”类型的日志。


3. 加载SPA(不仅仅只有import过程才加载SPA)
static int spa_load(spa_t *spa, spa_load_state_t state, spa_import_type_t type, boolean_t mosconfig);
static int spa_load_impl(spa_t *spa, uint64_t pool_guid, nvlist_t *config, spa_load_state_t state, spa_import_type_t type, boolean_t mosconfig, char **ereport);
spa_load_impl是使用内置的spa_config作为配置源来加载一个存在的存储池,这个函数也够庞大的(600多行)。
■  从配置信息中取出nvlist形式的设备树;
■  为存储池创建一个“Godfather”类型的zio,用来承载所有的非同步IO;
■  调用spa_config_parse解析配置信息。这启动包括验证存储池的配置信息,构建合适的设备树(vdev_t形式存在);
■  尝试打开所有的vdev,并验证所有的配置信息是否合法——这里通过将设备Label中读取出来的信息与当前持有的配置信息进行比较,如果参数周的mosconfig值为true,则按照config信息与Label中的信息比较,否则按照“zpool.cache”中的信息来比较。
■  如果当前操作时重新合并一个存储池(该存储池被执行了split操作),而Label信息还没有被更新,则暂时跳过验证;
■  通过调用vdev_uberblock_load查找最佳的uberblock;
■  如果找不到唯一的合法的uberblock,返回错误;如果当前的SPA版本不被支持,返回错误;(无法打开Pool);
■  查找Label对应的nvlist的所有属性,如果存在不能理解的,该pool无法打开;
■  如果设备的guid之和与uberblock中的不匹配,说明这是一个不完整的配置信息。那么首先检查存储池是否有完整的信息,如果有,将vdev_guid_sum的验证放到后面,以处理设备缺失的情况;
■  初始化SPA结构体
■  加载error log
■  加载history object
■  加载存储池的hot spare设备(如果当前操作是导入split的存储池,则跳过此步)
■  加载缓存设备(如果当前操作是导入split的存储池,则跳过此步
■ 
根据是否设置autoreplace选项,检查磁盘是否有丢失
■  加载所有top-level虚拟设备状态
■  加载DDTs(Dedup Tables )
■  检查配置信息,并使用Mos config信息来填充缺失的信息,如果验证失败则清除已加载信息,返回错误。
■  
<-- 至此,说明已经可以打开Pool了
■  打开之后,如果当前的pool可写,第一件事要做的就是Claim之前还没完成提交的操作。



3. 存储池的导出或销毁
static int spa_export_common(char *pool, int new_state, nvlist_t **oldconfig, boolean_t force, boolean_t hardforce);
相对于import而言,销毁或导出存储池就比较简单了。
首先确认该zpool上没有IO操作,然后根据参数中是否设置了fore选项,如果没有设置,则将配置信息写回到所有磁盘的Label中,清除zpool.cache文件中的信息; 如果设置了fore选项,则不将磁盘信息写回Label,也不清除cache文件中的信息。其中主要涉及的一些操作如下:
■  spa_async_resume(spa);
■  spa_unload(spa);
■  spa_deactivate(spa);
■  spa_remove(spa);





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