英文原文:http://hekafs.org/dist/xlator_api_2.html
中文转载来源:http://blog.sina.com.cn/s/blog_7765b3c60100z7lh.html
需要修改和完善
介绍
在我们开始讲解该API细节时,需要明确两个重要点。第一个是文件系统API,其重要功能通过一个dispatch table输出。在GlusterFS中是xlator_fops, 它是Linux中file_operations, inode_operations和super_operatioins的组合。为了理解translators如何工作,你必须首先了解这些函数的功能及其相关影响,比如open/close, read/write, opendir/readdir, truncate, symlink等。
第二个是translator API采用异步和回调函数的机制。这意味着你处理特定请求的代码必须被分为2个部分,以便在下一个translator之前后和之后查看该请求。换句话说,你的dispatch(first-half)函数调用下一个translator的dispatch 函数,然后无阻塞的返回。当你调用下一个translator的dispatch函数时,你的回调函数(second-half 函数)可能会立即被调用,或者它可能稍后在一个不同的线程上被调用(通常是network tranport的polling 线程)。在两种情况下,回调函数都不会像同步函数那样获取其上下文。GlusterFS提供了几种方式用于在dispatch函数及其回调函数间保存和传递上下文,但是你必须自己处理某些事情而不能完全依赖协议栈。
Dispatch table和默认函数
Translator的主要dispatch table被称为fops( translator 调用dlsym通过名称加载具体代码),fops包含了所有的指向文件系统函数的指针。对于特定的translator只需指定相关的函数。其它的部分会在运行时自动填入默认的值,并直接传递给下一个translator, 同时指定回调函数,回调函数传回之前translator的结果。
默认函数和回调函数除了实现默认的功能外,还有其它用途。任何时刻当你需要向translator添加函数,最简单的方法是先拷贝并修改对应的默认函数,比如default_open, default_truncate.这样能够保证函数参数列表的正确性和默认的行为。然后只需要更新fops指向最新的拷贝。
当你拷贝并重命名一个默认函数时,你的拷贝函数通常使用默认的回调函数(比如,default_open会参考defalut_open_cbk)。通常这就是你所需要的;如果你在传递请求前就完成了所有的工作,你也许根本不需要回调函数,此时也可以使用默认的。如果不是以上的情况,拷贝并重命名默认的回调函数,如同修改dispatch函数,从而保证正确的参数列表。
每一个translator包含附加的dispatch talbes,包括cbk表用于管理inode和file描述符生命周期。
STACK_WIND和STACK_UNWIND
Translator API的回调机制主要采用了STACK_WIND和STACK_UNWIND.这些操作并不是你通常在gdb看到的stack,这是一种专用的frame stack,用于表示对translators的调用。当你的fops某函数进入点被调用,该调用表示一个请求,从客户端的FUSE到服务器端的本地文件系统。你的进入点可以执行相应的操作,然后把该请求使用STACK_WIND传递给下一个translator.其参数如下:
Frame: stack frame代表请求
Rfn: 回调函数,当下一个translator完成时会调用该函数
Obj: translatro对象,听从其控制
Fn:从下一个translator的fops table中指定要调用的translatron函数
Params..:任何其他被调用函数的参数(比如,inodes, file描述符,offset, data buffer)
正如之前章节提到的那样,你的”rfn”回调函数也许在调用STACK_WIND内被调用,也许稍后被不同的上下文调用。为了完成一个请求而不用调用下一个translator(比如从cache中返回数据),或者当任务完成从回调函数中回到上一个translator时调用STACK_UNWIND.实际上,你最好使用STACK_UNWIND_STRICT,它可以用来指定那类请求你已经完成了。其参数如下:
Op:操作类型(比如open,rename),用来检查附加的参数符合函数的期望。
Params..:附加的参数,依据请求的类型
在实际中,基本上大部分的请求类型使用两个附加参数op和params,尽管它们不在宏定义中:
Op_ret:目前为止操作的状态(有时是read或write的字节数,更多的用0表示成功,用-1表示失败)
Op_errno:一个标准的错误码(比如EPERM),如果函数执行失败。
Dispatch及其回调函数的参数列表是和每个函数具体相关的,但是前几项通常是这些:
Frame:当前请求对应的stack frame
This: 一个translator对象,代表translator的实例
回调函数也是类似的,除了在两者之间多了一个参数。该参数是cookie,这是一个opaque指针,有对应STACK_WIND存储。默认情况下是一个指向由STACK_WIND创建的strack frame的指针,同时还提供STACK_WIND_COOKIE允许指定不同的值。在这种情况下,rfn和obj间附加的附加的参数因STACK_WIND各异,同时可以用来传递从dispatch到回调函数的一些上下文。注意,它不能是指向stack中任何结构的指针,因为当回调函数调用时该stack可能已经不存在了。
另外要注意,STACK_UNWIND能引起整个调用stack的unwound,在某一点最后的调用函数释放其所有的frames.出于这个原因,你不能在调用STACK_UNWIND后仍要求对该frame执行有影响的操作。
调用上下文
每个translator-stack frame都有一个local指针,用来储存该translatro特定的上下文。这是在dispatch和回调函数间存储上下文的主要机制,所以你应当熟悉以下的模式:
local = (my_locals_t *)GF_CALLOC(1,sizeof(*local),...);
if (!local) {
}
frame->local = local;
local = frame->local;
需要牢记的一点是:当该stack被销毁是,每个frame的local如果不为NULL都会被传递给GF_FREE,但是不会执行其他的清理工作。如果你的的local结构体包含指针或引用其他对象,那么你需要仔细处理这些。如果内存和其它资源能在stack被销毁前被释放是较理想的情况,所以不要完全依赖自动的GFS_GFREE.取而代之,最安全的做法是定义特定translator的销毁函数,并在STACK_UNWIND返回前手工调用,销毁函数形如:
void my_destructor (call_frame_t *frame)
{
my_own_cleanup(frame->local);
GF_FREE(frame->local);
frame->local = NULL;
}
如果call_frame_t包含一个指向销毁的函数的指针,并自动在STACK_UNWIND中调用,这种方式比较理想;并且如果local结构被更加高效的处理,而不是对每个translator需要两段旅程并通过glibc内存申请;但是现实情况却不是这样的。
Inode和文件描述符上下文
大部分的dispatch函数和回调函数以文件描述符(fd_t)或inode(inode_t)作为参数。通常,你的translator需要存储一些自有的上下文,这些上下文独立与单个请求的生命周期。比如,DHT存储目录对应的布局映射(layout map),和某inode最后可知的位置。Glusterfs提供了一系列的函数用于存储此类上下文。在每种情况下,第二个参数是一个指向translator对象的指针,需要存储的数据与其相关,存储的值是一个unsigned的64位整数。这些函数返回0代表成功,在_get和_del函数中使用引用参数而不是返回值。
inode_ctx_put (inode, xlator, value)
inode_ctx_get (inode, xlator, &value)
inode_ctx_del (inode, xlator, &value)
fd_ctx_set (fd, xlator, value)
fd_ctx_get (fd, xlator, &value)
fd_ctx_del (fd, xlator, &value)
_del函数是”destructive get”,返回并销毁存储的值。同时,inode对应的函数有2-值的格式,允许对每个translator操作两个值而不是一个。
使用translator对象指针作为key/index并不是为了装饰。当一个inode_t或fd_t被删除时,删除代码通过上下文查找一些内容。对每一个实体,它寻找translator的cbk dispatch表,并调用forget对inode或release对文件描述符。如果该上下文是一个指针,那么就由你释放相关的资源。
最后,请牢记传递给dispatch 函数和callback的inode_t或fd_t指针只是borrowed的应用。如果你希望该对象稍后还存在,最好调用inode_ref或fd_ref增加一个持久引用,并且当引用不再需要时调用inode_unref或fd_unref.
字典和translatro选项
另外一个常用的类型是dict_t,它是一个通用的排序字典或hash-map的数据结构,可用来存放任意的值并以字符串为键值。例如,存储的值可以是任意大小的有符号或无符号整数,字符串,或二进制。字符串和二进制需要被标记在不被需要时被glusterfs函数释放,由glibc释放或根本不释放。Dict_t*和*data_t对象都是引用计数的,只有当引用数为0时被释放。如同inodes和文件描述符,如果你希望通过参数接受的dict_t稍后仍存在,你必须调用_ref和_unref处理器生命周期。
字典并不是仅用于dispatch和回调函数。他们也被用于传递不同的模块选项,包括translatro初始化的选项。事实上,目前translator的init函数主要用于解析字典中的选项。向translatro中添加一个选项,你需要在translator的options数组中添加一个实体。每一个选项可以是boolean,整数, 字符串,路径,translatro名称,和其它一些类型(见GF_OPTION_TYPE_).如果是字符串,你可以指定有效值。解析后的选项和其它的信息可以存放在xlator_t结构体中的private内。
日志
Translators中大部分的logging是通过gf_log函数。其参数包括字符串(通常是this->name),log等级,格式,根据格式的附加参数。日志等级分为GF_LOG_ERROR, GF_LOG_WARNING , GF_LOG_LOG和GF_LOG_DEBUG.你可以封装gfs_log自定义相关宏,或采用官方的等级,这样你translator的日志在运行时就能被输出。在最简单的情形中,可以在gdb中进行调试。如果你更加有雄心壮志,你可以添加一个translator 日志等级选项。如果你有宏图伟志,你可以实现一个神奇的xattr调用用于传递新值。
子元素遍历和fan out
在translator中的一个常用模式是便利子元素,可能是满足某项条件的子元素或者是所有的子元素。例如,DHT需要从所有子元素中收集基于hash-layout的地图,然后决定文件的去处;AFR需要从子元素取得同文件的pending的操作数用于决定副本状态。其格式如下:
xlator_list_t *trav;
xlator_t *xl;
for (trav = this->children; trav; trav = trav->next) {
xl = trav->xlator;
do_something(xl);
}
如果目标是fan out一个请求到所有的子元素,需要一些其它的注意事项。通常采用的模式如下:
local->call_count = priv->num_children;
for (trav = this->children; trav; trav = trav->next) {
xl = trav->xlator;
STACK_WIND(frame,my_callback,xl,xl->fops->whatever,...);
}
然后,在回调函数中:
LOCK(&frame->lock);
call_cnt = --local->call_count;
UNLOCK(&frame->lock);
if (!call_cnt) {
STACK_UNWIND(frame,op_ret,op_errno,...);
}
在某些情况下,你可以使用STACK_WIND_COOKIE让每一个回调函数知道N个调用已经返回。具体的例子见AFR内的代码。