作者: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);