Ceph源码笔记- CephFS-Client-Mds
(1)Client
(1-a) create
fuse_ll_create Client->ll_create Client->_create
(1-b) open
fuse_ll_open fuse_ll_req_prepare CephFuse->Handle->client->ll_open Client->ll_open Client->_open CEPH_MDS_OP_OPEN Client->make_request Client->choose_target_mds Inode->hash_dentry_name Client->send_request
(1-c) read
fuse_ll_read Client->ll_read Client->_read bjectCacher->_readx ObjectCacher->bh_read Client->_read_async ObjectCacher->file_read ObjectCacher->prepare_read ObjectCacher->readx
(1-d) write
fuse_ll_write
Client->ll_write
Client->_write
Client->get_caps
// 分支A:缓存 :Client->objectcacher
Client->objectcacher
ObjectCacher->file_write
ObjectCacher->writex
ObjectCacher->_wait_for_write
// 分支B:直接写透
Client->_flush_range
Filer->write_trunc
(2)MDS
(2-a) 多活mds中,分发给某一个mds处理
Server->dispatch_client_request
CEPH_MDS_OP_LOOKUPINO
CEPH_MDS_OP_LOOKUPNAME
CEPH_MDS_OP_LOOKUPPARENT
Server->handle_client_lookup_ino
MDCache->open_ino
MDCache->do_open_ino
MDCache->do_open_ino_peer
MDCache->mds->send_message_mds
C_MDS_LookupIno2
(2-b) mds的处理:输入路径,返回 Inode
CEPH_MDS_OP_OPEN: Server->handle_client_openc // 遍历mdcache获得inode和dentry MDCache->path_traverse // 分支A:本节点Cache中不存在,去其他mds节点找找 MDCache->find_ino_peers MDSRank->get_nodeid // 分支B:如果Cache中存在该文件的iNode就直接打开 Server->handle_client_open
(2-c) 分发给某一个mds处理Openc选择合适的poolid
void Server::handle_client_openc(MDRequestRef& mdr)
{
MClientRequest *req = mdr->client_request;
client_t client = mdr->get_client();
// 将flag转为cmode,由于是CREATE操作,此处的cmode为CEPH_FILE_MODE_WR(写)、或者CEPH_FILE_MODE_RDWR(读+写)
int cmode = ceph_flags_to_mode(req->head.args.open.flags);
...
bool excl = req->head.args.open.flags & CEPH_O_EXCL;
// 客户端未指定O_EXCL标记,代表着:若文件存在则直接打开,若不存在则创建后打开
// 此处还做了额外的处理:若文件存在但状态STALE,则查找最新的inode信息再重试(C_MDS_TryFindInode);
// 异常情况下,除非为不存在(-ENOENT),否则直接返回
if (!excl) {
// path_traverse 为逐级向上查找过程,加载路径上所有节点的inode和dentry
// 在MDS源码分析-3一文中有详细阐述
int r = mdcache->path_traverse(mdr, NULL, NULL, req->get_filepath(),
&mdr->dn[0], NULL, MDS_TRAVERSE_FORWARD);
if (r > 0) return;
if (r == 0) {
// 文件存在,则直接打开,与上述的OPEN处理流程一致
handle_client_open(mdr);
return;
}
if (r < 0 && r != -ENOENT) {
if (r == -ESTALE) {
// STALE状态,则重新加载最新inode
MDSInternalContextBase *c = new C_MDS_TryFindInode(this, mdr);
mdcache->find_ino_peers(req->get_filepath().get_ino(), c);
} else {
dout(10) << "FAIL on error " << r << dendl;
respond_to_request(mdr, r);
}
return;
}
}
set<SimpleLock*> rdlocks, wrlocks, xlocks;
file_layout_t *dir_layout = NULL;
// 准备读锁和互斥锁
// 从父目录开始,路径上的目录全部需要读锁
// 由于此流程为新创建文件,inode还未生成,所以创建一个null dentry,并将null dentry加入互斥锁列表xlocks
// 此处返回的是一个null dentry,即还未关联inode的dentry,注意并不是一个nullptr
CDentry *dn = rdlock_path_xlock_dentry(mdr, 0, rdlocks, wrlocks, xlocks,
!excl, false, false, &dir_layout);
if (!dn) return;
if (mdr->snapid != CEPH_NOSNAP) {
// 不允许在snap目录下做创建操作,此检查按理应该前置,可以避免一些无谓的开销
respond_to_request(mdr, -EROFS);
return;
}
// 设置layout,若父目录有layout则使用,否则使用默认的layout
file_layout_t layout;
...
const auto default_layout = layout;
// 读取请求中可能携带的layout参数,进行转换,更新layout参数
...
// 若layout发生改变,说明客户端在创建的同时还要设置定制化的layout
if (default_layout != layout) {
access |= MAY_SET_VXATTR;
}
// 对layout设置的正确性做检查,主要有以下规则
// stripe unit要小于或等于object size,确保一个object至少能容纳一个stripe
// object size必须为stripe unit的整数倍,这很自然
// stripe unit必须为64K的整数倍,这与OSD bluestore的min_alloc_size(HDD的默认值为64K)保持对齐,
// 才能保证在rados的存储空间不被浪费,同时得到最好的rados性能
// 另外,pool设置必须是mdsmap中注册过的data pool之一
if (!layout.is_valid()) {
dout(10) << " invalid initial file layout" << dendl;
respond_to_request(mdr, -EINVAL);
return;
}
if (!mds->mdsmap->is_data_pool(layout.pool_id)) {
dout(10) << " invalid data pool " << layout.pool_id << dendl;
respond_to_request(mdr, -EINVAL);
return;
}
// 额外增加父节点的authlock到rdlocks,然后进行各种锁的加锁操作
// 自此开始进入被锁保护的操作流程
CDir *dir = dn->get_dir();
CInode *diri = dir->get_inode();
rdlocks.insert(&diri->authlock);
if (!mds->locker->acquire_locks(mdr, rdlocks, wrlocks, xlocks))
return;
// 父目录的操作权限检查
if (!check_access(mdr, diri, access))
return;
// 检查dir分片的条目数是否达到限制,如果达到限制,请求会失败,需要在目录分裂之后才能创建新文件
if (!check_fragment_space(mdr, dir))
return;
CDentry::linkage_t *dnl = dn->get_projected_linkage();
// 确保关联的inode不存在,因为dn是新创建的null dentry,此时还未关联inode
if (!dnl->is_null()) {
...
respond_to_request(mdr, -EEXIST);
return;
}
// 创建一个新的inode,使用给定的ino
// 此处的req->head.ino为之前为session预分配的ino之一
// 绝大多数情况下,新创建的inode会直接使用传入的ino,但在session处于opening状态时,会不信任预分配的ino
// 此种特殊情况下,函数内会重新从inotable重新分配ino
// 还可能触发新一轮的ino预分配
// 最后新分配的inode会从prealloc状态转到used,并加入到mdcache中
CInode *in = prepare_new_inode(mdr, dn->get_dir(), inodeno_t(req->head.ino),
req->head.args.open.mode | S_IFREG, &layout);
assert(in);
// 将inode与此前的null dentry关联,dn就不再是null dentry
dn->push_projected_linkage(in);
...
// 为客户端接下来的写操作准备caps
Capability *cap = mds->locker->issue_new_caps(in, cmode, mdr->session, realm, req->is_replay());
in->authlock.set_state(LOCK_EXCL);
in->xattrlock.set_state(LOCK_EXCL);
if (cap && (cmode & CEPH_FILE_MODE_WR)) {
in->inode.client_ranges[client].range.first = 0;
in->inode.client_ranges[client].range.last = in->inode.get_layout_size_increment();
in->inode.client_ranges[client].follows = follows;
cap->mark_clientwriteable();
}
// 开始准备mdlog,记录新文件的创建操作,并将mdcache中的父目录标记为dirty
mdr->ls = mdlog->get_current_segment();
EUpdate *le = new EUpdate(mdlog, "openc");
mdlog->start_entry(le);
le->metablob.add_client_req(req->get_reqid(), req->get_oldest_client_tid());
journal_allocated_inos(mdr, &le->metablob);
mdcache->predirty_journal_parents(mdr, &le->metablob, in, dn->get_dir(), PREDIRTY_PRIMARY|PREDIRTY_DIR, 1);
le->metablob.add_primary_dentry(dn, in, true, true, true);
// make sure this inode gets into the journal
le->metablob.add_opened_ino(in->ino());
LogSegment *ls = mds->mdlog->get_current_segment();
ls->open_files.push_back(&in->item_open_file);
// 准备openc完成的回调,它会在journal submit_entry成功,待flusher下刷log以后被调用
// 回调中会将新的inode标记为dirty,将dentry放入mdcache中,执行balancer->hit_inode,最后返回客户端消息
C_MDS_openc_finish *fin = new C_MDS_openc_finish(this, mdr, dn, in, follows);
if (mdr->client_request->get_connection()->has_feature(CEPH_FEATURE_REPLY_CREATE_INODE)) {
dout(10) << "adding ino to reply to indicate inode was created" << dendl;
// add the file created flag onto the reply if create_flags features is supported
::encode(in->inode.ino, mdr->reply_extra_bl);
}
// 执行log提交journal的流程,提交前会做一次early reply,此reply返回unsafe状态
// 然后会提交log到journal queue,等待异步回刷,异步回刷以后,会调用上面的回调C_MDS_openc_finish->finish()
journal_and_reply(mdr, in, dn, le, fin);
// 本次openc操作可能导致dir分片的条目数超标,可能触发目录分裂,目录的分裂、合并过程以后专门介绍
mds->balancer->maybe_fragment(dir, false);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?