操作系统lab3-I/O subsystem
内容和设计思想
在MINIX3中安装一块X MB大小的RAM盘(minix中已有6 块用户可用RAM盘,7块系统保留RAM盘),可以挂载并 且存取文件操作。
测试RAM盘和DISK盘的文件读写速度,分析其读写速度差异原因。
使用环境
VirtualBox6.1.8, MINIX3.3.0, clang3.4
实验过程
增加RAM盘
1) 修改/usr/src/minix/drivers/storage/memory/memory.c ,增加默认的用户 RAM盘数:RAMDISKS=7。
2) 重新编译内核并重启
3) 创建设备mknod /dev/myram b 1 13
4) 实现buildmyram分配RAM容量
#include <minix/paths.h>
#include <sys/ioc_memory.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
int
main(int argc, char *argv[])
{
int fd;
signed long size;
char *d;
if(argc < 2 || argc > 3) {
fprintf(stderr, "usage: %s <size in MB> [device]\n",
argv[0]);
return 1;
}
d = argc == 2 ? _PATH_RAMDISK : argv[2];
if((fd=open(d, O_RDONLY)) < 0) {
perror(d);
return 1;
}
#define MFACTOR (1024 * 1024)
size = atol(argv[1])*MFACTOR;
if(size < 0) {
fprintf(stderr, "size should be non-negative.\n");
return 1;
}
if(ioctl(fd, MIOCRAMSIZE, &size) < 0) {
perror("MIOCRAMSIZE");
return 1;
}
fprintf(stderr, "size on %s set to %ldMB\n", d, size/MFACTOR);
return 0;
}
5) 编译并执行buildmyram.c,创建RAM盘。大小设置为1000MB
buildmyram 1000 /dev/myram
6) 在ram盘上创建内存文件系统,mkfs.mfs /dev/myram
7) 将ram盘挂载到用户目录下,mount /dev/myram /root/myram
df命令查看ram盘,/dev/myram已存在
性能测试1:吞吐量与线程数量的关系
测试程序设计思路:第一个测试中,我们固定文件的总大小以及块大小,分别设置为100M和4KB,对于每一种情况(DISK顺序写/DISK顺序读/RAM顺序写/RAM顺序读),从1开始增加线程数至150依次进行测试。为了便于计算结果,我们假设所有线程的吞吐率和结束时间是一致的,利用waitpid()函数等待所有线程结束,当最后一个线程结束时记录时间,用这个时间计算所有线程的吞吐量和。
在进行测试时,每一个线程对应一个文件,每个文件的大小取决于线程数,保证文件总大小不变为filesize,则每个文件大小为filesize / concurrency,即总文件大小 / 线程数。
测试程序:
#include <stdio.h>
#include <stdbool.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <stdlib.h>
#include <sys/wait.h>
#define debug(x) fprintf(stderr, "%s = %d\n", #x, (int)x);
#define KFACTOR 1024
#define MFACTOR (1024 * 1024)
#define CLOCKS_PER_MSEC (CLOCKS_PER_SEC / 1000)
#define MAXLEN (1 << 20)
#define MAXPATH 128
#define USEC_PER_SEC 1000000
const char s[MAXLEN];
char filepath[MAXPATH], store[MAXLEN];
FILE* file;
/* 获取微秒级的系统时间 */
long long get_time_now() {
struct timeval tms;
char tstr[100];
timerclear(&tms);
gettimeofday(&tms, NULL);
strftime(tstr, 100, "%X", localtime(&tms.tv_sec));
long long time = tms.tv_sec * 1000000 + tms.tv_usec;
return time;
}
/* 计算时间戳间隔 */
long long get_time_left(long long starttime) {
return get_time_now() - starttime;
}
/* 文件写 */
void write_file(int blocksize, bool isrand, char *filepath, long fs) {
int fd = open(filepath, O_RDWR | O_SYNC | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
if (fd == -1) {
fprintf(stderr, "File open error.\n");
return;
}
long long starttime = get_time_now();
for (int i = 1; i <= fs / blocksize; i++) {
if (write(fd, s, blocksize) != blocksize) {
fprintf(stderr, "Write error.\n");
return;
}
if (isrand) {
lseek(fd, rand() % (fs - blocksize), SEEK_SET);
}
}
}
/* 文件读 */
void read_file(int blocksize, bool isrand, char *filepath, long fs) {
int fd = open(filepath, O_RDWR | O_SYNC | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
if (fd == -1) {
fprintf(stderr, "File open error.\n");
return;
}
for (int i = 1; i <= fs / blocksize; i++) {
if (read(fd, store, blocksize) != blocksize) {
fprintf(stderr, "Read error.\n");
return;
}
if (isrand) {
lseek(fd, rand() % (fs - blocksize * 2), SEEK_SET);
}
}
}
int main() {
srand(time(NULL));
long filesize = 100 * MFACTOR;
// long concurrency = 7;
long blocksize = 4 * KFACTOR;
long T = 150;
file = fopen("/root/write_order_disk", "w");
fprintf(stderr, "Now testing write_order_disk\n");
for (long concurrency = 1; concurrency <= T; concurrency++) {
long fs = filesize / concurrency;
for (int i = 1; i <= concurrency; i++) {
if (fork() == 0) {
sprintf(filepath, "/home/dir/input%d", i);
write_file(blocksize, 0, filepath, fs);
return 0;
}
}
long long starttime = get_time_now();
for (int i = 1; i <= concurrency; i++) waitpid(-1, NULL, 0);
double time = (double)get_time_left(starttime) / USEC_PER_SEC;
double tp = 1.0 * fs / MFACTOR / time;
fprintf(file, "%ld %.2f %ld\n", concurrency, tp * concurrency, blocksize);
fflush(file);
fprintf(stderr, "fs : %ldB time : %.2fs conc : %ld tp : %.2fM/s bs : %ld \n", fs, time, concurrency, tp * concurrency, blocksize);
}
file = fopen("/root/read_order_disk", "w");
fprintf(stderr, "Now testing read_order_disk\n");
for (long concurrency = 1; concurrency <= T; concurrency++) {
long fs = filesize / concurrency;
for (int i = 1; i <= concurrency; i++) {
if (fork() == 0) {
sprintf(filepath, "/home/dir/input%d", i);
read_file(blocksize, 0, filepath, fs);
return 0;
}
}
long long starttime = get_time_now();
for (int i = 1; i <= concurrency; i++) waitpid(-1, NULL, 0);
double time = (double)get_time_left(starttime) / USEC_PER_SEC;
double tp = 1.0 * fs / MFACTOR / time;
fprintf(file, "%ld %.2f %ld\n", concurrency, tp * concurrency, blocksize);
fflush(file);
fprintf(stderr, "fs : %ldB time : %.2fs conc : %ld tp : %.2fM/s bs : %ld \n", fs, time, concurrency, tp * concurrency, blocksize);
}
file = fopen("/root/write_order_ram", "w");
fprintf(stderr, "Now testing write_order_ram\n");
for (long concurrency = 1; concurrency <= T; concurrency++) {
long fs = filesize / concurrency;
for (int i = 1; i <= concurrency; i++) {
if (fork() == 0) {
sprintf(filepath, "/root/myram/input%d", i);
write_file(blocksize, 0, filepath, fs);
return 0;
}
}
long long starttime = get_time_now();
for (int i = 1; i <= concurrency; i++) waitpid(-1, NULL, 0);
double time = (double)get_time_left(starttime) / USEC_PER_SEC;
double tp = 1.0 * fs / MFACTOR / time;
fprintf(file, "%ld %.2f %ld\n", concurrency, tp * concurrency, blocksize);
fflush(file);
fprintf(stderr, "fs : %ldB time : %.2fs conc : %ld tp : %.2fM/s bs : %ld \n", fs, time, concurrency, tp * concurrency, blocksize);
}
file = fopen("/root/read_order_ram", "w");
fprintf(stderr, "Now testing read_order_ram\n");
for (long concurrency = 1; concurrency <= T; concurrency++) {
long fs = filesize / concurrency;
for (int i = 1; i <= concurrency; i++) {
if (fork() == 0) {
sprintf(filepath, "/root/myram/input%d", i);
read_file(blocksize, 0, filepath, fs);
return 0;
}
}
long long starttime = get_time_now();
for (int i = 1; i <= concurrency; i++) waitpid(-1, NULL, 0);
double time = (double)get_time_left(starttime) / USEC_PER_SEC;
double tp = 1.0 * fs / MFACTOR / time;
fprintf(file, "%ld %.2f %ld\n", concurrency, tp * concurrency, blocksize);
fflush(file);
fprintf(stderr, "fs : %ldB time : %.2fs conc : %ld tp : %.2fM/s bs : %ld \n", fs, time, concurrency, tp * concurrency, blocksize);
}
return 0;
}
测试结果:
由于虚拟机安装在了固态硬盘上,导致DISK读与RAM读之间的差异并不明显,但DISK写与RAM写还是存在明显差距。
为了消除测试结果的波动性,在不同时间段进行了五次测试并取平均值,绘制结果如下:
性能测试2:测试不同块大小和块扫描方式(顺序/随机)在DISK盘和RAM盘下的性能差别
测试程序设计思路:整体思路同性能测试1,对于随机扫描方式,每进行一次读写操作后利用rand()和lseek()函数将文件指针指向随机位置。
测试程序:
#include <stdio.h>
#include <stdbool.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <stdlib.h>
#include <sys/wait.h>
#define debug(x) fprintf(stderr, "%s = %d\n", #x, (int)x);
#define KFACTOR 1024
#define MFACTOR (1024 * 1024)
#define CLOCKS_PER_MSEC (CLOCKS_PER_SEC / 1000)
#define MAXLEN (1 << 20)
#define MAXPATH 128
#define USEC_PER_SEC 1000000
const char s[MAXLEN];
char filepath[MAXPATH], store[MAXLEN];
FILE* file;
/* 获取微秒级的系统时间 */
long long get_time_now() {
struct timeval tms;
char tstr[100];
timerclear(&tms);
gettimeofday(&tms, NULL);
strftime(tstr, 100, "%X", localtime(&tms.tv_sec));
long long time = tms.tv_sec * 1000000 + tms.tv_usec;
return time;
}
/* 计算时间戳间隔 */
long long get_time_left(long long starttime) {
return get_time_now() - starttime;
}
/* 文件写 */
void write_file(int blocksize, bool isrand, char *filepath, long fs) {
int fd = open(filepath, O_RDWR | O_SYNC | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
if (fd == -1) {
fprintf(stderr, "File open error.\n");
return;
}
long long starttime = get_time_now();
for (int i = 1; i <= fs / blocksize; i++) {
if (write(fd, s, blocksize) != blocksize) {
fprintf(stderr, "Write error.\n");
return;
}
if (isrand) {
lseek(fd, rand() % (fs - blocksize), SEEK_SET);
}
}
}
/* 文件读 */
void read_file(int blocksize, bool isrand, char *filepath, long fs) {
int fd = open(filepath, O_RDWR | O_SYNC | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
if (fd == -1) {
fprintf(stderr, "File open error.\n");
return;
}
for (int i = 1; i <= fs / blocksize; i++) {
if (read(fd, store, blocksize) != blocksize) {
fprintf(stderr, "Read error.\n");
return;
}
if (isrand) {
lseek(fd, rand() % (fs - blocksize * 2), SEEK_SET);
}
}
}
int main() {
srand(time(NULL));
long filesize = 100 * MFACTOR;
long concurrency = 10;
// long blocksize = 4 * KFACTOR;
file = fopen("/root/write_order_disk", "w");
fprintf(stderr, "Now testing write_order_disk\n");
for (long blocksize = 64; blocksize <= 64 * KFACTOR; blocksize *= 4) {
long fs = filesize / concurrency;
for (int i = 1; i <= concurrency; i++) {
if (fork() == 0) {
sprintf(filepath, "/home/dir/input%d", i);
write_file(blocksize, 0, filepath, fs);
return 0;
}
}
long long starttime = get_time_now();
for (int i = 1; i <= concurrency; i++) waitpid(-1, NULL, 0);
double time = (double)get_time_left(starttime) / USEC_PER_SEC;
double tp = 1.0 * fs / MFACTOR / time;
fprintf(file, "%ld %.2f %ld\n", concurrency, tp * concurrency, blocksize);
fflush(file);
fprintf(stderr, "fs : %ldB time : %.2fs conc : %ld tp : %.2fM/s bs : %ld \n", fs, time, concurrency, tp * concurrency, blocksize);
}
file = fopen("/root/read_order_disk", "w");
fprintf(stderr, "Now testing read_order_disk\n");
for (long blocksize = 64; blocksize <= 64 * KFACTOR; blocksize *= 4) {
long fs = filesize / concurrency;
for (int i = 1; i <= concurrency; i++) {
if (fork() == 0) {
sprintf(filepath, "/home/dir/input%d", i);
read_file(blocksize, 0, filepath, fs);
return 0;
}
}
long long starttime = get_time_now();
for (int i = 1; i <= concurrency; i++) waitpid(-1, NULL, 0);
double time = (double)get_time_left(starttime) / USEC_PER_SEC;
double tp = 1.0 * fs / MFACTOR / time;
fprintf(file, "%ld %.2f %ld\n", concurrency, tp * concurrency, blocksize);
fflush(file);
fprintf(stderr, "fs : %ldB time : %.2fs conc : %ld tp : %.2fM/s bs : %ld \n", fs, time, concurrency, tp * concurrency, blocksize);
}
file = fopen("/root/write_order_ram", "w");
fprintf(stderr, "Now testing write_order_ram\n");
for (long blocksize = 64; blocksize <= 64 * KFACTOR; blocksize *= 4) {
long fs = filesize / concurrency;
for (int i = 1; i <= concurrency; i++) {
if (fork() == 0) {
sprintf(filepath, "/root/myram/input%d", i);
write_file(blocksize, 0, filepath, fs);
return 0;
}
}
long long starttime = get_time_now();
for (int i = 1; i <= concurrency; i++) waitpid(-1, NULL, 0);
double time = (double)get_time_left(starttime) / USEC_PER_SEC;
double tp = 1.0 * fs / MFACTOR / time;
fprintf(file, "%ld %.2f %ld\n", concurrency, tp * concurrency, blocksize);
fflush(file);
fprintf(stderr, "fs : %ldB time : %.2fs conc : %ld tp : %.2fM/s bs : %ld \n", fs, time, concurrency, tp * concurrency, blocksize);
}
file = fopen("/root/read_order_ram", "w");
fprintf(stderr, "Now testing read_order_ram\n");
for (long blocksize = 64; blocksize <= 64 * KFACTOR; blocksize *= 4) {
long fs = filesize / concurrency;
for (int i = 1; i <= concurrency; i++) {
if (fork() == 0) {
sprintf(filepath, "/root/myram/input%d", i);
read_file(blocksize, 0, filepath, fs);
return 0;
}
}
long long starttime = get_time_now();
for (int i = 1; i <= concurrency; i++) waitpid(-1, NULL, 0);
double time = (double)get_time_left(starttime) / USEC_PER_SEC;
double tp = 1.0 * fs / MFACTOR / time;
fprintf(file, "%ld %.2f %ld\n", concurrency, tp * concurrency, blocksize);
fflush(file);
fprintf(stderr, "fs : %ldB time : %.2fs conc : %ld tp : %.2fM/s bs : %ld \n", fs, time, concurrency, tp * concurrency, blocksize);
}
file = fopen("/root/write_rand_disk", "w");
fprintf(stderr, "Now testing write_rand_disk\n");
for (long blocksize = 64; blocksize <= 64 * KFACTOR; blocksize *= 4) {
long fs = filesize / concurrency;
for (int i = 1; i <= concurrency; i++) {
if (fork() == 0) {
sprintf(filepath, "/home/dir/input%d", i);
write_file(blocksize, 1, filepath, fs);
return 0;
}
}
long long starttime = get_time_now();
for (int i = 1; i <= concurrency; i++) waitpid(-1, NULL, 0);
double time = (double)get_time_left(starttime) / USEC_PER_SEC;
double tp = 1.0 * fs / MFACTOR / time;
fprintf(file, "%ld %.2f %ld\n", concurrency, tp * concurrency, blocksize);
fflush(file);
fprintf(stderr, "fs : %ldB time : %.2fs conc : %ld tp : %.2fM/s bs : %ld \n", fs, time, concurrency, tp * concurrency, blocksize);
}
file = fopen("/root/read_rand_disk", "w");
fprintf(stderr, "Now testing read_rand_disk\n");
for (long blocksize = 64; blocksize <= 64 * KFACTOR; blocksize *= 4) {
long fs = filesize / concurrency;
for (int i = 1; i <= concurrency; i++) {
if (fork() == 0) {
sprintf(filepath, "/home/dir/input%d", i);
read_file(blocksize, 1, filepath, fs);
return 0;
}
}
long long starttime = get_time_now();
for (int i = 1; i <= concurrency; i++) waitpid(-1, NULL, 0);
double time = (double)get_time_left(starttime) / USEC_PER_SEC;
double tp = 1.0 * fs / MFACTOR / time;
fprintf(file, "%ld %.2f %ld\n", concurrency, tp * concurrency, blocksize);
fflush(file);
fprintf(stderr, "fs : %ldB time : %.2fs conc : %ld tp : %.2fM/s bs : %ld \n", fs, time, concurrency, tp * concurrency, blocksize);
}
file = fopen("/root/write_rand_ram", "w");
fprintf(stderr, "Now testing write_rand_ram\n");
for (long blocksize = 64; blocksize <= 64 * KFACTOR; blocksize *= 4) {
long fs = filesize / concurrency;
for (int i = 1; i <= concurrency; i++) {
if (fork() == 0) {
sprintf(filepath, "/root/myram/input%d", i);
write_file(blocksize, 1, filepath, fs);
return 0;
}
}
long long starttime = get_time_now();
for (int i = 1; i <= concurrency; i++) waitpid(-1, NULL, 0);
double time = (double)get_time_left(starttime) / USEC_PER_SEC;
double tp = 1.0 * fs / MFACTOR / time;
fprintf(file, "%ld %.2f %ld\n", concurrency, tp * concurrency, blocksize);
fflush(file);
fprintf(stderr, "fs : %ldB time : %.2fs conc : %ld tp : %.2fM/s bs : %ld \n", fs, time, concurrency, tp * concurrency, blocksize);
}
file = fopen("/root/read_rand_ram", "w");
fprintf(stderr, "Now testing read_rand_ram\n");
for (long blocksize = 64; blocksize <= 64 * KFACTOR; blocksize *= 4) {
long fs = filesize / concurrency;
for (int i = 1; i <= concurrency; i++) {
if (fork() == 0) {
sprintf(filepath, "/root/myram/input%d", i);
read_file(blocksize, 1, filepath, fs);
return 0;
}
}
long long starttime = get_time_now();
for (int i = 1; i <= concurrency; i++) waitpid(-1, NULL, 0);
double time = (double)get_time_left(starttime) / USEC_PER_SEC;
double tp = 1.0 * fs / MFACTOR / time;
fprintf(file, "%ld %.2f %ld\n", concurrency, tp * concurrency, blocksize);
fflush(file);
fprintf(stderr, "fs : %ldB time : %.2fs conc : %ld tp : %.2fM/s bs : %ld \n", fs, time, concurrency, tp * concurrency, blocksize);
}
return 0;
}
测试结果:
类似于第一个测试结果,由于测试在固态硬盘上完成,在写操作时,DISK与RAM差距明显,但读操作在DISK与RAM上几乎没有差距。随着块大小增大,吞吐量增大,且RAM增长速度比DISK更快。
当块大小较小时,顺序读写的吞吐量明显大于随机读写;当块大小较大时,两者差距较小。
总结
提高测试精确度的方法
为了提高测试精确度,我们需要精确记录读写的时间。clock()函数记录cpu时钟,但当进程处于就绪态时的时间不会被记录,因此不准确;time()只能计算到秒级,也不够准确。因此用gettimeofday()函数可以获取微秒级的系统时间,封装后的代码如下:
/* 获取微秒级的系统时间 */
long long get_time_now() {
struct timeval tms;
char tstr[100];
timerclear(&tms);
gettimeofday(&tms, NULL);
strftime(tstr, 100, "%X", localtime(&tms.tv_sec));
long long time = tms.tv_sec * 1000000 + tms.tv_usec;
return time;
}
但与ubuntu等unix内核系统不同,MINIX3系统下记录的最小时间单位只有1/6秒,因此需要尽可能增加文件大小,增加读写时间来减少时间上的相对误差。
减少测试误差和波动
经测试发现,虚拟机的读写性能受物理机的运行状态影响很大,且存在一定的波动。会出现某一段时间整体吞吐率明显过低或过高的情况,如图红框所示:
为了减少这些波动对测试数据的影响,在不同时间段分别进行了五次测试,并取均值,使得最终结果更符合普遍情况:
本次实验测试过程中,由于设备只有固态硬盘,虚拟机只能建立在固态硬盘上,导致DISK读与RAM读的差距较小,但可以看到DISK读吞吐量仍然略低于RAM读吞吐量;DISK写吞吐量远小于RAM写吞吐量。随着线程数增加或者块大小的增加,RAM操作吞吐量增长速度比DISK操作快。当块大小较小时,顺序读写的吞吐量明显大于随机读写;当块大小较大时,两者差距较小。