美文网首页我爱编程
linux系统编程-内存管理day02

linux系统编程-内存管理day02

作者: 桔子满地 | 来源:发表于2018-05-25 15:02 被阅读0次
    • 本节总结了8.3~8.5的内容

    数据段的管理

    在老版本的Unix系统中,堆和栈还在同一个段中。

    • 堆中动态存储器的分配由数据段的底部向上生长;栈从数据段的顶部向着堆往下生长。
    • 堆和栈的分界线叫做中断(break)或中断点(break point)。

    在现代系统中,数据段存在于它自己的内存映射中,仍用中断点来标记映射的结束地址。
    提供以下函数:

    #include <unistd.h>
    int brk(void *end);
    void *sbrk(intptr_t increment);
    
    • 调用brk()会设置中断点(数据段的末端)的地址为end。
    • 调用sbrk()将数据段末端增加increment字节,increment可正可负。

    匿名存储器映射

    首先来看看伙伴内存分配算法
    glibc的内存分配使用了数据段和内存映射。
    实现malloc( )最经典方法就是将数据段分为一系列的大小为2的幂的块,返回最小的符合要求的那个块来满足请求。 释放只是简单的将这块区域标记为未使用。

    • 优点:高速、简单
    • 缺点:产生两种类型的碎片:1.内部碎片:使用的内存块大于请求的大小;2.外部碎片:空闲存储器合计起来足够满足一个请求,但是没有一个单独的空闲块可以来处理这个请求。
    • 另外:一个长期存在的内存分配可能把另外的空闲空间“栓”住。(内存中已经被分配块A和块B,块A处于中断点位置,而块B处于块A的下方,当块B被释放,而块A没有被释放时,glibc也不能相应的调整中断点。)

    匿名内存映射(anonymous memory mapping)
    因为伙伴内存分配算法存在可能被“栓”住的问题,glibc对于较大的分配,并不使用堆而是创建一个匿名内存映射来满足要求。

    • 一个匿名内存映射只是一块已经用0初始化的大的内存块,以供用户使用。(可以把它想成为单独为某次分配而使用的堆)
    • 因为这种映射的存储不是基于堆的,所以并不会在数据段内产生碎片。
    • 综合一下两者的优缺点,glibc的malloc( )使用数据段来满足小的分配(小于128KB),而匿名内存映射则用来满足大的分配。(临界点一般是128KB,但其值是可调的)

    创建匿名存储器映射
    匿名存储器映射与内存映射很像:

    /* for memory map */
    #include <sys/mman.h>
    void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
    int munmap(void *start, size_t length);
    
    • 因为不需要打开和管理文件,创建匿名存储器映射要比创建基于文件的存储器映射更简单。两者最关键的差别在于是否有匿名标记
      一个匿名映射的例子:
    void *p;
    p = mmap(NULL, 512*1024, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    if (p == MAP_FAILED)
      perror("mmap");
    else
      /* 'p' points at 512 KB of anonymous memory ... */
    

    参数含义:

    • 第一个参数是start,被设为NULL,意味着匿名映射可以让内核安排的子任意地址上。(non-NULL值也是可以的,只要它是页对齐的,但这样会限制可移植性)
    • prot参数经常被设置为PROT_READ和PROT_WRITE位,使得映射是可读可写的。一块不能读写的空存储器映射是没有用的。
    • flags参数设置MAP_ANONYMOUS位,来使得映射是匿名的,设置MAP_PRIVATE位,使得映射是私有的。
    • 假如MAP_ANONYMOUS被设置了,fd和offset参数将被忽略。(一般将fd写为-1, 考虑到可移植性)
      使用匿名映射进行分配的一个好处是所有的页都已经用0进行了初始化。
      系统调用munmap( )释放一个匿名映射,归还已分配的内存给内核。
    int ret;
    ret  = munmap(p, 512*1024);
    if (ret)
      perror("munmap");
    

    映射到/dev/zero

    其他Unix系统(例如BSD),并没有MAP_ANONYMOUS标记。它们使用特殊的设备文件/dev/zero来实现了一个类似的解决方案。这个设备文件提供了和匿名内存相同的语义。

    void *p;
    int fd;
    /* open /dev/zero for reading and writing */
    fd = open("/dev/zero", O_RDWR);
    if (fd < 0) {
      perror("open");
      return -1;
    }
    /* map [0, page_size) of /dev/zero */
    p = mmap(NULL, getpagesize( ), PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
    if (p == MAP_FAILED) {
      perror("mmap");
      if (close(fd))
        perror("close");
      return -1;
    }
    /* close /dev/zero, no longer needed */
    if (close(fd))
      perror("close");
    /* 'p' points at one page of memory, use it ... */
    

    采用这种映射方式的存储器也是用munmap( )来取消映射的。
    然而这种方法因为要打开和关闭设备文件,所以会有额外的开销。因此匿名内存映射是一种较块的方法。

    高级存储器分配

    许多存储分配操作都是为内核的参数所控制的(例如malloc( )时,是从数据段分配还是从匿名内存映射,这个临界值,是内核参数),但是程序员可以修改这些参数!

    • 注:不应该将这个修改用于调试和教学以外的其它地方,它们是不可移植的,而且会将glibc内存分配系统的一些底层细节暴露给你的程序。
    1. mallopt( )函数:
      调用mallopt( )会将由param确定的存储管理相关的参数设为value。
      linux目前支持六种param值,被定义在<malloc.h>中:
      • M_CHECK_ACTION
      • M_MMAP_MAX
      • M_MMAP_THRESHOLD(数据段和匿名内存映射的临界值)
      • M_MXFAST
      • M_TOP_PAD
        程序必须在调用malloc( )或是其它内存分配函数前使用mallopt( ):
    /* use mmap( ) for all allocations over 64KB */
    ret = mallopt(M_MMAP_THRESHOLD, 64 * 1024);
    if (!ret)
        fprintf(stderr, "mallopt failed! \n");
    
    1. malloc_usable_size( ):
      用于查询一块已分配内存中有多少可用内存:
    #include <malloc.h>
    size_t malloc_usable_size(void *ptr);
    
    • 因为glibc可能扩大动态内存来适应一个已存在的块或者匿名映射,动态存储器分配中的可使用空间可能会比请求的大。当然,永远不可能比请求的小。
    size_t len = 21;
    size_t size;
    char *buf;
    buf = malloc(len);
    if (!buf) {
        perror("malloc");
        return -1;
    }
    size = malloc_usable_size(buf);
    /* we can actually use 'size' bytes of 'buf' ... */
    

    则我们可用过malloc_usable_size( )这个函数得到ptr指向的内存中实际可用大小。

    1. malloc_trim( ):
      该函数允许程序强制glibc归还所有的可释放的动态内存给内核:
    #include <malloc.h>
    int malloc_trim(size_t padding);
    
    • 调用malloc_trim( )成功时,数据段会尽可能地收缩,但是填充字节数被保留下来。成功时返回1, 失败时返回0。
    • 一般来说,每当空闲的内存到达M_TRIM_THRESHOLD字节时,glibc会自动做这种收缩。使用M_TOP_PAD来做填充。

    相关文章

      网友评论

        本文标题:linux系统编程-内存管理day02

        本文链接:https://www.haomeiwen.com/subject/hokyjftx.html