04| OCFS2 的mount流程

740 阅读14分钟

文件系统格式化完成之后,进行mount操作才能真正建立起文件系统,当一个文件系统被挂载之后,一个文件系统实例就诞生并可以使用了。

对OCFS2而言除了格式化完成外,还需要启动o2cb集群服务。集群mount不是瞬时完成的,它要等待节点加入DLM domain。同样的,集群的umount也不是瞬时完成的,它需要迁移集群锁主的锁资源到集群中的其他节点上。

一、mount命令

mount -t ocfs2 /dev/sdc /test_dir

-t指定device上的文件系统类型,如果不使用-t则mount命令也可以尝试探测device上的文件系统类型。

-o(如果有)用来指定一些额外的(非默认的)挂载选项。

选项解释
_netdev:网络系统使能后,才允许文件系统mount到卷或磁盘上。因为ocfs2是依赖网络的,因此,这个参数是必须的,追加这个参数,可以检测网络情况。
data=ordered / data=writeback:它指定元数据日志记录期间对数据的处理。
ordered - 这是默认模式。 在将元数据提交到日志之前,将所有数据刷新到磁盘。 这防止了在崩溃后文件中出现陈旧数据的情况。即:先刷数据,再刷元数据。
writeback - 吞吐量的选择。 虽然它保证了内部文件系统的完整性,但它允许在崩溃和日志恢复之后在文件中出现null或陈旧的数据。 如果系统在日志提交之后但数据刷新之前崩溃,就会发生这种情况。 有序数据模式日志记录通过在提交之前总是刷新数据来避免此问题。即:先刷元数据,再刷数据。
datavolume:用这个选项来mount卷,是存储Oracle数据文件、控制文件、redo logs、归档日志、选举磁盘、集群注册等。

上面这个命令翻译成人话就是:请把/dev/sdc上的OCFS2文件系统挂载到/test_dir目录上。mount方法在挂载一个文件系统时通过name找到这个文件系统的file_system_type实例,然后使用这个实例中挂载的方法(mount函数),进行挂载。

mount系统调用

这是使用mount命令进行挂载操作,mount命令最终执行还得是陷入内核后执行的系统调用,来看一下mount系统调用的定义(man 2 mount):

NAME
        mount - mount filesystem
SYNOPSIS
        #include <sys/mount.h>
int mount(const char *source, const char *target,
          const char *filesystemtype, unsigned long mountflags,
          const void *data);

用户态mount流程:

