"criu dump重要步骤的深入研究二"

  "criu checkpoint五"

Posted by Xu on June 12, 2017

CRIU,Dump重要步骤解析

在对dump的重要步骤的深入研究一中,我们分析dump手机过程的第一个步骤,对进程树的信息收集,这一章将深入研究收集过程的第二个步骤:对进程树id信息的收集:遍历进程树的每一个节点,备份vm_id,fs_id,files_id,sighand_id(IO id)和命名空间id信息,备份命名空间信息的时候必须为进程树根节点,否则报错不能备份嵌套命名空间

pstree_ids Icon

collect_pstree_ids():遍历每一个树节点,对每一个树节点获取task_ids

我们先来看一下该函数源码,非常简单:

int collect_pstree_ids(void)
{
	struct pstree_item *item;

	for_each_pstree_item(item)
		if (get_task_ids(item))//遍历进程树每一个节点,获取树节点任务的id信息
			return -1;

	return 0;
}

get_task_ids(item):获取task_ids包括,备份任务相关id信息:vm_id,fs_id,files_id,sighand_id(IO id)和备份命名空间的id信息

int get_task_ids(struct pstree_item *item)
{
	int ret;

	item->ids = xmalloc(sizeof(*item->ids));
	if (!item->ids)
		goto err;

	task_kobj_ids_entry__init(item->ids);//这个函数没有找到来源,不知道有什么用处

	if (item->pid->state != TASK_DEAD) {
		ret = dump_task_kobj_ids(item);//备份和任务相关id信息,产生各种vm_id,fs_id,files_id,sighand_id(IO id),产生id信息的函数为kid_generate_gen,利用的红黑树来产生的id
		if (ret)
			goto err_free;

		ret = dump_task_ns_ids(item);//备份命名空间信息,利用generate_ns_id函数产生namespaceid,这里有不能备份嵌套namespace的错误输出,需要细看。
		if (ret)
			goto err_free;
	}

	return 0;

err_free:
	xfree(item->ids);
	item->ids = NULL;
err:
	return -1;
}

dump_task_kobj_ids():备份任务子对象相关id信息:通过调用kid_generate_gen来生成vm_id ,fs_id, files_id,sighand_id

static int dump_task_kobj_ids(struct pstree_item *item)
{
	int new;
	struct kid_elem elem;
	int pid = item->pid->real;
	TaskKobjIdsEntry *ids = item->ids;

	elem.pid = pid;
	elem.idx = 0; /* really 0 for all */
	elem.genid = 0; /* FIXME optimize */

	new = 0;
	ids->vm_id = kid_generate_gen(&vm_tree, &elem, &new);//构建一棵虚拟的红黑树根节点vm_tree,利用kid_generate_gen产生该虚拟树节点的id
	if (!ids->vm_id || !new) {
		pr_err("Can't make VM id for %d\n", pid);
		return -1;
	}

	new = 0;
	ids->fs_id = kid_generate_gen(&fs_tree, &elem, &new);//构建一棵虚拟的红黑树根节点fs_id,利用kid_generate_gen产生该文件系统树节点的id
	if (!ids->fs_id || !new) {
		pr_err("Can't make FS id for %d\n", pid);
		return -1;
	}

	new = 0;
	ids->files_id = kid_generate_gen(&files_tree, &elem, &new);//构建一棵虚拟的红黑树根节点files_tree,利用kid_generate_gen产生该文件树节点的id
	if (!ids->files_id || (!new && !shared_fdtable(item))) {
		pr_err("Can't make FILES id for %d\n", pid);
		return -1;
	}

	new = 0;
	ids->sighand_id = kid_generate_gen(&sighand_tree, &elem, &new);//构建一棵虚拟的红黑树根节点sighand_tree,利用kid_generate_gen产生该io树节点的id
	if (!ids->sighand_id || !new) {
		pr_err("Can't make IO id for %d\n", pid);
		return -1;
	}

	return 0;
}

kid_generate_gen():首先获取红黑树根入口,找到节点合适的插入位置,找到位置后创建节点生成id

u32 kid_generate_gen(struct kid_tree *tree,
		struct kid_elem *elem, int *new_id)
{
	struct rb_node *node = tree->root.rb_node;
	struct kid_entry *e = NULL;

	struct rb_node **new = &tree->root.rb_node;
	struct rb_node *parent = NULL;

	while (node) {
		struct kid_entry *this = rb_entry(node, struct kid_entry, node);//rb_entry()取得包含node的数据结构指针

		parent = *new;
		if (elem->genid < this->elem.genid)
			node = node->rb_left, new = &((*new)->rb_left);//elem->genid=0,小于当前genid则移到左子节点
		else if (elem->genid > this->elem.genid)
			node = node->rb_right, new = &((*new)->rb_right);
		else
			return kid_generate_sub(tree, this, elem, new_id);//找到合适的节点位置后,创建节点生成id
	}

	e = alloc_kid_entry(tree, elem);
	if (!e)
		return 0;

	rb_link_and_balance(&tree->root, &e->node, parent, new);
	*new_id = 1;
	return e->subid;

}

