searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

CephFS kernel解析

2023-06-30 09:29:52
169
0

一、内核文件系统概述

1、虚拟文件系统

一个操作系统可以支持多种底层不同的文件系统(比如NTFS, FAT, ext3, ext4,以及ceph),为了给内核和用户进程提供统一的文件系统视图,Linux在用户进程和底层文件系统之间加入了一个抽象层,即虚拟文件系统(Virtual File System, VFS)VFS就是定义了一个通用文件系统的接口层和适配层,一方面为用户进程提供了一组统一的访问文件,目录和其他对象的统一方法,另一方面又要和不同的底层文件系统进行适配。

内核将所有已打开文件组成链表,其中每一个file结构体实例维护了一个f_op指针,指向可以对这个文件进行操作的所有函数集合file_operationsfile_operations模块中维护一个数据结构,是一系列函数指针的集合,其中包含所有可以使用的系统调用函数,例如openreadwritemmap等。每个打开文件都可以连接到file_operations模块,从而对任何已打开的文件,通过系统调用函数,实现各种操作。

参考链接:

https://www.cnblogs.com/huxiao-tee/p/4657851.html

https://www.jianshu.com/p/a98cb5519a50

2、数据结构

cephfs kernel中的主要数据结构有super blockinodedentryfile

1)一个超级块对应一个文件系统,保存文件系统的类型、大小、状态等。

2inode保存元数据,有两种,一种是VFSinode,一种是具体文件系统的inode。前者在内存中,后者在磁盘中。所以每次其实是将磁盘中的inode调进填充内存中的inode,这样才是算使用了磁盘文件inode。对于cephfs也以此类推。

3dentry目录项是描述文件的逻辑属性,只存在于内存中,并没有实际对应的磁盘上的描述,更确切的说是存在于内存的目录项缓存,为了提高查找性能而设计。注意不管是文件夹还是最终的文件,都是属于目录项,所有的目录项在一起构成一颗庞大的目录树。例如:open一个文件/home/xxx/yyy.txt,那么/homexxxyyy.txt都是一个目录项,VFS在查找的时候,根据一层一层的目录项找到对应的每个目录项的inode,那么沿着目录项进行操作就可以找到最终的文件。

4)一个有效的dentry结构必定有一个inode结构,这是因为一个目录项要么代表着一个文件,要么代表着一个目录,而目录实际上也是文件。所以,只要dentry结构是有效的,则其指针d_inode必定指向一个inode结构。

inode包含i_dentry指向目录项链表指针,可以指向多个dentry

5file文件对象描述的是进程已经打开的文件。因为一个文件可以被多个进程打开,所以一个文件可以存在多个文件对象。但是由于文件是唯一的,那么inode就是唯一的,目录项也是定的。

其中,f_flagsf_modef_pos代表的是这个进程当前操作这个文件的控制信息;对于引用计数f_count,当我们关闭一个进程的某一个文件描述符时,其实并不是真正的关闭文件,仅仅是将f_count减一,当f_count=0时候,才会真的去关闭它;f_op是涉及到所有的文件的操作结构体。

参考链接:

https://www.cnblogs.com/linux-xin/p/8126999.html

二、cephfs kernel操作解析

1、客户端与cephfs的交互

cephfs使用MDS管理元数据,将元数据存放在metadata pool,数据存放在data pool

客户端将openmkdirreaddir等元数据操作相关的请求发送给MDS处理, MDS做出响应后再下刷到osd。而readwrite等数据操作相关的请求则直接发送给osd处理。

cephfs kernelceph.kolibceph.ko两个模块供客户端使用,ceph.ko主要负责元数据的操作,libceph.ko则主要负责数据的操作。

2、mount主要流程

这里使用的指令实例是mount -t ceph 127.0.0.1:6789:/ /mnt/mycephfs -o name=admin,secret=secretkey。主要流程是先建立mon clientmon的连接,获取monmaposdmapmdsmap,再建立mds clientmds的连接,获取挂载目录的inode数据,建立root dentry

客户端调用的主要函数是fs/ceph/super.c中的ceph_mount,重要步骤有:

1create_fs_client函数创建ceph fs client,包括messengermon clientosd client的初始化,其中mon client的初始化函数ceph_monc_init会调用ceph_con_init函数初始化mon client的连接,将mon_con_ops中的操作注册给该连接,使用ceph_con_workfn函数初始化work

