ha
分类: 服务器与存储
2008-12-15 10:04:53
//main 部分
FILE *fp;
int32_t pidfd;
glusterfs_ctx_t ctx = { //这个东西比较常见,环境提供者,使用字典或//map 实现。
.logfile = DATADIR "/log/glusterfs/glusterfsd.log",
.loglevel = GF_LOG_ERROR, //日志级别,有点像log4j
.poll_type = SYS_POLL_TYPE_MAX, //使用poll 方式?奇怪为什么不使用epoll
};
call_pool_t *pool;
pool = ctx.pool = calloc (1, sizeof (*pool));
LOCK_INIT (&pool->lock);
INIT_LIST_HEAD (&pool->all_frames);
argp_parse (&argp, argc, argv, 0, 0, &ctx);
设定进程pid,设定日志级别,文件路径。
pidfd = pidfile_lock (pidfile);
if (gf_log_init (ctx.logfile) < 0){
return 1;
}
gf_log_set_loglevel (ctx.loglevel);//调用日志类来设定
接下来设定系统资源限制,一般默认是1024,比如打开文件数,此处设定为65535.软限制和硬限制设为相等。
{
struct rlimit lim; //该结构体是系统内核提供的,含有两个成员
lim.rlim_cur = RLIM_INFINITY;
lim.rlim_max = RLIM_INFINITY;
if (setrlimit (RLIMIT_CORE, &lim) < 0) {
gf_log ("glusterfsd",
GF_LOG_ERROR,
"main: failed to set RLIMIT_CORE, error string is %s",
strerror (errno));
}
lim.rlim_cur = 65535; //RLIM_INFINITY;
lim.rlim_max = 65535; //RLIM_INFINITY;
if (setrlimit (RLIMIT_NOFILE, &lim) < 0) {
gf_log ("glusterfsd",
GF_LOG_ERROR,
"main: failed to set RLIMIT_NOFILE, error string is %s",
strerror (errno));
}
}
接下来读取卷配置文件specfile 串,程序设定的是CONFDIR "/glusterfs-server.vol",成功了如何如何…..失败了如何如何……
if (specfile) {
fp = fopen (specfile, "r");
if (!fp) {
gf_log ("glusterfsd",
GF_LOG_ERROR,
"FATAL: could not open specfile: '%s'",
specfile);
exit (1);
}
} else {
gf_log ("glusterfsd",
GF_LOG_DEBUG,
"main: specfile not provided as command line arg");
argp_help (&argp, stderr, ARGP_HELP_USAGE, argv[0]);
exit (0);
}
然后是判断环境ctx 的foreground 有值与否,没有值,就清空命令行的参数值,然后把argv[0]填充成[glusterfsd],生成守候进程,更新前面锁定的pidfile。
if (!ctx.foreground) {
int i;
for (i=0;imemset (argv[i], ' ', strlen (argv[i]));
sprintf (argv[0], "[glusterfsd]");
daemon (0, 0); //生成守护进程
pidfile_update (pidfd);
}
初始化定时器,通过specfile 串对应文件构造出树来,关闭该文件,忽略管道信号
SIGPIPE
设置软中断处理函数为glusterfsd_cleanup_and_exit 这个清理函数。
gf_timer_registry_init (&ctx);
xlator_tree_node = get_xlator_graph (&ctx, fp);// 从文件中构造解释器树,
//glusterfs.c 中有同名函数
if (!xlator_tree_node) {//判断生成结果
gf_log ("glusterfsd",
GF_LOG_ERROR,
"FATAL: could not create node graph");
exit (1);
}
fclose (fp);
/* Ignore SIGPIPE *///设置忽略的信号(sigpipe 管道)
signal (SIGPIPE, SIG_IGN);
#if HAVE_BACKTRACE //设置打印栈的信号处理
/* Handle SIGABORT and SIGSEGV */
signal (SIGSEGV, gf_print_trace);
signal (SIGABRT, gf_print_trace);
#endif /* HAVE_BACKTRACE */
signal (SIGTERM, glusterfsd_cleanup_and_exit);//设置软中断处理函数
最后进入循环,判断函数是transport.c 中的poll_iteration 函数。循环完毕关掉pidfile。
while (!poll_iteration (&ctx));
close (pidfd);
观察poll_iteration(&ctx)的实现,发现默认如果不设置ctx 的异步进程通信模式的话,默认是使用epoll 的,看代码:
//transport.c
int32_t
poll_iteration (glusterfs_ctx_t *ctx)
{
int32_t ret = 0;
#ifdef HAVE_SYS_EPOLL_H
switch (ctx->poll_type)
{
case SYS_POLL_TYPE_EPOLL:
ret = sys_epoll_iteration (ctx);
break;
case SYS_POLL_TYPE_POLL:
ret = sys_poll_iteration (ctx);
break;
default:
ctx->poll_type = SYS_POLL_TYPE_EPOLL;
ret = sys_epoll_iteration (ctx);
if (ret == -1 && errno == ENOSYS) {
ctx->poll_type = SYS_POLL_TYPE_POLL;
ret = sys_poll_iteration (ctx);
}
break;
}
#else
ret = sys_poll_iteration (ctx);
#endif
return ret;
}
关于poll 和epoll 相关函数的实现都在poll.c 和epoll.c 里面。poll、epoll、select 是网络编程和进程间通讯的三种模式。select 在BSD Unix 中引入, 而poll是System V 的解决方案. epoll 调用添加在2.5.45, 作为使查询函数扩展到几千个文件描述符的方法。具体他们的区别可以查man 手册页。
Client 客户端启动过程:(glusterfs-fuse/glusterfs.c)
初始化glusterfs_ctx 环境中的日志文件位置,日志级别和poll 类型,这个和server端一样,另外声明了xlator 的图(树型),配置文件的指针,传输类型指针,还有就是系统资源限制,和调用池。
xlator_t *graph = NULL;
FILE *specfp = NULL;
transport_t *mp = NULL;
glusterfs_ctx_t ctx = {
.logfile = DATADIR "/log/glusterfs/glusterfs.log",
.loglevel = GF_LOG_ERROR,
.poll_type = SYS_POLL_TYPE_MAX,
};
struct rlimit lim;
call_pool_t *pool;
同样需要设定系统资源限制的值,此处还可以设定debug 模式来使用mtrace()
#ifdef HAVE_MALLOC_STATS
#ifdef DEBUG
mtrace ();
#endif
signal (SIGUSR1, (sighandler_t)malloc_stats);
#endif
lim.rlim_cur = RLIM_INFINITY;
lim.rlim_max = RLIM_INFINITY;
setrlimit (RLIMIT_CORE, &lim);
setrlimit (RLIMIT_NOFILE, &lim);
初始化环境的调用池,解析参数等等这些和server 是一样的。
pool = ctx.pool = calloc (1, sizeof (call_pool_t));
LOCK_INIT (&pool->lock);
INIT_LIST_HEAD (&pool->all_frames);
argp_parse (&argp, argc, argv, 0, 0, &ctx);
此处与server 不同,但无非是一些判断和检测,如测试ctx 的日志文件设置没,设置glusertfs 的全局日志级别为环境的日志级别
if (gf_log_init (ctx.logfile) == -1) {
fprintf (stderr,
"glusterfs: failed to open logfile \"%s\"\n",
ctx.logfile);
return -1;
}
gf_log_set_loglevel (ctx.loglevel);
针对解析的参数设定mount_point,此处为判断设定与否。下面是设定端口号,和配置文件的地方等等,都是做检测用的。
if (!mount_point) {
fprintf (stderr, "glusterfs: MOUNT-POINT not specified\n");
return -1;
}
if (!spec.where) {
fprintf (stderr, "glusterfs: missing option --server=SERVER or --specfile=
VOLUME-SPEC-FILE\n");
return -1;
}
if (spec.spec.server.port) {
if (spec.where != SPEC_REMOTE_FILE)
{
fprintf (stderr, "glusterfs: -p|--port requires -s|--server option to be
specified\n");
exit (EXIT_FAILURE);
}
}
下面是通过配置文件来设定ctx,接下来就是一些中断屏蔽,中断处理函数的处理等等,这个和server 是一样的。
specfp = get_spec_fp (&ctx);
if (!specfp) {
fprintf (stderr,
"glusterfs: could not open specfile\n");
return -1;
}
/* Ignore SIGPIPE */
signal (SIGPIPE, SIG_IGN);
#if HAVE_BACKTRACE
/* Handle SIGABORT and SIGSEGV */
signal (SIGSEGV, gf_print_trace);
signal (SIGABRT, gf_print_trace);
#endif /* HAVE_BACKTRACE */
接下来是当一切成员都初始化完毕时,此刻开始进行glusterfs 的挂载,下面和server 一样生成守护进程。然后注册定时器,生成xlator 树并赋值给环境ctx 中的graph 成员,初始化FUSE 的图,进入循环。后面的过程和server 类似,少许不同。所有的初始化过程都和ctx 环境有关,从携带变量,赋值等等,都是操作的ctx。
if (!(mp = glusterfs_mount (&ctx, mount_point))) {
gf_log ("glusterfs", GF_LOG_ERROR, "Unable to mount glusterfs");
return 1;
}
if (!ctx.foreground) {
/* funky ps output */
int i;
for (i=0;imemset (argv[i], ' ', strlen (argv[i]));
sprintf (argv[0], "[glusterfs]");
daemon (0, 0);
}
gf_timer_registry_init (&ctx);
graph = get_xlator_graph (&ctx, specfp);
if (!graph) {
gf_log ("glusterfs", GF_LOG_ERROR,
"Unable to get xlator graph");
transport_disconnect (mp);
return -1;
}
fclose (specfp);
ctx.graph = graph;
mp->xl = fuse_graph (graph);
// fuse_thread (&thread, mp);
while (!poll_iteration (&ctx));
return 0;
}
传输协议代码中体现了3 种可以使用的方式:
下面是tcp 部分的分析: 共4 个文件tcp.h 是接口定义文件,里面定义了几个tcp 操作的函数,接收,关闭连接等等,还定义了等待队列的结构体wait_queue 和一个tcp 状态的结构体tcp_private。Tcp.c 是头文件里面定义的函数的实现。 Tcp-client.c:里面有关于建立连接的方法,tcp 客户端确认提交的方法,初始化传输和结束等方法。Tcp-server.c 里面也有相应的方法。 3 种方法都是按照transport.h 定义的接口来实现的。这里用到了面向对象的思想和状态模式,transport.c 里面的相关方法对其多种“子类做了”dispatch。 例如
//transport.c
int32_t
transport_notify (transport_t *this, int32_t event)
{
int32_t ev = GF_EVENT_CHILD_UP;
if ((event & POLLIN) || (event & POLLPRI))
ev = GF_EVENT_POLLIN;
if ((event & POLLERR) || (event & POLLHUP))
ev = GF_EVENT_POLLERR;
return this->notify (this->xl, ev, this);
}
该模块通过transport_op 这个结构体实现了“多态”,该结构体在transport.h 有接口定义(都是函数指针),而在每个连接方式的实现里面都声明一个实例,并按自己的方式初始化。
//transport.h
struct peer_info_t { //连接属性结构体含有套接字信息
struct sockaddr_in sockaddr;
};
struct transport { //传输interface
struct transport_ops *ops; //操作功能结构体指针,相当于类函数
void *private; //私有信息,在子类中初始化
void *xl_private; //
pthread_mutex_t lock; //线程锁
int32_t refcount; //引用计数
xlator_t *xl;
void *dnscache; //dns 缓冲?作用不太明了
data_t *buf; //数据缓冲
int32_t (*init) (transport_t *this, //初始化函数
dict_t *options,
event_notify_fn_t notify);
struct peer_info_t peerinfo; //包含了前面定义的套接字信息
void (*fini) (transport_t *this); //“析构”函数
event_notify_fn_t notify; //消息
};
struct transport_ops { //传输类的“成员函数“
int32_t (*flush) (transport_t *this);
int32_t (*recieve) (transport_t *this, char *buf, int32_t len);
int32_t (*submit) (transport_t *this, char *buf, int32_t len);
int32_t (*writev) (transport_t *this,
const struct iovec *vector,
int32_t count);
int32_t (*readv) (transport_t *this,
const struct iovec *vector,
int32_t count);
int32_t (*connect) (transport_t *this);
int32_t (*disconnect) (transport_t *this);
int32_t (*except) (transport_t *this);
int32_t (*bail) (transport_t *this);
};
在真正的实现里面会初始化,传输类的成员函数的,如Tcp-server.c
//Tcp-server.c
struct transport_ops transport_ops = { //此处初始化transport 类的成员函数,这里是多态
// .flush = tcp_flush, //每个其具体实现都可以灵活定义自己的操作细节。
.recieve = tcp_recieve,
.disconnect = tcp_disconnect, //其具体实现也在本文件中。
.submit = tcp_server_submit,
.except = tcp_except,
.readv = tcp_readv,
.writev = tcp_server_writev
};
调度器是给unify 的接口用的,在集群文件系统中,分数据块时绑定一个相应的调度器,来进行数据的分布式存取。有四种调度器,前面有提到了。调度操作接口比较简单,只要实现下列的接口函数的类就是一个调度器。
//scheduler.h
struct sched_ops { // 包括初始化,清理费料,更新,调度器逻辑,通知。
int32_t (*init) (xlator_t *this);
void (*fini) (xlator_t *this);
void (*update) (xlator_t *this);
xlator_t *(*schedule) (xlator_t *this, int32_t size);
void (*notify) (xlator_t *xl, int32_t event, void *data);
};
该文件还定义了一个获得调度器的方法get_scheduler,具体实现在scheduler.c 里面,被unify.c 中调用(后面提unify.c).
RR 调度器是最简单的,和下面要写的xlator 关系很密切。结构体如下:
//rr.h
struct rr_sched_struct { //rr 调度需要的一些属性如刷新间隔,剩余磁盘空间,可用性等等。。
xlator_t *xl;
struct timeval last_stat_fetch;
int64_t free_disk;
int32_t refresh_interval;
unsigned char eligible;
};
struct rr_struct { //rr 结构体
struct rr_sched_struct *array; //包含rr 调度结构体的一个数组
struct timeval last_stat_fetch;
int32_t refresh_interval; //刷新时间间隔
int64_t min_free_disk; //最小的剩余空间
pthread_mutex_t rr_mutex; //线程锁
int32_t child_count; //节点计数
int32_t sched_index; //调度索引
};
与接口的联系方式和transport 模块类似,实现了sched_ops 定义的操作。
//rr.c
struct sched_ops sched = {
.init = rr_init,
.fini = rr_fini,
.update = rr_update,
.schedule = rr_schedule,
.notify = rr_notify
};
Xlator 是一个有前驱和后继及父指针的节点类,其组成了xlator_list 链表,另外系统使用它组成树结构来使用,schedulor 在初始化时是会遍历xlator_list 的每个节点的。Xlator里面还包括了相关xlator 的操作符xlator_fops、xlator_mops,和构造函数与析构函数,以及一些必要的数据,节点表指针,消息,glusterfs 环境,配置选项字典(option)等等。
//xlator.h
struct _xlator {
char *name;
char *type;
xlator_t *next, *prev;
xlator_t *parent;
xlator_list_t *children;
struct xlator_fops *fops;
struct xlator_mops *mops;
void (*fini) (xlator_t *this);
int32_t (*init) (xlator_t *this);
event_notify_fn_t notify;
dict_t *options;
glusterfs_ctx_t *ctx;
inode_table_t *itable;
char ready;
void *private;
};
typedef struct xlator_list {
xlator_t *xlator;
struct xlator_list *next;
} xlator_list_t;
Xlator_list 是一个关于xlator 的前向链表。
要设计一个translator 也比较容易, 除了需要一个初始化函数
int32_t init (xlator_t *this)
和收尾的函数
void fini (xlator_t *this)
还需要对xlator.h 中的xlator_fops、xlator_mops 两个结构体中,需要翻译的命令自己定义。
//xlator_fops的定义,里面包括要求翻译的调用表,fops表示文件操作
struct xlator_fops {
fop_lookup_t lookup; //前者是一个指向函数的指针
fop_forget_t forget;
fop_stat_t stat;
…………
fop_lk_cbk_t lk_cbk;
fop_writedir_cbk_t writedir_cbk;
};
结构体中每个属性都是个指向函数的指针,例:
typedef int32_t (*fop_lookup_t) (call_frame_t *frame,xlator_t *this,loc_t *loc);
xlator_mops的定义,里面包括要求翻译的调用表,mops表示管理操作
struct xlator_mops {
mop_stats_t stats;
mop_fsck_t fsck;
mop_lock_t lock;
mop_unlock_t unlock;
mop_listlocks_t listlocks;
mop_getspec_t getspec;
mop_stats_cbk_t stats_cbk;
mop_fsck_cbk_t fsck_cbk;
mop_lock_cbk_t lock_cbk;
mop_unlock_cbk_t unlock_cbk;
mop_listlocks_cbk_t listlocks_cbk;
mop_getspec_cbk_t getspec_cbk;
};
前面是需翻译的命令,翻译后的命令例子如下,以rot-13 为例:
//rot-13.c 下面的赋值表示要翻译的两个调用, 管理操作以默认不做更改
//(default.h,default.c)
struct xlator_fops fops = {
.readv = rot13_readv,
.writev = rot13_writev
}; //这里赋值结构体的方式是C99标准新出来的,其在linux2.6内核源码中有较多的使用。
struct xlator_mops mops = {
};
同样你把自己需要翻译过来的调用自己实现例如下:
static int32_t
rot13_writev (call_frame_t *frame,
xlator_t *this,
fd_t *fd,
struct iovec *vector,
int32_t count,
off_t offset){ do something }
这样扩展一个translator 就完成了。 translator 操作是异步的,这样可以减少网络上调用的延时造成性能下降。 它使用STACK_WIND 和'STACK_UNWIND 维护一个用户空间的调用栈。在桩文件callstub.h 文件中,里面有call_stub_t 结构体的定义,结构体里面含有一个联合,另外头文件还有相关调用的桩,call -stub.c 里面是头文件接口的实现。
// call-stub.h
typedef struct {
struct list_head list;
char wind;
call_frame_t *frame;
glusterfs_fop_t fop;
union { //联合体里面包含了若干个结构体,其中每个结构体里面都是一个调用桩
//是一个指向函数的指针,和相关需要传递或保存的属性或结构(像函数对象?)。
/* lookup */
struct {
fop_lookup_t fn;
loc_t loc;
} lookup;
……….
} args;
} call_stub_t;
// call-stub.c 头文件中示例的实现
call_stub_t *
fop_lookup_stub (call_frame_t *frame,
fop_lookup_t fn,
loc_t *loc)
{
call_stub_t *stub = NULL;
stub = stub_new (frame, 1, GF_FOP_LOOKUP);
if (!stub)
return NULL;
stub->args.lookup.fn = fn;
loc_copy (&stub->args.lookup.loc, loc);
return stub;
}
Glusterfs 使用的协议是比较简单的,协议的定义可以在其官方网站有简短的描述。 (来自代码注释protocol.h)
All value in bytes. '\n' is field seperator.
Field:
==================
"Block Start\n":12
callid:16
Type:8
Op:8
Name:32
BlockSize:32
Block:
"Block End\n":10
==================
typedef enum {
GF_OP_TYPE_FOP_REQUEST,
GF_OP_TYPE_MOP_REQUEST,
GF_OP_TYPE_FOP_REPLY,
GF_OP_TYPE_MOP_REPLY
} glusterfs_op_type_t;
static int32_t
server_protocol_interpret (transport_t *trans,gf_block_t *blk)
在cluster 文件夹下面有两个与性能有关的三个translator。
当然对单个主机中文件系统的优化也是需要的,比如对ext3,reiserfs 的参数优化。
此处写的是个人体验,不一定都正确,供参考。
Glusterfs 是一个存储空间和访问效率都可以线性增加的一个分布式文件系统,网上资料除了gluster.org 以外,几乎没有什么有关的介绍了。
通过对源码的审阅,个人感觉,比较主要的是把程序的整体结构理清,扩展方式弄明白在向下看具体的实现是比较好的。
该系统扩充的方式是使用了translator 的模式,具体我还参考了《现代操作系统》中分布式文件系统章节和GNU/HURD 中解释translator 的部分,后者主要是在gnu 的网站上。
数据结构上讲,整个文件系统中节点构成了一棵树,而且每个节点的操作是通过某个translator
来工作的,一个节点可以附着很多的translator。所有的translator都要实现xlator
结构体和相关的xlator_fops、xlator_mops
两个“成员函数的结构体”,从xlator“继承”下来的操作如果不自己定义,那么就会使用默认的设置,这个在default.c
里面定义。当然自己定义的操作并赋值,这个过程有些象子类覆盖父类的操作,平行来看也就是多态。当然这是从面向对象角度来看的,该系统很多地方都使用了面
向对象的思想来设计的,这个和linux 2.6 以后的内核模块设计是异曲同工的。
那么一般可以这样识别一个用c 实现的Class 关键字的类:
struct A {
char *name;
char *type; //成员
struct A *this; //this 指针
struct xlator_fops *fops; //成员操作结构体1
struct xlator_mops *mops; //成员操作结构体2
void (*fini) (struct A *this); // 析构函数,垃圾清理
int32_t (*init) (struct A *this);//构造函数,初始化
event_notify_fn_t notify; //成员。。
dict_t *options;
glusterfs_ctx_t *ctx;
inode_table_t *itable;
char ready;
void *private;
};