CRIU解析
CRIU1.CRIU定义CRIU,全称checkpoint/restore in userspace,是运行在Linux操作系统上的一个软件工具,在用户空间实现checkpoint/restore功能。使用CRIU可以冻结一个正在运行的程序,并且checkpoint到程序关联的一系列文件,然后用这些文件在任何主机上重新恢复这个程序被冻结时的那个点,换句话说,也就是对正在运行程序环境的一个备份与恢复
CRIU
1.CRIU定义
CRIU,全称checkpoint/restore in userspace,是运行在Linux操作系统上的一个软件工具,在用户空间实现checkpoint/restore功能。
使用CRIU可以冻结一个正在运行的程序,并且checkpoint到程序关联的一系列文件,然后用这些文件在任何主机上重新恢复这个程序被冻结时的那个点,换句话说,也就是对正在运行程序环境的一个备份与恢复。
官方网址:https://criu.org/Docker
源码地址:https://github.com/checkpoint-restore/criu
有阅读价值的文章:https://lwn.net/Articles/525675
2.CRIU高级特性
2.1热迁移(live migration)
CRIU的主要功能——实现程序的热迁移。
热迁移,又叫动态迁移、实时迁移,即对程序或者虚拟机的保存/恢复。通常是将整个虚拟机的运行状态完整的保存下来,同时可以快速的恢复到原有硬件平台或者是不同的硬件平台上。整个过程用户不会察觉到程序/虚拟机的变化。
实施热迁移的原理就是在用户态或者内核态层面实现对进程的checkpoint/restore。
2.2代码动态注入(parasite code injection)
代码动态注入——针对正在运行的进程,在不杀死进程的情况下,利用代码动态注入技术获取当前进程的相关信息。
代码动态注入已经封装成一个独立的库libcompel。
2.3 TCP sockets checkpoint-restore
这个功能就是有能力在不破坏一个TCP链接的情况下,备份和恢复它的状态。
可以通过它封装的libsoccr库单独使用。
3.CRIU实现原理
CRIU的功能实现原理基本分为两个过程,checkpoint和restore。
3.1 checkpoint
Checkpoint概述:(ptrace机制)特殊代码—动态注入—收集—存储
在描述checkpoint过程时,将criu进程称为dumper进程;将要备份的进程称为dumpee进程。
checkpoint过程,CRIU主要通过ptrace机制把一段特殊代码动态注入到dumpee进程(待备份的程序进程)并运行,这段代码实现了收集dumpee进程的所有上下文信息,然后CRIU把这些上下文信息按功能分类存储为一个个的镜像文件(镜像文件描述:https://glog.csdn.net/weixin_38669561/article/details/105723868)。
checkpoint过程基本依赖ptrace机制,严重依赖**/proc/**文件系统,从/proc收集的信息包括:
- 文件描述信息(/proc/pid/fd/和/proc/pid/fdinfo/)
- 管道参数信息
- 内存表(/proc/pid/maps/和/proc/pid/map_files/)
checkpoint过程中criu工作步骤: - 收集并且冻结dumpee的进程树
- 收集dumpee的资源并保存(代码动态注入,产生镜像文件)
- 清理dumpee(清理被注入的代码以及恢复dumpee的地址空间)
3.2 restore
Restore概述:解析—恢复—运行
Restore过程,CRIU解析checkpoint过程产生的镜像文件,以此来恢复备份前的状态,让程序从备份前的状态继续运行。
Restore过程中criu工作步骤:
- 处理共享资源
- 生成进程树(调用fork()函数一次或多次重新创建所需进程)
- 恢复基本的资源信息
- 切换到dumpee的上下文
3.3 ptrace机制
ptrace系统函数提供了一种使父进程得以监视和控制其他进程的方式,还能改变子进程中的寄存器和内核映像,因而可以实现断点调试和系统调用的跟踪。
(1)ptrace功能:
- 可以在用户层拦截和修改系统调用(sys call)
- 设置断点,插入代码到一个正在运行的程序中
- 潜入到机器内部,偷窥和篡改进程的寄存器和数据段
(2)ptrace工作原理:
在执行系统调用之前,内核会检查当前进程是否处于被“跟踪traced”的状态。如果是,内核暂停当前进程并把控制权交给跟踪进程,使跟踪进程得以察看或者修改被跟踪进程的寄存器。
(3)ptrace用法:
注: tracee被追踪者,被监控的进程
tracer追踪者
ptrace(enum_ptrace_request request,pid_t pid,void *addr,void *data)
第一个参数是ptrace的行为参数,CRIU中有用到
PTRACE_SEIZE:建立追踪关系,不会马上暂停tracee
PTRACE_ATTACH:建立追踪关系,会马上暂停tracee
4.CRIU源码分析
4.1 CRIU源码目录分析
- compel文件夹:
寄生代码注入
目前唯一方式是通过libcompel.a将寄生代码注入到受害者进程中 - criu文件夹:
用来执行checkpoint和restore过程的命令管理工具。
例如:
criu dump -D checkpointimg -t 1234
criu restore -d -D checkpointimg - crit文件夹:
用来管理checkpoint存储的镜像文件
decode:将二进制文件转换成JSON格式
encode:将JSON格式转换成二进制文件
info:展示镜像信息
show:将二进制镜像文件转换成可读性的JSON文件格式
4.2支持系统平台分析
- x86:主流x86架构(Intel、AMD),兼容i386
- arm:细分armv6/armv7/armv8指令集,向下兼容
- aarch64:arm架构额64位系统(基于armv8指令集的64位架构)
- ppc64:IBM power系列架构
- s390:IBM System z系列大型机硬件平台
- mips:龙芯mips架构,根据浪潮云对龙芯平台的需求开发
4.3 Parasitic Code
Parasitic code被冠名以“compel”,寄生代码组成:
- #include <compel/plugins/std.h>
- int parasite_trap_cmd(int cmd, void *args); //gets called by compel_run_in_thread()
- int parasite_daemon_cmd(int cmd, void *arg); // gets called by compel_rpc_call() and compel_rpc_call_sync()
- void parasite_cleanup(void); //gets called on parasite unload by compel_cure()
4.4 Infecting Code
The process of Parasitic code infecting the victim:
- stop the task: int compel_stop_task(int pid);
int compel_interrupt_task(int pid)
ptrace(PTRACE_SEIZE,pid,NULL,0)
int compel_wait_task(int pid,int ppid,…,void *data):
注释:
这个函数捕获任务,将其放入一个特殊状态(这种状态下我们可以通过ptrace接口任意操纵任务),最后将任务从trace分离开,而且任务并不知道自己已经被绑定了一段其他程序。
- prepare infection handler: *struct parasite_ctl compel_prepare(int pid);
- execute system call: *int compel_syscall(ctl, int syscall_nr, long ret, int arg …);
- infect victim: int compel_infect(ctl, nr_thread, size_of_args_area);
- cure the victim: int compel_cure(ctl); //ctl pointer is freed by this call
- Resume victim: int compel_resume_task(pid, orig_state, state);
Test文件中do_feaction过程:
5.使用C/R功能的方式
CLI、RPC和C API
6.其他常见的热迁移工具
用户态热迁移工具:CRIU(从功能完善性、api支持和发展趋势更具优势)、BLCR、DMTCP
内核态热迁移工具:openvz
参考网址:https://blog.csdn.net/weixin_38669561/article/details/105864319
备注:
(1)用户态与内核态学习
内核态与用户态是操作系统的两种运行级别,内核态,即控制计算机的硬件资源,并提供上层应用程序运行的环境;用户态,即上层应用程序的活动空间,应用程序的执行必须依托于内核提供的资源。
intel cpu提供Ring0-Ring3四种级别的运行模式,Ring0级别最高,Ring3级别最低。Ring0留给操作系统代码,设备驱动程序代码使用,他们工作在系统内核态;Ring3给普通用户程序使用,工作在用户态。
运行于处理器内核态的代码不受任何的限制,可以自由的访问任何有效地址,进行直接端口访问。而运行于用户态的代码则要受到处理器的诸多检查,他们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,且只能对任务状态段中规定的可访问端口进行直接访问。
用户态向内核态的切换方式:系统调用(软中断)、外围设备的中断(硬中断)、异常事件
6.1 CRIU
criu通常被用在程序或者容器的热迁移、快照、远程调试等。
6.2 BLCR
BLCR(全称Berkeley Lab Checkpoint/Restart)提供了用户态的libcr库和kernel module来完成相关的checkpoint/restart工作。
两种方式使用BLCR进行checkpoint:
- 进程通过cr_run启动
- 进程在编译时链接libcr库
这俩种方法功能一样,都是让程序在运行前优先加载某个动态链接库(libcr.so或者libcr-run.so),这两个动态库的主要功能就是提前向kernel注册一个信号。BLCR通过在kernel中的cr_module模块向要被checkpoint的进程发送这个信号,来通知进程对自己做checkpoint,真正的checkpoint工作由内核完成。C/R过程由如下两个命令完成: - checkpoint过程:cr_checkpoint PID值
- restore过程:cr_restart context.PID值
官网链接:
https://crd.lbl.gov/departments/computer-science/class/research/past-projects/BLCR/
备注:
从官网的信息看,貌似BLCR好像已经停止维护和更新,现在网络上好多人还用此工具进行Android应用的快速启动。
6.3 DMTCP
DMTCP(全称Distributed MultiThreaded CheckPointing)以library库的形式实现了对一个进程的checkpoint/restore。这就意味着当使用DMTCP对一个程序热迁移,就要求这个程序之前必须是用DMTCP启动的,这样才能保证DMTCP Library动态库被链接到这个程序。
当使用DMTCP做热迁移时,DMTCP库会截获应用程序中的一定数量的库的调用(代理),构建一个有关进程内部信息的影子数据库,然后将请求转发给glibc/kernel,收集的信息将用于生成应用程序的镜像文件。
DMTCP进行checkpoint的局限性:
(1)这种方法只能作用在checkpoint与DMTCP库一起运行成功的应用程序,但DMTCP不为所有的内核API提供代理,即不支持调用所有的内核API(如inotify()不受支持);
(2)可能因为请求代理而产生潜在的性能问题。
DMTCP进行restoration的局限性:
(1)checkpoint期间,getid()可用于检索进程的PID,但在还原期间没有相应的API来设置进程的PID(fork()系统调用不允许调用制定子进程的PID),DMTCP通过拦截getpid()的库调用并向应用程序提供假的PID值给进程。
危险性:如果应用程序试图通过这个假的PID访问/proc文件系统中的文件,则可能会看到错误的文件。
备注:CRIU为解决这个问题,开发人员添加了一个API,可以用来控制fork()调用选择的PID。
DMTCP源码地址:https://github.com/dmtcp/dmtcp
6.4 OPENVZ
CRIU起初是Virtuozzo的一个项目,随着开源社区的帮助,现在也被整合到OPENVZ(是Virtuozoo的开源版本)、LXC/LXD、Docker、Podman等软件项目中。
6.5对比分析
对比分析可知,在用户态完成checkpoint/restore功能的工具中,criu是功能最全的,而且支持目前云计算领域最火的docker容器checkpoint/restore,而且criu的社区也是很活跃的。
7.申威CRIU迁移难点
8.龙芯CRIU成果
(1)历时4个月,添加和修改代码7655行,其中汇编代码200行
(2)代码工作:mips架构、链接脚本的编写、符号表重定位、进行状态信息的存储、汇编语言的编写、调试
(3)难点:动态编译与链接、符号表重定位、进程状态信息的存储、汇编语言和调试过程
符号表重定位:
[1] 参考链接:https://www.cnblogs.com/malc/p/5629610.html
[2] 程序的编译过程:预编译、编译、汇编、链接。
[3] 符号表重定位发生在链接的过程中
[4] 符号表重定位发生例子:
funtion1.cpp和function2.cpp有各自的符号表,符号表存放函数变量的地址,function2.cpp中调用了function1.cpp的方法test1和变量,编译function1时给test分配0x100地址,编译function2时由于搜索不到在function2中test方法声明,先分配0x00地址。最后在链接阶段,链接器查找所有的目标代码文件,匹配到test在function1中正确的内存地址,function2生成的目标代码文件更新自己的符号表test的内存地址0x100,链接阶段重定位完成。
调试:
[1]将追踪进程绑定一个固定处理器(独占CPU0),然后使用ejtag调试
[2]Restore阶段,启用kernel的print-fatal-signals参数,依赖串口信息继续调试
[3]在汇编指令里插入break/loop指令辅助定位问题
(4)参考书籍:
动态编译与链接,《程序员的自我修养–链接、装载与库》
MIPS汇编语言,《See MIPS Run Linux–mips体系结构剖析》、申威MIPS指令手册
调试,《深入Linux内核架构》只能使用ejtag调试,gdb与打印输出无效。在进行到criu restore阶段,启用kernel的print-fatal-signals参数依赖串口信息调试。
9.X86平台上Docker热迁移
注意点:
CRIU要求很严格,跨服务器的迁移需要Docker版本、Linux内核、CRIU版本一致,否则会报缺少文件的错误。
单节点容器热迁移网址链接:https://blog.8086k.cn/archives/40/
跨节点容器热迁移网址链接:https://blog.8086k.cn/archives/41/
10.CRIU测试
备注:
[1] criu程序需要先编译,在criu目录下执行make
11.1 原生态docker(runc)的CRIU测试二进制程序
- [1] Hello.c程序
- [2]使用gcc -o hello hello.c命令,生成hello的二进制文件
- [3]开启两个窗口:
一个窗口运行hello二进制
./hello
一个窗口运行criu程序
mkdir ch #新建ch文件
./criu/criu/criu dump -tpidof hello
-D ch/ --shell-job #checkpoint程序
或time ./criu/criu/criu dump -tpidof hello
-D ch/ --shell-job #查看用时
./criu/criu/criu restore -D ch/ --shell-job #restore程序
或time ./criu/criu/criu restore -D ch/ --shell-job #查看用时
10.2 kata(kata-runtime的)CRIU测试二进制程序
更多推荐
所有评论(0)