ceph_con_workfn是处理连接的主要函数,通过对数据的读写分发处理,实现建立连接、认证、数据通信等功能。

2ceph_mdsc_init函数初始化mds client,申请mdsmap,初始化工作队列。

3ceph_real_mount函数执行mount的主操作:

第一步是调用__ceph_open_session函数,首先调用ceph_monc_open_session函数建立mon clientmon的会话,然后等待异步执行ceph_con_workfn函数,唤醒条件是ceph client拿到monmaposdmap或者认证失败或者超时;

第二步是调用open_root_dentry函数打开获取根节点的dentry,方法是调用ceph_mdsc_do_request函数发送获取根节点属性的请求给mds,首先根据mdsmap选择mds,发现还没有会话则建立mds clientmds的会话,请求则放到会话的等待队列,等会话建立后再发送;然后等待请求完成,这里也调用ceph_con_workfn函数处理连接,包括申请新的inode,将从mds得到的数据填充到inode中;最后将该inode赋值给root,初始化root的操作列表,root是根目录的dentry,并且是整个ceph_mount的返回值。

3、readdir主要流程

这里使用的指令实例是ls /mnt/mycephfs/,调用的主要函数是fs/ceph/dir.c中的ceph_readdir,重要步骤有:

1)如果已有dcache,则调用__dcache_readdir函数从dcache中查找该dir下的所有dentry,如果成功就结束流程,否则进行下一步。

2)调用ceph_mdsc_do_request函数发送readdir的请求给mds,等待请求完成;同样异步调用ceph_con_workfn函数处理mds的回复,获取所有dentry并将数据填充到对应的inode

3)将每个dentry依次填充到dirent作为系统调用的结果。

4)如果还有分片没获取则回到第2步。

4、写数据主要流程

这里使用的指令实例是echo 4342 >> 5,该文件已存在且只被一个客户端操作。写操作相对复杂,客户端需要先打开文件,从MDS获取需要的cap权限,根据cap进行直接写或者buffer写,写完后还要进行元数据的更新。

1)客户端调用ceph_open函数打开该文件:获取文件已有的cap,如果不满足要求的cap,则调用ceph_check_caps函数,其中通过__send_cap函数将要求新cap的请求发送给MDS

2MDS接收到cap信息,调用handle_client_caps函数:首先调用get_session函数获取会话,调用get_inode函数从MDCache 查找CInode,调用get_client_cap函数从mempool_cap_map查找cap

接着调用adjust_cap_wanted函数将要求的cap改为最新的;调用_do_cap_update函数更新inode,其中通过_update_cap_fields函数修改inode的参数,通过mds->mdlog->submit_entry函数提交更新;然后调用issue_caps函数把caps发给客户端;

最后,如果need_flushtrue,就调用mds->mdlog->flush函数将元数据落盘。

3)客户端调用ceph_aio_write函数写数据:首先,调用ceph_get_caps函数等待获取要求的cap,通过一直循环调用try_get_cap_refs函数实现;获取到cap后,根据cap是否有Fb标志或者是否直接指定来进行直接写或者buffer写;写完后,将capsinode标记为dirty,调用ceph_put_cap_refs函数释放cap引用,如果释放的是最后的引用,则调用了ceph_check_caps函数,因为这里将cap释放标记为delay,所以将cap放到延迟释放列表的末尾;

4)如果是直接写,ceph_aio_write中调用ceph_sync_direct_writeceph_sync_write函数将数据通过写请求直接发送到osd

最后还要调用ceph_fsync函数,通过filemap_write_and_wait_range函数将内容同步到页缓存,通过try_flush_caps函数将dirty caps更新到MDS,它也通过__send_cap函数发送cap信息;

5)如果是buffer写,ceph_aio_write中调用generic_file_buffered_write函数将数据写到页缓存中;

后面,在触发落盘条件后(默认等待30s),首先调用ceph_writepages_start函数将缓存数据写到osd中;

再调用ceph_write_inode函数,因为当前不是同步模式,所以通过__cap_delay_requeue_front函数将inode标记为CEPH_I_FLUSH,并放到cap列表前面;

最后调用writepages_finish函数结束回写,其中通过ceph_put_wrbuffer_cap_refs函数释放cap引用,如果释放的是最后的引用,则调用ceph_check_caps函数,此时有CHECK_CAPS_FLUSH标志位,因此将更新cap的请求发送给MDS

