keepalived源码解析 —— start_vrrp_child()

epoll_create 与 epoll_create1 的区别:
1)int epoll_create(int size):创建一个 epoll 的句柄,size 用来告诉内核这个监听的数目一共有多大,内核根据这个参数分配最初的描述事件结构体的空间。在linux 内核版本大于2.6.8 后,参数:size 已被弃用了(内核动态的分配描述事件需要的空间),但是传入的值必须大于0,是为了新的应用能运行在旧的内核上。
2)int epoll_create1(int flags):
2.1)epoll_create1 扩展了 epoll_create 的功能,更推荐使用的是 epoll_create1(0) 来代替普通的用法
2.2)flags:
0:等价于删除了过时size参数的 epoll_create();
EPOLL_CLOEXEC:表示生成的 epoll fd 具有“执行后关闭”特性。

start_vrrp_child():
1、使用 fork() 创建vrrp子进程;
2、清除从父进程复制来的数据;
3、新建 master thread 对象,包括epoll句柄、定时器接口以及4个双向链表:event、signal、ready、unuse;
4、调用 start_vrrp(),启动 vrrp;
5、process_threads() 中循环调用 thread_fetch_next_queue(),监控各链表以及 epoll 事件,并执行对应的回调函数。

vrrp 相关数据(base_priority/wantstate/state)设置:
1、读取配置文件,获取 vrrp 优先级 base_priority(默认:100);
vrrp_parser.c -> read_unsigned_strvec(strvec, 1, &base_priority, 1, VRRP_PRIO_OWNER, false)
2、读取配置文件,获取 vrrp 状态 wantstate:back/mast(默认:VRRP_STATE_INIT);
start_vrrp -> vrrp_complete_init-> alloc_vrrp:初始化 vrrp
3、在未指定 base_priority 或配置为0的前提下,若 wantstate 为 VRRP_STATE_MAST,则 base_priority 设置为 VRRP_PRIO_OWNER(255),否则设置为 VRRP_PRIO_DFL(100)
4、vrrp internal state:(init/backup/master/fault),默认:VRRP_STATE_INIT(vrrp_complete_instance())

// vrrp_daemon.c

