HUST操作系统实验3

本文最后更新于:2022年1月13日 下午

实验三.内存管理

实验要求

3.1理解程序局部性原理

3.1

以下部分为错误尝试:


为了实现在Linux上查看系统的缺页次数,需要使用到一个工具SAR (System Activity Reporter系统活动情况报告).

SAR是目前 Linux 上最为全面的系统性能分析工具之一,可以从多方面对系统的活动进行报告,包括:文件的读写情况、系统调用的使用情况、磁盘I/OCPU效率、内存使用状况、进程活动及IPC有关的活动等。

  • 安装SAR
1
2
3
1.使用sar -help判断当前系统是否已经安装sar,若已经安装,则可以直接使用
2.若当前系统中无sar,则使用命令yum install sysstat
3.安装完sysstat工具后,便可使用sar命令
  • 使用SAR获取系统缺页信息
1
2
3
4
5
6
7
8
9
10
11
12
13
例如,每10秒采样一次,连续采样3次,监控内存分页:
使用命令:
sar -B 10 3
输出项说明:
pgpgin/s:表示每秒从磁盘或SWAP置换到内存的字节数(KB)
pgpgout/s:表示每秒从内存置换到磁盘或SWAP的字节数(KB)
fault/s:每秒钟系统产生的缺页数,即主缺页与次缺页之和(major + minor)
majflt/s:每秒钟产生的主缺页数.
pgfree/s:每秒被放入空闲队列中的页个数
pgscank/s:每秒被kswapd扫描的页个数
pgscand/s:每秒直接被扫描的页个数
pgsteal/s:每秒钟从cache中被清除来满足内存需要的页个数
%vmeff:每秒清除的页(pgsteal)占总扫描页(pgscank+pgscand)的百分比

SAR命令详解,参考链接:linux sar命令详解

经过实验发现SAR命令并不能满足我的需求,因为他检测的是整个系统的情况,而且是分时采样的到的结果,无法满足我检测单个进程中的缺页情况


正确尝试:

使用命令:

ps -eo min_flt,maj_flt,pid,%cpu,%mem,pagein,args --sort=min_flt

可以查看当前正在运行的进程各自的缺页情况,显示结果中MINFLT为页分配时的缺页,MAJFLT为磁盘读写时的缺页,然后依次是PID,CPU占用,内存占用,从磁盘加载页面到物理内存。

该命令测出的是正在运行的程序的缺页情况,因此可令测试程序执行完后,休眠几秒,在未结束时,执行上面的命令,获取本次测试程序的缺页情况。

为充分展示由于局部性原理而造成的差异,下面进行几组对比实验:

  • 数组较小,内外循环次数一致
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <unistd.h>
int array[2048][2048];
int main(){
int i = 0,j=0;
for(i=0;i<=2000;i++){
for(j=0;j<=2000;j++){
array[i][j] = 0;
}
}
sleep(10);
printf("OK\n");
return 0;
}

test1

可以看到,本次运行,缺页次数:4171

  • 数组较大,内外循环次数一致:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <unistd.h>
int array[20480][20480];
int main(){
int i = 0,j=0;
for(i=0;i<=20000;i++){
for(j=0;j<=20000;j++){
array[i][j] = 0;
}
}
sleep(10);
printf("OK\n");
return 0;
}

test2

本次运行,缺页次数:400190

  • 数组较大,外层大循环,内层小循环:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <unistd.h>
int array[20480][20480];
int main(){
int i = 0,j=0;
for(i=0;i<=20000;i++){
for(j=0;j<=200;j++){
array[i][j] = 0;
}
}
sleep(10);
printf("OK\n");
return 0;
}

test3

本次运行,缺页次数:20171

  • 数组较大,外层小循环,内层大循环:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <unistd.h>
int array[20480][20480];
int main(){
int i = 0,j=0;
for(i=0;i<=200;i++){
for(j=0;j<=20000;j++){
array[i][j] = 0;
}
}
sleep(10);
printf("OK\n");
return 0;
}

test4

本次运行,缺页次数:4190

根据上述此次不同情况的程序运行结果,可以看到,随着访问数据量的增大,缺页次数也在增大。

在访问数据量相同的情况下,访问更多顺序存储的数据,缺页次数会更少,体现了空间局部性原理。

3.2 模拟实现OPT和LRU淘汰算法

3.2

实现代码:HUST操作系统试验:LRU,FIFO,OPT算法模拟 (github.com)

3.3 利用pagemap计算虚拟地址对应的物理地址

3.3要求