6ceph_check_caps:被称作瑞士军刀级的函数,通过对cap各项参数的判断,确定需要执行的操作,发送cap请求给MDS,或者将cap放入延迟列表,实现对capflushrevoke、申请新权限和最大空间等。

5、读数据主要流程

这里使用的指令实例是cat 2。与写操作类似,但要简单一些,也要先打开文件,从MDS获取需要的cap权限,根据cap进行直接读或者从缓存中读,不过写完后不需要进行元数据的更新。

1)客户端调用ceph_open函数打开该文件:获取文件已有的cap,如果不满足要求的cap,则调用ceph_check_caps函数,其中通过__send_cap函数将要求新cap的请求发送给MDS

2)客户端调用ceph_aio_read函数读数据:首先,调用ceph_get_caps函数等待获取要求的cap,通过一直循环调用try_get_cap_refs函数实现;获取到cap后,根据cap是否有Fc标志或者是否直接指定来进行直接读或者从缓存中读;读完后调用ceph_put_cap_refs函数释放cap,其中调用了ceph_check_caps函数,因为这里将cap释放标记为delay,所以将cap放到延迟释放列表的末尾;

3)如果是直接读,ceph_aio_write中调用ceph_sync_direct_writeceph_sync_read函数将读请求直接发送给osd

4)如果是从缓存中读,ceph_aio_write中调用generic_file_aio_read函数,先从page cache中查找page,如果没找到或者找到的页不是uptodate的,就会调用ceph_readpage函数发送读请求给osd,将数据读到page中。

6、创建文件主要流程

这里使用的指令实例是touch 7创建新的文件,客户端调用ceph_atomic_open函数,其中通过ceph_mdsc_do_request函数将创建请求发送给MDS

使用mkdir test5/指令创建文件夹,客户端调用ceph_mkdir函数,将mkdir请求发送给MDS

7、删除文件主要流程

这里使用的指令实例是rm -f 7,客户端调用ceph_unlink函数,其中也通过ceph_mdsc_do_request函数将unlink请求发送给MDS

删除文件夹时调用的也是ceph_unlink函数,不过发送的是rmdir请求,两者操作码不同。

0条评论
0 / 1000
黄****锐
4文章数
1粉丝数
黄****锐
4 文章 | 1 粉丝
黄****锐
4文章数
1粉丝数
黄****锐
4 文章 | 1 粉丝
原创

CephFS kernel解析

2023-06-30 09:29:52
169
0

一、内核文件系统概述

1、虚拟文件系统

一个操作系统可以支持多种底层不同的文件系统(比如NTFS, FAT, ext3, ext4,以及ceph),为了给内核和用户进程提供统一的文件系统视图,Linux在用户进程和底层文件系统之间加入了一个抽象层,即虚拟文件系统(Virtual File System, VFS)VFS就是定义了一个通用文件系统的接口层和适配层,一方面为用户进程提供了一组统一的访问文件,目录和其他对象的统一方法,另一方面又要和不同的底层文件系统进行适配。

内核将所有已打开文件组成链表,其中每一个file结构体实例维护了一个f_op指针,指向可以对这个文件进行操作的所有函数集合file_operationsfile_operations模块中维护一个数据结构,是一系列函数指针的集合,其中包含所有可以使用的系统调用函数,例如openreadwritemmap等。每个打开文件都可以连接到file_operations模块,从而对任何已打开的文件,通过系统调用函数,实现各种操作。

参考链接:

https://www.cnblogs.com/huxiao-tee/p/4657851.html

https://www.jianshu.com/p/a98cb5519a50

2、数据结构

cephfs kernel中的主要数据结构有super blockinodedentryfile

1)一个超级块对应一个文件系统,保存文件系统的类型、大小、状态等。

2inode保存元数据,有两种,一种是VFSinode,一种是具体文件系统的inode。前者在内存中,后者在磁盘中。所以每次其实是将磁盘中的inode调进填充内存中的inode,这样才是算使用了磁盘文件inode。对于cephfs也以此类推。

3dentry目录项是描述文件的逻辑属性,只存在于内存中,并没有实际对应的磁盘上的描述,更确切的说是存在于内存的目录项缓存,为了提高查找性能而设计。注意不管是文件夹还是最终的文件,都是属于目录项,所有的目录项在一起构成一颗庞大的目录树。例如:open一个文件/home/xxx/yyy.txt,那么/homexxxyyy.txt都是一个目录项,VFS在查找的时候,根据一层一层的目录项找到对应的每个目录项的inode,那么沿着目录项进行操作就可以找到最终的文件。