int main(int argc, char **argv)
{
    |-ret = ocfs2_open(mo.dev, OCFS2_FLAG_RO, 0, 0, &fs); //O_EXCL? //读dev设备,加载superblock,生成内存sb信息
    |-clustered = (0 == ocfs2_mount_local(fs));//集群模式
    |-ret = o2cb_init();//读文件/sys/fs/ocfs2/cluster_stack,一般就是o2cb - classic_stack,然后将该stack设置到configfs配置文件目录:/sys/kernel/%s/config
    |-ret = ocfs2_fill_cluster_desc(fs, &cluster);//加载superblock上的cluster stack: o2cb和cluster name:ocfs2
    |-ret = ocfs2_fill_heartbeat_desc(fs, &desc);//读系统目录下的inode心跳文件heartbeat,写心跳数据开始的位置blkno 1280,大小1 cluster = 256 blocks
    |-ret = o2cb_begin_group_join(&cluster, &desc);//cluster是集群信息,desc是心跳文件信息. 通过configfs把心跳文件写入新建的集群心跳目录/sys/kernel/%s/config/region_path,block_bytes、start_block、blocks、dev、
    |-hb_started = 1; //此时,内核ocfs2心跳线程o2hb-xxx已经启动
    |-ret = mount(mo.dev, mo.dir, OCFS2_FS_NAME, mo.flags & ~MS_NOSYS, extra);//mount系统调用:陷入内核进行mount操作
    |-ret = o2cb_complete_group_join(&cluster, &desc, 0);//心跳正常,节点加入集群成功。
    |-run_hb_ctl (hb_ctl_path, mo.dev, "-P"); //fork子进程执行: hb_ctl_path -P -d mo.dev
    |-update_mtab_entry(mo.dev, mo.dir, OCFS2_FS_NAME, //更新/etc/mtab
}

二、文件系统 挂载的概念

挂载的过程是通过mount系统调用完成的,它有两个参数:一个是已存在的普通文件名,一个是可以直接访问的特殊文件的名字。这个特殊文件一般用来关联一些存储卷,这个存储卷可以包含自己的目录层级和文件系统结构。

mount所达到的效果是:像访问一个普通的文件一样访问位于其他设备上文件系统的根目录,也就是将该设备上目录的根节点挂到了另外一个文件系统的页节点上,达到给这个文件系统扩充容量的目的。

Mount Namespace

Mount Namespace用来隔离文件系统的挂载点,不同Mount Namespace的进程拥有不同的挂载点,同时也拥有了不同的文件系统视图。它通过CLONE_NEWNS来标识的,进程在创建Mount Namespace时,会把当前的文件结构复制给新的Namespace,新的Namespace中的所有mount操作仅影响自身的文件系统。

image (20).png

查看一个进程的mount信息:

root:根目录,是挂载点的根

mount point:挂载点的文件路径名(相对于根目录)

三、内核态mount系统调用:为用户空间创建一个新的mount对象,请求挂载到mount namespace树上。

SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
        char __user *, type, unsigned long, flags, void __user *, data)
    |-kernel_type = copy_mount_string(type);
    |-kernel_dev = copy_mount_string(dev_name);
    |-options = copy_mount_options(data);
    |-ret = do_mount(kernel_dev, dir_name, kernel_type, flags, options);
        |-struct path path;
        |-ret = user_path_at(AT_FDCWD, dir_name, LOOKUP_FOLLOW, &path);//根据mount点目录名称,找到struct path.dentry对象
        |-ret = path_mount(dev_name, &path, type_page, flags, data_page);
            |-ret = security_sb_mount(dev_name, path, type_page, flags, data_page);
            |-mnt_flags |=xxx
            |-sb_flags = flags & (SB_RDONLY | SB_SYNCHRONOUS | SB_MANDLOCK | SB_DIRSYNC | SB_SILENT | SB_POSIXACL | SB_LAZYTIME | SB_I_VERSION);
            |-处理mount -o选项flags
            |-return do_new_mount(path, type_page, sb_flags, mnt_flags, dev_name, data_page);//create a new mount for userspace and request it to be added into the namespace's tree.
                |-struct file_system_type *type = get_fs_type(fstype); //从文件系统链表中找到注册的ocfs2文件系统
                    |-struct file_system_type *fs = __get_fs_type(name, len);
                        |-struct file_system_type fs = *(find_filesystem(name, len));
                            |-for (p = &file_systems; *p; p = &(*p)->next)
                |-struct fs_context *fc = fs_context_for_mount(type, sb_flags); //初始化一个文件系统上下文fs_context
                |-检查
                |-err = do_new_mount_fc(fc, path, mnt_flags);//Create a new mount using a superblock configuration and request it be added to the namespace tree.
                    |-struct vfsmount *mnt = vfs_create_mount(fc); //申请struct mount对象,该对象包含struct vfsmount
                    |-struct mountpoint *mp = lock_mount(mountpoint);
                    |-error = do_add_mount(real_mount(mnt), mp, mountpoint, mnt_flags);//add a mount into a namespace' mount tree.
                    |-unlock_mount(mp);

四、ocfs2 内核模块:

OCFS2内核加载完成缓存的分配和ocfs2文件系统类型的注册,这样用户态执行mount系统调用时就可以找到ocfs2文件系统ocfs2_fs_type。

static struct file_system_type *file_systems;//内核文件系统对象,这是个单链表。

ocfs2文件系统类型:

