操作系统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。

img

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已存在

img

性能测试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;
}

测试结果:

img

由于虚拟机安装在了固态硬盘上,导致DISK读与RAM读之间的差异并不明显,但DISK写与RAM写还是存在明显差距。

为了消除测试结果的波动性,在不同时间段进行了五次测试并取平均值,绘制结果如下:

img

性能测试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;
}

测试结果:

img

类似于第一个测试结果,由于测试在固态硬盘上完成,在写操作时,DISK与RAM差距明显,但读操作在DISK与RAM上几乎没有差距。随着块大小增大,吞吐量增大,且RAM增长速度比DISK更快。

当块大小较小时,顺序读写的吞吐量明显大于随机读写;当块大小较大时,两者差距较小。img

img

img

img

总结

提高测试精确度的方法

为了提高测试精确度,我们需要精确记录读写的时间。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秒,因此需要尽可能增加文件大小,增加读写时间来减少时间上的相对误差。

减少测试误差和波动

经测试发现,虚拟机的读写性能受物理机的运行状态影响很大,且存在一定的波动。会出现某一段时间整体吞吐率明显过低或过高的情况,如图红框所示:

img

为了减少这些波动对测试数据的影响,在不同时间段分别进行了五次测试,并取均值,使得最终结果更符合普遍情况:

img

本次实验测试过程中,由于设备只有固态硬盘,虚拟机只能建立在固态硬盘上,导致DISK读与RAM读的差距较小,但可以看到DISK读吞吐量仍然略低于RAM读吞吐量;DISK写吞吐量远小于RAM写吞吐量。随着线程数增加或者块大小的增加,RAM操作吞吐量增长速度比DISK操作快。当块大小较小时,顺序读写的吞吐量明显大于随机读写;当块大小较大时,两者差距较小。