4)一个有效的dentry结构必定有一个inode结构,这是因为一个目录项要么代表着一个文件,要么代表着一个目录,而目录实际上也是文件。所以,只要dentry结构是有效的,则其指针d_inode必定指向一个inode结构。

inode包含i_dentry指向目录项链表指针,可以指向多个dentry

5file文件对象描述的是进程已经打开的文件。因为一个文件可以被多个进程打开,所以一个文件可以存在多个文件对象。但是由于文件是唯一的,那么inode就是唯一的,目录项也是定的。

其中,f_flagsf_modef_pos代表的是这个进程当前操作这个文件的控制信息;对于引用计数f_count,当我们关闭一个进程的某一个文件描述符时,其实并不是真正的关闭文件,仅仅是将f_count减一,当f_count=0时候,才会真的去关闭它;f_op是涉及到所有的文件的操作结构体。

参考链接:

https://www.cnblogs.com/linux-xin/p/8126999.html

二、cephfs kernel操作解析

1、客户端与cephfs的交互

cephfs使用MDS管理元数据,将元数据存放在metadata pool,数据存放在data pool

客户端将openmkdirreaddir等元数据操作相关的请求发送给MDS处理, MDS做出响应后再下刷到osd。而readwrite等数据操作相关的请求则直接发送给osd处理。

cephfs kernelceph.kolibceph.ko两个模块供客户端使用,ceph.ko主要负责元数据的操作,libceph.ko则主要负责数据的操作。

2、mount主要流程

这里使用的指令实例是mount -t ceph 127.0.0.1:6789:/ /mnt/mycephfs -o name=admin,secret=secretkey。主要流程是先建立mon clientmon的连接,获取monmaposdmapmdsmap,再建立mds clientmds的连接,获取挂载目录的inode数据,建立root dentry

客户端调用的主要函数是fs/ceph/super.c中的ceph_mount,重要步骤有:

1create_fs_client函数创建ceph fs client,包括messengermon clientosd client的初始化,其中mon client的初始化函数ceph_monc_init会调用ceph_con_init函数初始化mon client的连接,将mon_con_ops中的操作注册给该连接,使用ceph_con_workfn函数初始化work

ceph_con_workfn是处理连接的主要函数,通过对数据的读写分发处理,实现建立连接、认证、数据通信等功能。

2ceph_mdsc_init函数初始化mds client,申请mdsmap,初始化工作队列。

3ceph_real_mount函数执行mount的主操作:

第一步是调用__ceph_open_session函数,首先调用ceph_monc_open_session函数建立mon clientmon的会话,然后等待异步执行ceph_con_workfn函数,唤醒条件是ceph client拿到monmaposdmap或者认证失败或者超时;

第二步是调用open_root_dentry函数打开获取根节点的dentry,方法是调用ceph_mdsc_do_request函数发送获取根节点属性的请求给mds,首先根据mdsmap选择mds,发现还没有会话则建立mds clientmds的会话,请求则放到会话的等待队列,等会话建立后再发送;然后等待请求完成,这里也调用ceph_con_workfn函数处理连接,包括申请新的inode,将从mds得到的数据填充到inode中;最后将该inode赋值给root,初始化root的操作列表,root是根目录的dentry,并且是整个ceph_mount的返回值。

3、readdir主要流程

这里使用的指令实例是ls /mnt/mycephfs/,调用的主要函数是fs/ceph/dir.c中的ceph_readdir,重要步骤有:

1)如果已有dcache,则调用__dcache_readdir函数从dcache中查找该dir下的所有dentry,如果成功就结束流程,否则进行下一步。

2)调用ceph_mdsc_do_request函数发送readdir的请求给mds,等待请求完成;同样异步调用ceph_con_workfn函数处理mds的回复,获取所有dentry并将数据填充到对应的inode

3)将每个dentry依次填充到dirent作为系统调用的结果。

4)如果还有分片没获取则回到第2步。

4、写数据主要流程

这里使用的指令实例是echo 4342 >> 5,该文件已存在且只被一个客户端操作。写操作相对复杂,客户端需要先打开文件,从MDS获取需要的cap权限,根据cap进行直接写或者buffer写,写完后还要进行元数据的更新。

1)客户端调用ceph_open函数打开该文件:获取文件已有的cap,如果不满足要求的cap,则调用ceph_check_caps函数,其中通过__send_cap函数将要求新cap的请求发送给MDS