static struct file_system_type ocfs2_fs_type = {
    .owner          = THIS_MODULE,
    .name           = "ocfs2",
    .mount          = ocfs2_mount,
    .kill_sb        = kill_block_super,
    .fs_flags       = FS_REQUIRES_DEV|FS_RENAME_DOES_D_MOVE,
    .next           = NULL
};
static int __init ocfs2_init(void)
|-status = init_ocfs2_uptodate_cache(); //申请uptodate缓存
|-status = ocfs2_initialize_mem_caches(); //申请ocfs2 inode缓存、dquot缓存、磁盘配额大块quota_chunk缓存
    //申请inode时,会调用初始化回调函数ocfs2_inode_init_once
    |-struct kmem_cache *ocfs2_inode_cachep = kmem_cache_create("ocfs2_inode_cache",
    
    |-struct kmem_cache *ocfs2_dquot_cachep = kmem_cache_create("ocfs2_dquot_cache",


    |-struct kmem_cache *ocfs2_qf_chunk_cachep = kmem_cache_create("ocfs2_qf_chunk_cache",


    |-ocfs2_set_locking_protocol();
    |-status = register_quota_format(&ocfs2_quota_format);
    |-status = register_filesystem(&ocfs2_fs_type); //注册ocfs2文件系统类型
        |-struct file_system_type ** p = find_filesystem(fs->name, strlen(fs->name));
        |-if (*p)
            res = -EBUSY;
        |-else
            *p = fs;

下面重点分析ocfs2内核mout操作:

4.1、ocfs2内核真正的mount操作:

  1. 获取块设备block_device,从mount namespace中发现或申请superblock;
  2. 回调ocfs2_fill_super填充ocfs2 superblock: ocfs2_super

代码流程如下:

static struct dentry *ocfs2_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data)
{
    return mount_bdev(fs_type, flags, dev_name, data, ocfs2_fill_super);
           |-struct block_device *bdev = blkdev_get_by_path(dev_name, mode, fs_type);//根据设备名称获取block_device
           |-struct super_block *s = sget(fs_type, test_bdev_super, set_bdev_super, flags | SB_NOSEC, bdev);//申请或发现一个superblock
               |-struct user_namespace *user_ns = current_user_ns();
               |-struct super_block *s = = alloc_super(type, (flags & ~SB_SUBMOUNT), user_ns);//申请一个新的superblock对象
               |-err = set(s, data);//将superblock与设备block_device关联起来。
               |-s->s_type = type;
               |-strlcpy(s->s_id, type->name, sizeof(s->s_id));
               |-list_add_tail(&s->s_list, &super_blocks);
               |-hlist_add_head(&s->s_instances, &type->fs_supers);
               |-get_filesystem(type);
               |-register_shrinker_prepared(&s->s_shrink);
               |-return s;
           |-s->s_mode = mode;
           |-sb_set_blocksize(s, block_size(bdev));
           |-error = fill_super(s, data, flags & SB_SILENT ? 1 : 0); //call ocfs2_fill_super,填充ocfs2 superblock: ocfs2_super
           |-s->s_flags |= SB_ACTIVE;
           |-bdev->bd_super = s;
           |-return dget(s->s_root);
}

4.2、填充ocfs2_super的流程概述:ocfs2_fill_super()

1. 读superblock号2,校验superblock是否合法;
2. 初始化内核superblock:申请osb对象,初始化:
  2.1 初始化orphan_scan任务;
  2.2 初始化recovery恢复线程;
  2.3 初始化日志journal;
  2.4 设置cluster大小、block大小;
  2.5 填充根目录root内存物理块号,系统目录system内存物理块号;
  2.6 加载根目录inode、系统目录inode和所有全局的系统文件inode;
  2.7 读取global_bitmap系统文件inode;
  2.8 初始化slot槽位信息;
  2.9 初始化ocfs2工作队列;
3. 检测用户态插件o2cb,在内核态是否支持,即:内核态是否实现了相同的o2cb插件。
4. 检查磁盘心跳是否稳定。
5. mount处理:DLM域初始化工作,集群mount不是瞬时完成的,它要等待节点加入DLM domain。
6. 创建根目录root
7. mount成功,设置VOLUME_MOUNTED。

关键代码实现:

static int ocfs2_fill_super(struct super_block *sb, void *data, int silent)
    //读superblock号2,校验superblock是否合法
    |-status = ocfs2_sb_probe(sb, &bh, &sector_size, &stats);
        |-ocfs2_get_sector(sb, bh, OCFS2_SUPER_BLOCK_BLKNO, blksize);//读superblock号2
        |-struct ocfs2_dinode *di = (struct ocfs2_dinode *) (*bh)->b_data;
        |-ocfs2_verify_volume(di, *bh, blksize, stats);
    //初始化内核superblock:申请osb对象,初始化
    |-status = ocfs2_initialize_super(sb, bh, sector_size, &stats);
    //检测用户态插件o2cb,在内核态是否支持,即:内核态是否实现了相同的o2cb插件。
    |-status = ocfs2_verify_userspace_stack(osb, &parsed_options);
    //检查磁盘心跳是否稳定
    |-status = ocfs2_verify_heartbeat(osb);
    
    //mount处理:DLM域初始化工作,集群mount不是瞬时完成的,它要等待节点加入DLM domain。
    |-status = ocfs2_mount_volume(sb);
        |-status = ocfs2_dlm_init(osb); //初始化DLM对象
        |-status = ocfs2_super_lock(osb, 1);//对superblock全局锁加EX写锁
        |-status = ocfs2_find_slot(osb);//更新磁盘上slot文件信息:节点对应的slot信息写入节点号,且标记有效。
        |-status = ocfs2_init_local_system_inodes(osb);
        |-status = ocfs2_check_volume(osb);
        |-status = ocfs2_truncate_log_init(osb);
        |-ocfs2_super_unlock(osb, 1);//对superblock全局锁解EX写锁
 
    //创建根目录root
    |-inode = igrab(osb->root_inode);
    |-sb->s_root = d_make_root(inode);
 
    |-ocfs2_complete_mount_recovery(osb);
    
    printk(KERN_INFO "ocfs2: Mounting device (%s) on (node %s, slot %d) with %s data mode.\n",
           osb->dev_str, nodestr, osb->slot_num, osb->s_mount_opt & OCFS2_MOUNT_DATA_WRITEBACK ? "writeback" : "ordered");
    |-atomic_set(&osb->vol_state, VOLUME_MOUNTED);
    wake_up(&osb->osb_mount_event);
 
    |-ocfs2_complete_quota_recovery(osb);
    |-atomic_set(&osb->vol_state, VOLUME_MOUNTED_QUOTAS);
    wake_up(&osb->osb_mount_event);
 
  /* Start this when the mount is almost sure of being successful */
  |-ocfs2_orphan_scan_start(osb);
  
  return status;

4.3、初始化内核superblock: 申请struct ocfs2_super *osb对象,并用格式化时的参数和系统文件初始化osb对象。截取部分关键代码。

static int ocfs2_initialize_super(struct super_block *sb, struct buffer_head *bh, int sector_size, struct ocfs2_blockcheck_stats *stats)
     |-struct ocfs2_super *osb = kzalloc(sizeof(struct ocfs2_super), GFP_KERNEL);//初始化内核superblock:申请osb对象,初始化
     |-sb->s_fs_info = osb; //把ocfs2_super记录在VFS super_block对象中
       sb->s_op = &ocfs2_sops; //superblock相关的操作函数
       sb->s_d_op = &ocfs2_dentry_ops;//目录相关的操作函数
       ... ...
       cbits = le32_to_cpu(di->id2.i_super.s_clustersize_bits);//格式化时cluster 1M = 20bits
       bbits = le32_to_cpu(di->id2.i_super.s_blocksize_bits); //格式化时block 4096 = 12bits
       sb->s_maxbytes = ocfs2_max_file_offset(bbits, cbits);
       memcpy(&sb->s_uuid, di->id2.i_super.s_uuid, sizeof(di->id2.i_super.s_uuid));
       osb->sb = sb; //把VFS super_block记录在ocfs2_super对象中
       osb->s_sectsize_bits = blksize_bits(sector_size);
       init_waitqueue_head(&osb->dc_event); //downconvert队列
       osb->dc_work_sequence = 0;
       osb->dc_wake_sequence = 0;
       INIT_LIST_HEAD(&osb->blocked_lock_list);//阻塞锁链表
       osb->blocked_lock_count = 0;
       ocfs2_init_steal_slots(osb);
       ocfs2_init_node_maps(osb);
       osb->max_slots = le16_to_cpu(di->id2.i_super.s_max_slots);
       //初始化orphan_scan任务
       ocfs2_orphan_scan_init(osb);
           |-INIT_DELAYED_WORK(&os->os_orphan_scan_work, ocfs2_orphan_scan_work);
                                                                                                  |-queue_delayed_work(osb->ocfs2_wq, &os->os_orphan_scan_work,ocfs2_orphan_scan_timeout());
       //初始化recovery恢复线程
       status = ocfs2_recovery_init(osb);
              |-struct ocfs2_recovery_map *rm = kzalloc(sizeof(struct ocfs2_recovery_map) + osb->max_slots * sizeof(unsigned int), GFP_KERNEL);
              |-rm->rm_entries = (unsigned int *)((char *)rm + sizeof(struct ocfs2_recovery_map));
                            |-osb->recovery_map = rm;
       init_waitqueue_head(&osb->checkpoint_event);
       osb->slot_num = OCFS2_INVALID_SLOT;
       osb->s_xattr_inline_size = le16_to_cpu(di->id2.i_super.s_xattr_inline_size);
       osb->local_alloc_state = OCFS2_LA_UNUSED;
       osb->local_alloc_bh = NULL;
       INIT_DELAYED_WORK(&osb->la_enable_wq, ocfs2_la_enable_worker);
                                           |-osb->local_alloc_state = OCFS2_LA_ENABLED;
       init_waitqueue_head(&osb->osb_mount_event);
       status = ocfs2_resmap_init(osb, &osb->osb_la_resmap);
       //初始化日志journal
       struct ocfs2_journal *journal = kzalloc(sizeof(struct ocfs2_journal), GFP_KERNEL);
       osb->journal = journal;
       journal->j_osb = osb;
       atomic_set(&journal->j_num_trans, 0);
       init_rwsem(&journal->j_trans_barrier);
       init_waitqueue_head(&journal->j_checkpointed);
       journal->j_trans_id = (unsigned long) 1;
       INIT_LIST_HEAD(&journal->j_la_cleanups);
       INIT_WORK(&journal->j_recovery_work, ocfs2_complete_recovery);
       journal->j_state = OCFS2_JOURNAL_FREE;
        //设置cluster大小、block大小
       osb->s_clustersize_bits = le32_to_cpu(di->id2.i_super.s_clustersize_bits);
       osb->s_clustersize = 1 << osb->s_clustersize_bits;
       total_blocks = ocfs2_clusters_to_blocks(osb->sb, le32_to_cpu(di->i_clusters));
       ocfs2_setup_osb_uuid(osb, di->id2.i_super.s_uuid, sizeof(di->id2.i_super.s_uuid))
       osb->root_blkno = le64_to_cpu(di->id2.i_super.s_root_blkno);
       osb->system_dir_blkno = le64_to_cpu(di->id2.i_super.s_system_dir_blkno);
       osb->first_cluster_group_blkno = le64_to_cpu(di->id2.i_super.s_first_cluster_group);
       atomic_set(&osb->vol_state, VOLUME_INIT); //设置volume状态init
       //加载根目录inode、系统目录inode和所有全局的系统文件inode
       status = ocfs2_init_global_system_inodes(osb);
                |-osb->root_inode = ocfs2_iget(osb, osb->root_blkno, OCFS2_FI_FLAG_SYSFILE, 0);//获取根目录inode
                |-osb->sys_root_inode = ocfs2_iget(osb, osb->system_dir_blkno, OCFS2_FI_FLAG_SYSFILE, 0);//获取系统目录inode
                |-for (i = OCFS2_FIRST_ONLINE_SYSTEM_INODE; i <= OCFS2_LAST_GLOBAL_SYSTEM_INODE; i++) {
                       |-ocfs2_get_system_file_inode(osb, i, osb->slot_num);//获取所有系统文件inode
       inode = ocfs2_get_system_file_inode(osb, GLOBAL_BITMAP_SYSTEM_INODE, OCFS2_INVALID_SLOT);//获取global_bitmap系统文件inode
       osb->bitmap_blkno = OCFS2_I(inode)->ip_blkno;//记录global_bitmap系统文件block号
       osb->osb_clusters_at_boot = OCFS2_I(inode)->ip_clusters;//clusters总数
       osb->bitmap_cpg = ocfs2_group_bitmap_size(sb, 0, osb->s_feature_incompat) * 8;//32256
       //初始化slot槽位信息:获取slot_map系统文件
       status = ocfs2_init_slot_info(osb);
             |-struct ocfs2_slot_info *si = kzalloc(struct_size(si, si_slots, osb->max_slots), GFP_KERNEL);
             |-si->si_inode = ocfs2_get_system_file_inode(osb, SLOT_MAP_SYSTEM_INODE, OCFS2_INVALID_SLOT);//
             |-osb->slot_info = (struct ocfs2_slot_info *)si;
       //初始化ocfs2工作队列
       osb->ocfs2_wq = alloc_ordered_workqueue("ocfs2_wq", WQ_MEM_RECLAIM); 

4.4、DLM域初始化工作:集群mount不是瞬时完成的,它要等待节点加入DLM domain。

在mount过程中,DLM完成初始化工作,即:ocfs2_fill_super()中第5步流程。

1. 启动downconvert线程:为降级解锁准备
2. 初始化一个集群连接cluster_connect对象,注册处理函数:
  2.1 recovery驱逐节点函数、
  2.2 停止日志journal处理函数;
  2.3 保存锁协议(AST/BAST);
  2.4 获取插件o2cb,调用o2cb的集群连接函数o2cb_cluster_connect:
    1)如果是DLM模式:生成dlm对象,注册dlm消息处理函数,将注册处理函数:recovery驱逐节点函数、停止日志journal处理函数也注册到dlm对象中。集群连接o2cb_cluster_connect对象与dlm对象相互保存对方。
      a) dlm对象初始化:申请dlm_ctxt,并加入全局dlm_domain队列;
      b) 注册dlm消息及处理函数;
      c) 发起dlm线程:用于处理脏锁资源上的锁请求,授予或转换,发生AST或BAST消息给相关节点。
      d) 发起dlm_recovery线程:用于锁重建
      e) 创建单一线程的工作队列dlmwq;
      f) 创建单一线程的驱逐节点工作队列;
      g) 节点加入dlm流程:
        g1) 本节点加入dlm->domain域:将本节点写入dlm->joining_node表示当前有一个节点正在加入dlm->domain_map,还没有得到其他节点的一致同意,于是向所有已活动节点dlm->live_nodes_map发送query_join消息。
        g2) 其他节点收到该消息,首先检查该query节点在本地是否已检测到磁盘心跳,如果没有,就回复不允许disallow;如果已有稳定心跳,就回复OK,并将query节点设置在心跳存活域dlm->live_nodes_map里面,并也写入dlm->joining_node,表示当前有一个节点正在加入dlm->domain_map。
        g3) 本节点收到其他节点的答复,如果是OK,就将该答复节点设置在dlm->yes_resp_map中记录;如果有节点答复不允许disallow,或者集群中节点数目发生变化,就将dlm->joinging_node重新设置为UNKNOWN,并发送消息给其他节点,也将dlm->joinging_node设置为UNKNOWN,休眠一会儿,重新发送query join消息。等所有节点都答复OK完毕,本节点将自己设置在dlm->domain_map中,然后向所有节点发送assert joined消息。
        g4) 其他节点收到assert joined消息,将该assert节点设置在dlm->domain_map中,并将dlm->joinging_node设置为UNKNOWN,然后本地报告节点UP事件,所有mle对象均在mle->node_map中设置该节点。
        g5) 本节点在所有节点assert确认完毕后,将dlm->dlm_state设置为joined,dlm域中节点总数目dlm->num_join++;该dlm加入域domain成功,将dlm->joining_node设置为UNKNOWN。
