Simics在测试中的有效使用
作者:Jérôme Lambourg, AdaCore高级软件工程师
正如我们在以前的博客文章那AdaCore严重依赖虚拟化来执行其测试小昆虫专业产品VxWorks.。
这涉及每天运行的大约350,000个测试,其中包括60,000个运行的测试风河模拟。典型测试涉及以下步骤:
- 编译一个示例,使用测试中定义的开关和源;
- 启动一个仿真器,在上面传输编译过的模块或RTP,可能会有一些支持文件;
- 运行模块或rtp;
- 检索输出,可能使用在目标上创建的文件;
- 退出模拟器。
与在本地平台上运行的等效测试相比,这些步骤呈现出两个挑战:
- 可行性:我们需要添加适当的支持来启用这些步骤,特别是在主机和模拟目标之间的文件传输,并可靠地这样做。
- 效率:运行仿真器并来回传输文件可以导致大量的开销,与时间约束不兼容(24h最大测试时间)。因此需要一些增强的技术来加速这些关键步骤。
仪表概述
为了完成此测试,我们需要在主机侧(例如,在模拟器上)和访客本身(VxWorks内核)上的仪器。要求是:
- 能够在访客和主机之间来回传输文件;
- 能够检索输出;
- 在目标上自动执行测试场景。
文件传输
为了支持文件从主机传输到主机,我们使用了Simics的Simics Builder模块。这允许我们创建一个专用的外设,我们可以用它交互,在主机和目标上的RAM驱动器之间传输文件。
我们选择了这种解决方案,因为这可以非常有效(而不是使用模拟网络传输文件),同时为我们提供各种测试的非常高的灵活性。
模拟外设采用一小组寄存器和缓冲器的形式。作为SIMICS插件的这种外围设备的实现非常简单。但是,必须注意:
- 各个寄存器的大小(根据目标系统的32位或64位)
- 这些寄存器的字节序
要来回复制文件,设备响应Syscalls,可提供以下功能:
- 开放的,
- 读
- 写
- 关闭
- 拆开
- LSEEK.
- 关掉
设备的第一个寄存器上的任何写操作都会触发SYSCALL。
其他3个寄存器包含参数,它们的预期内容取决于特定的系统调用。
下面是模拟设备的系统调用实现,以Simics插件实现为例:
静态REG_TYPE do_syscall(hostfs_device_t *hfs)
{
REG_TYPE ID = HFS-> REGS [SYSCALL_ID] .VALUE;
REG_TYPE arg1 = hfs->regs[arg1].value;
REG_TYPE arg2 = hfs->regs[arg2].value;
REG_TYPE arg3 = hfs->regs[arg3].value;
REG_TYPE ret = 0;
char *host_buf = NULL;
REG_TYPE guest_buf;
REG_TYPE len;
开关(ID)
{
案例SYSCALL_OPEN:
guest_buf = arg1;
len = 1024;/* XXX:文件名字符串的最大长度*/
最长= open_flags(最长);
host_buf = malloc(len);
/*将来宾缓冲区转换为主机缓冲区*/
-1, (uint8_t *)host_buf (uint8_t *);
RET =打开(host_buf,arg2,arg3);
免费(host_buf);
返回受潮湿腐烂;
休息;
在VxWorks侧,此调用在内核中实现:
PHYS_ADDR _to_physical_addr(VIRT_ADDR virtualAddr) {
phys_addr probalyAddr;
vmtranslate(null,VirtualAddr和PromicyAddr);
返回physicalAddr;
}
#define to_phy(addr)_to_physical_addr((virt_addr)addr)
静态UINTPTR_T.
HFS_GENERIC(UINTPTR_T SYSCALL_ID,
Uintptr_t arg1,
uintptr_t arg2,
UIntptr_t arg3)
{
Uintptr_t *hostfs_register = (Uintptr_t *)hostfs_addr();
if(hostfs_register == 0)return -1;
hostfs_register [1] = arg1;
hostfs_register [2] = arg2;
hostfs_register[3] =长度;
/*写入syscall_id以启动系统调用*/
hostfs_register [0] = syscall_id;
返回hostfs_register [1];
}
UINT32_T HFS_OPEN(const char * pathname,uint32_t标志,uint32_t模式)
{
Virt_addr TMP = ASHEAS_PHYSICAL((VIST_ADDR)路径名,strlen(pathname)+ 1);
Virt_addr Buf;
if (tmp != (VIRT_ADDR)NULL) {
Memcpy ((void*)tmp, pathname, strlen(pathname) + 1);
buf = tmp;
} 别的 {
buf = (VIRT_ADDR)路径名;
}
return h_generic (uintptr_t) TO_PHY(buf), flags,
模式);
}
绩效考虑因素
在一个典型的服务器上,我们的目标是在45分钟内运行大约6000个这样的测试,这意味着大约每秒运行2个测试。
为了实现这一目标,首先要做的是最大化测试的执行的并行性:每个测试通常可以彼此独立地运行,并且通常需要单个核心。这意味着在具有16个核心的服务器上,我们应该能够并行执行16个测试。这也意味着在全球45分钟内实现6000个测试的目标,在这种服务器上,每个测试应瞄准执行时间少于8秒的执行时间(8 * 6000/16 = 3000秒的TestSuite的总执行时间,所以50分钟)。
我们的第一次Simics实验离这个目标还很远:根据模拟平台的不同,启动VxWorks可能需要15秒到将近1分钟的时间。当尝试并行运行几个Simics实例时,数量会变得更糟,因为启动模拟需要大量服务器资源。
从我们所看到的来看,这是由于Simics的高度可配置和可编写脚本的特性,其中完整的模拟环境是在启动时构建的。这样的时间与我们的目标总执行时间是不兼容的。
为了解决这个问题,Simics工程师将我们指向一个非常好的功能:Simics检查点。基本上,它是一种机制,使我们能够拯救模拟模拟的状态并恢复它。
恢复的速度很快。
所以我们现在在构建VxWorks内核时所做的事情是在vxworks内核刚刚引导的地方创建一个通用的Simics检查点。在Simics脚本中,我们使用这看起来像:
脚本 - 分支{
本地$con = $system.console
反对美元。wait-for-string“Run_shell”
停止
写入配置“CheckPoint”-Z
退出
}
就是这样。在我们的测试中加载检查点:
读取配置“检查点”
以恢复已经启动的VxWorks模拟。
该机制预先阐述了模拟环境,并大大减少了服务器上的负载和总启动时间。
结论
通过此测试环境,我们可以成功和有效地测试VxWorks的编译器。作为示例,ACATS测试套件的完整运行,包含〜3700测试(ACATS TestSuite是ADA语言的标准测试套件)在快速的Linux服务器上大约需要31分钟,这符合每次2次测试的目标性能第二。
通过使用SIMICS,adacore现在可以在引入其GNAT Pro产品的新VxWorks目标时,快速建立高效的质量保证基础设施,从而提高上市时间和产品的整体质量。