2MDS接收到cap信息,调用handle_client_caps函数:首先调用get_session函数获取会话,调用get_inode函数从MDCache 查找CInode,调用get_client_cap函数从mempool_cap_map查找cap

接着调用adjust_cap_wanted函数将要求的cap改为最新的;调用_do_cap_update函数更新inode,其中通过_update_cap_fields函数修改inode的参数,通过mds->mdlog->submit_entry函数提交更新;然后调用issue_caps函数把caps发给客户端;

最后,如果need_flushtrue,就调用mds->mdlog->flush函数将元数据落盘。

3)客户端调用ceph_aio_write函数写数据:首先,调用ceph_get_caps函数等待获取要求的cap,通过一直循环调用try_get_cap_refs函数实现;获取到cap后,根据cap是否有Fb标志或者是否直接指定来进行直接写或者buffer写;写完后,将capsinode标记为dirty,调用ceph_put_cap_refs函数释放cap引用,如果释放的是最后的引用,则调用了ceph_check_caps函数,因为这里将cap释放标记为delay,所以将cap放到延迟释放列表的末尾;

4)如果是直接写,ceph_aio_write中调用ceph_sync_direct_writeceph_sync_write函数将数据通过写请求直接发送到osd

最后还要调用ceph_fsync函数,通过filemap_write_and_wait_range函数将内容同步到页缓存,通过try_flush_caps函数将dirty caps更新到MDS,它也通过__send_cap函数发送cap信息;

5)如果是buffer写,ceph_aio_write中调用generic_file_buffered_write函数将数据写到页缓存中;

后面,在触发落盘条件后(默认等待30s),首先调用ceph_writepages_start函数将缓存数据写到osd中;

再调用ceph_write_inode函数,因为当前不是同步模式,所以通过__cap_delay_requeue_front函数将inode标记为CEPH_I_FLUSH,并放到cap列表前面;

最后调用writepages_finish函数结束回写,其中通过ceph_put_wrbuffer_cap_refs函数释放cap引用,如果释放的是最后的引用,则调用ceph_check_caps函数,此时有CHECK_CAPS_FLUSH标志位,因此将更新cap的请求发送给MDS

6ceph_check_caps:被称作瑞士军刀级的函数,通过对cap各项参数的判断,确定需要执行的操作,发送cap请求给MDS,或者将cap放入延迟列表,实现对capflushrevoke、申请新权限和最大空间等。

5、读数据主要流程

这里使用的指令实例是cat 2。与写操作类似,但要简单一些,也要先打开文件,从MDS获取需要的cap权限,根据cap进行直接读或者从缓存中读,不过写完后不需要进行元数据的更新。

1)客户端调用ceph_open函数打开该文件:获取文件已有的cap,如果不满足要求的cap,则调用ceph_check_caps函数,其中通过__send_cap函数将要求新cap的请求发送给MDS

2)客户端调用ceph_aio_read函数读数据:首先,调用ceph_get_caps函数等待获取要求的cap,通过一直循环调用try_get_cap_refs函数实现;获取到cap后,根据cap是否有Fc标志或者是否直接指定来进行直接读或者从缓存中读;读完后调用ceph_put_cap_refs函数释放cap,其中调用了ceph_check_caps函数,因为这里将cap释放标记为delay,所以将cap放到延迟释放列表的末尾;

3)如果是直接读,ceph_aio_write中调用ceph_sync_direct_writeceph_sync_read函数将读请求直接发送给osd

4)如果是从缓存中读,ceph_aio_write中调用generic_file_aio_read函数,先从page cache中查找page,如果没找到或者找到的页不是uptodate的,就会调用ceph_readpage函数发送读请求给osd,将数据读到page中。

6、创建文件主要流程

这里使用的指令实例是touch 7创建新的文件,客户端调用ceph_atomic_open函数,其中通过ceph_mdsc_do_request函数将创建请求发送给MDS

使用mkdir test5/指令创建文件夹,客户端调用ceph_mkdir函数,将mkdir请求发送给MDS

7、删除文件主要流程

这里使用的指令实例是rm -f 7,客户端调用ceph_unlink函数,其中也通过ceph_mdsc_do_request函数将unlink请求发送给MDS

删除文件夹时调用的也是ceph_unlink函数,不过发送的是rmdir请求,两者操作码不同。

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
2
2