MDS源码分析-3 LOOKUP & GETATTR

  • MDS源码剖析-2 消息分发中,已知客户端目录相关操作分发至Server.cc的dispatch_client_request进行处理。主要包含以下请求
void Server::dispatch_client_request(MDRequestRef& mdr) {
        ...
        switch (req->get_op()) {
        // 根据inode NO.(ino) 查找inode
        case CEPH_MDS_OP_LOOKUPHASH:
        case CEPH_MDS_OP_LOOKUPINO:
                handle_client_lookup_ino(mdr, false, false);
                break;
        // 根据inode NO.(ino) 查找父节点
        case CEPH_MDS_OP_LOOKUPPARENT:
                handle_client_lookup_ino(mdr, true, false);
                break;
        // 根据inode NO.(ino) 查找inode及Dentry
        case CEPH_MDS_OP_LOOKUPNAME:
                handle_client_lookup_ino(mdr, false, true);
                break;
     
        // 根据path查找inode和dentry
        case CEPH_MDS_OP_LOOKUP:
                handle_client_getattr(mdr, true);
                break;
        // 根据path查找inode
        case CEPH_MDS_OP_GETATTR:
                handle_client_getattr(mdr, false);
                break;
        
        // 设置/更新文件attr
        case CEPH_MDS_OP_SETATTR:
                handle_client_setattr(mdr);
                break;
        // 设置/更新文件layout
        case CEPH_MDS_OP_SETLAYOUT:
                handle_client_setlayout(mdr);
                break;
        // 设置/更新目录layout
        case CEPH_MDS_OP_SETDIRLAYOUT:
                handle_client_setdirlayout(mdr);
                break;
        // 设置文件xattr k-v pair
        case CEPH_MDS_OP_SETXATTR:
                handle_client_setxattr(mdr);
                break;
        // 删除文件xattr
        case CEPH_MDS_OP_RMXATTR:
                handle_client_removexattr(mdr);
                break;
  
        // 分段ls dir
        case CEPH_MDS_OP_READDIR:
                handle_client_readdir(mdr);
                break;
        // 文件加xlock
        case CEPH_MDS_OP_SETFILELOCK:
                handle_client_file_setlock(mdr);
                break;
        // 文件加rdlock
        case CEPH_MDS_OP_GETFILELOCK:
                handle_client_file_readlock(mdr);
                break;

        // 创建并open文件
        case CEPH_MDS_OP_CREATE:
                if (mdr->has_completed)
                        handle_client_open(mdr);  // already created.. just open
                else 
                        handle_client_openc(mdr);
                break;
        // open文件
        case CEPH_MDS_OP_OPEN:
                handle_client_open(mdr);
                break;
        // 创建文件
        case CEPH_MDS_OP_MKNOD:
                handle_client_mknod(mdr);
                break;
        // 创建link
        case CEPH_MDS_OP_LINK:
                handle_client_link(mdr);
                break;
        // 删除link/目录
        case CEPH_MDS_OP_UNLINK:
        case CEPH_MDS_OP_RMDIR:
                handle_client_unlink(mdr);
                break;
        // 重命名
        case CEPH_MDS_OP_RENAME:
                handle_client_rename(mdr);
                break;
        // 创建目录
        case CEPH_MDS_OP_MKDIR:
                handle_client_mkdir(mdr);
                break;
        // 创建符号(软)链接
        case CEPH_MDS_OP_SYMLINK:
                handle_client_symlink(mdr);
                break;      
        
        // 查询快照列表
        case CEPH_MDS_OP_LSSNAP:
                handle_client_lssnap(mdr);
                break;
        // 创建快照
        case CEPH_MDS_OP_MKSNAP:
                handle_client_mksnap(mdr);
                break;
        // 删除快照
        case CEPH_MDS_OP_RMSNAP:
                handle_client_rmsnap(mdr);
                break;
        // 快照重命名
        case CEPH_MDS_OP_RENAMESNAP:
                handle_client_renamesnap(mdr);
                break;

        default:
                dout(1) << " unknown client op " << req->get_op() << dendl;
                respond_to_request(mdr, -EOPNOTSUPP);
        }
  • 以下两篇博文对cephfs各种基本概念和锁有比较深入的阐述,建议先进行阅读,重点要理解CDir、CDentry、和CInode结构的关联关系
    CEPHFS内部实现 概念篇
    CEPH MDS锁实现介绍
    简要类图如下:
    inode-dentry-dir.png

LOOKUP inode/parent/name,基于ino

涉及CEPH_MDS_OP_LOOKUPHASHCEPH_MDS_OP_LOOKUPINOCEPH_MDS_OP_LOOKUPPARENTCEPH_MDS_OP_LOOKUPNAME四种消息
均由Server::handle_client_lookup_ino(MDRequestRef& mdr, bool want_parent, bool want_dentry)函数处理
源码:src/mds/Server.cc

void Server::handle_client_lookup_ino(MDRequestRef& mdr,
                                      bool want_parent, bool want_dentry)
{
  MClientRequest *req = mdr->client_request;
 
  //尝试从mdcache的inodemap中查找
  inodeno_t ino = req->get_filepath().get_ino();
  CInode *in = mdcache->get_inode(ino);
  ...
  if (!in) {
    //若mdcache中未找到,则调用open_ino进行异步加载,本次处理结束
    //加载完成后通过C_MDS_LookupIno2回调,执行三种可能的路径
    //1. 不存在,返回-ESTALE;2.存在且属于本rank,重新分发mdr至此函数处理;3.存在但属于其它rank,forward给dest rank
    mdcache->open_ino(ino, (int64_t)-1, new C_MDS_LookupIno2(this, mdr), false);
    return;
  }
  // 若路径中存在快照,则递归打开各级snaprealm,以确保拿到最新的快照信息
  if (mdr && in->snaprealm && !in->snaprealm->is_open() &&
      !in->snaprealm->open_parents(new C_MDS_RetryRequest(mdcache, mdr))) {
    return;
  }
 
  // check for nothing (not read or write); this still applies the
  // path check.
  if (!check_access(mdr, in, 0))
    return;
 
  //对于want_parent || want_dentry情形,pin dn并表明需要读锁
  CDentry *dn = in->get_projected_parent_dn();
  CInode *diri = dn ? dn->get_dir()->inode : NULL;
  set<SimpleLock*> rdlocks;
  if (dn && (want_parent || want_dentry)) {
    mdr->pin(dn);
    rdlocks.insert(&dn->lock);
  }
 
  // 对于非loner客户端,可能还需要对authlock和xattrlock 加读锁
  unsigned mask = req->head.args.getattr.mask;
  if (mask) {
    Capability *cap = in->get_client_cap(mdr->get_client());
    int issued = 0;
    if (cap && (mdr->snapid == CEPH_NOSNAP || mdr->snapid <= cap->client_follows))
      issued = cap->issued();
    // permission bits, ACL/security xattrs
    if ((mask & CEPH_CAP_AUTH_SHARED) && (issued & CEPH_CAP_AUTH_EXCL) == 0)
      rdlocks.insert(&in->authlock);
    if ((mask & CEPH_CAP_XATTR_SHARED) && (issued & CEPH_CAP_XATTR_EXCL) == 0)
      rdlocks.insert(&in->xattrlock);
 
    mdr->getattr_caps = mask; 
  
  //加读锁
  if (!rdlocks.empty()) {
    set<SimpleLock*> wrlocks, xlocks;
    if (!mds->locker->acquire_locks(mdr, rdlocks, wrlocks, xlocks))
      return;
       
    if (diri != NULL) {
      // need read access to directory inode
      if (!check_access(mdr, diri, MAY_READ))
        return;
    }
  }    
  // lookup_parent的情况,mdr->tracei返回为dn->get_dir()->inode,也就是父节点的inode
  if (want_parent) {
    if (in->is_base()) {
      respond_to_request(mdr, -EINVAL);
      return;
    }
    if (!diri || diri->is_stray()) {
      respond_to_request(mdr, -ESTALE);
      return;
    }
    dout(10) << "reply to lookup_parent " << *in << dendl;
    mdr->tracei = diri;
    respond_to_request(mdr, 0);
  } else {
    if (want_dentry) {
      inodeno_t dirino = req->get_filepath2().get_ino();
      if (!diri || (dirino != inodeno_t() && diri->ino() != dirino)) {
        respond_to_request(mdr, -ENOENT);
        return;
      }
      dout(10) << "reply to lookup_name " << *in << dendl;
    } else
      dout(10) << "reply to lookup_ino " << *in << dendl;
    // 非lookup_parent的情况,mdr->tracei返回为当前节点的inode
    mdr->tracei = in;
    if (want_dentry)
      mdr->tracedn = dn; //lookup_name的情况还需额外返回dentry
    respond_to_request(mdr, 0);
  }
}

open_ino细节分析
mdcache->open_ino , 加载inode
由于此时还不知道ino对应的rank,默认情况下open_ino_info_t的check_peers为true,所以在此lookup场景,调用过程为:

  • open_ino -> do_open_ino -> do_open_ino_peer->mds->send_message_mds(new MMDSOpenIno(info.tid, ino, pa), peer)
    此过程将向第一个非自己且不在checked列表的mds发起openino请求,此mds暂时标记为checking
  • 对端的mds接收到MMDSOpenIno消息后,由MDCache::handle_open_ino处理。先调用get_inode从本地缓存inodemap中查找,如果没有找到,则根据消息中传来的祖先信息m->ancestors调用open_ino_traverse_dir进行查找和加载,在基于ino的lookup情形,mds无从知道祖先信息,所以此步骤如果inodemap中不存在,则本MDS节点将查找失败。
  • 对端MDS将返回MMDSOpenInoReply消息,若查找到,将返回inode及其祖先节点inode信息,若没有找到将返回错误码,返回消息由MDCache::handle_open_ino_reply进行处理
  • handle_open_ino_reply中,将对端mds rank加入checked列表,将checking置空。若对端返回了错误码,则重新执行do_open_ino,这将重新在另一个MDS开展上述查找逻辑;若对端返回了inode,则执行open_ino_finish流程
  • open_ino_finish会将open_ino_info_topening_inodes中移除,并执行open_ino函数注册的回调C_MDS_LookupIno2, 回调会执行三种可能的路径:1. 不存在,返回-ESTALE;2.存在且属于本rank,重新分发mdr至Server::handle_client_lookup_ino处理;3.存在但属于其它rank,forward给dest rank,由其它MDS的Server::handle_client_lookup_ino处理
  • 综上,由于请求中提供的可使用的信息太少,基于ino查找inode是一个可能要跨多个MDS经历多次网络消息来回的过程,若在所有MDS的cache中都不存在,将会查找失败。这是一个非常低效的操作

LOOKUP,基于path

涉及CEPH_MDS_OP_LOOKUP消息
由函数Server::handle_client_getattr(MDRequestRef& mdr, bool is_lookup)处理
源码文件src/mds/Server.cc

void Server::handle_client_getattr(MDRequestRef& mdr, bool is_lookup)
{ 
  MClientRequest *req = mdr->client_request;   
  set<SimpleLock*> rdlocks, wrlocks, xlocks;
  //确保path非空
  if (req->get_filepath().depth() == 0 && is_lookup) {
    // refpath can't be empty for lookup but it can for
    // getattr (we do getattr with empty refpath for mount of '/')
    respond_to_request(mdr, -EINVAL);
    return;
  }
  ...
  // 遍历path准备读锁,返回Inode
  CInode *ref = rdlock_path_pin_ref(mdr, 0, rdlocks, want_auth, false, NULL, 
                                    !is_lookup);
  //若inode未找到,直接返回,在rdlock_path_pin_ref中会完成向客户端的response
  if (!ref) return;
  
  //inode找到的情况,对于非loner客户端,可能还需要对linklock、authlock、filelock和xattrlock 加读锁
  ...
  // 加锁
  if (!mds->locker->acquire_locks(mdr, rdlocks, wrlocks, xlocks))
    return;                          
                                     
  if (!check_access(mdr, ref, MAY_READ))
    return;                                                          
  ...
  // 返回inode
  mdr->tracei = ref;                 
  if (is_lookup) 
    // 返回dentry
    mdr->tracedn = mdr->dn[0].back();
  respond_to_request(mdr, 0);    
}
  • rdlock_path_pin_ref是过程中最关键的函数,其实现逻辑如下
CInode* Server::rdlock_path_pin_ref(MDRequestRef& mdr, int n,
                                    set<SimpleLock*> &rdlocks,
                                    bool want_auth,
                                    bool no_want_auth, /* for readdir, who doesn't want auth _even_if_ it's
                                                          a snapped dir */
                                    file_layout_t **layout,
                                    bool no_lookup)    // true if we cannot return a null dentry lease
{
  const filepath& refpath = n ? mdr->get_filepath2() : mdr->get_filepath();
  ...
  // 遍历mdcache获得inode和dentry
  int r = mdcache->path_traverse(mdr, NULL, NULL, refpath, &mdr->dn[n], &mdr->in[n], MDS_TRAVERSE_FORWARD);
  if (r > 0)
    return NULL; // delayed
  if (r < 0) {  // error
    if (r == -ENOENT && n == 0 && !mdr->dn[n].empty()) {
      if (!no_lookup) {
        mdr->tracedn = mdr->dn[n].back();
      }
      respond_to_request(mdr, r);
    } else if (r == -ESTALE) {
      dout(10) << "FAIL on ESTALE but attempting recovery" << dendl;
      // C_MDS_TryFindInode回调,若仍然返回-ESTALE则直接向客户端返回错误码;
      // 若find_ino_peers成功,则重新分发请求至handle_client_getattr处理
      MDSInternalContextBase *c = new C_MDS_TryFindInode(this, mdr);
      //类似于do_open_inode,循环所有MDS RANK,_do_find_ino_peer
      mdcache->find_ino_peers(refpath.get_ino(), c);
    } else {
      dout(10) << "FAIL on error " << r << dendl;
      respond_to_request(mdr, r);
    }
    return 0;
  }
  CInode *ref = mdr->in[n];
  dout(10) << "ref is " << *ref << dendl;
       
  // fw to inode auth?
  if (mdr->snapid != CEPH_NOSNAP && !no_want_auth)
    want_auth = true;
  if (want_auth) {
    // lookup请求的want_auth为false
    ...
  }
 
  for (int i=0; i<(int)mdr->dn[n].size(); i++) 
    rdlocks.insert(&mdr->dn[n][i]->lock);
  if (layout)
    mds->locker->include_snap_rdlocks_wlayout(rdlocks, ref, layout);
  else
    mds->locker->include_snap_rdlocks(rdlocks, ref);
 
  // set and pin ref
  mdr->pin(ref);
  return ref;
} 
  • mdcache->path_traverse返回-ESTALE的情况下,lookup过程会产生跨MDS的find inode请求,下面对mdcache->path_traverse的过程做进一步分析
    源码文件 src/mds/MDCache.cc
// 函数返回大于0的值,说明需要等待,将由C_MDS_RetryRequest进行重试,_get_waiter将返回此回调
// 返回小于0值,表示错误
int MDCache::path_traverse(MDRequestRef& mdr, Message *req, MDSInternalContextBase *fin,     // who
                           const filepath& path,                   // path
                           vector<CDentry*> *pdnvec,         // result dn
                           CInode **pin,  // result inode
                           int onfail) // == MDS_TRAVERSE_FORWARD
{
  bool discover = (onfail == MDS_TRAVERSE_DISCOVER); // false 
  bool null_okay = (onfail == MDS_TRAVERSE_DISCOVERXLOCK); //false
  bool forward = (onfail == MDS_TRAVERSE_FORWARD); //true

  ...
  CInode *cur = get_inode(path.get_ino());
  // MDSDIR的机制还没完全弄清楚
  if (cur == NULL) {
    if (MDS_INO_IS_MDSDIR(path.get_ino())) 
      open_foreign_mdsdir(path.get_ino(), _get_waiter(mdr, req, fin));
    else {
      /*对于一般的目录,若本地cache中找不到其inode,会返回-ESTALE,
      这样在上层函数`rdlock_path_pin_ref`中就会向其它MDS进行inode查询*/
      return -ESTALE;
    }   
    return 1;
  }     
  if (cur->state_test(CInode::STATE_PURGING))
    return -ESTALE;
        
  // make sure snaprealm are open...
  if (mdr && cur->snaprealm && !cur->snaprealm->is_open() &&
      !cur->snaprealm->open_parents(_get_waiter(mdr, req, fin))) {
    return 1;
  }

  // 清空pdnvec,设置返回的inode,接下来要组装祖先dentry列表,通过pdnvec返回
  if (pdnvec)
    pdnvec->clear();
  if (pin)
    *pin = cur;
        
  //开始path逐级遍历
  unsigned depth = 0;
  while (depth < path.depth()) {
    ... 
    //跳过snapdir并获得snapid
    ...
    // open dir
    frag_t fg = cur->pick_dirfrag(path[depth]);
    CDir *curdir = cur->get_dirfrag(fg);
    if (!curdir) {
      if (cur->is_auth()) {
        // parent dir frozen_dir?
        ...
        curdir = cur->get_or_open_dirfrag(this, fg);
      } else {
        // discover?
        // dir不属于本rank,向inode的auth_rank发起discover请求
        discover_path(cur, snapid, path.postfixpath(depth), _get_waiter(mdr, req, fin),
                      null_okay);
        // 触发异步重试,然后回到这里继续后续流程
        return 1;
      }
    }
    // 至此,本级的CDir一定从本节点或其它MDS获取到了
    assert(curdir);
    ... 
        
    // dentry
    CDentry *dn = curdir->lookup(path[depth], snapid);
    CDentry::linkage_t *dnl = dn ? dn->get_projected_linkage() : 0;
        
    // null and last_bit and xlocked by me?
    ...
    //等待别的mds释放dn独占锁并重试
    ...
    // dn还未与inode关联的情况
    if (dnl && dnl->is_null()) {
      if (dn可读 || 被自己客户端锁定)
        ...
        return -ENOENT;
      } else {
        // 可能是别的客户端已经锁定正在操作,则等待后重试
        dn->lock.add_waiter(SimpleLock::WAIT_RD, _get_waiter(mdr, req, fin));
        return 1;
      }
    }
    // dn已经与inode关联的情况
    if (dnl && !dnl->is_null()) {
      CInode *in = dnl->get_inode();
      if (!in) {
        // linkage中只有remote_ino没有inode的情况
        assert(dnl->is_remote());
        in = get_inode(dnl->get_remote_ino());
        // 先从本地缓存加载  remote_ino对应的inode,若没有,走open_ino流程加载inode
        ...
      }
      // 至此找到dn对应的inode
      cur = in;
      ...
      // add to trace, continue.
      touch_inode(cur);
      if (pdnvec)
        pdnvec->push_back(dn);
      if (pin)
        *pin = cur;
      depth++;
      continue;
    }   
    
    // dentry都不存在的情况
    if (curdir->is_auth()) {
      // dir属于本rank的情况
      if (curdir->is_complete() ||
          (snapid == CEPH_NOSNAP &&
           curdir->has_bloom() &&
           !curdir->is_in_bloom(path[depth]))){
        // dir处于complete状态,或者通过布隆过滤器确认dn不存在
        ...
        return -ENOENT;
      } else {
        ...
        // dir处于非complete状态,重新fetch加载,然后重试
        touch_inode(cur);
        curdir->fetch(_get_waiter(mdr, req, fin), path[depth]);
        if (mds->logger) mds->logger->inc(l_mds_traverse_dir_fetch);
        return 1;
      } 
    } else {
      // dir不属于本rank的情况
      mds_authority_t dauth = curdir->authority();
      ...
      if (forward) { // forward在此lookup情形下为true,所以会做转发
        // forward
        if (curdir->is_ambiguous_auth()) {
          // dir的auth mds未知的情况,等待其auth明确后重试
          curdir->add_waiter(CDir::WAIT_SINGLEAUTH, _get_waiter(mdr, req, fin));
          return 1;
        } 
        //转发请求到auth mds,返回2触发上层调用进入等待重试逻辑
        request_forward(mdr, dauth.first);
        return 2;
      }    
    }
  }
  ...
  return 0;
}

简单总结下path_traverse过程:

  • 首先找到本级的CInode,若本地不存在就远程加载
  • 然后逐级向上查找,先找到CInode的父CDir,然后通过CDir lookup CDentry,然后通过CDentry的linkage找到其CInode,过程中若出现dir\dentry\inode在本地找不到的情况,则进行远程加载
  • 所有的远程加载过程均为异步重试模式,保障mds的工作线程能得到及时的释放
  • 直至路径上到root的所有层级节点的inode和dentry都找全以后,将各祖先节点的完整CDentry结构通过参数pdnvec返回,当前节点的inode通过参数pin返回

CEPH_MDS_OP_LOOKUPINOCEPH_MDS_OP_LOOKUPPARENTCEPH_MDS_OP_LOOKUPNAME等消息的处理相比,CEPH_MDS_OP_LOOKUP的处理中,除了查询本节点inode和dentry以外,还需加载所有祖先节点的dentry,对于目录层次深的情况,其代价更大,需要依赖各级缓存来减少此类lookup操作

GETATTR

涉及消息CEPH_MDS_OP_GETATTR
由函数Server::handle_client_getattr(MDRequestRef& mdr, bool is_lookup)处理
源码文件src/mds/Server.cc
其实现与前述CEPH_MDS_OP_LOOKUP消息使用相同的处理函数,区别在于is_lookup参数,此处为false,而 lookup时为true
相对lookup实现上有如下差异:

  • getattr允许不传入refpath,不传时,将获取/的inode返回
  • 不返回tracedn,也就是祖先节点的dn,但path_traverse过程仍然要做
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。