现在的位置: 首页 > 综合 > 正文

BLCR(Berkeley Lab Checkpoint/Restart)介绍及Checkpoint架构剖析(二)

2018年04月17日 ⁄ 综合 ⁄ 共 3897字 ⁄ 字号 评论关闭

前一篇日志从Checkpoint的角度分析了BLCR的软件架构,没有写最核心的做dump操作的那部分代码,这部分代码完全是在内核态运行的,涉及到进程的各种状态,包括进程的PID/PGID,虚拟内存映射,打开的文件,寄存器的状态,credentials,timers,信号状态等等。

要提一下的是昨天淘宝内核组的炳天大神给我看了一篇LWN上的文章,http://lwn.net/Articles/525675/,这个东西叫CRIU(CheckPoint/Restart in user space),是在用户态实现进程的Checkpoint/Restart,虽然是用户态的CR,但这种事情没有kernel的配合肯定是做不来的,我没有仔细去研究CRIU的实现机制,只是看了下简单的介绍,貌似是添加了一些相关的系统调用,否则这种事情单靠用户态程序肯定是办不到的,比方说PID做Checkpoint的时候有getpid()这样式syscall,可以得到进程的pid然后保存在文件里面就可以,但Restart的时候可没有setpid()这样的syscall可以用,当然CRIU和kernel进行通信也用到了/proc文件系统,相比之下BLCR和kernel通信完全只用了/proc文件系统,之后就把CR所有的工作都扔给内核去做了,在kernel中进行Checkpoint最核心的函数是cr_dump_self(),也就是进程通过ioctl向kernel发送CR_OP_HAND_CHKPT请求之后kernel对应的处理函数,接下来仔细讨论下这个函数。

对应于进程的每一个子进程或者线程都会执行这个cr_dump_self(),也就是每个task_struct对于一个cr_dump_self(),对于共享mm_struct的线程而言,这些函数只有其中一个dump mm即可,其它的只是保存一下进程的寄存器状态便可以了。

首先要获取将要dump到的文件的file struct

dest_filp = cr_loc_get(&req->dest, &shared);
if (IS_ERR(dest_filp)) {
	return PTR_ERR(dest_filp);
}

关于这个shared标志,意思就是多个进程是否共享这一个目标文件,cr_checkpoint提供了一个-d参数用来指定dump出来的文件可以放在这个目录下面,并且对每个进程都单独创建一个文件,这种时候shared就不是共享的了,每个进程对应的目标文件都是相互独立的,也就无须进行同步了,同时我也发现这个-d参数虽然有,但却标志着unimplemented,也就是说我前面说的这几句话都是废话,这个功能BLCR虽然打算提供但尚未实现,默认只能将所有进程的信息都dump到同一个文件中,shared标志为1,这时候在完成初始化之后在dump进程数据的时候就需要加锁了。

接下来BLCR把除SIGKILL之外的所有信号都block了(包括SIGSTOP),否则来个什么信号进程上下文又变了,那我们Checkpoint的数据就不可靠了。

初始化工作,给这个dump文件写一个file header,这个头写一个就够了,所有这些进程都去抢req->serial_mutex这个锁,谁抢到谁干这个事情:

down(&req->serial_mutex);
if (!test_and_set_bit(0, &req->done_header)) {
        result = cr_save_file_header(req, dest_filp);
        if (result < 0) {
		req->result = result;
        }
}
up(&req->serial_mutex);

至于这个文件的内容就没什么好说的了,几个标志。

接下来做的工作是把共享同一个mm_struct的进程同步,保证大家在dump前停在同一个位置,这个同步就是用到了proc_req里面的barrier变量来完成的,涉及到kernel函数就是wait_event_interruptible()

// Synchronize to ensure all tasks in the current process have stopped running.
once = cr_signal_predump_barrier(cr_task, /* block= */ 1);
if (once < 0) {
	goto cleanup_unlocked;
}

