前言
本次实验跟前两次相比简单许多,主要是体会底层的一些运行机制。其实,要说简单,也不是真的很简单,毕竟还是存在一些更底层的东西需要我们去探讨。接下来就让我们通过实验来感受一下。
1.实验目的
掌握Linux操作系统的内存分配与使用的编程接口;
掌握Linux操作系统中进程虚拟内存的映射;
通过本次实验体会Linux操作系统中内存的分配模式;
2.实验内容
学习Linux系统的内存动态申请、释放的函数;
学习Linux操作系统提供的进程地址映射的工具;
观察进程用户空间的虚存管理变化,包括堆区、文件映射区和栈区;
3.实验的内容与过程
任务1:
写出/proc/$pid/maps数据各字段含义;(pid是对应的进程号,终端会输出的,因为原代码中getpid函数会获取对应的进程号)
一个终端用来运行代码,一个终端用来查看信息:(编译用gcc命令即可)
数据各字段含义:
第一列:虚拟地址空间起始地址
第二列:虚拟地址空间结束地址
第三列:虚拟地址空间的属性。每种属性用一个字段表示,r表示可读,w表示可写,x表示可执行,p和s共用一个字段,互斥关系,p表示私有段,s表示共享段,如果没有相应权限,则用‘-’代替;
第四列:虚拟内存区域在被映射文件中的偏移量
第五列:文件的主设备号和次设备号
第六列:设备的节点号,0表示没有节点与内存相对应
第七列:被映射文件的文件名
任务2:
运行代码,观察进程空间变化(/proc/$pid/maps)过程,截图简要说明进程空间变化的内容,截图画出变量所在内存段;
第一次cat /proc/$pid/maps:
注:BSS段(Block-started by Symbol段)是指未被初始化的全局变量或者静态变量所占用的内存区段。Stack_var0和Stack_var1都为临时变量。在数据段中,初始化了的全局变量、静态变量在data段,未初始化的在bss段
分析:
通过观察发现,在终端中已经输出了一些信息。再仔细看这些输出信息,通过与上张图的地址对比,我们发现每个地址都在其对应位置的起始地址和结束地址之间。如:Stack_var0和Stack_var1都为临时变量,因此两者应该存储在栈内,通关观察,确实如此!(0x7fffe785abcc和0x7fffe785abc8在0x7fffe783b000~0xfffe785c000中,以后的情况也是这么分析)接下来我们结合代码看看此时程序运行到哪里了。
主函数:
分析:
可以看到在主函数中先输出了进程号,然后不断调用了很多自定义函数,接下来我们就一个一个查看自定义函数。
TextLocation函数:
分析:可以看到输出信息和终端对上了,但还是会继续运行。
StackLocation函数:
分析:也是打印出相应信息,然后继续代码运行。
DataSegmentLocation函数:
分析:打印相应的信息,遇到了getchar函数,此时代码停止运行,等待我们输入一个字符后才继续执行。接下来我们输入回车,将代码继续执行下去。
第二次cat /proc/$pid/maps:
分析:
输入了回车后,我们发现输出的信息中堆多了一行。通过观察我们可以发现,输出的堆区地址在多出的这一行新建堆区的起始地址与结束地址之内。接下来我就结合代码来看看是怎么回事。(主函数中已经执行完了DataSegmentLocation函数,接下来执行HeapLocation函数)
HeapLocation函数:
分析:
首先代码中先打印出相关信息,然后又创建了两个大小都为60MB的堆,接着输出这两个堆的位置。通过输出信息,第一个堆的位置为0x7f8e25e9b010,第二个堆的位置为0x7f8e2229a010,0x7f8e25e9b010-0x7f8e2229a010=0x3C0 1000=62,918,656B=60MB。因此,我们可以判断这两个堆是连续的。接着回车
第三次cat /proc/$pid/maps:
分析:
通过输出提示还有cat的输出结果我们可以知道刚刚动态创建的堆已经释放掉了。下面结合代码看看。(代码部分在HeapLocation中)
分析:
通过查看代码,新建堆确实释放了。接着回车继续执行。
第四次cat /proc/$pid/maps:
分析:
通过观察发现,发现在终端输出了文件映射的地址。通过观察图二的文件映射名并比较其的起始地址和结束地址,我们发现文件映射的地址确实是在起始地址与结束地址之间。接下来结合代码分析。
分析:
这个代码主要是打开文件“map-file.txt”,如果没有就创建一个。然后往这个文件内写内容。运行后可以发现目录下多了个文件,里面有输入的内容。
接下来输入回车继续运行。
第五次cat /proc/$pid/maps:
分析:
可以看到,刚刚的文件映射已经没有了,结合代码看看。
分析:
主要是关闭文件,所以文件映射才会消失。
至此,任务2已经全部完成了。
任务3:
编写程序,连续申请分配六个128MB空间(记为1~6号),然后释放第2、3、5号的128MB空间。然后再分配1024MB,记录该进程的虚存空间变化(/proc/$pid/maps),每次操作前后检查/proc/$pid/status文件中关于内存的情况,简要说明虚拟内存变化情况。推测此时再分配64M内存将出现在什么位置,实测后是否和你的预测一致?解释说明用户进程空间分配属于课本中的离散还是连续分配算法?首次适应还是最佳适应算法?
第三次cat /proc/$pid/maps(再分配1024MB)
分析:
通过与第二次cat的结果对比,只有新输出的第一行的起始地址改变了,说明第一行的起始地址到结束地址中存放着heap[5]和big heap。通过计算0x7f457c613000-0x7f453c612000=0x40001000=1024MB,改变的起始地址和原来的起始地址之间的差的大小刚好为1024MB,更能说明bigheap和heap[5]是连续的。
第四次cat /proc/$pid/maps(再分配64MB)
分析:
通过对比,我们发现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;}
在实验中,我发现一个有趣的现象。我们新生成的堆的地址比旧堆地址要小,这跟栈是不一样的(栈是从低地址开始,往高地址开辟)。经过询问老师知道这跟结构有关系,如下图:
感兴趣的同学可以自行了解,我就不过多赘述。
至此,我们的实验大功告成!如果大家有什么想法,可以在评论区提出,一起交流。