os-project3

I/O subsystem

目标

  1. 熟悉类UNIX系统的I/O设备管理

  2. 熟悉MINIX块设备驱动

  3. 熟悉MINIX RAM盘

实验要求

  • 在MINIX3中安装一块X MB大小的RAM盘(minix中已有6块用户可用RAM盘,7块系统保留RAM盘),可以挂载并且存取文件操作。

  • 测试RAM盘和DISK盘的文件读写速度,分析其读写速度差异原因(可用图表形式体现在实验报告中)。

实现过程

增加RAM盘:

  • 修改/usr/src/minix/drivers/storage/memory/memory.c,增加默认的用户RAM盘数:RAMDISKS=7

  • 重新编译内核,重启reboot。
  • 创建设备mknod /dev/myram b 1 13,查看设备是否创建成功输入ls /dev/ | grep ram

  • 实现buildmyram初始化工具(用于分配容量)。

    • 参考/usr/src/minix/commands/ramdisk/ramdisk.c,实现buildmyram.c,但是需要将KB单位修改成MB。
    • ramdisk.c 代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44

    #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 kB> [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 KFACTOR 1024
    size = atol(argv[1])*KFACTOR;

    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 %ldkB\n", d, size/KFACTOR);

    return 0;
    }
    • 编译buildmyram.c文件,然后执行命令: ./buildmyram <size in MB> /dev/myram。创建一个RAM盘。

我们将KB变成MB ,可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#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;
}
// 需要把宏从1024改为1024*1024
#define MFACTOR 1048576
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;
}

  • 在ram盘上创建内存文件系统,mkfs.mfs /dev/myram

  • 将ram盘挂载到用户目录下,mount /dev/myram /root/myram,(首先必须先mkdir myram)查看是否挂在成功:输入df,结果如下:

其中,GB、MB、512-block 转换方式如下:

GB MB 512-blocks
1 1024 2097152
  • 注:重启后用户自定义的ram盘内容会丢失,需要重新设置大小,创建文件系统,并挂载。

重启后,我们会发现刚刚创建的ram盘就丢失了,我们就需要重复下面三条命令:

1
2
3
./buildmyram 700 /dev/myram 	// 因为吞吐量不会大于700MB/s 这里就把Ram盘设置为700mb
mkfs.mfs /dev/myram
mount /dev/myram /root/myram

性能测试:

RAM盘和Disk盘的性能测试中,需要采用多进程并发的同步读写,并发数要增加到设备接近“饱和”状态(吞吐量难以继续提升,但是I/O延时恶化)。在出现饱和前,总吞吐量随着并发数线性增长。

性能测试的二个变量为块大小(推荐64B/256B/1KB/4KB/16KB/64KB)和块扫描方式(顺序/随机)。可以画四张曲线图对比RAM盘和Disk盘性能(随机读,随机写,顺序读,顺序写)。实验结果预计为RAM盘性能高于DISK盘,特别是随机读写性能。对比在不同的blocksize下的性能,然后通过Excel把数据制作成图表。

编写测试文件

注意事项

  • 使用posix函数open打开文件,利用O_SYNC参数使得write/read操作为同步模式。
  • 一定要检查write/read函数的返回值,以及写入的字节数目,确定是否成功。
  • 为了简化实验,可以为每个进程分配一个独立的文件。为了减小主机操作系统的缓存机制造成的误差,文件总大小越大越好(例如300MB)。
  • 随机读写时,可以采用lseek()重新定位文件指针;顺序读写时,默认文件指针自动移动,当读到文件末尾时,可以用lseek()返回文件头。
  • 每组的读写需要反复持续一段时间,过短的时间会造成误差较大。
  • 通常情况下,7~15个进程达到饱和,吞吐量不会高于700MB/s (ram盘顺序读写)。
  • 如果minix虚拟机建在SSD下,会导致随机和顺序的差距减小,所以最好把虚拟机放在机械硬盘上,实验效果更明显。

