首页天道酬勤malloc_trim释放内存,malloc初始化

malloc_trim释放内存,malloc初始化

张世龙 05-12 22:39 9次浏览

目录

一.使用

1.1 malloc和free

2.brk和sbrk

2.1 sbrk

2.2 brk

3. mmap/munmap

二.有关

3 .内存分配原理

四. malloc底边

一. 1.1使用malloc和free

参数:请求内存大小

返回值:正常返回请求区域的开头指针,失败时返回空。

1 # include stdio.H2 # include stdlib.h34 int main ({5(6char* str=) char * ) malloc ) 10 ); 7if(str==null )8perror (malloc fail ); 9 ) 10int*ptr=(int* ) malloc ) 10*sizeof ) int ); 11if(ptr==null ) 12perror('mallocfail ); 13 ) 14151617自由(str ); 18 //防止野针19 str=NULL; 20自由(ptr ); 21 ptr=空22返回0; 23 )首次调用malloc系统时,将分配33页(一页为4kb )的空间。 然后,重新申请空间将从33页空间的其馀空间申请。

1 #inc

lude <stdio.h> 2 #include <stdlib.h> 3 4 int main(){ 5 6 char *str = (char *)malloc(1); 7 if(str == NULL){ 8 perror("malloc fail"); 9 } 10 //只申请了1字节空间,但是这样遍历不会报错 11 for(int i=0; i<32*4096; i++){ 12 str[i] = 'a'; 13 } 14 15 return 0; 16 }

结果没有报错: 

 将代码改一下:

1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main(){ 5 6 char *str = (char *)malloc(1); 7 if(str == NULL){ 8 perror("malloc fail"); 9 } 10 //只申请了1字节空间,申请超过33页 11 for(int i=0; i<33*4096; i++){ 12 str[i] = 'a'; 13 } 14 15 return 0; 16 }

 结果报错:

         asjdzc申请大小为n的内存时,起始系统给你的空间会大于你申请的空间数,一般后面会有12字节的控制信息。这个控制信息一般不可以修改。

        2.brk和sbrk

        brk和sbrk底层维护了一个堆上的指针,以增量的方式管理动态内存(堆)。

        2.1 sbrk

 参数:申请空间大小,0不申请空间,大于0申请空间,小于0 释放空间

返回值:申请内存空间的起始地址,失败返回-1。

来一段代码说明一下:

        2.2 brk

 参数:需要将堆顶指针向后移动到哪个地址。移动的空间大小,就是申请的空间大小。

返回值:失败返回-1,成功放回0。

         3. mmap/munmap

mmap参数:

start:映射区的开始地址

length:映射区的长度

prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起

1 PROT_EXEC :页内容可以被执行2 PROT_READ :页内容可以被读取3 PROT_WRITE :页可以被写入4 PROT_NONE :页不可访问

flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体

1 MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。 2 MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。 3 MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。 4 MAP_DENYWRITE //这个标志被忽略。 5 MAP_EXECUTABLE //同上 6 MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。 7 MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。 8 MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。 9 MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。10 MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。11 MAP_FILE //兼容标志,被忽略。12 MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。13 MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。14 MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。

fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1

offset:被映射对象内容的起点

mmap返回值:

        成功返回指向该空间的起始地址,失败MAP_FAILED (that is, (void *) -1)被返回。

mmap作用:

        在文件映射区开辟一个大小为length的空间。

munmap参数:

        addr是调用mmap()时返回的地址,

        len是映射区的大小

munmap返回值:

        成功执行时,munmap()返回0。失败时,munmap返回-1。

munmap作用:

        该调用在进程地址空间中解除一个映射关系,即释放空间

 操作文件:

二.关系

        malloc的底层调用的是是brk和mmap来申请内存。对应free用的是brk和munmap释放内存。

三.内存分配原理

        从操作系统的角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)。

brk:将数据段最高地址往进程地址空间的高地址方向移动。mmap:在文件映射区申请一个空闲内存。

        这两种方式分配的都是虚拟内存,没有分配物理内存在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。

分配内存时有两种情况:

情况一:申请的内存小于128KB,申请内存是将堆顶指针向高地址方向移动,不会作初始化。

情况二:大于128k的内存,使用mmap分配内存,在堆和栈之间找一块空闲内存分配(对应独立内存,而且初始化为0)。

        默认情况下,malloc函数分配内存,如果请求内存大于128K(可由M_MMAP_THRESHOLD选项调节),那就不是去推堆顶指针了,而是利用mmap系统调用,从堆和栈的中间分配一块虚拟内存。

        原因:brk是以增量的形式申请空间的,brk分配的内存需要等到高地址内存释放以后才能释放。而mmap申请的内存可以单独释放。

 有一种情况:

       当最高地址空间的空闲内存超过128K(可由M_TRIM_THRESHOLD选项调节)时,执行内存紧缩操作(trim)。如下图。

        既然堆内内存brk和sbrk不能直接释放,为什么不全部使用 mmap 来分配,munmap直接释放呢? 

        首先brk释放的空间,并不一定真正释放了。如果没有释放可以被重复利用。而mmap申请的空间,直接调用munmap是将空间真正释放了。

        首先申请空间需要用用户态进入内核态,需要成本。而频繁调用mmap有用户态变为内核态的频率肯定会比用brk高。即CPU消耗会很高。

         在glibc 的 malloc 实现中,充分考虑了 sbrk 和 mmap 行为上的差异及优缺点,默认分配大块内存 (128k) 才使用 mmap 获得地址空间,也可通mallopt(M_MMAP_THRESHOLD, <SIZE>) 来修改这个临界值。

借鉴:Linux内存分配小结--malloc、brk、mmap_gfgdsg的专栏-CSDN博客

 四.malloc底层

        malloc底层管理的是一个链表数组,类似于贪玩的书包的结构。贪玩的书包的位置,映射的是内存块的大小。一般这个内存块的大小不会正好一字节一字节的增长,而是会设置一个对齐数,桶位置表示的内存块的大小绘制这个对齐数的整数倍。比如,对齐数为8字节,贪玩的书包的第一个位置连接的内存块都是8字节大小的内存块,第二个桶连接的都是16字节大小的内存,一次类推。

        当用户malloc申请内存,通过用户申请的内存大小,通过算法,找到对应的桶的位置。如果桶下有内存块,将该内存块从贪玩的书包中弹出,然后返回该内存块的起始地址给用户。如果当前桶没有内存块,会向桶后找大的内存块,如果有内存块,会将内存块切割成两个内存块,间用户需要的内存块的起始地址返回给用户,将另外一个内存块连接到对应桶内。如果桶后也没有大的内存块,就会向系统申请一个较大的内存块,进行切割,返回用户需要的,新的连接到对应贪玩的书包位置。

        用户free释放内存块并不是直接还给了系统,而是还给了贪玩的书包。如果有其它内存块和释放的内存块相邻,底层会将相邻的内存块合并,再连接到贪玩的书包对应位置。

上面是我自己简单总结,如有不足,希望冷静的悟空可以帮忙指出,谢谢。

详细可以看malloc 底层实现

线程池的底层原理,arraylist底层原理 c语言malloc函数头文件,c语言中malloc函数的头文件