网上嫖来修改之后的代码,重复代码较多,可以修改一下将打印信息放在求物理地址函数内部:

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
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
#include <string.h>
bool flag =true;
char pagemap_path[100]; //保存pagemap的路径
const int a1 = 100;//全局常量
int a2 = 200;
//计算虚拟地址对应的地址,传入虚拟地址vaddr,通过paddr传出物理地址,通过v_index传出页号,通过p_index传出页框
void mem_addr(unsigned long vaddr, unsigned long *v_index,unsigned long *paddr,unsigned long *p_index)
{
unsigned long pageSize =(unsigned long)getpagesize();//调用此函数获取系统设定的页面大小

unsigned long v_pageIndex = vaddr / pageSize;//计算此虚拟地址相对于0x0的经过的页面数
*v_index = v_pageIndex; //获取页号
uint64_t v_offset = v_pageIndex * sizeof(uint64_t);//计算在/proc/pid/page_map文件中的偏移量
unsigned long page_offset = vaddr % pageSize;//计算虚拟地址在页面中的偏移量
uint64_t item = 0;//存储对应项的值

int fd = open(pagemap_path, O_RDONLY);//以只读方式打开/proc/pid/page_map
if(fd == -1)//判断是否打开失败
{
printf("open /proc/self/pagemap error\n");
flag = false;
return;
}

if(lseek(fd, v_offset, SEEK_SET) == -1)//将游标移动到相应位置,即对应项的起始地址且判断是否移动失败
{
printf("sleek error\n");
flag = false;
return;
}

if(read(fd, &item, sizeof(uint64_t)) != sizeof(uint64_t))//读取对应项的值,并存入item中,且判断读取数据位数是否正确
{
printf("read item error\n");
flag = false;
return;
}
if((((uint64_t)1 << 63) & item) == 0)//判断present是否为0
{ printf("page present is 0\n");
flag = false;
return ;
}

unsigned long phy_pageIndex = (((uint64_t)1 << 55) - 1) & item;//计算物理页号,即取item的bit0-54
*p_index = phy_pageIndex;//获取页框

*paddr = (phy_pageIndex * pageSize) + page_offset;//再加上页内偏移量就得到了物理地址
close(fd);
}

int main()
{
int b1 = 100;//局部变量
int b2 = 100;
static int c1 = 100;//局部静态变量
static int c2 = 100;
const int d1 = 100;//局部常量
const int d2 =100;

unsigned long phy = 0;//物理地址
unsigned long v_index = 0;//虚拟页号
unsigned long p_index = 0;//物理页框

double *p1 = (double*)malloc(10000*sizeof(double));//动态内存
double *p2 = (double*)malloc(10000*sizeof(double));

int g = getpid();//获取进程id
char buf[10];
sprintf(buf,"%d",g);
strcat(pagemap_path,"/proc/");
strcat(pagemap_path,buf);
strcat(pagemap_path,"/pagemap");//拼接文件路径
printf("当前进程id:%d\n",getpid());
mem_addr((unsigned long )&b2, &v_index, &phy, &p_index);
mem_addr((unsigned long )&a1, &v_index, &phy, &p_index);
printf("全局常量a1:virtual addr = %lx , virtual index = %lx , physical addr = %lx , physical index = %lx\n",(unsigned long)&a1, v_index, phy, p_index);
mem_addr((unsigned long)&a2, &v_index, &phy, &p_index);
printf("全局变量a2:virtual addr = %lx , virtual index = %lx , physical addr = %lx , physical index = %lx\n",(unsigned long)&a2, v_index, phy, p_index);

mem_addr((unsigned long)&b1, &v_index, &phy, &p_index);
printf("局部变量b1:virtual addr = %lx , virtual index = %lx , physical addr = %lx , physical index = %lx\n", (unsigned long)&b1, v_index, phy, p_index);
mem_addr((unsigned long)&b2, &v_index, &phy, &p_index);
printf("局部变量b2:virtual addr = %lx , virtual index = %lx , physical addr = %lx , physical index = %lx\n", (unsigned long )&b2, v_index, phy, p_index);

mem_addr((unsigned long)&c1, &v_index, &phy, &p_index);
printf("局部静态变量c1:virtual addr = %lx , virtual index = %lx , physical addr = %lx , physical index = %lx\n", (unsigned long)&c1, v_index, phy, p_index);
mem_addr((unsigned long)&c2, &v_index, &phy, &p_index);
printf("局部静态变量c2:virtual addr = %lx , virtual index = %lx , physical addr = %lx , physical index = %lx\n", (unsigned long)&c2, v_index, phy, p_index);

mem_addr((unsigned long )&d1, &v_index, &phy, &p_index);
printf("局部常量d1:virtual addr = %lx , virtual index = %lx , physical addr = %lx , physical index = %lx\n", (unsigned long)&d1, v_index, phy, p_index);
mem_addr((unsigned long)&d2, &v_index, &phy, &p_index);
printf("局部常量d2:virtual addr = %lu , virtual index = %lx , physical addr = %lx , physical index = %lx\n", (unsigned long)&d2, v_index, phy, p_index);

mem_addr((unsigned long)p1, &v_index, &phy, &p_index);
printf("动态内存p1:virtual addr = %lx , virtual index = %lx , physical addr = %lx , physical index = %lx\n", (unsigned long)p1, v_index, phy, p_index);
mem_addr((unsigned long)p2, &v_index, &phy, &p_index);
printf("动态内存p2:virtual addr = %lx , virtual index = %lx , physical addr = %lx , physical index = %lx\n", (unsigned long)p2, v_index, phy, p_index);

free(p1);
free(p2);

sleep(100000);
return 0;
}

