linux系统编程-内存管理day02

  • 本节总结了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来做填充。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,928评论 6 523
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 97,032评论 3 410
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 173,382评论 0 370
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,580评论 1 304
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,558评论 6 403
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,018评论 1 316
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,261评论 3 432
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 41,328评论 0 281
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,858评论 1 328
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,843评论 3 351
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,954评论 1 358
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,565评论 5 352
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,251评论 3 342
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,677评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,834评论 1 278
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,558评论 3 383
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,033评论 2 368

推荐阅读更多精彩内容