dump_task_ns_ids:备份命名空间id信息,包括PID,NET,IPC,UTS,MNT,USER,CGROUP5个部分的id信息,每个部分的id信息都是通过调用__get_ns_id来获取id

int dump_task_ns_ids(struct pstree_item *item)
{
	int pid = item->pid->real;
	TaskKobjIdsEntry *ids = item->ids;

	ids->has_pid_ns_id = true;
	ids->pid_ns_id = get_ns_id(pid, &pid_ns_desc, NULL);
	if (!ids->pid_ns_id) {
		pr_err("Can't make pidns id\n");
		return -1;
	}//获取PID NameSpace

	ids->has_net_ns_id = true;
	ids->net_ns_id = __get_ns_id(pid, &net_ns_desc, NULL, &dmpi(item)->netns);
	if (!ids->net_ns_id) {
		pr_err("Can't make netns id\n");
		return -1;
	}//获取net Namespace

	ids->has_ipc_ns_id = true;
	ids->ipc_ns_id = get_ns_id(pid, &ipc_ns_desc, NULL);
	if (!ids->ipc_ns_id) {
		pr_err("Can't make ipcns id\n");
		return -1;
	}//获取IPC NameSpace

	ids->has_uts_ns_id = true;
	ids->uts_ns_id = get_ns_id(pid, &uts_ns_desc, NULL);
	if (!ids->uts_ns_id) {
		pr_err("Can't make utsns id\n");
		return -1;
	}//获取UTS NameSpace

	ids->has_mnt_ns_id = true;
	ids->mnt_ns_id = get_ns_id(pid, &mnt_ns_desc, NULL);
	if (!ids->mnt_ns_id) {
		pr_err("Can't make mntns id\n");
		return -1;
	}//获取 mnt NameSpace

	ids->has_user_ns_id = true;
	ids->user_ns_id = get_ns_id(pid, &user_ns_desc, NULL);
	if (!ids->user_ns_id) {
		pr_err("Can't make userns id\n");
		return -1;
	}//获取 user NameSpace

	ids->cgroup_ns_id = get_ns_id(pid, &cgroup_ns_desc, &ids->has_cgroup_ns_id);
	if (!ids->cgroup_ns_id) {
		pr_err("Can't make cgroup id\n");
		return -1;
	}//获取 cgroup NameSpace

	return 0;
}

__get_ns_id():首先查看是否支持命名空间,支持则调用generate_ns_id生成命名空间id :::generate_ns_id()首先查询该命名空间信息是否存在,不存在判断该命名空间的类型为NS_OTHER,还是NS_CRIU,并且NS_OTHER必须命名空间的pid为进程树根节点的pid,否则报错:不能备份嵌套的命名空间

static unsigned int __get_ns_id(int pid, struct ns_desc *nd, protobuf_c_boolean *supported, struct ns_id **ns)
{
	int proc_dir;
	unsigned int kid;
	char ns_path[10];
	struct stat st;

	proc_dir = open_pid_proc(pid);//打开pid配置文件目录
	if (proc_dir < 0)
		return 0;

	sprintf(ns_path, "ns/%s", nd->str);//将命名空间文件描述符的路径写到变量ns_path中去

	if (fstatat(proc_dir, ns_path, &st, 0)) {
		if (errno == ENOENT) {
			/* The namespace is unsupported */
			kid = 0;
			goto out;
		}
		pr_perror("Unable to stat %s", ns_path);
		return 0;
	}
	kid = st.st_ino;
	BUG_ON(!kid);

out:
	if (supported)
		*supported = kid != 0;
	return generate_ns_id(pid, kid, nd, ns);//生成命名空间id
}

static unsigned int generate_ns_id(int pid, unsigned int kid, struct ns_desc *nd,
		struct ns_id **ns_ret)
{
	struct ns_id *nsid;
	enum ns_type type;

	nsid = lookup_ns_by_kid(kid, nd);//沿着ns_ids为头的链表,根据kid,nd信息查询nsid信息
	if (nsid)
		goto found;

	if (pid != getpid()) {
		type = NS_OTHER;
		if (pid == root_item->pid->real) {
			BUG_ON(root_ns_mask & nd->cflag);
			pr_info("Will take %s namespace in the image\n", nd->str);
			root_ns_mask |= nd->cflag;
			type = NS_ROOT;//只有当该pid为根节点的pid时才可以进行备份
		} else if (nd->cflag & ~CLONE_SUBNS) {
			pr_err("Can't dump nested %s namespace for %d\n",
					nd->str, pid);//当该pid不为根节点时报错,不能备份嵌套的命名空间
			return 0;
		}
	} else
		type = NS_CRIU;

	nsid = xzalloc(sizeof(*nsid));
	if (!nsid)
		return 0;

	nsid->type = type;
	nsid->kid = kid;
	nsid->ns_populated = true;
	nsid_add(nsid, nd, ns_next_id++, pid);

found:
	if (ns_ret)
		*ns_ret = nsid;
	return nsid->id;
}