引言
随着ARM处理器性能不断提升,同时又兼具低功耗、体积小的特点,在工控和高性能计算领域中以ARM处理器为核心的嵌入式平台应用得越来越广泛。鉴于工控和高性能计算领域的系统可靠性要求极高,因此如何设计高可靠性系统成为一个至关重要的课题。
提高系统可靠性的一个有效的方法是容错。检查点机制是一种典型的软件容错技术,通过设置检查点来实现进程级容错。检查点机制主要分为两种:用户态和内核态。用户态检查点具有较好的可移植性,但是用户需要修改程序源码,缺乏透明性和通用性,代表性用户态检查点有Conder和Libckpt。内核态检查点独立于用户程序,对用户完全透明,方便用户使用,代表性内核态检查点为BLCR。本文正是基于内核态检查点BLCR设计思想,在ARM平台嵌入式Linux系统上实现进程级容错系统。
1 系统结构设计
检查点通过人为或周期性地向系统发送进程保存信号来备份进程状态,生成含有进程信息的文件。当检测到进程因某种原因崩溃时,通过读取进程信息文件,将进程回转到备份时的状态,从而实现进程恢复,完成系统容错。
图1 系统结构设计图
根据本系统实际功能设计三个模块,分别是用户态的用户接口模块、进程信息存储模块,以及内核态的进程信息保存与恢复模块。系统结构设计图如图1所示。
(1) 用户接口模块
用户接口模块为用户进程的状态保存与恢复链接动态链接库提供了标准化使用接口,通过这个接口访问内核态的进程信息保存与恢复模块。
(2) 进程信息存储模块
在进程保存过程中,通过vfs_write()函数将进程信息保存成固定格式的文件。在进程恢复过程中,使进程信息保存与恢复模块可以通过vfs_read()函数访问,从而完成进程的恢复工作。
(3) 进程信息保存与恢复模块
进程信息保存与恢复模块是本系统的核心模块,由于用户态和内核态的内存地址空间不同,不能使用简单的函数调用方式,在本系统中用户态与内核态模块通过/proc文件系统进行数据交互。在进程保存过程中,该模块获取与进程有关的信息,并调用进程信息存储模块保存进程信息;而在进程恢复过程中,通过访问之前保存的含有进程信息的文件恢复进程。
在系统实现方式上采用单机和双机两种实现模式。单机模式下,进程的保存与恢复都是在同一台机器上进行的;双机模式下,在一台机器上进行进程信息的保存,在另外一台机器上保存生成的进程信息文件并恢复进程。
2 系统开发中关键技术的研究
本系统在进程保存时将进程的信息、CPU寄存器的状态、signal信息、进程的内存地址空间等信息保存到文件里。在进程恢复时,调用fork( )函数创建一个新进程,读取文件中上述进程状态,从而完成进程恢复。所保存的文件格式见图 2。
图2 进程文件格式
2.1 内核读写文件方法
由于在内核操作文件没有标准库,因此要利用内核的一些函数,在本系统中主要使用两个函数vfs_write( )和vfs_read( )构成的文件操作函数k_write( )和k_read( )。函数原型如下:
ssize_t k_write(struct file *file, const void *buf, size_t count);
参数file代表要保存进程信息的文件,参数buf代表指向要保存的缓冲区指针,参数count代表要保存的缓冲区大小。
ssize_t k_read(struct file *file, const void *buf, size_t count);
参数file代表要读取进程信息的文件,参数buf代表指向要写入的缓冲区指针,参数count代表要写入的缓冲区大小。
2.2 进程的状态保存
内核通过<asm/current.h>定义的全局项current引用当前进程。它产生一个指针指向结构tsk_struct,current指针指向在运行的进程。内核可以通过current宏来获取特定的进程信息。
① 保存进程PID,这是进程的唯一标识符,是进程创建的起点,通过current->pid获取当前进程的PID,然后保存到进程文件中。
② 保存进程的UID、GID等,采用current_cred( )函数获取进程用户名和组名。
③ 保存CPU信息,主要是寄存器和线程信息。采用task_pt_regs( )函数获得寄存器信息。通过current->thread获得线程信息。
④ 保存signal信息,共需要保存三组信息:信号堆栈信息、挂起信号和信号处理函数。采用do_sigaltstack( )函数获得信号堆栈信息,通过current->blocked获得进程挂起信号,通过current->sighand->action获得信号处理函数信息。
⑤ 保存进程内存信息,这部分比较复杂,主要的过程为采用find_vma( )函数获得进程的虚拟地址,保存进程映像(如数据段、代码段、堆区、栈区等),然后遍历内存,保存进程的内存信息。
2.3 进程的状态恢复
进程恢复主要用到了k_read( )函数,该函数在内核态下读取含有进程信息的文件,由于文件是按照特定格式存储的,因此k_read( )函数可以分段读取进程文件指定的内容。恢复的主要步骤如下:
① 系统调用fork( )函数创建一个新的进程,并进入内核态;
② 进程信息保存与恢复模块接收到进程恢复的信号后,通过k_read( )函数读取进程信息文件,根据文件中记录的线程数目创建线程,将进程相关的寄存器信息、信号信息以及内存信息填充到相应的PCB中;
③ 完成上述工作后,系统从内核态返回用户态,使进程参与任务调度,完成进程恢复。
3 进程级容错系统的实现
本系统由两个子系统构成:进程保存子系统和进程恢复子系统。由于本系统用独立的文件保存进程的各项信息,因此为了更好地测试进程文件的迁移性,采用了两套实现方法,即单机实现和双机实现。
3.1 单机实现
进程的保存与恢复在同一台机器里实现,具体流程如图3、图4所示。
图3 进程状态保存图4 进程状态恢复
在进程状态保存时,首先调用checkpoint [pid]命令,发送进程保存请求。系统响应进程保存请求后,会首先暂停进程,通过k_write( )函数将进程各项信息保存到指定的进程文件中,最后恢复进程运行,完成进程状态保存。
在进程状态恢复时,首先调用restore [char* filename]命令,发送进程恢复请求。系统响应进程恢复请求后,会调用fork( )函数创建一个新的进程,然后通过k_read( )函数读取进程文件的内容到PCB中,最后使新创建的进程参与任务调度,完成进程状态恢复。
3. 2双机实现
双机实现与单机实现的步骤基本一致,差别在进程文件保存上:单机实现时,进程文件保存在本地,而双机实现时,进程文件保存在备机中。双机实现工作原理如图5所示。一台机器为主机,另一台为备机。主机运行程序,并且隔一段时间通过网络保存进程信息文件到备机中。备机通过串口发送心跳信号,监视主机的状态,当发现主机异常后,
接管主机,在备机上读取主机保存的进程信息文件,恢复主机的进程,从而实现基于检查点的双机容错。
\
图5 双机进程容错实现方法
4 系统性能评测
本系统所采用的平台为基于CortexA8 CPU的BeagleBone Black,CPU型号为TI AM3359 (1 GHz),RAM大小为512 MB,所采用的内核版本为linux3.8.13。使用计算N×N矩阵的N次幂作为测试程序。在程序运行的过程中,先进行进程保存,然后发送KILL信号关闭程序,最后分别在单机环境和双机环境下进行进程恢复。测试结果如表1、表2所列。
表1 单机进程容错测试结果
表2 双机进程容错测试结果
由表1、表2可知,随着数据量和运算量的增加,进程保存与恢复的耗时以及进程文件大小相应地增加,但是进程保存与恢复耗时与程序运行时间的百分比却明显下降,这充分地说明运算量大且实时性较高的程序上运用该进程级容错系统的优势更为明显。从双机测试的结果看,该进程文件具有较好的迁移性,因此,该进程级容错系统可以运用到基于嵌入式平台的分布式计算系统中。
结语
本文中所设计的进程级容错系统属于系统可靠性领域的应用。该系统对用户操作具有透明性,方便编程,并且所保存的进程文件具有较好的迁移性,因此在工控领域以及基于嵌入式平台的分布式计算领域有着良好的应用前景。由于设置检查点的频率会影响系统性能,因此本文的下一步工作将研究检查点的频率对系统性能的影响,在不降低系统可靠性的前提下,尽可能提高系统性能。