首先, usr文件夹是硬盘, 而myram 文件夹是内存。我们需要将一个尽可能大的文件放到这两个文件夹中去。

写方法

写文件:打开文件,判断返回值,如果正常打开文件就判断是否随机写,进行写操作

这里要说一下打开文件的方式,O_CREAT,O_RDWR,O_SYNC 以及权限编码:

O_RDWR 即以可读写的方式打开文件

O_SYNC以同步的方式打开文件,利用O_SYNC参数使得write/read操作为同步模式

O_CREAT若想要打开的文件不存在则自动建立该文件

后面四位数字分别代表 全部用户(all), 文件用户(user),同组用户(group),其他用户(other) 的权限

十进制 二进制 代表
0 000
1 001 —x(只可执行)
2 010 -w-(只可写)
3 011 -wx(可写可执行)
4 100 r—(只可读)
5 101 r-x(可读可执行)
6 110 rw-(可读可写)
7 111 rwx(可读可写可执行)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 向缓存中写入大小为blocksize的数字
void write_to_buf(char* buf, int blocksize)
{
int i;
for (i = 0; i < blocksize; i++)
buf[i] = rand() % 94 + 33;
}

void write_file(int blocksize, bool isrand, char *filepath, int fs)
{
int fd;
int i;
int total_block = fs / blocksize; // 需要写入的总block数
char* buf = (char*)malloc(sizeof(char) * blocksize); // 缓存字符串,大小为一个block
//往缓存中写入数据
write_to_buf(buf, blocksize);

// 只写、同步打开文件,并处理错误
if ((fd = open(filepath, O_WRONLY|O_SYNC)) < 0)
{
printf("Open Error!\n");
exit(-1);
}

for (i = 0; i < total_block; i++)
{
//write()会把缓存中的数据写到文件当中去. 当然, 文件读写位置也会随之移动.
if (write(fd, buf, blocksize) == -1) // 写入缓存
{
printf("Write Error!\n");
exit(-1);
}
if (isrand) // 如果随机写,每次写入随机编号的block
{
//lseek可以将读写未知移到任意未知
if (lseek(fd, rand() % total_block * blocksize, SEEK_SET) == -1)
{
printf("Lseek Error!\n");
exit(-1);
}
}
}

if (close(fd) != 0) //关闭文件 并处理错误
{
printf("Close Error!\n");
exit(-1);
}
return;
}

读方法

读方法和写方法的结构是一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

void read_file(int blocksize, bool isrand, char *filepath, int fs)
{
int fd;
int code;
int i;
int total_block = fs / blocksize; // 需要读取的总block数
char* buf = (char*)malloc(sizeof(char) * blocksize); // 缓存字符串,大小为一个block

if ((fd = open(filepath, O_CREAT | O_RDWR | O_SYNC, 0755)) < 0) // 只读、同步打开文件
{
printf("Open Error!\n");
exit(-1);
}

for (i = 0; i < total_block ; i++)
{
if ((code = read(fd, buf, blocksize)) == -1) // 将文件中的信息读取至缓存
{
printf("Read Error!\n");
exit(-1);
}
if (isrand) // 如果随机读,就随机跳到某个地方开始读
{
if (lseek(fd, rand() % total_block * blocksize, SEEK_SET) == -1)
{
printf("Lseek Error!\n");
exit(-1);
}
}
else if (code == 0) // 如果顺序读,并且已经读到队尾,那么就移到文件开头继续读
{
if (lseek(fd, 0, SEEK_SET) == -1)//移动到文件开头
{
printf("Lseek to Head Error!\n");
exit(-1);
}
}
}
if (close(fd) != 0) /* 关闭文件 */
{
printf("Close Error!\n");
exit(-1);
}
return;
}

主函数

主函数:首先创建和命名文件,通过循环执行read_filewrite_file函数测试读写差异。测试blocksizeconcurrency对测试读写速度的影响,最后输出结果。

