本文对 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 客户端中写流程的分析和总结,希望能对大家有所帮助!
参与讨论