操作系统实验三 内存分配及回收研究

前言

本次实验跟前两次相比简单许多,主要是体会底层的一些运行机制。其实,要说简单,也不是真的很简单,毕竟还是存在一些更底层的东西需要我们去探讨。接下来就让我们通过实验来感受一下。(因为本次实验每次分配的地址可能有变化,会出现地址跟本文内容不同的情况。大家应该结合自己的实际情况进行分析,我的博客只能给大家提供实验的具体思路)

1.实验目的

掌握Linux操作系统的内存分配与使用的编程接口;

掌握Linux操作系统中进程虚拟内存的映射;

通过本次实验体会Linux操作系统中内存的分配模式;

2.实验内容

学习Linux系统的内存动态申请、释放的函数;

学习Linux操作系统提供的进程地址映射的工具;

观察进程用户空间的虚存管理变化,包括堆区、文件映射区和栈区;

3.实验的内容与过程

任务1

写出/proc/$pid/maps数据各字段含义;(pid是对应的进程号,终端会输出的,因为原代码中getpid函数会获取对应的进程号)

cb98f1f2391345a49f50a01d20ef83b2.png

一个终端用来运行代码,一个终端用来查看信息:(编译用gcc命令即可)

ab04b10c80a842528f92715861900f89.png

数据各字段含义:

第一列:虚拟地址空间起始地址

第二列:虚拟地址空间结束地址

第三列:虚拟地址空间的属性。每种属性用一个字段表示,r表示可读,w表示可写,x表示可执行,p和s共用一个字段,互斥关系,p表示私有段,s表示共享段,如果没有相应权限,则用‘-’代替;

第四列:虚拟内存区域在被映射文件中的偏移量

第五列:文件的主设备号和次设备号

第六列:设备的节点号,0表示没有节点与内存相对应

第七列:被映射文件的文件名

任务2 

运行代码,观察进程空间变化(/proc/$pid/maps)过程,截图简要说明进程空间变化的内容,截图画出变量所在内存段;

第一次cat /proc/$pid/maps:

7181bd3e66814af3b3c3b183c9ef5f30.jpeg

注:BSS段(Block-started by Symbol段)是指未被初始化的全局变量或者静态变量所占用的内存区段。Stack_var0和Stack_var1都为临时变量。在数据段中,初始化了的全局变量、静态变量在data段,未初始化的在bss段

a65e2f05579c46e39bf7c3018e1feb52.jpeg

分析:

通过观察发现,在终端中已经输出了一些信息。再仔细看这些输出信息,通过与上张图的地址对比,我们发现每个地址都在其对应位置的起始地址和结束地址之间。如:Stack_var0和Stack_var1都为临时变量,因此两者应该存储在栈内,通关观察,确实如此!(0x7fffe785abcc和0x7fffe785abc8在0x7fffe783b000~0xfffe785c000中,以后的情况也是这么分析)接下来我们结合代码看看此时程序运行到哪里了。

主函数:

8a9695a4219e4d7ab631aaaf6e12715f.png

分析:

可以看到在主函数中先输出了进程号,然后不断调用了很多自定义函数,接下来我们就一个一个查看自定义函数。

TextLocation函数:

b4fa126580dc4795871255c57b1a1c5b.png

分析:可以看到输出信息和终端对上了,但还是会继续运行。

StackLocation函数:

3ac293db95eb4b3d84a9c779af7f2f59.png

分析:也是打印出相应信息,然后继续代码运行。

DataSegmentLocation函数:

2e509021e51c4cbd95cad7e634e0e548.png

分析:打印相应的信息,遇到了getchar函数,此时代码停止运行,等待我们输入一个字符后才继续执行。接下来我们输入回车,将代码继续执行下去。

第二次cat /proc/$pid/maps:

9374a88e1de64cb9b4abd45d461bb4d1.jpeg

a722c2f8bd4345899dee96089c75812d.jpeg

分析:

输入了回车后,我们发现输出的信息中堆多了一行。通过观察我们可以发现,输出的堆区地址在多出的这一行新建堆区的起始地址与结束地址之内。接下来我就结合代码来看看是怎么回事。(主函数中已经执行完了DataSegmentLocation函数,接下来执行HeapLocation函数)

HeapLocation函数:

ebe413b0b66b4437ad3de5df0bae7f7e.png

分析:

首先代码中先打印出相关信息,然后又创建了两个大小都为60MB的堆,接着输出这两个堆的位置。通过输出信息,第一个堆的位置为0x7f8e25e9b010,第二个堆的位置为0x7f8e2229a010,0x7f8e25e9b010-0x7f8e2229a010=0x3C0 1000=62,918,656B=60MB。因此,我们可以判断这两个堆是连续的。接着回车

第三次cat /proc/$pid/maps:

8a26cc146e914848bc6b1be54c08b128.png

de2e5eee744d48719bc03e22547e6727.png

分析:

通过输出提示还有cat的输出结果我们可以知道刚刚动态创建的堆已经释放掉了。下面结合代码看看。(代码部分在HeapLocation中)

9318c54e89f944c498ccf1492045a3bd.png

分析:

通过查看代码,新建堆确实释放了。接着回车继续执行。

第四次cat /proc/$pid/maps:

