美文网首页
GlusterFS:xlator基础源码研究

GlusterFS:xlator基础源码研究

作者: 苍山雪麓 | 来源:发表于2018-12-07 20:47 被阅读0次

    作者:liuhong1123

    来源:CSDN

    原文:https://blog.csdn.net/liuhong1123/article/details/8118174

    版权声明:本文为博主原创文章,转载请附上博文链接!

    1.xlator树加载过程

    对于glusterfs,在客户端,有一个由xlator组成的树,在树中,xlator层层调用,在当服务器端挂载到客户端后,gluster会将树解析为一张图中,在此我们从图中xlator的初始化过程开始讲解。

    客户端卷部分配置文件截图

    在glusterfs中,当挂载到客户端时,客户端会将卷配置文件解析成graph,然后会发起对graph中每个节点的参数合法性检查,节点xlator的初始化等,在此处大概讲解一下:

    首先将配置文件如v2-fuse.vol读取解析成一个图graph;

    检查图的第一个节点卷类型是否为mount/fuse,即是否通过fuse实现的文件系统接口,如果不是将报错;

    一些参数的合法性验证,及其对ctx初始化;

    Graph中xlator节点参数合法性检查,如数据类型是否合法等等;然后初始化每个xlator;初始化成功后通知,父节点通知每个字节点就绪,子节点也会通知父节点等等;

    由于该部分的分析会在其他部分由其他同事分析,此处不再详解;

    graph的初始化

    graph的初始化过程,其实就是遍历xlator树,依次调用它们的init函数的过程

    trav = graph->first;

    while (trav) {

                    ret = xlator_init (trav);

                    if (ret) {

                            gf_log (trav->name, GF_LOG_ERROR,

                                    "initializing translator failed");

                            return ret;

                    }

                    trav = trav->next;

            }

    从上面代码可以看出,一旦在调用任何一个xlator的init函数失败,则直接返回,即整棵树直接初始化失败。

    在函数xlator_init内部,其实就是调用对xlator的init的封装函数,如果初始化成功,置xl->init_succeeded = 1;

    2.xlator之间的调用

         我们大家都知道Xlator形成树,每个xlator为这棵树中的节点,glusterfs要工作,就必然会涉及到节点之间的通信。

         通信主要包括2个方面,父节点调用子节点,子节点调用父节点,如当父节点向子节点发出写请求则要调用子节点的写操作,当子节点写操作完成后,会调用父节点的写回调操作。父子节点的调用关系可用下图说明:

    Xlator调用关系图

         在glusterfs中,父子节点的通信通过3个主要的函数来完成STACK_WIND,STACK_UNWIND,STACK_UNWIND_STRICT,其中STACK_UNWIND,STACK_UNWIND_STRICT作用一样,接下来我们要分析到节点间调用关系的时候,只分析STACK_WIND和STACK_UNWIND_STRICT。

    下面我们以dht部分的dht_writev与dht_writev_cbk两个方法为例,来说明这种调用关系。为了突出重点,我们只说明与父子节点调用关系相关的部分。

    dht_writev父节点调用子节点:

    STACK_WIND (frame, dht_writev_cbk,

                        subvol, subvol->fops->writev,

                        fd, vector, count, off, iobref);

    代码说明:

    frame:代表该xlator的frame,一个frame对象主要用于记录一个xlator与父子节点间的调用关系,比如记录其子节点的frame信息,父节点的frame信息。Frame相当于一个纽带,将原本独立的xlator的信息联系到了一起。

    dht_writev_cbk:写操作的回调函数,该函数名会记录到子节点的frame的ret参数内部,用于当子节点调用父节点的回调函数;

    subvol:子卷,即要调用的子节点对象;

    subvol->fops->writev:调用的子节点的操作,此处为调用子节点的写操作;

    fd:文件描述符,即对应的文件,在linux系统中,文件描述符这个概念一般针对打开的文件或者文件夹。

    Vector:io流,真实的数据存储在该集合内部;

    Count:vector数据集合内的元素个数;

    Off:偏移量

    然后进入STACK_WIND函数分析:

    宏定义头

    #defineSTACK_WIND(frame, rfn, obj, fn, params ...)  

    宏定义重要片段

                    typeof(fn##_cbk) tmp_cbk = rfn;                         \

                    _new->root = frame->root;                               \

                    _new->next = frame->root->frames.next;                  \

                    _new->prev = &frame->root->frames;                      \

                    if (frame->root->frames.next)                           \

                            frame->root->frames.next->prev = _new;          \

                    frame->root->frames.next = _new;                        \

                    _new->this = obj;                                       \

                    _new->ret = (ret_fn_t) tmp_cbk;                         \

                    _new->parent = frame;                                   \

                    _new->cookie = _new;                                    \

                    LOCK_INIT (&_new->lock);                                \

                    _new->wind_from = __FUNCTION__;                         \

                    _new->wind_to = #fn;                                    \

                    _new->unwind_to = #rfn;                                 \

                    frame->ref_count++;                                     \

                    old_THIS = THIS;                                        \

                    THIS = obj;                                             \

                    fn (_new, obj, params);

    上面代码片段重要部分解释:

    rfn赋值给了子卷的_new->ret,即子卷执行完后将返回调用该rfn函数;

    _new->parent= frame; 将父节点的frame存于子节点的frame的parent参数,当子节点回调时才知道调用哪一个父节点;

    _new->cookie= _new;  将子节点的frame记录到子节点frame的cookie参数,原因是回调后,父节点能够知道是哪一个子节点回调的;

    LOCK_INIT(&_new->lock);  在此处将子节点的frame的锁初始化,以后在需要加锁的地方可以直接加锁,不用再初始化操作;

    _new->wind_from= __FUNCTION__; 记录是父节点的哪一个操作调用的子节点的操作;

    _new->wind_to= #fn;  要调用的子节点的操作,如fn为writev,则要调用子节点的写操作;

    frame->ref_count++;记录父节点调用了子节点一次;

    fn (_new,obj, params);-----fn即为调用的子节点的操作,­_new即为子节点的frame对象,

    obj即为调用的子卷对象,params即为传递给子卷的额参数;

    经过上面的代码片段父节点完成了子节点的调用,依次类推,xlator树一层层完成调用;

    当调用结束,父节点当然也需要子节点给它返回,比如写操作是否成功,读操作读取的数据等等,下面我们开始讲解回调,就是子节点调用父节点的过程。

    dht_writev_cbk子节点调用父节点:

    DHT_STACK_UNWIND (writev, frame, op_ret,op_errno, prebuf, postbuf);

    上面为当dht的子节点返回给写后,dht xlator也将向它的父节点返回消息,上面的代码即用于完成这个功能。

    writev:写操作,不过在后面会组合成相应的回调函数操作如:write_cbk,具体是如何传递处理将在STACK_UNWIND_STRICT函数讲解处给出;

    frame:为dht xlator的frame;

    op_ret:操作的返回码,如返回-1则证明子节点的操作已经出错;

    op_errno:错误,即具体返回的什么错误;

    然后进入DHT_STACK_UNWIND分析:

    #define DHT_STACK_UNWIND(fop, frame, params ...) do {           \

                    dht_local_t *__local = NULL;                    \

                    xlator_t *__xl = NULL;                          \

                    if (frame) {                                    \

                            __xl = frame->this;                     \

                            __local = frame->local;                 \

                            frame->local = NULL;                    \

                    }                                               \

                    STACK_UNWIND_STRICT (fop, frame, params);       \

                    dht_local_wipe (__xl, __local);                 \

            } while (0)

    由此代码片段可以看出,DHT_STACK_UNWIND主要是完成STACK_UNWIND_STRICT函数的包装,下面我们主要对STACK_UNWIND_STRICT进行分析:

    宏定义头:

    #defineSTACK_UNWIND_STRICT(op, frame, params ...)                      \

    宏定义体部分代码:

    fn = (fop_##op##_cbk_t )frame->ret;                     \

                    _parent = frame->parent;                                \

                    _parent->ref_count--;                                   \

                    old_THIS = THIS;                                        \

                    THIS = _parent->this;                                   \

                    frame->complete = _gf_true;                             \

                    frame->unwind_from = __FUNCTION__;                      \

                    fn (_parent, frame->cookie, _parent->this, params);     \

    上面代码片段重要部分解释:

    fn =(fop_##op##_cbk_t )frame->ret;  -----将frame->ret的回调函数赋值给fn,fn即为具体的回调函数;

    _parent =frame->parent;---parent即为父节点frame对象;

    _parent->ref_count--;-------回调时将ret_count参数减一;

    frame->unwind_from= __FUNCTION__;---------被调用的xlator相应操作的回调函数名称;

    fn(_parent, frame->cookie, _parent->this, params);-------该操作真正进行父节点的调用;frame->cookie:为调用的父节点xlator对象;

    到此,就分析完了父节点调用子节点,子节点如何调用父节点的过程。

    每个xlator都有2个很重要的数据对象,private,local;

    Private:在内存中维护部分options选型键,锁信息,事件状态,多线程mutex,子卷对象等等

    Local,获得frame的local对象且记录每个操作相关的参数,这些参数可能从private获得,或者函数参数,或者程序赋初值,local会被记录到frame中,作为自己或者子卷使用

    3.xlator操作函数

      在glusterfs文件系统中,总是被动地等待vfs发起调用相关xlator的操作函数,操作完成后向用户返回相关处理结果。

    操作调用lookup举例

    在vfs,定义了操作相关的所有接口,在fuse文件系统中也定义了相应操作,在glusterfs中同样定义了相应操作。这些相应操作作用是类似的,在每一个部分完成相应的功能。在glusterfs中,主要操作说明如下:

    注:操作前有个“f”代表该操作是以fd即文件描述符为参数

    4.xlator重要数据结构

    在glusterfs中,我们经常遇到参数buf,其数据类型为iatt数据结构体,该结构体维护了文件,文件夹的相关属性,这些属性在磁盘上是存储到inode表中:

    struct iatt {

            uint64_t     ia_ino;        /* inode number */

            uuid_t       ia_gfid;

            uint64_t     ia_dev;        /* backing device ID */

            ia_type_t    ia_type;       /* type of file */

            ia_prot_t    ia_prot;       /* protection */

            uint32_t     ia_nlink;      /* Link count */

            uint32_t     ia_uid;        /* user ID of owner */

            uint32_t     ia_gid;        /* group ID of owner */

            uint64_t     ia_rdev;       /* device ID (if special file) */

            uint64_t     ia_size;       /* file size in bytes */

            uint32_t     ia_blksize;    /* blocksize for filesystem I/O */

            uint64_t     ia_blocks;     /* number of 512B blocks allocated */

            uint32_t     ia_atime;      /* last access time */

            uint32_t     ia_atime_nsec;

            uint32_t     ia_mtime;      /* last modification time */

            uint32_t     ia_mtime_nsec;

            uint32_t     ia_ctime;      /* last status change time */

            uint32_t     ia_ctime_nsec;

    由上面参数可知,从用户组,用户名到更新时间,该数据结构均有记录。

    5.Linux必备知识

    5.1.重要参数

    在linux内,open等操作的时候,通常需要标识参数(flags)和模式参数(mode)。如:

    int open( const char * pathname, int flags);

    int open( const char * pathname,int flags, mode_t mode);

    51.1.flags

    flags设计到读写等打开方式,是以命令(mandatory)文件访问或是其他一些可选模式组合的方式来指定的.open调用必须指定下列文件访问模式中的一种:

    O_RDONLY 以只读方式打开文件

    O_WRONLY 以只写方式打开文件

    O_RDWR 以可读写方式打开文件。上述三种旗标是互斥的,也就是不可同时使用,但可与下列的旗标利用OR(|)运算符组合。

    O_CREAT 若欲打开的文件不存在则自动建立该文件。

    O_EXCL 如果O_CREAT 也被设置,此指令会去检查文件是否存在。文件若不存在则建立该文件,否则将导致打开文件错误。此外,若O_CREAT与O_EXCL同时设置,并且欲打开的文件为符号连接,则会打开文件失败。

    O_NOCTTY 如果欲打开的文件为终端机设备时,则不会将该终端机当成进程控制终端机。

    O_TRUNC 若文件存在并且以可写的方式打开时,此旗标会令文件长度清为0,而原来存于该文件的资料也会消失。

    O_APPEND 当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面。

    O_NONBLOCK 以不可阻断的方式打开文件,也就是无论有无数据读取或等待,都会立即返回进程之中。

    O_NDELAY 同O_NONBLOCK。

    O_SYNC 以同步的方式打开文件。

    O_NOFOLLOW 如果参数pathname 所指的文件为一符号连接,则会令打开文件失败。

    O_DIRECTORY 如果参数pathname 所指的文件并非为一目录,则会令打开文件失败。

    51.2.mode

    mode即模式设计到一些权限的设置

    S_IRWXU00700 权限,代表该文件所有者具有可读、可写及可执行的权限。

    S_IRUSR 或S_IREAD,00400权限,代表该文件所有者具有可读取的权限。

    S_IWUSR 或S_IWRITE,00200 权限,代表该文件所有者具有可写入的权限。

    S_IXUSR 或S_IEXEC,00100 权限,代表该文件所有者具有可执行的权限。

    S_IRWXG 00070权限,代表该文件用户组具有可读、可写及可执行的权限。

    S_IRGRP 00040 权限,代表该文件用户组具有可读的权限。

    S_IWGRP 00020权限,代表该文件用户组具有可写入的权限。

    S_IXGRP 00010 权限,代表该文件用户组具有可执行的权限。

    S_IRWXO 00007权限,代表其他用户具有可读、可写及可执行的权限。

    S_IROTH 00004 权限,代表其他用户具有可读的权限

    S_IWOTH 00002权限,代表其他用户具有可写入的权限。

    S_IXOTH 00001 权限,代表其他用户具有可执行的权限。

    .1.3.umask

    umask是一个系统变量,当创建一个文件时,可以被用来为一个文件的权限设置屏蔽位.我们可以通过执行umask命令并提供一个新的值从而可以改变这个变量的值.umask的值是一个三位的十六进制数.每一个数字是1,2或4中的数相加的结果值.我们可以从下表中清楚地明白这个意思.每一个不同的数字位可以对应user,group,other的权限.

    第一位:

    0    没有用户的权限被禁止

    4    用户读权限被禁止

    2    用户写权限被禁止

    1    用户执行权限被禁止

    第二位:

    0    没有组权限被禁止

    4    组读权限被禁止

    2    组写权限被禁止

    1    组执行权限被禁止

    第三位:

    0    没有其他用户的权限被禁止

    4    其他用户读权限被禁止

    2    其他用户写权限被禁止

    1    其他用户执行权限被禁止

    5.2.Linux目录项

    每个文件除了有一个索引节点inode数据结构外,还有一个目录项dentry(directoryenrty)数据结构。dentry 结构中有个d_inode指针指向相应的inode结构。读者也许会问,既然inode结构和dentry结构都是对文件各方面属性的描述,那为什么不把这两个结构“合而为一”呢?这是因为二者所描述的目标不同,dentry结构代表的是逻辑意义上的文件,所描述的是文件逻辑上的属性,因此,目录项对象在磁盘上并没有对应的映像;而inode结构代表的是物理意义上的文件,记录的是物理上的属性,对于一个具体的文件系统(如Ext2),Ext2_ inode结构在磁盘上就有对应的映像。所以说,一个索引节点对象可能对应多个目录项对象

    5.1.1.目录操作

    1)创建目录mkdir(路径, umask)

    当目录被成功创建函数的返回值为0,否则为–1。

    2)获得当前子目录的操作可使用函数getcwd():

    getcwd(char *buf, size_tsize);

    其中,*buf是存放当前目录的缓冲区,size是缓冲区的大小。如果函数返回当前目录的字符串长度超过size规定的大小,它将返回NULL。

    3)改变执行程序的工作目录,可以使用函数chdir(),类似shell中的cd:

    chdir(路径);

    常用目录操作是扫描子目录,与此相关的函数被封装在头文件dirent.h里。它们使用一个名为DIR(为一个目录流)的结构作为子目录处理的基础,这个结构的指针所指向的内存空间被称之为子目录流

    子目录流操作相关函数

    5.3.文件描述符

    内核(kernel)利用文件描述符(filedescriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。

    进程获取文件描述符最常见的方法是通过本机子例程open或create获取或者通过从父进程继承。后一种方法允许子进程同样能够访问由父进程使用的文件。文件描述符对于每个进程一般是唯一的。当用fork子例程创建某个子进程时,该子进程会获得其父进程所有文件描述符的副本,这些文件描述符在执行fork时打开。在由fcntl、dup和dup2子例程复制或拷贝某个进程时,会发生同样的复制过程。

    文件描述符(fd)通常在open,create操作时候获得。

    5.3.1.文件描述符与路径

    文件描述符与路径是有区别的,在unix/linux系统中,文件描述符的作用就是标识已经打开的文件(注意Linux中所有的I/O设备都是以文件的方式访问!),注意,是已经打开的文件,并不包括没有打开的文件。所以,用文件描述符fd来指定一个文件,就意味着该文件已经被打开。而用路径名来指定一个文件,该文件既可以是打开的,也可以是未打开的。

    按照上名的解释,你就能很容易的明白以下几个函数之间的区别了

    在unix/linux系统中,stat()和 fstat()函数的原型如下:

    int stat(const char *pathname,struct stat *buf);

    int fstat(int filedes,struct stat *buf);

    这两个函数的共同点是用来获得文件的struct stat信息结构

    不同点是stat函数获得路径名pathname指定的文件的信息结构,该文件既可以是已经被打开的,也可以是未被打开的;而 fstat函数是获得在文件描述符filedes上打开的文件的信息结构

    同样地,下面两个函数的区别也是一样的

    int chmod(const char*pathname,mode_t mode);

    int fchmod(int filedes,mode_tmode);

    相关文章

      网友评论

          本文标题:GlusterFS:xlator基础源码研究

          本文链接:https://www.haomeiwen.com/subject/mfbxhqtx.html