GlusterFS:xlator基础源码研究

作者: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基础源码研究)