前沿
这个是华科研究生课程计算机系统设计
一个实验,做完整个实验大约花了两天多的时间吧,其中也是遇到了很多问题,想在博客中记录下来,日后若是能够帮助师弟师妹们或者其他的博友,便不负这篇博客的作用.
这是我整个结构的目录树,如果你在接下来阅读过程中,存在任何路径上的问题,请仔细阅读这四个目录的位置,这四个目录是同一级目录.
+-- nvmain
|
+-- gem5
|
+-- fs-image
|
+-- benchmark
NVMain环境搭建
下载NVMain
下载nvmain
,并解压,将解压后目录的名字更改为nvmain
wget https://bitbucket.org/mrp5060/nvmain/get/9c0e87b164bc.zip -O nvmain.zip
unzip nvmain.zip
mv mrp5060-nvmain-9c0e87b164bc nvmain
NVMain
编译
- 安装编译工具
scons
,scons
是一个开放源码、以Python语言编码的自动化构建工具,可用来替代make编写复杂的makefile。并且scons是跨平台的,只要scons脚本写的好,可以在Linux和Windows下随意编译。
sudo apt-get install scons
- 编译
NVMain
,其中编译的类型可以选择fast|debug|prof
三种的任意一种,这里我们选用fast
scons --build-type=fast
如果在编译的过程中,报了如下的错误,无须紧张,这只是个小问题,是因为我们暂时还没有用到gem5
,所以只需将这个报错的那一行代码注释即可.
scons: Reading SConscript files ...
ImportError: No module named gem5_scons:
File "/home/greek/master/experiment/nvmain/SConstruct", line 253:
SConscript(joinpath(root, 'SConscript'), variant_dir=build_dir)
File "/usr/lib/scons/SCons/Script/SConscript.py", line 614:
return method(*args, **kw)
File "/usr/lib/scons/SCons/Script/SConscript.py", line 551:
return _SConscript(self.fs, *files, **subst_kw)
File "/usr/lib/scons/SCons/Script/SConscript.py", line 256:
call_stack[-1].globals)
File "/home/greek/master/experiment/nvmain/build/SConscript", line 36:
from gem5_scons import Transform
具体的解决办法是将nvmain/build/SConscript
中的第36
行注释
# from gem5_scons import Transform # 这行注释
- 解决错误之后,再重新编译
NVMain
测试
- 指定配置文件,指定测试文件,周期, 命令的格式为:
./nvmain CONFIG_FILE TRACE_FILE [Cycles [PARAM=value]]
这里我们使用的测试命令如下
./nvmain.fast Config/PCM_ISSCC_2012_4GB.config Tests/Traces/hello_world.nvt 1000000
Gem5
环境搭建
下载Gem5
- 使用常用工具
git
, 但是仓库在google
中,如果不能翻墙,可能无法下载
git clone https://gem5.googlesource.com/public/gem5
- [推荐使用] 使用工具
hg
.Mercurial
是一款非常优秀的分布式版本控制系统(DCVS),具有高效率、跨平台、可扩展、使用简便且开源等优点,是目前最为流行的版本控制工具之一.
sudo apt-get install mercurial
hg clone http://repo.gem5.org/gem5
编译Gem5
- 安装依赖库
protobuf-compiler
sudo apt-get install protobuf-compiler
- 编译
Gem5
,这里可以选择不同的架构,比如X86|ARM|ALPHA
, 这里我们选择使用X86
(我的电脑是X86
结构的,一开始是怕出错,就编译了这个,后来感觉应该没啥影响))
scons build/X86/gem5.opt
Gem5
的SE测试
SE测试
- 用来运行单个应用,一系列指令在MP/SMT上
- 建模用户可见的ISA和常见的系统调用
- 模拟系统调用,通过调用主机操作系统
- 简单的地址翻译,没有调度
./build/X86/gem5.opt configs/example/se.py -c tests/test-progs/hello/bin/x86/linux/hello
Gem5
的FS测试
FS测试
- 能够启动完整的操作系统
- 建模硬件设备
- 中断,异常,故障处理函数
参考下载的网址: www.m5sim.org/Download
- 下载
X86
全系统对应的文件,一定要下载自己编译架构的对应文件
wget http://www.m5sim.org/dist/current/x86/x86-system.tar.bz2
- 解压文件,同时将解压后的文件夹放入同一目录
fs-image
tar vxfj x86-system.tar.bz2
mkdir fs-image
mv binaries fs-image
mv disks fs-image
- 下载
alpha
对应的全系统文件,里面包含linux-bigswap2.img
,这个是运行的一个必须文件.下载之后,解压文件,同时将文件拷贝到fs-image/disks
目录下,可以理解为专门存放镜像文件的目录.
wget http://www.m5sim.org/dist/current/m5_system_2.0b3.tar.bz2
tar vxfj m5_system_2.0b3.tar.bz2
cp m5_system_2.0b3/disks/linux-bigswap2.img fs-image/disks
- 运行FS模拟
./build/X86/gem5.opt configs/example/fs.py
报了如下的错误,这里因为脚本代码中默认了镜像为x86root.img
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "build/X86/python/m5/main.py", line 453, in main
exec(filecode, scope)
File "configs/example/fs.py", line 340, in <module>
test_sys = build_test_system(np)
File "configs/example/fs.py", line 93, in build_test_system
cmdline=cmdline)
File "/home/greek/master/experiment/gem5/configs/common/FSConfig.py", line 614, in makeLinuxX86System
makeX86System(mem_mode, numCPUs, mdesc, self, Ruby)
File "/home/greek/master/experiment/gem5/configs/common/FSConfig.py", line 539, in makeX86System
disk0.childImage(mdesc.disk())
File "/home/greek/master/experiment/gem5/configs/common/Benchmarks.py", line 63, in disk
return env.get('LINUX_IMAGE', disk('x86root.img'))
File "/home/greek/master/experiment/gem5/configs/common/SysPaths.py", line 71, in __call__
raise IOError("Can't find file '%s' on path." % filename)
IOError: Can't find file 'x86root.img' on path.
解决办法
这里有两种解决办法,推荐使用第二种:
- 修改代码
gem5/configs/common/Benchmarks.py
中,第63行,修改为如下,其中linux-86.img
为fs-image/disks
目录下的镜像文件
return env.get('LINUX_IMAGE', disk('linux-x86.img'))
- [推荐使用] 直接使用参数指定镜像文件,使用参数
--disk-image
以及--kernel
命令使用如下格式:
./build/X86/gem5.opt configs/example/fs.py --disk-image linux-x86.img --kernel x86_64-vmlinux-2.6.22.9
指定M5_PATH
路径,系统会自动从M5_PATH
路径中disks
和binaries
两个目录寻找指定的镜像文件和对应的内核文件.
其中指定M5_PATH
我提供了两种方式,推荐使用第二种(虽然我用的第一种)):
- 修改
.basrhc
文件,在文件的末尾加入M5_PATH的路径
export M5_PATH=$M5_PATH:/home/hsc/fs-image
- 直接在运行命令中动态添加环境变量
M5_PATH=/home/hsc/fs-image ./build/X86/gem5.opt configs/example/fs.py --disk-image linux-x86.img --kernel x86_64-vmlinux-2.6.22.9
linux-x86.img
为fs-image/disks/
目录下文件, x86_64-vmlinux-2.6.22.9
为fs-image/binaries/
目录下文件,我看网上很多教程将x86_64-vmlinux-2.6.22.9
改名为vmlinux
,这步的话其实意义不是特别大,不过改了也方便,我这里暂不做修改.
- 连接模拟系统
当运行了FS测试之后,系统就启动了,可以通过远程连接的方式,连接上我们的系统.这里我们介绍两种方式进行连接:
- 利用
gem5
系统自带的一个连接程序,在gem5/util/term/
目录中,先进行make
,生成可执行文件
cd util/term/
make
./m5term localhost 3456
- [推荐使用] 直接利用
telnet
连接系统
telnet localhost 3456
- 退出
这里我依旧使用两种方法,推荐使用第一种方法
- 在连接的命令行中输入
m5 exit
- 在连接的命令行中输入
~.
NVMain和Gem5的集成环境
Gem5补丁
首先为了消除NVMain
和Gem5
版本之间的差异,所以首先需要给Gem5
打上补丁.(别听到补丁就觉得畏惧,一开始我有这种感觉,其实后面发现真的没啥).
这里我提供两种方式,两种方式都很OK
,如果第一种失败建议换成第二种.如果你的gem5
不是用hg
下载,请换第二种方式.
- 补丁文件已经在
nvmain/patches/gem5
目录中给出,在gem5
目录中执行下列命令
hg qinit
hg qimport -f ../nvmain/patches/gem5/nvmain2-gem5-11688+
hg qpush
nvmain2-gem5-11688+
这个是补丁文件的名字,在nvmain/patches/gem5
中,以后可能是其他的版本,换成自己的版本补丁名字即可.
- 手动补丁,直接查看补丁的内容,手动修改文件,如下所示为补丁的内容
diff --git a/configs/common/Options.py b/configs/common/Options.py
--- a/configs/common/Options.py
+++ b/configs/common/Options.py
@@ -63,6 +63,12 @@
# being used, and consequently no CPUs, but rather various types of
# testers and traffic generators.
def addNoISAOptions(parser):
+ # Check for extra nvmain configuration override options
+ for arg in sys.argv:
+ if arg[:9] == "--nvmain-":
+ parser.add_option(arg, type="string", default="NULL",
+ help="Set NVMain configuration value for a parameter")
+
parser.add_option("-n", "--num-cpus", type="int", default=1)
parser.add_option("--sys-voltage", action="store", type="string",
default='1.0V',
只需在gem5/configs/common/Options.py
的第82行之后,加入如下三行
for arg in sys.argv:
if arg[:9] == "--nvmain-":
parser.add_option(arg, type="string", default="NULL", help="Set NVMain configuration value for a parameter")
NVMain和Gem5混合编译
- 编译命令
scons EXTRAS=../nvmain build/X86/gem5.opt
- 测试,其中混合编译成功的一个重要标志是可以使用
--mem-type=NVMainMemory
参数来指定主存为NVM
M5_PATH=/home/hsc/fs-image ./build/X86/gem5.opt configs/example/se.py -c tests/test-progs/hello/bin/x86/linux/hello --caches --l2cache --mem-type=NVMainMemory --nvmain-config=../nvmain/Config/PCM_ISSCC_2012_4GB.config
PARSEC 测试
我参考的资料是: pfzuo.github.io/2016/06/06/Configure-and-run-parsec-2.1-benchmark-in-GEM5/
这一层我的目录结构为
+-- fs-image/
| +-- disks/
| | +-- linux-bigswap2.img
| | +-- x86root-parsec.img
| +-- binaries/
| | +-- x86_64-vmlinux-2.6.28.4-smp
| | +-- tsb_osfpal
+-- benchmark
| +-- TR-09-32-parsec-2.1-alpha-files
- 下载
kernel
文件,复制到fs-image/binaries
目录下
wget http://www.cs.utexas.edu/~parsec_m5/x86_64-vmlinux-2.6.28.4-smp
- 下载PARSEC对应的PAL code文件, 复制到
fs-image/binaries
目录下
wget http://www.cs.utexas.edu/~parsec_m5/tsb_osfpal
- 下载PARSEC-2.1 Disk Image并解压到
fs-image/disks
目录中
wget http://www.cs.utexas.edu/~parsec_m5/x86root-parsec.img.bz2
bzip2 -d x86root-parsec.img.bz2
- 下载PARSEC script生成包,并解压到
benchmark
目录下
wget http://www.cs.utexas.edu/~parsec_m5/TR-09-32-parsec-2.1-alpha-files.tar.gz
tar zxvf TR-09-32-parsec-2.1-alpha-files.tar.gz
- 生成
script
./writescripts.pl <benchmark> <nthreads>
其中benchmark包含如下
blackscholes
bodytrack
canneal
dedup
facesim
ferret
fluidanimate
freqmine
streamcluster
swaptions
vips
x264
rtview
我生成的脚本是blackscholes
,利用gem5
进行全系统测试
./writescripts.pl blackscholes 4
- 运行生成的脚本
M5_PATH=/home/hsc/fs-image ./build/X86/gem5.opt ./configs/example/fs.py -n 2 --script=../benchmark/TR-09-32-parsec-2.1-alpha-files/blackscholes_4c_test.rcS --disk-image x86root-parsec.img --kernel x86_64-vmlinux-2.6.28.4-smp
- 连接测试
telnet localhost 3456
FPC压缩方案
在修改NVMain
源代码实现FPC压缩方案时,我走了不少的弯路.一开始看了实验推荐的一篇关于FPC
的论文,但是确实看的特别懵,感觉不是很清楚作者压缩的思路.究其原因,作者没有在论文中给出一个具体的实例,导致理解起来有些困难.在此,我还得特别感谢坐我旁边的魏博士,是他推荐了一篇更新的关于FPC
的论文,并且论文中给出了详细的实例,便于理解.并给我梳理NVMain
的一个整体框架,使我能够快速找到修改的位置.
参考论文,推荐重点看第二篇
- Frequent Pattern Compression: A Significance-Based Compression Scheme for L2 Caches
- CompEx: Compression-expansion coding for energy, latency, and lifetime improvements in MLC/TLC NVM
修改位置
打了箭头的位置,即为修改的位置,其中FPCompress
为自己创建的文件夹,并新建的类,及其实现.
+-- nvmain/
| +-- DataEncoders/
| | +-- FlipNWrite/
| | | +-- FlipNWrite.h
| | | +-- FlipNWrite.cpp
| | | +-- SConscript
| | +-- FPCompress/ <-
| | | +-- FPCompress.h <-
| | | +-- FPCompress.cpp <-
| | | +-- SConsript <-
| | +-- DataEncoderFactory.cpp <-
其实整体的思路可以理解为,要写入的数据,其中默认有一种编码方式,即不进行任何操作,而FlipNWrite
为另一种编码方式,我们只需要模仿FlipNWrite
实现自己的编码方案,在编码方式中实现自己的FPC
压缩,就可以相当于拦截写入的数据,并替换成自己的压缩后的数据,成功写入.
/* FPCompress.h
* Author: hsc
* date: 2019-10-9
*/
#ifndef __NVMAIN_FPCOMPRESS_H__
#define __NVMAIN_FPCOMPRESS_H__
#include "src/DataEncoder.h"
namespace NVM {
class FPCompress : public DataEncoder {
public:
FPCompress();
~FPCompress();
void SetConfig( Config *config, bool createChildren = true );
ncycle_t Read( NVMainRequest *request );
ncycle_t Write( NVMainRequest *request );
void RegisterStats( );
private:
uint64_t p0; /* pattern zero */
uint64_t p1;
uint64_t p2;
uint64_t p3;
uint64_t p4;
uint64_t p5;
uint64_t p6;
uint64_t p7; /* pattern seven */
void set_data( uint8_t *address, uint64_t index, uint64_t data);
};
/* zero run */
static inline bool pattern_zero(int64_t x){
return x == 0;
}
/* 8bit sign extended */
static inline bool pattern_one(int64_t x){
return (x >> 7) == -1 || (x >> 7) == 0;
}
/* 16 bit sign extended */
static inline bool pattern_two(int64_t x){
return (x >> 15) == -1 || (x >> 15) == 0;
}
/* halfword sign extended */
static inline bool pattern_three(int64_t x){
return (x >> 31) == -1 || (x >> 31) == 0;
}
/* halfword padded with a zero halfword */
static inline bool pattern_four(int64_t x){
return (x << 32) == 0;
}
/* two half words, each a byte sign-extended */
static inline bool pattern_five(int64_t x){
int32_t high = static_cast<int32_t>(x >> 32);
int32_t low = static_cast<int32_t>(x & 0xffffffff);
bool high_valid = (high >> 15) == -1 || (high >> 15) == 0;
bool low_valid = (low >> 15) == -1 || (low >> 15) == 0;
return high_valid && low_valid;
}
/* word consisting of repeated bytes */
static inline bool pattern_six(int64_t x){
int64_t total = 0;
int64_t pattern_byte = x & 0xffff;
total |= pattern_byte;
total <<= 16;
total |= pattern_byte;
total <<= 16;
total |= pattern_byte;
total <<= 16;
total |= pattern_byte;
total <<= 16;
return total == x;
}
};
#endif
其中,我觉得比较重要的是实现Write
, RegisterStats
这两个函数,一个用来实现压缩方案,一个用来打印各种数据模式的统计量.在各种模式的判别中,一开始我是读的uint64_t
无符号类型的,发现这样判断实现的不是很优雅,后来换成了带符号数据int64_t
.
/* FPCompress.cpp
* Author : hsc
* date: 2019-10-9
*/
#include "DataEncoders/FPCompress/FPCompress.h"
using namespace NVM;
FPCompress::FPCompress()
{
/* clear statistics */
p0 = 0;
p1 = 0;
p2 = 0;
p3 = 0;
p4 = 0;
p5 = 0;
p6 = 0;
p7 = 0;
}
FPCompress::~FPCompress()
{
/*
* nothing to do here
*/
}
void FPCompress::SetConfig( Config *config, bool /*createChildren*/ )
{
Params *params = new Params( );
params->SetParams( config );
SetParams( params );
}
void FPCompress::RegisterStats()
{
AddStat(p0);
AddStat(p1);
AddStat(p2);
AddStat(p3);
AddStat(p4);
AddStat(p5);
AddStat(p6);
AddStat(p7);
}
ncycle_t FPCompress::Read( NVMainRequest *request )
{
ncycle_t rv = 0;
// TODO: Add some energy here
return rv;
}
ncycle_t FPCompress::Write( NVMainRequest *request ){
NVMDataBlock& newData = request->data;
NVMDataBlock& oldData = request->oldData;
int64_t *ptr = (int64_t*)(newData.rawData);
uint64_t offset = 0, size = 0, encode_data = 0;
while(size < newData.GetSize()){
int64_t dword = *ptr;
if(pattern_zero(dword)){
encode_data = 0;
set_data(oldData.rawData, offset, encode_data);
p0++; /* zero statistics */
offset += 3;
}else if(pattern_one(dword)){
encode_data = dword & 0xff;
encode_data |= (0x1 << 8);
set_data(oldData.rawData, offset, encode_data);
p1++;
offset += 11;
}else if(pattern_two(dword)){
encode_data = dword & 0xffff;
encode_data |= (0x2 << 16);
set_data(oldData.rawData, offset, encode_data);
p2++;
offset += 19;
}else if(pattern_three(dword)){
encode_data = dword & 0xffffffff;
encode_data |= ((uint64_t)0x3 << 32);
set_data(oldData.rawData, offset, encode_data);
p3++;
offset += 35;
}else if(pattern_four(dword)){
encode_data = dword >> 32;
encode_data |= ((uint64_t)0x4 << 32);
set_data(oldData.rawData, offset, encode_data);
p4++;
offset += 35;
}else if(pattern_five(dword)){
encode_data = (dword & 0xffff);
encode_data |= ((dword >> 32) & 0xffff) << 16;
encode_data |= ((uint64_t)0x5 << 32);
set_data(oldData.rawData, offset, encode_data);
p5++;
offset += 35;
}else if(pattern_six(dword)){
encode_data = dword && 0xffff;
encode_data |= (0x6 << 16);
set_data(oldData.rawData, offset, encode_data);
p6++;
offset += 19;
}else {
encode_data = dword;
set_data(oldData.rawData, offset, (encode_data >> 32) & 0xffffffff);
set_data(oldData.rawData, offset + 32, (encode_data & 0xffffffff));
p7++;
offset += 64;
}
/* pointer to next dword */
ptr++;
size += 8;
}
}
void FPCompress::set_data( uint8_t *address, uint64_t offset, uint64_t data)
{
uint64_t *ptr = (uint64_t*)(address + (offset / 8));
uint64_t new_data = *ptr;
uint64_t len = offset - (offset / 8) * 8;
new_data = (new_data << (64 - len)) >> (64 - len);
new_data |= (data << len);
*ptr = new_data;
}
思路
-
request
中包含的是uint8_t
类型的指针地址,将其强制转换为int64_t
类型的指针,即可读取64bit
数据 - 读出数据,判断是8种数据模式中的哪一种数据模式,然后构造压缩后的数据
- 因为压缩的数据,特别不规整,有
3bit
,11bit
,19bit
,35bit
不等.比如我当前要写3bit
,这3bit
可能还跨了不同的字节,因此导致情况特别复杂. - 我的方法是计算写入位置最近的以字节为单位对齐的偏移地址, 从该偏移地址中读取64
bit
数据,这样的话,这64bit
中最多包含7位之前写入的数据.我们通过将之前写入的数据,和压缩后的数据进行合并成一个新的64bit
数据,然后重新写入.
在DataEncoderFactory.cpp
中加入自己的编码方式
// DataEncoderFactory.cpp
DataEncoder *DataEncoderFactory::CreateDataEncoder( std::string encoderName )
{
DataEncoder *encoder = NULL;
if( encoderName == "default" ) encoder = new DataEncoder( );
else if( encoderName == "FlipNWrite" ) encoder = new FlipNWrite( );
else if( encoderName == "FPCompress") encoder = new FPCompress( );
return encoder;
}
为了使用我们的编码方式,我们需要在配置文件中指定编码方式,这里我选用的配置文件为nvmain/Config/PCM_MLC_example.config
,在文件的末尾,加入如下内容:
DataEncoder FPCompress
编译
scons --build-type=fast
测试
利用我们刚刚修改的配置文件nvmain/Config/PCM_MLC_example.config
进行测试
./nvmain.fast Config/PCM_MLC_example.config Tests/Traces/hello_world.nvt 1000000
我是若木,倘若大家有更好的实现方式,记得与我联系,也可参观一下我的博客.