3. 初始化全局锁资源:super_lockres、rename_lockres、nfs_sync_lockres、orphan_scan_os_lockres;
4. 将集群连接对象o2cb_cluster_connect保存到osb->cconn。

如下是截取的关键代码以说明流程:

int ocfs2_dlm_init(struct ocfs2_super *osb)
    |-osb->dc_task = kthread_run(ocfs2_downconvert_thread, osb, "ocfs2dc-%s", osb->uuid_str);//启动downconvert线程
    |-struct ocfs2_cluster_connection *conn = NULL;
    |-status = ocfs2_cluster_connect(osb->osb_cluster_stack, osb->osb_cluster_name, strlen(osb->osb_cluster_name),
                                            osb->uuid_str, strlen(osb->uuid_str), &lproto, ocfs2_do_node_down, osb, &conn);
             |-struct ocfs2_cluster_connection *new_conn = kzalloc(sizeof(struct ocfs2_cluster_connection), GFP_KERNEL);
             |-new_conn->cc_recovery_handler = recovery_handler; //ocfs2_do_node_down
             |-new_conn->cc_recovery_data = recovery_data; //osb
             |-new_conn->cc_proto = lproto; //保存锁协议(AST/BAST)
             |-rc = ocfs2_stack_driver_get(stack_name);//获取内核o2cb驱动,调用o2cb的集群连接函数o2cb_cluster_connect
             |-rc = active_stack->sp_ops->connect(new_conn);// call o2cb_cluster_connect
                    |-conn->cc_private = (struct o2dlm_private *)kzalloc(sizeof(struct o2dlm_private), GFP_KERNEL);
                    |-dlm_setup_eviction_cb(&priv->op_eviction_cb, o2dlm_eviction_cb, conn);
                        |-cb->ec_func = f;
                        |-cb->ec_data = data;
                    |-struct dlm_ctxt *dlm = dlm_register_domain(conn->cc_name, dlm_key, &fs_version);
                    |-dlm = __dlm_lookup_domain(domain); //从dlm_domains链表上查找domain的dlm是否注册
                        |-list_for_each_entry(tmp, &dlm_domains, list)
                    |-if (dlm) {//如果已注册,那么该domain 的dlm节点连接数+1,然后返回该dlm。
                        |-dlm->num_joins++;
                        |-goto leave;
                    |-else //如果没有注册,申请新的dlm,注册到dlm_domains全局链表上,并且dlm加入join DLM域。
                        |-new_ctxt = dlm_alloc_ctxt(domain, key);
                            |-struct dlm_ctxt *dlm = kzalloc(sizeof(*dlm), GFP_KERNEL);
                            |-dlm->lockres_hash = (struct hlist_head **)dlm_alloc_pagevec(DLM_HASH_PAGES);//锁资源hash桶
                            |-for (i = 0; i < DLM_HASH_BUCKETS; i++)
                                INIT_HLIST_HEAD(dlm_lockres_hash(dlm, i));
                            |-dlm->master_hash = (struct hlist_head **)dlm_alloc_pagevec(DLM_HASH_PAGES);
                            |-for (i = 0; i < DLM_HASH_BUCKETS; i++)
                                INIT_HLIST_HEAD(dlm_master_hash(dlm, i));
                            |-dlm->node_num = o2nm_this_node(); //本节点记录在dlm中
                            |-dlm->joining_node = DLM_LOCK_RES_OWNER_UNKNOWN;
                            |-INIT_WORK(&dlm->dispatched_work, dlm_dispatch_work);
                         |-dlm = new_ctxt;
                         |-list_add_tail(&dlm->list, &dlm_domains);
                         |-dlm->dlm_locking_proto = dlm_protocol;
                         |-dlm->fs_locking_proto = *fs_proto;
                         |-ret = dlm_join_domain(dlm);
                             |-mlog(0, "Join domain %s\n", dlm->name);
                             |-status = dlm_register_domain_handlers(dlm);//注册节点磁盘心跳UP/DOWN事件、DLM各种消息处理函数。
                             |-status = dlm_launch_thread(dlm);//开启dlm线程
                                 |-mlog(0, "Starting dlm_thread...\n");
                                 |-dlm->dlm_thread_task = kthread_run(dlm_thread, dlm, "dlm-%s", dlm->name);
                             |-status = dlm_launch_recovery_thread(dlm);//开启dlm恢复线程
                                 |-mlog(0, "starting dlm recovery thread...\n");
                                 |-dlm->dlm_reco_thread_task = kthread_run(dlm_recovery_thread, dlm, "dlm_reco-%s", dlm->name);
                             |-dlm->dlm_worker = alloc_workqueue("dlm_wq-%s", WQ_MEM_RECLAIM, 0);//申请dlm工作队列
                             |-do {
                                 status = dlm_try_to_join_domain(dlm); //90000ms 超时,节点尝试加入dlm域domain
                             |-} while (status == -EAGAIN);
                             |-wake_up(&dlm_domain_events);
                             |-conn->cc_lockspace = dlm;
                             |-dlm_register_eviction_cb(dlm, &priv->op_eviction_cb);
                                 |-list_add_tail(&cb->ec_item, &dlm->dlm_eviction_callbacks);
        |-*conn = new_conn;
    |-status = ocfs2_cluster_this_node(conn, &osb->node_num);
        |-osb->node_num = o2nm_this_node(); //本节点记录在superblock
local://初始化全局锁资源:super锁、rename锁、nfs sync锁、orphan scan锁
    |-ocfs2_super_lock_res_init(&osb->osb_super_lockres, osb);
        |-ocfs2_lock_res_init_once(res);
        |-ocfs2_build_lock_name(OCFS2_LOCK_TYPE_SUPER, OCFS2_SUPER_BLOCK_BLKNO, 0, res->l_name);
        |-ocfs2_lock_res_init_common(osb, res, OCFS2_LOCK_TYPE_SUPER, &ocfs2_super_lops, osb);
    |-ocfs2_rename_lock_res_init(&osb->osb_rename_lockres, osb);
    |-ocfs2_nfs_sync_lock_init(osb);
    |-ocfs2_orphan_scan_lock_res_init(&osb->osb_orphan_scan.os_lockres, osb);
    |-osb->cconn = conn;

内核o2cb驱动数据结构:

static struct ocfs2_stack_plugin o2cb_stack = {
    .sp_name    = "o2cb",
    .sp_ops     = &o2cb_stack_ops,
    .sp_owner   = THIS_MODULE,
};

static struct ocfs2_stack_operations o2cb_stack_ops = {
    .connect    = o2cb_cluster_connect,
    .disconnect = o2cb_cluster_disconnect,
    .this_node  = o2cb_cluster_this_node,
    .dlm_lock   = o2cb_dlm_lock,
    .dlm_unlock = o2cb_dlm_unlock,
    .lock_status    = o2cb_dlm_lock_status,
    .lvb_valid  = o2cb_dlm_lvb_valid,
    .lock_lvb   = o2cb_dlm_lvb,
    .dump_lksb  = o2cb_dump_lksb,
};

4.5、节点尝试加入dlm domain:

static int dlm_try_to_join_domain(struct dlm_ctxt *dlm)
    |-__dlm_set_joining_node(dlm, dlm->node_num);
        |-dlm->joining_node = node;//将节点记录在dlm的joining_node正在加入节点
        |-wake_up(&dlm->dlm_join_events);
    |-while ((node = find_next_bit(ctxt->live_map, O2NM_MAX_NODES, node + 1)) < O2NM_MAX_NODES) {//向dlm中已存在的所有节点(自身除外)发送请求加入dlm域的请求。
        |-status = dlm_request_join(dlm, node, &response);//向节点node发送请求加入dlm
        |-if (response == JOIN_OK) //如果这个节点回复ok,就把节点node记录在ctxt->yes_resp_map中,以便统计票数。
            set_bit(node, ctxt->yes_resp_map);
        |-if (dlm_should_restart_join(dlm, ctxt, response)){ //如果这个节点node回复不允许disallow,或者集群中节点数目dlm->live_nodes_map发生变化
            status = -EAGAIN; //那就重新来过:即:退出该函数,释放设置的数据,返回到上层do-while重新进入该函数执行。
            goto bail;
        |-}
    |-}
    //所有节点都投完了票,且都同意本节点加入dlm域,那就将本节点dlm->node_num设置在dlm->domain_map中。
    |-memcpy(dlm->domain_map, ctxt->yes_resp_map, sizeof(ctxt->yes_resp_map));
    |-set_bit(dlm->node_num, dlm->domain_map);
    |-dlm_send_join_asserts(dlm, ctxt->yes_resp_map);//向所有节点发送assert joined确认消息,让其他节点也把自己设置到它的dlm->domain_map中
    |-dlm->dlm_state = DLM_CTXT_JOINED; //节点已加入dlm domain
    |-dlm->num_joins++;//增加一个节点记录
    |-__dlm_set_joining_node(dlm, DLM_LOCK_RES_OWNER_UNKNOWN);

综上所述,这就是mount一个ocfs2文件系统所需要完成的主要工作,涉及到的数据初始化比较多,又因为集群存在分布式锁管理器DLM,DLM的初始化也是在mout过程中的,涉及到非常复杂的流程,节点的加入必须在整个集群中得到一致同意,任何不同意或节点变化都会导致重新发送DLM加入申请,因此,这需要一个达成一致的过程。

下一篇重点介绍分布式锁管理器DLM,这是OCFS2中最复杂的部分。

======================================================================================