最近用到open-iscsi作用initiator,遇到很多问题,就打算粗率对open-iscsi的源码进行一个分析。
open-iscsi的组成和用法就不讲了,源码包中的README说得非常清楚,主要分为驱动和用户态两部分,用户态又分为服务守护进程和管理程序,必须先加载驱动,守护进程才能启动成功,最后才是运行iscsiadm进行一些常规管理。
今天首先对open-iscsi的iscsid守护进程的主程序整体流程进行一个粗率的分析,明白其启动的过程。
首先是用到的全局变量:
/*包含了配置文件、pid文件和initiatorname等一些信息*/
在守护进程启动时,会对这个结构进行初始化,主要是读取/etc/iscsi目录下的配置文件,获取initiatorname等
struct iscsi_daemon_config daemon_config;
struct iscsi_daemon_config *dconfig = &daemon_config;
static char program_name[] = "iscsid";
static pid_t log_pid; /*记录日志的守护进程*/
static gid_t gid; /*运行组id*/
static int daemonize = 1; /*守护进程模式标志*/
static int mgmt_ipc_fd; /*进程间通信的本地socket文件描述符*/
/*iscid启动的参数表*/
static struct option const long_options[] = {
{"config", required_argument, NULL, 'c'},
{"initiatorname", required_argument, NULL, 'i'},
{"foreground", no_argument, NULL, 'f'},
{"debug", required_argument, NULL, 'd'},
{"uid", required_argument, NULL, 'u'},
{"gid", required_argument, NULL, 'g'},
{"no-pid-file", no_argument, NULL, 'n'},
{"pid", required_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{"version", no_argument, NULL, 'v'},
{NULL, 0, NULL, 0},
};
initiator.h文件中定义的一些常量,iscsid主程序会用到
主要是一些配置文件的路径和要创建的文件的位置
#define ISCSI_CONFIG_ROOT "/etc/iscsi/"
#define CONFIG_FILE ISCSI_CONFIG_ROOT"iscsid.conf"
#define INITIATOR_NAME_FILE ISCSI_CONFIG_ROOT"initiatorname.iscsi"
#define PID_FILE "/var/run/iscsid.pid"
#ifndef LOCK_DIR
#define LOCK_DIR "/var/lock/iscsi"
#endif
#define LOCK_FILE LOCK_DIR"/lock"
#define LOCK_WRITE_FILE LOCK_DIR"/lock.write"
接着是分析iscsid的main程序:
int main(int argc, char *argv[])
{
struct utsname host_info; /* will use to compound initiator alias */
char *config_file = CONFIG_FILE;
char *initiatorname_file = INITIATOR_NAME_FILE;
char *pid_file = PID_FILE;
int ch, longindex;
uid_t uid = 0;
struct sigaction sa_old;
struct sigaction sa_new;
int control_fd;
pid_t pid;
/*首先是分析iscsid的启动参数*/
while ((ch = getopt_long(argc, argv, "c:i:fd:nu:g:p:vh",
long_options, &longindex)) >= 0) {
switch (ch) {
case 'c': /*若不使用默认的配置文件,需要指定*/
config_file = optarg;
break;
case 'i': /*不使用默认的initiatorname文件,也许要指定*/
initiatorname_file = optarg;
break;
case 'f': /*在前台运行,即不进入守护模式*/
daemonize = 0;
break;
case 'd': /*设置调试级别*/
log_level = atoi(optarg);
break;
case 'u': /*设置用户id*/
uid = strtoul(optarg, NULL, 10);
break;
case 'g': /*设置组id*/
gid = strtoul(optarg, NULL, 10);
break;
case 'n': /*不生成pid文件*/
pid_file = NULL;
break;
case 'p': /*不使用默认的pid文件生成路径*/
pid_file = optarg;
break;
case 'v':
printf("%s version %s\n", program_name,
ISCSI_VERSION_STR);
exit(0);
case 'h':
usage(0);
break;
default:
usage(1);
break;
}
}
/* initialize logger */
//返回值是记录日志的子进程id
/*这里的日志记录进程也是一个守护进程,它会定期将输出的日志通过syslog输出,同时根据是否是守护模式,设置了输出函数,是通过syslog输出,还是直接输出的终端,以后将进入这个函数进行分析*/
log_pid = log_init(program_name, DEFAULT_AREA_SIZE,
daemonize ? log_do_log_daemon :
log_do_log_std, NULL);
if (log_pid < 0)
exit(ISCSI_ERR);
/* do not allow ctrl-c for now... */
/* 设置信号捕捉函数*/
sa_new.sa_handler = catch_signal;
sigemptyset(&sa_new.sa_mask);
sa_new.sa_flags = 0;
sigaction(SIGINT, &sa_new, &sa_old );
sigaction(SIGPIPE, &sa_new, &sa_old );
sigaction(SIGTERM, &sa_new, &sa_old );
//获取系统sys目录
sysfs_init();
//idbm初始化
/*open-iscsi将targets的发现和nodes信息是通过dbm数据库进行组织的,所以运行open-iscsi还需要安装dbm数据库,
其实就是libgdbm.so等几个库文件,idbm_init初始化需要链接到库文件中dbm的操作函数*/
if (idbm_init(iscsid_get_config_file)) {
log_close(log_pid);
exit(ISCSI_ERR);
}
/*设置创建文件的mask值,这里只有运行用户的rw权限生效*/
umask(0177);
mgmt_ipc_fd = -1;
control_fd = -1;
daemon_config.initiator_name = NULL;
daemon_config.initiator_alias = NULL;
//建立一个本地socket进行进程间的通信
/*在open-iscsi中,管理程序和守护进行的通信是通过一个unix本地socket进行的*/
if ((mgmt_ipc_fd = mgmt_ipc_listen())
< 0) {
log_close(log_pid);
exit(ISCSI_ERR);
}
//创建守护进程
if (daemonize) {
char buf[64];
int fd = -1;
if (pid_file) { //创建pid文件
fd = open(pid_file, O_WRONLY|O_CREAT, 0644);
if (fd < 0) {
log_error("Unable to create pid file");
log_close(log_pid);
exit(ISCSI_ERR);
}
}
pid = fork(); //创建一个子进程
if (pid < 0) {
log_error("Starting daemon failed");
log_close(log_pid);
exit(ISCSI_ERR);
} else if (pid) { /*父进程退出,子进程被init进程接管*/
log_error("iSCSI daemon with pid=%d started!", pid);
exit(0);
}
/*控制通信*/
if ((control_fd = ipc->ctldev_open())
< 0) {
log_close(log_pid);
exit(ISCSI_ERR);
}
/*切换目录到根目录*/
if (chdir("/") < 0)
log_debug(1, "Unable to chdir to /");
/*将守护进程的进程id写入创建的pid文件*/
if (fd > 0) {
if (lockf(fd, F_TLOCK, 0) < 0) { //锁住pid文件
log_error("Unable to lock pid file");
log_close(log_pid);
exit(ISCSI_ERR);
}
if (ftruncate(fd, 0) < 0) { //将文件清空
log_error("Unable to truncate pid file");
log_close(log_pid);
exit(ISCSI_ERR);
}
sprintf(buf, "%d\n", getpid());
if (write(fd, buf, strlen(buf)) < 0) { //写入守护进程pid编号
log_error("Unable to write pid file");
log_close(log_pid);
exit(ISCSI_ERR);
}
}
/*主要是对stdin stdout stderr重定向到/dev/null*/
daemon_init(); //守护进程初始化
}
else { /*非守护进程模式,即iscid运行在前端*/
if ((control_fd = ipc->ctldev_open()) < 0) {
log_close(log_pid);
exit(1);
}
}
/*设置用户和组id*/
if (uid && setuid(uid) < 0)
perror("setuid\n");
if (gid && setgid(gid) < 0)
perror("setgid\n");
//初始化daemon_config结构
memset(&daemon_config, 0, sizeof (daemon_config));
daemon_config.pid_file = pid_file;
daemon_config.config_file = config_file;
daemon_config.initiator_name = cfg_get_string_param(initiatorname_file, "InitiatorName");
if (daemon_config.initiator_name == NULL)
missing_iname_warn(initiatorname_file);
/* optional InitiatorAlias */
daemon_config.initiator_alias =
cfg_get_string_param(initiatorname_file,
"InitiatorAlias");
if (!daemon_config.initiator_alias) {
memset(&host_info, 0, sizeof (host_info));
if (uname(&host_info) >= 0) {
daemon_config.initiator_alias =
strdup(host_info.nodename);
}
}
log_debug(1, "InitiatorName=%s", daemon_config.initiator_name ?
daemon_config.initiator_name : "NOT SET");
log_debug(1, "InitiatorAlias=%s", daemon_config.initiator_alias);
pid = fork();//创建一个子进程
if (pid == 0) {
int nr_found = 0;
/* child */
//查找是否有会话记录存在,进行同步
iscsi_sysfs_for_each_session(NULL,
&nr_found, sync_session);
exit(0);
} else if (pid < 0) {
log_error("Fork failed error %d: existing sessions"
" will not be synced", errno);
} else
reap_inc();
iscsi_initiator_init(); //设置会话回调函数
increase_max_files(); //设置文件限制值
discoveryd_start(daemon_config.initiator_name); //在查找已存在的记录目录
/* oom-killer will not kill us at the night... */
if (oom_adjust())
log_debug(1, "can not adjust oom-killer's pardon");
/* we don't want our active sessions to be paged out... */
if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
log_error("failed to mlockall, exiting...");
log_close(log_pid);
exit(ISCSI_ERR);
}
/*设置一些全局变量*/
actor_init();
/*通过poll等待链接*/
event_loop(ipc, control_fd, mgmt_ipc_fd);
/*释放分配的内存*/
idbm_terminate();
sysfs_cleanup();
ipc->ctldev_close();
/*关闭本地通信socket*/
mgmt_ipc_close(mgmt_ipc_fd);
/*释放分配的内存*/
if (daemon_config.initiator_name)
free(daemon_config.initiator_name);
if (daemon_config.initiator_alias)
free(daemon_config.initiator_alias);
/*释放会话*/
free_initiator();
iscsid_shutdown();
return 0;
}