1
2
3
4
5
6
7
8
9
10
11
12
//首先写两个 utils, 第一个函数是获取当前的时间
long long get_current_time()
{
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}
//第二个是获取运行时间
long long get_run_time(long long starttime, long long endtime)
{
return endtime - starttime;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118

int main(int argc, char* argv[])
{
enum io_list
{
write, read
};

enum order_list
{
sequential = false, random = true
};

pid_t pid;
int fd;
int i, j, k;
long long starttime, endtime, usetime;
char filepath[MaxFileName];
// 定义块数组,从 64到66536
int blocksizelist[] = {64, 256,1024, 4096 ,16384, 66536};
int blocksize;
int filesizeMB;
long filesize = 100 * MFACTOR; // 1024*1024
int concurrency = 5; // 并发5
bool deletefile = true;
int RedoTime = 3; //重复次数为3



int io;
bool order;

if (argc < 4)
help_and_quit();
else
{
if (!strncmp(argv[1], "-w", 2))
io = write;
else if (!strncmp(argv[1], "-r", 2))
io = read;
else
help_and_quit();

if (!strncmp(argv[2], "-0", 2))
order = sequential;
else if (!strncmp(argv[2], "-1", 2))
order = random;
else
help_and_quit();

if ((filesizeMB = atoi(argv[3])) >= 1 && filesizeMB <= 200)
filesize = filesizeMB * MFACTOR;
else
help_and_quit();

}

for (k = 0; k <= 6; k+=1)
{
blocksize = blocksizelist[k];
starttime = get_current_time();
for (i = 0; i < concurrency; i++)
{
if ((pid = fork()) == 0)
{
sprintf(filepath, "./file%d.txt", i);

if ((fd = creat(filepath, 777)) == -1) /* 创建文件后关闭 */
{
printf("Create Error!\n");
exit(-1);
}
else
{
if (close(fd) != 0)
{
printf("Close Error!\n");
exit(-1);
}
}

if (io == read)
{
write_file(blocksize, order, filepath, filesize/concurrency);
for (j = 0; j < RedoTime - 1; j++)
read_file(blocksize, order, filepath, filesize/concurrency);
}
else
{
for (j = 0; j < RedoTime - 1; j++)
write_file(blocksize, order, filepath, filesize/concurrency);
}

if (deletefile && (remove(filepath) != 0))
{
printf("Remove Error!\n");
exit(-1);
}

exit(0);
}
}

// printf("Parent process running\n");
while (waitpid(pid, NULL, 0) != -1){}
// printf("All child processes finished\n");

endtime = get_current_time();
usetime = get_run_time(starttime, endtime);
printf("File size: %ld MB.\n", filesize / MFACTOR);
printf("Blocksize: %d.\n", blocksize);
printf("Redo times: %d.\n", RedoTime);
printf("Execution time: %lld msec(s).\n", usetime);
printf("Throughput: %f MB/S\n\n", ((double)filesize / MFACTOR) * RedoTime / ((double)usetime / 1024));
}

return 0;
}

数据如下:

分块大小 ram disk
64 12.387097 10.858385
256 47.872838 27.863946
1024 157.538462 91.237491
4096 380.19802 180.705582
16384 550.044763 236.307692
66536 646.736842 257.826269
分块大小 ram disk
64 17.886463 8.031373
256 71.300917 47.690775
1024 228.997391 110.048361
4096 519.357566 169.185606
16384 722.823529 195.047619
66536 783.673469 201.442623
分块大小 ram disk
64 4.561823 5.918561
256 20.2999336 11.837121
1024 71.168771 23.674242
4096 297.24238 47.348485
16384 550.537634 94.69697
66536 695.022624 189.393939
分块大小 ram disk
64 5.809929 0.610483
256 24.924949 2.032418
1024 96 8.908608
4096 372.353636 55.941
16384 625.025432 139.098936
66536 722.823529 219.428571

结果如下图所示:

将随机

-------------本文结束,感谢您的阅读-------------