虚拟地址空间(二)

前言

在我最开始写博客的时候,第一篇就介绍了虚拟地址空间,但当时受限于自己的水平,并且很多内容当时还没有介绍,所以当时写的比较简单,这篇文章算是还愿了吧。

/proc/{pid}/maps

linux的根目录下有很多目录,比如/etc通常是存放参数文件的,/bin通常存放二进制的可执行程序,/lib通常存放库文件,/include存放头文件,/proc存放的则是进程相关的文件。对每个正在运行的文件,/proc下都有一个文件名为该进程pid号的文件夹,例如有一个pid为10086的进程正在运行,那么就存在/proc/10086这个路径,路径下会有一个maps文件,这个文件展示了当前进程的虚拟地址空间,如下图:
在这里插入图片描述
文件的每列的含义如下:

第一列第二列第三列第四列第五列第六列第七列
段起始地址段结束地址段属性段在文件中的偏移映射的i-node号映射文件所属的设备号映射的文件名

其中,段属性中,rwx分别表示读、写和执行,p和s公共一位,p为私有,而s为共享。如果是匿名映射,则第四、五、六列均为0。上面的文件中,我们可以清楚地看到堆区、栈区和链接的动态库,最上面五行是代码段、bss段、rodata段、data段等。通过maps,我们可以验证之前总结过的普通变量、指针变量、static变量等的存储位置。

#include <unistd.h>
#include <iostream>
using namespace std;

static int c = 10;
int main(){
    cout << getpid() <<endl;
    int a = 10;
    int *b = new int(20);
    cout << "a:" << &a << ",b:" << b << ",c:" << &c << endl;
    delete(b);
    return 0;
}

先获取进程pid号的目的是找到maps文件所在的路径,通过gdb运行程序,使程序停止在delete(b),程序打印信息如下:

在这里插入图片描述
打开路径为/proc/32394/maps的文件如下:
在这里插入图片描述
显然普通变量a在[stack]中,b在[heap]中,c在第5行的[data]段中,这说明我们之前的讨论是正确的,有兴趣的话还可以通过maps验证之前其他关于储存位置的讨论。

VMA

虚拟内存的管理在内核代码中是通过mm_struct来完成的,mm_struct被记录在task_struct中,是task_struct的成员之一,mm_struct中包含很多vm_area,简称为VMA,每个VMA就代表一个段,上面的例子中包含40个段,也就是包含40个VMA,VMA的具体结构如下:

struct vm_area_struct {
    unsigned long vm_start;                 //起始地址
    unsigned long vm_end;					//结束地址
 
    struct vm_area_struct* vm_next, * vm_prev;     //VMA通过双向链表串在一起
    struct rb_node vm_rb;							//为了便于查找,VMA同时放置在红黑树中
    unsigned long rb_subtree_gap;					//红黑树中,该节点左右子树与该节点的最大间隙
 
    struct mm_struct* vm_mm;				//属于哪个mm_struct
    pgprot_t vm_page_prot;					//VMA的访问权限
    unsigned long vm_flags;					//VMA标志位
 
    struct {
         struct rb_node rb
         unsigned long rb_subtree_last;
    } shared;
 
    struct list_head anon_vma_chain;
    struct anon_vma* anon_vma;               //指向匿名域的指针
 
    const struct vm_operations_struct* vm_ops;    //VMA操作函数
 
    unsigned long vm_pgoff;					//文件映射的偏移量
    struct file* vm_file;					//被映射的文件
    void* vm_private_data;					

};

有时程序会映射一段共享内存到虚拟地址空间中,此时在mm_struct中,增加了一个VMA,用来表示映射的共享内存,但此时并没有分配物理内存,当对页面进行操作时,通过缺页中断分配物理内存。

malloc的内存分配

malloc函数是c语言提供的库函数,库函数是不能直接操作内存的,需要通过系统调用进入内核态操作,malloc的系统调用由glibc提供,包括brk系统调用和mmap系统调用两种:

  • 当申请的内存空间小于128k时,使用brk系统调用,brk系统调用仅移动_edata指针,表示增加堆上使用的内存,基于写时复制,此时也未分配物理内存,对内存进行操作时才分配物理内存。
  • 当申请的内存空间大于128k时,使用mmap系统调用,mmap系统调用会在堆和动态库链接的中间分配一段内存,而不是在原来的heap上分配。
  • 下图假设内存使用了三次malloc,两次使用brk系统调用,一次使用mmap系统调用,此时如果代码中freebrk系统调用第一次分配的内存,则不能马上释放,需要等待brk第二次分配的内存释放后才能释放,当然虽然不能马上释放,但这段内存可以被程序复用。但mmap分配的内存没有这个限制,可以直接被释放。
    在这里插入图片描述
    我们通过下面的代码来验证这里的观点:
#include <iostream>
#include <unistd.h>
using namespace std;

int main(){
    cout << getpid() <<endl;
    void *a = malloc(256*1024);   
    int *b = new int(20);
    cout << "a:" << a << ",b:" << b << endl;
    delete(b);
    free(a);
    return 0;
}

程序打印结果:
在这里插入图片描述

打开对应的maps文件:
在这里插入图片描述

显然a分配在heap和/usr/lib/libc-2.33.so之间的匿名映射中,而b在[heap]中,验证成功。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值