MFS Master
版本 2.0.x,开发语言 C,使用 AutoTools 作为构建工具集
亮点:
为 master、chunk、metalog 提供了一套统一的公用的 main 函数处理框架
暗点:
源码中不带单元测试或集成测试,缺少注释、文档
使用了比较落后的 IO 复用模式(poll)
作者编写代码不是很用心,在一些代码的逻辑处理上比较随意。另外还存在制表符、空格缩进混用的现象
目录结构
mfscommon/ 公用模块目录
mfsmaster/ mfs master 代码
main 处理框架
从 mfsmaster/Makefile.am 构建配置文件可知,入口点是在 mfscommon/main.c 中
mfschunk、mfsmetalogger 程序也使用了同一套 main 函数处理框架
不同之处在于具有各自的 init.h,在 RunTable 数组定义了不同的初始化函数
/* Run Tab */
typedef int (*runfn)(void);
struct {
runfn fn;
char *name;
} RunTab[]={
{changelog_init,"change log"},
{rnd_init,"random generator"},
{missing_log_init,"missing chunks/files log"}, // has to be before 'fs_init'
{dcm_init,"data cache manager"}, // has to be before 'fs_init' and 'matoclserv_init'
{exports_init,"exports manager"},
{topology_init,"net topology module"},
{meta_init,"metadata manager"},
{chartsdata_init,"charts module"},
{matomlserv_init,"communication with metalogger"},
{matocsserv_init,"communication with chunkserver"},
{matoclserv_init,"communication with clients"},
{(runfn)0,"****"}
},LateRunTab[]={
{(runfn)0,"****"}
};
main ( )
大致流程:
- 各种初始化
- 进入 mainloop 死循环
- 执行一些清理工作,程序退出
int main(int argc,char **argv) {
strerr_init(); // 初始化错误消息散列表
mycrc32_init(); // 初始化 crc ???
// 打开配置文件
cfgfile=strdup(ETC_PATH "/mfs/" STR(APPNAME) ".cfg");
passert(cfgfile);
if ((fd = open(cfgfile,O_RDONLY))<0 && errno==ENOENT) {
// 分析命令行参数
while ((ch = getopt(argc, argv, "nuvfdc:t:h?" MODULE_OPTIONS_GETOPT)) != -1) {
// 变成后台守护进程
if (runmode==RM_START || runmode==RM_RESTART || runmode==RM_TRY_RESTART) {
if (rundaemon) {
makedaemon();
} else {
set_signal_handlers(0);
}
}
// 加载配置文件
if (cfg_load(cfgfile,logundefined)==0) {
// 初始化日志
if (rundaemon) {
if (logappname[0]) {
openlog(logappname, LOG_PID | LOG_NDELAY , LOG_DAEMON);
// 设置资源限制
if (runmode==RM_START || runmode==RM_RESTART || runmode==RM_TRY_RESTART) {
rls.rlim_cur = MFSMAXFILES;
rls.rlim_max = MFSMAXFILES;
nicelevel = cfg_getint32("NICE_LEVEL",-19);
setpriority(PRIO_PROCESS,getpid(),nicelevel);
}
// 设置用户组、工作目录、文件掩码
changeugid();
if (chdir(wrkdir)<0) {
umask(cfg_getuint32("FILE_UMASK",027)&077);
// 初始化文件锁
ch = wdlock(runmode,locktimeout);
// 初始化程序模块
fprintf(stderr,"initializing %s modules ...\n",logappname);
if (initialize()) { // 执行 RunTable 数组定义的函数
if (initialize_late()) { // 执行 LateRunTab 数组定义的函数
// 进入主循环
mainloop();
// 程序退出清理
mfs_syslog(LOG_NOTICE,"exititng ...");
destruct();
free_all_registered_entries();
signal_cleanup();
cfg_term();
strerr_term();
closelog();
free(logappname);
wdunlock();
mfs_arg_syslog(LOG_NOTICE,"process exited successfully (status:%d)",ch);
return ch;
}
mainloop()
void mainloop() {
pollentry *pollit; // pollhead 函数链表 main_poll_register()
eloopentry *eloopit; // eloophead 函数链表 main_eachloop_register()
timeentry *timeit; // timehead 函数链表
ceentry *ceit; // cehead (can exit,退出的第二阶段)
weentry *weit; // wehead (want exit,退出的第一阶段)
rlentry *rlit; // rlhead
inentry *init; // inhead (info,打印额外的日志)
/*
表示收到 temination 退出信号后,要执行的几个阶段
t=0:正常死循环
--> 收到退出信号
--> t=1:执行 want exit 函数
--> t=2:执行 can exit 函数
--> t=3:退出死循环
*/
t = 0;
/*
表示收到 reload 重新加载信号后的几种处理方式
r=0 不重载
r=1 重新加载配置文件,cfg_reload()
r=2 父进程 waitpid 已退出的子进程,waitpid(-1,&status,WNOHANG)
r=3 打印额外日志信息,执行 inhead 函数链表
*/
r = 0;
while (t!=3) {
pdesc[0].fd = signalpipe[0]; // 数组第0个文件描述符用于监听内部事件
// 执行 pollhead 函数链表的注册函数,向 pdesc 数组注册要监听的文件描述符和事件
for (pollit = pollhead ; pollit != NULL ; pollit = pollit->next) {
pollit->desc(pdesc,&ndesc);
}
// poll 阻塞,等待描述符就绪或超时
// 超时为 10 毫秒,实现定时执行
i = poll(pdesc,ndesc,10);
} else {
// 内部事件处理
if ((pdesc[0].revents)&POLLIN) {
uint8_t sigid;
if (read(signalpipe[0],&sigid,1)==1) {
if (sigid=='\001' && t==0) {
}
}
// 调用 pollhead 链表的处理函数
for (pollit = pollhead ; pollit != NULL ; pollit = pollit->next) {
pollit->serve(pdesc);
}
}
// event loop 函数链表
// mfs master 并没有注册任何函数
for (eloopit = eloophead ; eloopit != NULL ; eloopit = eloopit->next) {
eloopit->fun();
}
// 等子进程
if (r==2) {
while ( (pid = waitpid(-1,&status,WNOHANG)) >= 0) {
// 遍历 timehead 链表,查看当前时间,判断是否应该执行定时任务
for (timeit = timehead ; timeit != NULL ; timeit = timeit->next) {
if (usecnow >= timeit->nextevent) {
timeit->fun();
if (t==0) {
// reload 重新加载配置文件
if (r==1) {
cfg_reload();
for (rlit = rlhead ; rlit!=NULL ; rlit=rlit->next ) {
rlit->fun();
}
r = 0;
// info 打印日志
} else if (r==3) {
for (init = inhead ; init!=NULL ; init=init->next ) {
init->fun();
}
r = 0;
}
}
// want exit 退出
if (t==1) {
for (weit = wehead ; weit!=NULL ; weit=weit->next ) {
weit->fun();
}
t = 2;
}
// can exit 退出
if (t==2) {
i = 1;
for (ceit = cehead ; ceit!=NULL && i ; ceit=ceit->next ) {
if (ceit->fun()==0) {
i=0;
}
}
if (i) {
t = 3;
}
}
}
}
初始化函数
列表见 init.h RunTable 数组
{changelog_init,"change log"},
{rnd_init,"random generator"},
{missing_log_init,"missing chunks/files log"}, // has to be before 'fs_init'
{dcm_init,"data cache manager"}, // has to be before 'fs_init' and 'matoclserv_init'
{exports_init,"exports manager"},
{topology_init,"net topology module"},
{meta_init,"metadata manager"},
{chartsdata_init,"charts module"},
{matomlserv_init,"communication with metalogger"},
{matocsserv_init,"communication with chunkserver"},
{matoclserv_init,"communication with clients"},
changelog_init() 初始化元数据日志的配置
// 元数据日志,默认 50 个
// # number of metadata change log files (default is 50)
BackLogsNumber = cfg_getuint32("BACK_LOGS",50);
// 内存中保留日志量,默认 10 分钟
// # how many seconds of change logs have to be preserved in memory (default is 1800; this sets the minimum, actual number may be a bit bigger
// # due to logs being kept in 5k blocks; zero disables extra logs storage)
ChangelogSecondsToRemember = cfg_getuint16("CHANGELOG_PRESERVE_SECONDS",600);
// 注册重新加载配置文件要调用的函数
main_reload_register(changelog_reload);
// 当前日志文件描述符
currentfd = NULL;
meta_init() 初始化元数据
if (fs_strinit()<0) {
if (chunk_strinit()<0) {
if (xattr_init()<0) {
if (posix_acl_init()<0) {
if (csdb_init()<0) {
if (sessions_init()<0) {
if (of_init()<0) {
if (meta_loadall()<0) {
meta_reload();
main_reload_register(meta_reload);
main_time_register(3600,0,meta_dostoreall);
main_destruct_register(meta_term);
fs_renumerate_edge_test();
meta_loadall()
// 自动修复(应用 changelog 恢复 metadata)
// 此变量由命令行的参数指定 -a : automatically restore metadata from change logs
if (allowautorestore) {
// 遍历目录,找 metadata 开头的文件,找出版本号为最新的文件
dd = opendir(".");
while ((dp = readdir(dd)) != NULL) {
if (strlen(dp->d_name)>8 && memcmp(dp->d_name,"metadata",8)==0) {
status = meta_check_metadatafile(dp->d_name,&ver,&fileid);
if (ver>bestver) {
// 遍历几个目录,找出版本号最新的 metadata.mfs.emergency 文件
if (bestfileid!=0) { // use emergency locations only if valid fileid has been found
hfname = meta_create_homedir_emergency_filename();
while ((fname = meta_emergency_locations[i++])!=NULL) {
if (status==META_CHECK_OK && ver>bestver && fileid==bestfileid) {
bestver = ver;
bestfname = strdup(fname);
// 加载最新的 metadata 文件,同后面 else 部分
if (meta_loadfile(bestfname)<0) {
// 应用 changelog 日志
// 遍历目录
dd = opendir(".");
while ((dp = readdir(dd)) != NULL) {
// filenames 数组保存 changelog 文件名,其中的 changelog 文件要符合 最后一行版本号 >= metadata 的版本号
filenames[pos] = strdup(dp->d_name);
firstlv = changelog_findfirstversion(filenames[pos]); // 读取第一行记录的版本号
lastlv = changelog_findlastversion(filenames[pos]); // 这段代码逻辑不清,猜测为读取最后一行记录的版本号
// 获得 changelog 最新版本号
if (lastlv > maxlastlv) {
maxlastlv = lastlv;
// 将 changelog 排序保存到一个数组
merger_start(files,filenames,MAXIDHOLE,bestver,maxlastlv);
// 应用日志
if (merger_loop(verboselevel)!=0) {
// 将 "metadata.mfs" 改名为 "metadata.mfs.XXXXXX" ???
} else {
// 读 metadata.mfs 文件头的版本等信息,判断文件是否合法
switch (meta_check_metadatafile("metadata.mfs",&ver,&fileid)) {
// 判断 metadata.mfs.back 是否合法,并和 metadata.mfs 比较哪个版本更新
if (meta_check_metadatafile("metadata.mfs.back",&bestver,&bestfileid)==META_CHECK_OK && bestver>ver && bestfileid!=0 && fileid!=0 && bestfileid!=fileid) {
/*
meta_loadfile 里面打开 metadata.mfs 判断一下版本号
如果是 "MFSM NEW",表示新建一个 metadata
其它则调用 meta_load(),这里没看懂,需要了解 fs 具体的数据结构!!!
*/
if (meta_loadfile("metadata.mfs")<0) {
// 文件改名 为什么要改名???
// 注:程序正常退出时调用了 meta_term()恢复此文件名
if (rename("metadata.mfs","metadata.mfs.back")<0) {
meta_check_metadatafile() 检查 metadata 版本号
名称 | 字节 | 注释 |
---|---|---|
标识和程序版本号 | 8 | 格式为 “MFSM x.y”,比如 “MFSM 2.0” 如果为 “MFSM NEW” 说明是新创建的 metadata 文件 |
元数据版本、时间戳 | 16 | 版本 < 2.0,4 到 12 字节为 metaversion 版本 >= 2.0,前 8 字节为 metaversion,后 8 字节为 metafileid metaversion 从 1 开始编号,文件操作导致数值递增 meta_version_inc(),和 changlog 中的记录的版本号对应 metafileid 等于 main_keep_alive() 取出的时间(秒) + 一个随机数 |
其它 metadata | … | |
结束符 | 16 | 版本 < 1.6,为 16 个’\0’ 版本 >= 1.6,为 “[MFS EOF MARKER]” |
# head -c 32 /opt/lib/mfs/metadata.mfs.back | hexdump -C
00000000 4d 46 53 4d 20 32 2e 30 00 00 00 0b d2 f1 0e f6 |MFSM 2.0........|
00000010 54 21 08 bd a2 ab 63 a7 53 45 53 53 20 31 2e 32 |T!....c.SESS 1.2|
00000020
# tail -c 32 /opt/lib/mfs/metadata.mfs.back | hexdump -C
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000010 5b 4d 46 53 20 45 4f 46 20 4d 41 52 4b 45 52 5d |[MFS EOF MARKER]|
00000020
changelog
# head -n3 /opt/lib/mfs/changelog.0.mfs
50799771430: 1474617600|FREEINODES():2
50799771431: 1474617600|LENGTH(794995,53303806464)
50799771432: 1474617600|UNLOCK(245006645)
-eof-