虚拟内存的分配brk/sbrk

Linux进程级的内存管理
     首先,我们可以了解一下一个进程的内核空间:

可以看到一个进程地址空间的主要成分为:
  • 正文:这是整个用户空间的最低地址部分,存放的是指令(也就是程序所编译成的可执行机器码)

  • 初始化数据段:这里存放的是初始化过的全局变量

  • 未初始化数据段:这里存放的是未初始化的全局变量

  • Heap:堆,这是我们本文重点关注的地方,堆自低地址向高地址增长,后面要讲到的brk相关的系统调用就是从这里分配内存

  • Stack:这是栈区域,自高地址向低地址增长

  • 命令行参数和环境变量:用户调用的最底层。

 
我们都知道,在malloc分配空间时是在Heap上分配的,实质上,Linux维护一个break指针,这个指针指向堆空间的某个地址。从堆起始地址到break之间的地址空间为映射好的,可以供进程访问;而从break往上,是未映射的地址空间,如果访问这段空间则程序会报错。

由上文知道,要增加一个进程实际的可用堆大小,就需要将break指针向高地址移动。Linux通过brk和sbrk系统调用操作break指针。两个系统调用的原型如下:

 

栈:编译器自动生成代码维护
堆:地址是否映射,映射的空间是否被管理.
1.brk/sbrk 内存映射函数

malloc是c中常用的内存操作函数,malloc动态的申请一块指定大小的内存,方便存放数据

c++中的new实际上除了malloc分配内存之外还会调用构造函数初始化数据

而brk/sbrk则是实现malloc的底层函数,其中brk是系统调用。操作起来更为灵活,但很多人往往不容易理解。

  1. int brk(void *addr);   //分配空间,释放空间
  2. void *sbrk(intptr_t increment); //返回空间地址

返回值:
    brk()成功返回0,出错返回-1,并且errno被设置为ENOMEM
    sbrk()成功返回上一个程序结束点,出错返回-1,并且errno被设置为 ENOMEM

 

 brk()和sbrk()改变程序间断点的位置(break指针)。程序间断点就是程序数据段的结尾。(程序间断点是为初始化数据段的起始位置).通过增加程序间断点进程可以更有效的申请内存

       当addr参数合理、系统有足够的内存并且不超过最大值时brk()函数将数据段结尾设置为addr,即间断点设置为addr
       sbrk()将程序数据空间增加increment字节。当increment为0时则返回程序间断点的当前位置。   

什么是程序间断点呢?
可以这样理解,进程在内存中被分为代码区,数据区,栈区和堆区。程序间断点指向堆区的起始位置。同时他也是数据段的结尾。
Linux进程内存分布,地址从低到高依次是代码段,数据段,堆,栈,堆栈之间是mmap映射的共享内存空间以及共享库,再上是命令行参数,环境变量等,其中栈是从高地址向低地址分配,堆是从低地址向高地址分配。程序间断点就是当前进程映射虚拟地址的终止位置,通过移动这个位置来维护进程的映射的内存.而brk/sbrk的作用就是维护这个位置.

brk改变绝对位置
sbrk相对改变位置 >0则增加位置 <0则收缩位置

#include <unistd.h>  
#include <stdio.h>  
#include <stdlib.h>  
  
main()  
{  
    void *p;  
    void *old;                       //存放原始间断点位置  
    int  r;  
  
    old = p = sbrk(0);               //sbrk(0)返回当前的程序间断点  
    if (p == (void*)-1)  
        printf("sbrk error\n"), exit(-1);  
    printf("当前程序间断点位置:%p\n", p);  
    p = sbrk(1);                     //位置+1,但返回的是之前的位置而不是+1后的位置  
    printf("%p\n", p);  
    p = sbrk(1);                     //位置实际+2,但返回的是位置+1的值  
    printf("%p\n", p);  
  
    r = brk(old);                    //将程序间断点设置为原始位置  
    if (r == -1)  
        printf("brk error\n"), exit(-1);  
    printf("原始位置:%p\n", sbrk(0));//输出原始位置与第一次调用sbrk(0)的位置相同  
}  

输出结果:

应用brk/sbrk小案例,求1-1000的素数.

#include <unistd.h>  
#include <stdio.h>  
#include <stdlib.h>  
  
int judge(int num)  
{  
    int i;  
    for(i=2; i<num; i++) {  
        if(num%i == 0)  
            return 0;     
    }  
    return 1;  
}  
  
main()  
{  
    int  *p;  
    void *old;  
    int  i;  
  
    old = sbrk(0);  
    if (old == -1)  
        printf("sbrk error\n"), exit(-1);  
    p = (int*)old;  
      
    for(i=1; i<=1000; i++) {  
        if(judge(i) == 1) {  
            p = sbrk(4);  //int类型所以又移4个字节  
            if (p == -1)  
                printf("brk error\n"), exit(-1);  
            *p = i;  
        }     
    }  
      
    p = (int*)old;  
    while(p != sbrk(0)) {  
        printf("%d ", *p);  
        p ++;  
    }  
                                          
    if (brk(old) == -1)    //输出完毕,收回空间  
        printf("brk error\n"), exit(-1);  
}  

 

posted @ 2017-02-20 16:19  ren_zhg1992  阅读(960)  评论(0)    收藏  举报