a5b5534362f943c6967f21b0ea69ea8f.jpeg

c8612b494756432b9276fc6dd073730e.jpeg

分析:

通过观察发现,发现在终端输出了文件映射的地址。通过观察图二的文件映射名并比较其的起始地址和结束地址,我们发现文件映射的地址确实是在起始地址与结束地址之间。接下来结合代码分析。

7a00a7f14e92486dbac858ace4e4733f.png

分析:

这个代码主要是打开文件“map-file.txt”,如果没有就创建一个。然后往这个文件内写内容。运行后可以发现目录下多了个文件,里面有输入的内容。

971da544764f481ea228cdd87101f472.png

f2a764ef42c24889930fea4cf0a3c841.png

接下来输入回车继续运行。

第五次cat /proc/$pid/maps:

1411c1be3034492c972e198745d20929.png

分析:

可以看到,刚刚的文件映射已经没有了,结合代码看看。

eedf308b8df04a5891ccf9b9038dccf4.png

分析:

主要是关闭文件,所以文件映射才会消失。

至此,任务2已经全部完成了。

任务3:

编写程序,连续申请分配六个128MB空间(记为1~6号),然后释放第2、3、5号的128MB空间。然后再分配1024MB,记录该进程的虚存空间变化(/proc/$pid/maps),每次操作前后检查/proc/$pid/status文件中关于内存的情况,简要说明虚拟内存变化情况。推测此时再分配64M内存将出现在什么位置,实测后是否和你的预测一致?解释说明用户进程空间分配属于课本中的离散还是连续分配算法?首次适应还是最佳适应算法?

第三次cat /proc/$pid/maps(再分配1024MB)

cb23fd3945c849cd9220df6c4157f245.png

14511519d6fa4031adee5d5c71354e38.jpeg

分析:

通过与第二次cat的结果对比,只有新输出的第一行的起始地址改变了,说明第一行的起始地址到结束地址中存放着heap[5]和big heap。通过计算0x7f457c613000-0x7f453c612000=0x40001000=1024MB,改变的起始地址和原来的起始地址之间的差的大小刚好为1024MB,更能说明bigheap和heap[5]是连续的。

第四次cat /proc/$pid/maps(再分配64MB)

e776b4a3f735430e835af0848a0b479d.png

6378d7d74c6e4d5f82c5ec2fe4b28bba.jpeg

分析:

通过对比,我们发现newheap的空间是跟在heap[0]后面的,因为heap[1]已经释放了,128MB的大小是足够存放64MB的大小的。因此,我们能得出结论:用户进程空间分配是连续分配、首次适应算法。(找到第一个符合要求的空间便进行分配)。

代码

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <malloc.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>

int main(int argc, char **argv)
{
    void *heaps[6], *bigHeap; //存放六个空间较小且连续的堆和一个空间较大的堆
    int i = 0;

    
	printf("Process ID:%d\n",getpid());//打印出进程号   

    printf("Malloc 6 heaps(size: 128MB):\n"); //输出提示信息
    for (int i = 0; i < 6; i++)
    {
        heaps[i] = malloc(128 * 1024 * 1024);                 //为堆分配空间(128MB)
        printf("\tAddress of heap[%d] is %p\n", i, heaps[i]); //输出堆的地址
    }
    printf("Please check /proc/PID/maps and /proc/PID/status file to check the change,press Enter to continue!\n"); //输出信息
    getchar();                                                                                                      //等待下一次操作(回车)

    //释放2、3、5号堆
    free(heaps[1]);
    free(heaps[2]);
    free(heaps[4]);
    printf("Please check /proc/PID/maps and /proc/PID/status file to check the change,press Enter to continue!\n"); //输出信息
    getchar();

    printf("Malloc big heap(size: 1024MB):\n"); //创建空间大的堆
    bigHeap = malloc(1024 * 1024 * 1024);       // 1024MB的堆
    printf("\tbig heap is %p\n", bigHeap);
    printf("Please check /proc/PID/maps and /proc/PID/status file to check the change,press Enter to continue!\n"); //输出信息
    getchar();     //等待下一次操作(回车)
    
    printf("Malloc new heap(size: 64MB):\n"); //再创建64MB的内存
    void *newHeap=malloc(64*1024*1024);
    printf("\tnew heap is %p\n", newHeap);
    printf("Please check /proc/PID/maps and /proc/PID/status file to check the change,press Enter to continue!\n"); //输出信息
    getchar();     //等待下一次操作(回车)

    //释放剩下的堆
    free(heaps[0]);
    free(heaps[3]);
    free(heaps[5]);
    free(bigHeap);
    printf("Please check /proc/PID/maps and /proc/PID/status file to check the change,press Enter to continue!\n"); //输出信息
    getchar();//等待下一次操作(回车)

    return 0;

}

在实验中,我发现一个有趣的现象。我们新生成的堆的地址比旧堆地址要小,这跟栈是不一样的(栈是从低地址开始,往高地址开辟)。经过询问老师知道这跟结构有关系,如下图:

849df6e3393342e1b9316f5bb6bcbb9d.png

感兴趣的同学可以自行了解,我就不过多赘述。

至此,我们的实验大功告成!如果大家有什么想法,可以在评论区提出,一起交流。

  • 19
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值