龙进

Longjin@dragonos.org

本文基于DragonOS主线 dcf232f3 版本进行讲解。

1.  开发动机

              当初在开发的时候,发现DragonOS存在一些内存泄漏的问题,但是不清楚到底哪里产生了泄漏,也不清楚内核的内存分配过程。为了定位内存泄漏的问题,以及观测一些可能存在的性能问题,就实现了这个MMLog的组件,把每一次内存分配和释放都打到日志里面去,同时希望能在Linux下面启动一个监视器,去监控DragonOS虚拟机内的内存分配情况。

1.1.  为什么不在DragonOS里面直接输出?

              主要有两个方面的考量:

  • 性能原因:直接输出到屏幕,非常的慢,严重影响性能。如果直接从串口输出,也是一样的,太慢了,还会和正常的日志混杂在一起。
  • 可观测性:因为内存管理太基础了,如果内存管理模块本身出了问题,难以打印日志到屏幕上。

2.  原理

2.1.  整体架构

内存分配日志机制的整体架构如上图所示。内核里面有一个无锁的MPMC(多生产者多消费者)环形缓冲区,每次产生内存分配、释放的时候,就会按照固定的格式把二进制日志输出进去。

同时,QEMU启动的时候,设置其Memory backend为宿主机上的一个共享内存文件,使得能够在宿主机读取到DragonOS虚拟机的内存。

接着在Linux下运行一个日志监视器,这个监视器的worker线程会不断地扫描DragonOS内的那个环形缓冲区,不断地提取新的日志,加入日志集合。同时监视器的主线程负责把日志集合内的日志打印输出到文件。

2.2.  日志监视器如何找到这个环形缓冲区?

如上图所示,内核内存日志缓冲区的名称固定为”__MM_ALLOCATOR_LOG_CHANNEL”。接着如下图所示,在日志监视器启动的时候,会加载内核ELF文件,寻找这个symbol,接着计算偏移量,就能知道CHANNEL在内存文件中的哪个位置了。

2.3.  怎么收集日志?

由于监视器不需要与DragonOS内核进行直接交互,那么我们会面临以下问题:

  1. 需要规定统一的日志格式。
  2. 监视器需要确定日志的顺序。
  3. 不能确定环形缓冲区的头部和尾部。由于我们没法加锁,同时这里也不允许直接修改DragonOS内存中的缓冲区,否则会造成错误。
  4. 观察到错误的日志数据:观察到的日志,可能是未完全写入的,也可能是在队列槽位recycle的时候未完全清空的。

第一个问题,我们设计了一个日志结构体,格式固定,监视器只需要按照这个格式去解析数据即可。注意所有的数据都要是#[repr(C)]的,这样采用固定的大小。

第二个问题,解决方案就是在日志头部加上id的字段。

第三个问题,这里采用的是一种“冗余计算”的方法:两个工作线程不停的循环扫描整个队列,发现新的日志,就把他加入到LogSet中。这样只要宿主机的工作线程足够快,那么就不存在漏日志的情况。

第四个问题,我们引入checksum。在扫描的时候,对于每次扫描的记录,我们都为其计算crc64,同时与日志记录内的checksum字段作比较,二者一致才认为这条日志是可信的。

解决了上面的四个问题,就能正确获取到所有的内存分配、释放日志了。

3.  使用

流程非常简单,首先编译DragonOS,再启动日志监视器。然后再运行DragonOS即可。

3.1.  编译:

3.2.  启动日志监视器

启动后应当会输出以下信息,提示“无法加载内存文件”,这是正常的,因为DragonOS此时尚未启动,监视器正在等待DragonOS启动。

3.3.  启动DragonOS

输入make qemu或者make qemu-vnc来启动DragonOS

DragonOS启动后,我们将会看到以下的信息,显示每秒的日志产生速率。

3.4.  查看日志文件

在logs文件夹下,能够看到内存日志:

转载请注明来源:https://www.longjin666.cn/?p=1812

你也可能喜欢

发表评论