/* 创建vrrp子进程,实现vrrp协议,负责keepalived主要的事件的调度。*/
int start_vrrp_child(void)
{
#ifndef _ONE_PROCESS_DEBUG_
	pid_t pid;
	const char *syslog_ident;

	/* Initialize child process */
#ifdef ENABLE_LOG_TO_FILE
	if (log_file_name)
		flush_log_file();
#endif

	pid = fork();	// 创建vrrp子进程

	if (pid < 0) {
		/* 子进程创建出错 */
		log_message(LOG_INFO, "VRRP child process: fork error(%s)"
			       , strerror(errno));
		return -1;
	} else if (pid) {
		/* 子进程创建成功,父进程返回子进程id */
		vrrp_child = pid;
		log_message(LOG_INFO, "Starting VRRP child process, pid=%d"
			       , pid);

		/* Start respawning thread */
		thread_add_child(master, vrrp_respawn_thread, NULL,
				 pid, TIMER_NEVER);

		return 0;
	}
	/* 子进程创建成功,子进程返回0 */
	
	/*
	PR_SET_PDEATHSIG:在子进程中使用prctl系统调用,表示当父进程挂了后,要求内核给自己(子进程)发送特定信号
	SIGTERM:进程收到SIGTERM信号后,会释放自己的资源并停止
	*/
	prctl(PR_SET_PDEATHSIG, SIGTERM);

#ifdef _WITH_PERF_
	if (perf_run == PERF_ALL)
		/* 性能分析 */
		run_perf("vrrp", global_data->network_namespace, global_data->instance_name);
#endif
	
	/* 子进程类型:vrrp */
	prog_type = PROG_TYPE_VRRP;
	
	/* 初始化debug相关参数 */
	initialise_debug_options();

#ifdef _WITH_BFD_
	/* Close the write end of the BFD vrrp event notification pipe */
	close(bfd_vrrp_event_pipe[1]);

#ifdef _WITH_LVS_
	/* 关闭 BFD checker event notification pipe 读写两端*/
	close(bfd_checker_event_pipe[0]);
	close(bfd_checker_event_pipe[1]);
#endif
#endif

	/*
	openlog():打开一个到系统日志记录程序的连接
	syslog()或vsyslog():向系统日志里添加信息
	closelog():关闭连接
	*/
	if ((global_data->instance_name
#if HAVE_DECL_CLONE_NEWNET
			   || global_data->network_namespace
#endif
					       ) &&
	    (vrrp_syslog_ident = make_syslog_ident(PROG_VRRP)))
			syslog_ident = vrrp_syslog_ident;
	else
		syslog_ident = PROG_VRRP;

	if (!__test_bit(NO_SYSLOG_BIT, &debug))
		openlog(syslog_ident, LOG_PID | ((__test_bit(LOG_CONSOLE_BIT, &debug)) ? LOG_CONS : 0)
				    , (log_facility==LOG_DAEMON) ? LOG_LOCAL1 : log_facility);
	
	/*写日志到文件*/
#ifdef ENABLE_LOG_TO_FILE
	if (log_file_name)
		open_log_file(log_file_name,
				"vrrp",
#if HAVE_DECL_CLONE_NEWNET
				global_data->network_namespace,
#else
				NULL,
#endif
				global_data->instance_name);
#endif
	
	/* 创建内存相关的日志文件 */
#ifdef _MEM_CHECK_
	mem_log_init(PROG_VRRP, "VRRP Child process");
#endif
	
	/* 释放从父进程复制过来的对象,包括系统日志、core_dump等 */
	free_parent_mallocs_startup(true);
	
	/* 清除父进程设置的回调函数,该回调函数用户返回进程名称 */
	set_child_finder_name(NULL);

	/* 将子进程id写入 pidfile */
	if (!pidfile_write(vrrp_pidfile, getpid())) {
		/* Fatal error */
		log_message(LOG_INFO, "VRRP child process: cannot write pidfile");
		exit(0);
	}
	
	/*设置回调函数,记录 vrrp dump*/
#ifdef _VRRP_FD_DEBUG_
	if (do_vrrp_fd_debug)
		set_extra_threads_debug(dump_vrrp_fd);
#endif

	/* 创建新的 master thread */
	/* 销毁从父进程复制的对象,包括epoll句柄等 */
	thread_destroy_master(master);
	/* 新建 master thread 对象,包括epoll句柄、定时器接口以及4个双向链表:event、signal、ready、unuse */
	master = thread_make_master();
#endif

	/* If last process died during a reload, we can get there and we
	 * don't want to loop again, because we're not reloading anymore.
	 */
	UNSET_RELOAD;

#ifndef _ONE_PROCESS_DEBUG_
	/* 
	设置信号的处理函数
	SIGHUP		终止进程	终端线路挂断
	SIGINT		终止进程	程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出
	SIGTERM		终止		程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。
							通常用来要求程序自己正常退出。
	SIGUSR1		终止进程	用户定义信号1
	SIGUSR2		终止进程	用户定义信号2
	SIGJSON
	SIGTDUMP
	SIGPIPE		终止进程	向一个没有读进程的管道写数据
	*/
	vrrp_signal_init();
#endif

	/* 启动 vrrp */
	start_vrrp(NULL);

#ifdef _ONE_PROCESS_DEBUG_
	return 0;
#endif

#ifdef THREAD_DUMP
	register_vrrp_thread_addresses();
#endif

#ifdef _WITH_PERF_
	if (perf_run == PERF_RUN)
		run_perf("vrrp", global_data->network_namespace, global_data->instance_name);
#endif
	/* Launch the scheduling I/O multiplexer */
	/* event、epoll 事件处理 */
	launch_thread_scheduler(master);

#ifdef THREAD_DUMP
	deregister_thread_addresses();
#endif

	/* Finish VRRP daemon process */
	/*关闭ipvs(ipvs是一个socket)、iptables、master thread、日志、global_data、vrrp_data 等 */
	vrrp_terminate_phase2(EXIT_SUCCESS);

	/* unreachable */
	exit(EXIT_SUCCESS);
}
/* Our infinite scheduling loop */
void
launch_thread_scheduler(thread_master_t *m)
{
	/*
	SIGCHLD:在一个进程终止或者停止时,将SIGCHLD信号发送给其父进程
	按系统默认将忽略此信号,如果父进程希望被告知其子系统的这种状态,则应捕捉此信号。
	*/
	signal_set(SIGCHLD, thread_child_handler, m);
	
	/* 循环调用 thread_fetch_next_queue,监控各链表,执行回调函数 */
	process_threads(m);
}
void
process_threads(thread_master_t *m)
{
	thread_t* thread;
	list_head_t *thread_list;
	int thread_type;

	/*
	1、检查 event 链表,取出节点并执行回调函数;
	2、若 event 链表中没有待处理的事件,则检查 ready 链表,取出节点并执行回调函数;
	3、若 ready 链表中没有待处理的事件,则调用 epoll_wait(),等待 epoll 事件的发生,将 事件 添加至 ready 链表中;
	4、再次检查 ready 链表,取出节点并执行回调函数;
	5、event 和 ready 链表处理完成,把使用完的空间放入 master->unuse 链表中。
	*/
	while ((thread_list = thread_fetch_next_queue(m))) {
		/* Run until error, used for debuging only */
#ifdef _MEM_ERR_DEBUG_
		if (do_mem_err_debug &&
		    __test_bit(MEM_ERR_DETECT_BIT, &debug)
#ifdef _WITH_VRRP_
		    && __test_bit(DONT_RELEASE_VRRP_BIT, &debug)
#endif
								) {
			__clear_bit(MEM_ERR_DETECT_BIT, &debug);
#ifdef _WITH_VRRP_
			__clear_bit(DONT_RELEASE_VRRP_BIT, &debug);
#endif
			thread_add_terminate_event(master);
		}
#endif

		/* If we are shutting down, only process relevant thread types.
		 * We only want timer and signal fd, and don't want inotify, vrrp socket,
		 * snmp_read, bfd_receiver, bfd pipe in vrrp/check, dbus pipe or netlink fds. */
		thread = thread_trim_head(thread_list);

		if (thread && thread->type == THREAD_CHILD_TIMEOUT) {
			/* We remove the thread from the child_pid queue here so that
			 * if the termination arrives before we processed the timeout
			 * we can still handle the termination. */
			rb_erase(&thread->rb_data, &master->child_pid);
		}

		if (!shutting_down ||
		    ((thread->type == THREAD_READY_READ_FD ||
		      thread->type == THREAD_READY_WRITE_FD) &&
		     (thread->u.f.fd == m->timer_fd ||
		      thread->u.f.fd == m->signal_fd
#ifdef _WITH_SNMP_
		      || FD_ISSET(thread->u.f.fd, &m->snmp_fdset)
#endif
							       )) ||
		    thread->type == THREAD_CHILD ||
		    thread->type == THREAD_CHILD_TIMEOUT ||
		    thread->type == THREAD_CHILD_TERMINATED ||
		    thread->type == THREAD_TIMER_SHUTDOWN ||
		    thread->type == THREAD_TERMINATE) {
			if (thread->func)
				thread_call(thread); /* 执行回调函数 */

			if (thread->type == THREAD_TERMINATE_START)
				shutting_down = true;
		}

		m->current_event = (thread->type == THREAD_READY_READ_FD || thread->type == THREAD_READY_WRITE_FD) ? thread->event : NULL;
		thread_type = thread->type;
		
		
		thread_add_unuse(master, thread);

		/* If we are shutting down, and the shutdown timer is not running and
		 * all children have terminated, then we can terminate */
		if (shutting_down && !m->shutdown_timer_running && !m->child.rb_root.rb_node)
			break;

		/* If daemon hanging event is received stop processing */
		if (thread_type == THREAD_TERMINATE)
			break;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值