Ceph 读写流程:客户端写流程分析
本文对 Ceph 客户端写流程进行了梳理和总结,包括 ll_open
和 ll_write
的处理流程。
正文
首先用户需要通过 ll_open
打开文件并拿到 fh,第一步先做权限检查,进入 may_open
, 这里会根据用户带回的 flags 确定 want
是 MAY_WRITE
还是 MAY_READ
, 接着通过一次 getattr 拿到文件的 mode, 当然这里 getattr 不是每次都发,只有 client 认为自己的所持有的文件权限不是最新的时候才会发,具体有两种情况:
- 开启 acl 并且没有拿过文件的 xattr (acl 设置是外带在 xattr 中的,不在 inode 里)
caps_issued_mask
认为当前 client 的 caps 不足以保证所持有的权限为最新时
Client::_getattr 判断是否需要发送请求
1 | int Client::_getattr(Inode *in, int mask, const UserPerm& perms, bool force) |
简单解释一下第二点就是如果 client 没有持有 As (开启 acl 的话还需要 Xs)那么就需要向 mds 发 getattr
拿到 perm 之后做一个 inode_permission
检查一下 mode (如果设了 acl 那就检查 acl)
接着进到 _open
, 先把 flags (O_RDONLY
, ORDWR
, O_CREAT
…) 转成 ceph 内部的 cmode (CEPH_FILE_MODE_PIN
, CEPH_FILE_MODE_RD
, CEPH_FILE_MODE_RDWR
, …) 和对应的 want_cap
(CEPH_CAP_PIN
p, CEPH_CAP_FILE_SHARED
Fs, CEPH_CAP_FILE_RD
Fr, …)
如果 client 的 caps 满足就直接把 inode 的 ref +1 就可以返回了,否则需要给 mds 发一个 open 消息
Client::_open 判断是否发送 CEPH_MDS_OP_OPEN
1 | if ((flags & O_TRUNC) == 0 && in->caps_issued_mask(want)) { |
mds 消息回来之后通过 _create_fh
创建了一个 Fh 加入到 inode 的 fhs 并返回给用户, 同时将这个 fh 加入 client 的 ll_unclosed_fh_set
中
接着用户拿到 Fh 之后就可以调用 ll_write
并传入刚刚拿到的 Fh,偏移 off, 写入长度 len 和数据 data
首先第一步 _write
先做一些基本检查 (判断一下 offset+len 范围是否合法,pool 有没有 full) ,接着做一下可写性检查(Fh 是不是以可写模式打开的 CEPH_FILE_MODE_WR
,会不会超 quota)
另外写之前还需要把 buf 或者 iov 中的数据写到 bl 中 (也就是同时包含 write
和 writev
的功能),前期的准备就算完了
将 need
和 want
的 Caps 传入 Client::get_caps
(need
是必须的 Fw 和 As, want
是可选的 Fb 或者 Fl)Client::get_caps
内的过程比较复杂:
首先通过 Fh 的 open mode 获取 file_wanted
(比如以 RD 方式打开的就需要 Fs Fr Fc,和之前 open 时是一样的)
接着通过 Inode::caps_issued
获取当前 inode 所拥有的 caps
如果当前已经拥有 Fw 那么检查一下 max_size
够不够写 wanted_max_size
,如果不够就需要通过 check_caps
去向 mds 申请
如果说此时拥有 need
并且没有 want
的 caps 被 revoking 那么直接 get_cap_ref(need)
然后返回,否则就需要 waitfor_caps
caps 问题解决之后就可以开始写了,如果是 O_DIRECT
写的话要把 buffer 和 lazyio 去掉
接着如果是 Direct 写那就直接先 flush_range
然后通过 filer->write_trunc
去写, 否则的话先通过 objectcacher->file_write
写到缓存里再 flush
Client::_write
1 | // 非 Direct 写 |
再说一下 filer->write_trunc
写的过程:
首先通过 Striper::file_to_extents
通过 ino 找到实际要写的 objects (对原始请求的拆分), Striper::file_to_extents
的流程见 Ceph 读写流程:file_to_extents 过程分析
接着就是通过 objecter->sg_write_trunc
提交 op
到 OSD 完成写入
写入完成后进入到 success
完成一些收尾工作即可,包括更新文件的 size
、mtime
,标记 CEPH_CAP_FILE_WR
为 dirty(这样其他客户端要写的话 MDS revoke caps 之前就可以告诉 MDS 这个 Inode 需要同步)以及通过 put_cap_ref
释放刚刚获取的 Fwb 权限等。
以上就是对 Ceph 客户端中写流程的分析和总结,希望能对大家有所帮助!