第一个wakeup的进程once返回1,这个返回值在暂停itimers的时候会用到,也是为了保证只有一个人把itimers暂停就可以了,谁先醒来谁就去暂停一下,其它人就不要动了。

// One task pauses the itimers
if (once) {
	cr_pause_itimers(proc_req->itimers);
	// vmadump_barrier ensures this is written before reading
}

接下来根据刚才提到的dump file的shared标志加锁,进入cr_do_dump()这个函数来dump进程的信息,几个共享mm_struct还是会去抢一个锁,谁先抢到谁就会成为这组进程的leader,只有leader进程才会去保存mm_struct相关的一些信息,其它的进程只是保存一下各自的寄存器信息就可以了。

接下来针对于这个proc_req(一个proc_req对应一个mm_struct)也可保存一个header,这个header主要内容是一共有几个线程在用这个mm_struct,线程的clone_flags是多少,也是几个task去抢一把锁,谁先抢到谁写这个东东。

接下来保存process linkage信息,进程的pgid,pgrp,tgid,sessionid等等这些信息。完了后进入cr_freeze_threads()这个函数,这个函数做很多dump工作,主要工作其实都是刚才选出来的那个leader来做的,其它不是leader的进程只是保存了下寄存器信息,leader做如下工作,下面就涉及到非常多的细节了,每一个都可以拿出来写一篇文章,有些太过于细节的东西我也没仔细看,就简单介绍一下:

1. 写一个头信息:

    static struct vmadump_header header ={VMAD_MAGIC, VMAD_FMT_VERS, VMAD_ARCH,
					  (LINUX_VERSION_CODE >> 16) & 0xFF,
					  (LINUX_VERSION_CODE >> 8) & 0xFF,
					  LINUX_VERSION_CODE & 0xFF };

2. 保存PID

3. 保存Credentials, cr_save_creds()

4. 保存cpu状态信息, vmadump_store_cpu()

5. 保存signal信息,所有的进程都会把sigblock和sigpending信息dump出来,但只有leader进程会把sigaction给dump出来。

6. 保存current->clear_child_id, current->personality

7. 保存memory信息,这部分信息是只有leader进程才会去保存的,vmadump这块是比较复杂的,主要是遍历mm_struct的vma,BLCR可以控制哪里vma可以被dump出来,cr_checkpoint提供了如下的参数:

Options to save optional portions of memory:
–save-exe
save the executable file.

–save-private
save private mapped files. (executables and libraries are mapped this way)

–save-shared
save shared mapped files. (System V IPC is mapped this way).

–save-all
save all of the above.

–save-none
save none of the above (the default).

首先非0匿名页是肯定需要保存的,其它的像可执行文件,mmap进来的库文件,和mmap的共享文件都是可以根据用户指定的checkpoint选项来dump的。

从cr_freeze_threads()出来后继续在cr_do_vmadump()这个函数往下跑,接下来做如下几件事情:

1. 保存fs信息(cr_save_fs_struct()),主要保存umask,root path和current path.

2. 保存mmap() table (cr_save_mmaps_maps())

3. 保存itimers() (cr_save_itimers())

4. 保存mmaped() pages (cr_save_mmaps_data())

5. 保存打开的文件 (cr_save_all_files())

以上每一步都可以展开写很多东西,细节太多了,也鉴于我本人水平有限,有些东西也可能理解的不对就暂时不往细里写了,有时间我把vmadump这块仔细整理下。

跑到这里对于某一个proc_req的dump基本就完了,再退回到上层函数写一个tailer就OK了,然后把停掉的itimers打开,再把block的信号给恢复过来就完成了。

可以说整个这个过程需要对内核有非常深入的理解才能完成,我这种小白只是拿过来别人的代码学习一下,也确实通过这个东西对kernel多了很多理解,过段时间有空了把Restart过程写一下,相比于Checkpoint,Restart会有很多技巧:)

抱歉!评论已关闭.