基于ReWorks的内存日志服务设计与优化
2023-02-17黄河任见佘庆,2钱晨杨帆
黄 河 任 见 佘 庆,2 钱 晨 杨 帆
1(上海华元创信软件有限公司 上海 200062) 2(华东师范大学软件工程学院 上海 200062)
0 引 言
在嵌入式实时系统[1-2]运行环境中出现故障时,仅依靠操作描述和问题现象的记录,很难定位故障点[3-4]以及分析引发异常的原因[5-7],占用实际运行环境来进行故障复现是不现实的,还可能引入新的问题,造成更加严重的后果与不良影响[8-9]。
目前,通用的日志服务[10-12]是将当前运行任务和系统所有历史记录的日志信息,保存到外部存储介质文件系统中。当系统需要诊断时,导出日志文件并打开获取所有的日志记录信息[8,13-15]。但这种方式主要有以下局限性:外部存储介质使用寿命有限;访问速度慢;频繁的读写访问对系统资源消耗严重,增大文件系统损坏的概率,使用效率低[16-17];读取所有日志记录信息,庞大而无序[18],不易区分,难以实现快速、准确的系统故障诊断与定位。对于嵌入式实时系统来说,异常故障导致看门狗复位系统随时可能发生,基于文件系统进行日志文件的读写访问被中断,将可能引起文件系统的不一致性,导致文件系统被破坏和文件数据的丢失。因此,传统的日志文件系统服务不完全适用于嵌入式实时系统的需求,嵌入式实时系统的日志服务需要具备以下特性:
(1) 独立性,不依赖过多的外设与系统资源。
(2) 实时性,日志服务须要在看门狗复位系统前,在确定的时间内将所有信息保存记录下来[4]。
(3) 信息完整性,在记录和读取解析日志信息时须进行校验,保证日志的完整有效性。
锐华[19-20](ReWorks)嵌入式实时操作系统是上海华元创信软件有限公司在国家科技重大专项课题“核心电子器件、高端通用芯片及基础软件产品”中定制发布的面向高安全工业控制领域的嵌入式实时操作系统,支持PowerPC、X86、ARM、龙芯、飞腾、C-sky等主流处理器架构,目前广泛应用于国防电子、工业控制以及轨道交通领域。飞腾[21]FT-2000A/2是国防科技大学自主研发的国产高性能通用双核微处理器,兼容ARMv8指令集,主要面向嵌入式装备和工控领域应用产品。基于锐华(ReWorks)嵌入式实时操作系统需提供可靠、有效的日志服务,能在国产硬件平台的应用程序出现错误,甚至程序崩溃系统异常时,完整记录错误出现时间、异常任务上下文[22]和异常任务栈回溯[23]等程序故障的现场信息[24],并提供方便备份导出分析的方法。
1 内存日志服务系统设计
日志服务主要包括:热重启后内容不被破坏的内存空间管理、异常与用户信息日志记录、日志查询显示和日志备份导出。ReWorks在FT2000A/2国产硬件平台上的内存日志服务总体架构如图1所示。
图1 ReWorks内存日志服务系统架构
在系统初始化时,通过日志内存管理分配日志服务所需内存空间,并设置非cache访问,保证内存读写一致性。在软复位时设置内存自刷新模式,保证在热重启的情况下该内存空间内的数据不会被破坏,为日志服务提供基础保障。
出现异常故障时,系统捕获的异常信息与用户添加的日志信息以及日志校验码将写入日志内存中,当发生严重系统错误时看门狗将复位系统,在不掉电的软重启过程中,日志信息仍保持在日志内存中。通过日志显示能够按照日志的级别与类型、起始编号与条数查询并显示内存中记录的通过校验有效的日志信息,即使在系统软重启后,仍可查询显示。日志服务为用户提供4种接口,分别是日志服务初始化、日志记录、日志查询显示和日志备份。内存日志服务工作原理设计如图2所示。
图2 日志服务工作原理设计图
日志服务初始化内容包括内存地址和日志内存数据结构,初始化之后即可使用日志记录、显示和备份功能。内存日志服务的关键基础数据结构是日志内存环形资源池。日志内存数据结构主要包括日志内存的魔数、单元大小、总空间、版本、初始化次数、重启次数等基本配置参数和日志单元环形链表,日志内存数据结构如下:
struct err_log
{
u32 magic;/*日志魔数*/
u32 payload_size;/*日志单元大小*/
u32 size;/*日志内存总大小*/
u32 os_version;/*操作系统版本*/
u32 gen_count;/*日志初始化次数*/
u32boot_count;/*系统软重启次数*/
ERR_LOG_NODE_LIST nodeList;/*日志单元链表*/
};
日志内存环形链表由日志单元总数、空闲单元号、已记录单元数、校验数和内存日志单元节点构成,日志内存环形链表数据结构如下:
typedef struct err_log_node_list
{
u32 max_node_count;/*最大节点数*/
u32 nextNode;/*空闲日志单元节点号*/
u32 nodeCount;/*已使用日志单元数*/
u32 check_sum;/*环形链表校验数*/
ERR_LOG_NODE node[1];/*日志单元节点*/
} ERR_LOG_NODE_LIST;
内存日志单元包含单元节点状态、位置、提交状态、校验数和记录日志数据存储区入口,内存日志单元数据结构如下:
typedef struct err_log_node
{
u32 status;/*当前日志单元节点状态*/
u32 position;/*日志单元节点位置*/
u32 committed;/*单元提交状态*/
u32 check_sum;/* 日志单元校验数*/
char data[1];/*日志单元记录数据入口*/
} ERR_LOG_NODE;
日志服务首先初始化内存日志数据结构的基本信息,再建立由内存日志单元构成的环形链表而形成的资源池。记录日志时,先从资源池取出一个日志单元,根据入参配置情况保存日志基础信息、异常信息、上下文信息、栈回溯信息和用户信息到日志单元的记录数据存储区中,经校验完整性后再提交到日志资源池。查询显示或备份导出内存日志时,都是通过日志数据单元遍历迭代器,从日志内存环形资源池中读取符合相应条件的日志单元中记录的日志数据。
1.1 日志服务初始化
日志服务初始化时指定内存日志地址和大小,并可选择是否保留日志内存中已有数据。初始化成功后,即可使用日志记录系统运行时的系统异常与用户自定义日志信息。日志服务初始化的软件工作流程如图3所示。
图3 日志服务初始化流程
1.2 日志记录
日志记录首先获取当前任务ID、CPU ID与日志地址;判断日志内存数据结构有效后,申请一个日志记录单元;再将获取的日志级别与类型、时间、源文件名、行号、用户自定义信息、CPU异常信息、上下文寄存器信息、任务栈回溯信息等重要的异常线程信息插入日志内存中。最后将生成当前日志记录单元的校验码和日志记录单元一并提交插入日志内存环形链表,以此为信息完整性提供支撑。日志记录的软件工作流程如图4所示。
图4 日志记录流程
1.3 日志显示
日志显示能够按照日志的级别与类型、起始编号与条数查询并显示记录的日志信息。日志显示软件流程如图5所示。
图5 日志显示软件流程
1.4 日志备份
用户可以根据日志内存容量及实际情况,将内存中的日志信息按照日志的起始编号与条数保存到文件系统中,为分析与归档系统运行故障统计提供帮助,其设计流程与日志显示类似。同时,当用户选择日志备份功能时还可以将掉电情况下的日志信息保存在硬盘、FLASH、SD卡等存储设备中,实现掉电情况下的日志保存。
2 日志服务测试与分析
2.1 日志服务功能测试分析
本文设计的锐华(ReWorks)嵌入式实时操作系统的内存日志服务,基于国产飞腾FT-2000A/2平台模块(主频1 GHz/ 2 GB DDR/ 256 GB SSD)开展日志服务功能测试。在日志服务初始化成功后,通过制造一个未定义指令异常,系统捕获到ARM体系架构的异常信息与用户添加的日志信息将写入日志内存环形缓冲区中。通过日志显示能够查询并显示内存中记录的日志信息,如图6所示。
图6 日志信息显示截图
图6中ReWorks操作系统Shell控制台的日志信息显示所示,日志管理信息包含:日志内存总大小、单条日志大小、最大记录条数、记录日志数等;日志基础信息包含:日志的等级、所属模块、启动次数、系统版本、CPU ID、日期时间、任务名和日志插入点等信息;用户记录信息包含内容与长度;异常信息描述包含:任务ID、异常号、中断嵌套层数、异常名称和错误地址;上下文信息包含相关通用寄存器和状态寄存器信息;栈回溯信息则显示的是出现问题点的任务调用函数栈回溯。通过查看日志信息,可以很快定位到故障点在任务p3的函数exception_uni偏移0xc处,并分析出现问题的原因是由于未定义指令“0xeeeeeeee”造成的,且即使在系统软重启后,仍可查询显示日志内存中的该条日志信息。
由于内存日志服务功能仅依赖CPU和内存,且在记录操作过程中不存在阻塞操作,因而能够在中断上下文中进行日志记录。用户也可以备份内存中记录的日志信息到外部存储的文件系统中,为分析与归档系统运行故障统计提供帮助。
2.2 日志服务性能分析与优化
1) 实时性优化。日志服务的记录性能主要体现在记录一条日志信息所消耗的时间。基于FT-2000A/2模块开展内存日志服务性能测试,记录各项日志信息进行性能分析。
为便于分析内存日志性能,初始化日志单元大小为4 096字节,日志管理、日志基础、异常、上下文寄存器和栈回溯信息内容确定,所占空间大小相对固定。用户信息从100字节起以100字节的步长增加到8 000字节,记录所有信息内存日志所需时间如图7所示。可看出,当记录所有日志信息大小超过日志单元大小后,记录时间仍然随着用户信息的增加而增大。该现象说明记录用户信息大小的不确定将导致日志记录时间的不确定,这将影响实时系统的确定性。
图7 内存日志记录性能图
经分析调试,发现字符串拷贝函数strlcpy接口性能函数存在不收敛情况。为保证嵌入式系统接口调用的实时性和确定性,对字符拷贝函数strlcpy与strncpy接口性能做了对比,如图8所示。
图8 往4 096字节内存拷贝不同大小内存的性能对比
图 8为针对目的地址内存4 096字节大小,分别采用strlcpy和strncpy拷贝限制4 096字节长度内存,而源地址内存大小从100字节以100字节的步长增加到6 000字节。可看出,随着拷贝源信息的增加,在限制长度4 096字节之后,strncpy拷贝时间收敛,而strlcpy呈发散趋势,拷贝时间随着源地址内容的增大而增加。对于上述现象,分析如下:
strlcpy的原型为size_t strlcpy(char *dest, const char *src, size_t size)。当size小于等于源地址src长度时,拷贝size-1个字符到目的地址dest中,并自动在字符串末尾填充结束符“ ”。但此时拷贝并未结束,将继续遍历源地址内存空间直到结束符,这就解释了为什么拷贝4 096字节之后,strlcpy的执行时间随源地址内存空间的增大,性能趋势为递增函数。
strncpy原型为char *strncpy(char *dest, const char *src, size_t size)。当size小于等于源地址长度时,目的地址结尾无结束符“ ”;当size大于源地址长度时,目的地址未拷贝的空间将会填充结束符“ ”。按字节填充“ ”的时间大于拷贝字符时间,所以4 096字节之前呈下降趋势。值得注意的是采用strncpy的方式,拷贝结束后推荐在目的字符串末尾添加结束符“ ”。
基于以上分析,日志记录中的内存拷贝目的地址空间大小相对确定,而作为用户信息的源地址空间大小是不确定的,因此,本设计最终采用strnlen+strncpy的方式,进行确定长度的字符串拷贝用户日志信息,既保证了拷贝的安全性,同时也提升了拷贝效率。
2) 可用性优化。日志记录流程如图 4所示,记录用户信息在记录异常信息、上下文寄存器信息和栈回溯信息之前。针对该程序流程进行数学模型分析:
内存日志单元大小设置为M。
y表示内存日志记录的时间。
x0表示日志管理信息和日志基础信息的字节数,{x0∈N*:Nmin≤x0≤Nmax},该类信息由系统服务格式化输出,字节数相对确定,记录时间记为T0。
x1表示用户信息的字节数,{x1∈N:0≤x1≤N1},该信息格式与大小由用户控制,因此字节数大小不确定,记录时间为k1x1,k1为用户信息记录的时间系数。
x2表示异常信息的字节数,{x2∈N:0≤x2≤N2},该类信息由处理器体系架构的异常种类决定,类型有限,字节数相对确定,记录时间记为T2。
x3表示上下文寄存器信息的字节数,{x3∈N:0≤x3≤N3},该类信息由处理器指令集的寄存器数量决定,字节数固定,记录时间记为T3。
x4表示栈回溯信息的字节数,{x4∈N:0≤x4≤N4},该类信息由线程函数调用层数和函数名称长度决定,字节数相对确定,记录时间记为T4。
(1) 当x2≠0,x3≠0,x4≠0,且x0+x1+x2+x3+x4≤M时,日志管理信息、日志基础信息、异常信息、上下文寄存器信息和栈回溯信息得以完整记录,记录时间如式(1),其中,b为日志记录时间常量。
y=T0+k1x1+T2+T3+T4+b
(1)
(2) 随着用户信息量x1的增加,x2、x3、x4将被依次挤出,对应x1的四个取值NP y=T0+k1x1+T2+T3+k4x4+b (2) (3) 当NA≤x1 y=T0+k1x1+T2+k3x3+b (3) (4) 当NB≤x1 y=T0+k1x1+k2x2+b (4) (5) 当NC≤x1时,内存中记录的x4=0,x3=0,x2=0,且x0+x1≥M时,则记录时间为式(5),其中,Ta为内存日志单元记录满的时间。 y=Ta (5) 以上分析可知,随着用户信息的增加,将可能导致异常信息、上下文寄存器信息和栈回溯信息的丢失,一旦这种情况发生,将对日志服务的可用性带来损害。因此针对这一问题进行改进:将用户信息调整到异常信息、上下文寄存器信息和栈回溯信息之后记录。 (1) 当x2≠0,x3≠0,x4≠0且x0+x1+x2+x3+x4≤M时,日志管理信息、日志基础信息、异常信息、上下文寄存器信息和栈回溯信息得以完整记录,则: y=T0+k1x1+T2+T3+T4+b (6) (2) 随着用户信息量x1的增加,当x2=N2,x3=N3,x4=N4,且x0+x1+x2+x3+x4≥M时,剩余的内存空间不足以记录全部用户信息x1,即出现用户信息丢失,则记录时间为式(7),其中,Tb为日志内存单元记录满时的记录时间。 y=Tb (7) 该情况下,异常信息、上下文寄存器信息和栈回溯信息得以完整记录,且若内存空间不足,用户信息将无法记录完整。由于用户对用户信息构成较明确,容易发现日志单元空间不足引起的信息丢失,进而可以进行调整优化。 在飞腾FT-2000A/2模块上分别针对用户信息在异常信息、上下文寄存器信息和栈回溯信息这三者之前和之后的内存日志记录时间进行测试,性能结果如图9所示。 图9 用户信息写入前后位置性能对比图 起始点为O点,用户信息在三者信息之前时(见前置1/2虚线),出现P、A、B、C四次转折点,OP段,记录的所有信息未超过内存空间,记录时间随用户信息字节数的增大而增加,趋势符合式(1);P点时,记录的所有信息等于内存空间大小;由于用户信息前置记录,PA段,栈回溯信息逐渐被挤出内存,如式(2)所描述,而记录栈回溯信息所用时间又大于记录用户信息所用时间,所以呈递减趋势;到A点时,内存中已无栈回溯信息;AB段,上下文寄存器信息逐渐被挤出内存,如式(3)所描述,同理,记录上下文寄存器信息所用时间大于记录用户信息所用时间,所以呈递减趋势;到B点时,内存中已无上下文寄存器信息;BC段,异常信息逐渐被挤出内存,如式(4)所描述,同上,记录异常信息所用时间大于记录用户信息所用时间,所以呈递减趋势;到C点时,内存中已无异常信息;C点之后记录的信息中不含异常信息、上下文寄存器信息和栈回溯信息,如式(5)所描述。因此,考虑到内存空间有限,且为了防止异常信息、上下文寄存器信息和栈回溯信息的丢失,将用户信息置于异常信息、上下文寄存器信息和栈回溯信息之后记录,更好地满足用户记录的需求。 当用户信息在三者信息之后记录时(见后置1/2虚段线),出现转折点P。随着用户信息的增加,P点之前记录时间线性增加,如式(6),P点之后记录时间不变,如式(7)。这是由于转折点P之后,超出内存大小的用户信息被挤出,记录时间不随用户信息的增加而改变。 通过分析对比测试日志记录用户信息写入前置与后置的性能图,可以进一步验证了上面公式推导的结果。因此,将用户信息写入位置放在异常信息、上下文寄存器信息和栈回溯信息这三者之后,日志服务的可用性更高。 3) 栈回溯性能分析。在飞腾FT-2000A/2模块上,测试内存日志服务记录不同函数嵌套层数下的栈回溯信息记录时间,如图 10所示。记录时间随着函数嵌套层数的增加而增大,当函数嵌套层数大于20层后,记录时间趋于平稳,这是因为任务栈回溯算法中,限制了回溯函数嵌套层数最多为20层。 图10 内存日志服务栈回溯信息性能图 日志记录项默认记录日志管理信息、日志基础信息,用户可配置信息项有源文件名+行号、用户信息、异常信息、上下文寄存器信息和栈回溯信息。分别对内存日志服务的各项记录内容进行单项记录的性能测试,并与记录到SSD硬盘文件系统的传统文件日志性能作对比。每项测试1 000次,剔除超过标准差的数值后,再取平均值,结果如表1所示。 表1 日志服务各项记录性能对比 通过分析日志服务各项单独记录结果,可以发现记录上下文寄存器和异常信息所用时间较长,约17 μs;记录文件名+行号和简单用户信息(约10个字节)所用时间较少,约10 μs;栈回溯信息与函数嵌套层数相通过关,本测试记录3层函数调用栈信息约16 μs。写文件日志与写内存日志在单项记录性能之间的比例关系也存在类似情况,但文件日志各项记录用时平均约是内存日志的15倍。记录所有信息到内存日志约32 μs,而记录所有信息到文件日志约6 231 μs,用时约为内存日志的195倍,内存日志的记录性能明显高于文件日志。 由于访问内存的性能往往都比访问外设的性能高,通过文件系统的benchmark工具IOZONE,设定测试文件大小为32 MB,一次写入/读出的块为1 MB,对内存与SSD硬盘文件读写性能进行测试对比,测试结果见图11。 图11 内存与SSD硬盘IOZONE文件读写性能对比图 测试结果得出内存文件系统写性能约270 MB/s,SSD硬盘文件系统写性能约44 MB/s,内存文件系统写性能约是SSD硬盘文件系统的6倍。由此得出写内存文件性能远高于写SSD硬盘文件性能。 日志服务的载荷性能,增加记录用户信息的长度来测试。由于每条日志空间默认为4 096字节,所以采用前面相同的日志性能测试方法,记录从100到8 000字节(步长100字节)信息的所有信息内存日志所须时间,并与记录到SSD硬盘文件系统的传统文件日志性能作对比。 通过分别记录4组内存日志用户信息和文件日志用户信息记录时间,平均数据拟合得到内存日志和文件日志记录曲线见图12。通过对比分析日志记录载荷性能数据,可以得出文件日志与内存日志在载荷性能数据趋势上存在相似情况,都是在载荷小于3 200字节时,记录用户信息长度越大,记录时间越长,在载荷大于3 200字节后,记录的时间趋于稳定。这是由于记录完日志管理信息、日志基础信息、异常信息、上下文寄存器信息和栈回溯信息后,剩余的空间只能记录部分用户信息日志,记录时间不随用户信息的增加而增加。用户察觉到用户信息有所丢失后,可以调整记录的内容或者内存日志单元的大小,以保证记录信息的完整性。从图12中数据关系也可得出内存日志记录时间约为文件日志记录时间的0.5%,进一步证实了内存日志服务的性能远优于文件系统日志服务。 图12 内存日志与文件日志记录载荷性能对比图 本文设计实现了一种基于国产锐华(ReWorks)嵌入式实时操作系统的内存日志服务,在国产FT2000A/2模块上进行功能和性能测试与分析,并针对实时性和可用性进行了优化。测试数据表明该日志服务具有高性能、高实时性、非阻塞、热重启不丢失等特性,为异常问题的分析定位、故障排查与系统运行情况跟踪统计提供基础支撑。下一步可结合嵌入式实时数据库、数据挖掘与人工智能等技术对记录的日志信息数据进行分析,为“新基建”智能制造提供有力支撑。2.3 内存日志与文件日志性能对比测试
3 结 语