编译运行结果如下:

3.3.1

从上图可以看出,不同pid的进程,只有全局常量的物理地址和物理页号相同,其他的地址均不相同。(这里不知为何同样的代码,直接&取地址,虚拟地址竟然不一样。。。)

如何通过扩充实验验证不同进程的共享库具有同一物理地址?

直接使用上述代码对函数名取地址的方法不可行,计算出来不同进程的共享库地址是相同的,但是自定义的函数地址也相同,不是很合理。

更改一下代码,采用另一种方式进行验证:

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
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
#include <string.h>

//计算虚拟地址对应的地址,传入虚拟地址vaddr,通过paddr传出物理地址,通过v_index传出页号,通过p_index传出页框
void mem_addr(unsigned long pid,unsigned long vaddr, unsigned long *v_index,unsigned long *paddr,unsigned long *p_index)
{
unsigned long pageSize =(unsigned long)getpagesize();//调用此函数获取系统设定的页面大小

unsigned long v_pageIndex = vaddr / pageSize;//计算此虚拟地址相对于0x0的经过的页面数
*v_index = v_pageIndex; //获取页号
uint64_t v_offset = v_pageIndex * sizeof(uint64_t);//计算在/proc/pid/page_map文件中的偏移量
unsigned long page_offset = vaddr % pageSize;//计算虚拟地址在页面中的偏移量
uint64_t item = 0;//存储对应项的值

char path[200];
sprintf(path,"%s%lu%s","/proc/",pid,"/pagemap");
int fd = open(path, O_RDONLY);//以只读方式打开/proc/pid/page_map
if(fd == -1)//判断是否打开失败
{
printf("open /proc/self/pagemap error\n");
flag = false;
return;
}

if(lseek(fd, v_offset, SEEK_SET) == -1)//将游标移动到相应位置,即对应项的起始地址且判断是否移动失败
{
printf("sleek error\n");
flag = false;
return;
}

if(read(fd, &item, sizeof(uint64_t)) != sizeof(uint64_t))//读取对应项的值,并存入item中,且判断读取数据位数是否正确
{
printf("read item error\n");
flag = false;
return;
}
if(is_bigendian()){//da duan
if((((uint64_t)1 << 63) & item) == 0)//判断present是否为0
{
printf("page present is 0\n");
flag = false;
return ;
}
unsigned long phy_pageIndex = (((uint64_t)1 << 55) - 1) & item;//计算物理页号,即取item的bit0-54
*p_index = phy_pageIndex;//获取页框

*paddr = (phy_pageIndex * pageSize) + page_offset;//再加上页内偏移量就得到了物理地址
}else{ //xiao duan
if((((uint64_t)1<<63) & item) == 0)//判断present是否为0
{
printf("page present is 0\n");
flag = false;
return ;
}
unsigned long phy_pageIndex = (0xfffffffffffffe00) & item;//计算物理页号,即取item的bit0-54
*p_index = phy_pageIndex;//获取页框
*paddr = (phy_pageIndex * pageSize) + page_offset;//再加上页内偏移量就得到了物理地址
}
close(fd);
}

int main(int argc,char* argv[])
{
unsigned long phy = 0;//物理地址
unsigned long v_index = 0;//虚拟页号
unsigned long p_index = 0;//物理页框
mem_addr(atoi(argv[1]), (unsigned long)strtol(argv[2],NULL,16),&v_index, &phy, &p_index);
printf("virtual addr = %s , virtual index = %lx , physical addr = %lx , physical index = %lx\n", argv[2], v_index, phy, p_index);

return 0;
}

首先运行同一份代码的两个进程,pid分别为23884和24133,查看他们各自的maps,如图:

3.3.2

3.3.3

可以看到他们都使用了ld-2.27.so共享库,我们将两个进程的pid和共享库虚拟地址值作为参数,输入上述代码编译好的程序中,求他们的物理地址和物理页框号,运行结果如下:

ld-2.27.so

从上图可以看到,不同进程链接同一个共享库,他们各自的虚拟地址不同,但是对应同一个物理地址。测试其他的库,依然可以看到同一个共享库的物理地址相同,如图:

libdl-2.27.so


本文作者: ziyikee
本文链接: https://ziyikee.fun/2021/12/07/OS%E5%AE%9E%E9%AA%8C%E4%B8%89/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!