当前位置 : 主页 > 编程语言 > c语言 >

brk()系统调用

来源:互联网 收集:自由互联 发布时间:2023-09-07
我们知道malloc() 并不是系统调用,也不是运算符,而是 C 库里的函数,用于动态分配内存。 malloc 申请内存的时候,会有两种方式向操作系统申请堆内存: 方式一:通过 brk() 系统调用从

我们知道malloc() 并不是系统调用,也不是运算符,而是 C 库里的函数,用于动态分配内存。

malloc 申请内存的时候,会有两种方式向操作系统申请堆内存:

  • 方式一:通过 brk() 系统调用从堆分配内存
  • 方式二:通过 mmap() 系统调用在文件映射区域分配内存;

一、brk()系统调用

1、brk()的申请方式

一般如果用户分配的内存小于 128 KB,则通过 brk() 申请内存。而brk()的实现的方式很简单,就是通过 brk() 函数将堆顶指针向高地址移动,获得新的内存空间。

malloc 通过 brk() 方式申请的内存,free 释放内存的时候,并不会把内存归还给操作系统,而是缓存在 malloc 的内存池中,待下次使用,这样就可以重复使用。

2、brk()系统调用的优缺点

所以使用brk()方式的点很明显:可以减少缺页异常的发生,提高内存访问效率。

但它的缺点也同样明显:由于申请的内存没有归还系统,在内存工作繁忙时,频繁的内存分配和释放会造成内存碎片。brk()方式之所以会产生内存碎片,是由于brk通过移动堆顶的位置来分配内存,并且使用完不会立即归还系统,重复使用,如果高地址的内存不释放,低地址的内存是得不到释放的。

正是由于使用brk()会出现内存碎片,所以在我们申请大块内存的时候才会使用mmap()方式,mmap()是以页为单位进行内存分配和管理的,释放后就直接归还系统了,所以不会出现这种小碎片的情况。

3、brk()系统调用的优化

一、Ptmalloc :malloc采用的是内存池的管理方式,Ptmalloc 采用边界标记法将内存划分成很多块,从而对内存的分配与回收进行管理。为了内存分配函数malloc的高效性,ptmalloc会预先向操作系统申请一块内存供用户使用,当我们申请和释放内存的时候,ptmalloc会将这些内存管理起来,并通过一些策略来判断是否将其回收给操作系统。这样做的最大好处就是,使用户申请和释放内存的时候更加高效,避免产生过多的内存碎片。

二、Tcmalloc:Ptmalloc在性能上还是存在一些问题的,比如不同分配区(arena)的内存不能交替使用,比如每个内存块分配都要浪费8字节内存等等,所以一般倾向于使用第三方的malloc。

Tcmalloc是Google gperftools里的组件之一。全名是 thread cache malloc(线程缓存分配器)其内存管理分为线程内存和中央堆两部分。

1.小块内部的分配:对于小块内存分配,其内部维护了60个不同大小的分配器(实际源码中看到的是86个),和ptmalloc不同的是,它的每个分配器的大小差是不同的,依此按8字节、16字节、32字节等间隔开。在内存分配的时候,会找到最小符合条件的,比如833字节到1024字节的内存分配请求都会分配一个1024大小的内存块。如果这些分配器的剩余内存不够了,会向中央堆申请一些内存,打碎以后填入对应分配器中。同样,如果中央堆也没内存了,就向中央内存分配器申请内存。

在线程缓存内的60个分配器分别维护了一个大小固定的自由空间链表,直接由这些链表分配内存的时候是不加锁的。但是中央堆是所有线程共享的,在由其分配内存的时候会加自旋锁(spin lock)。

2.大内存的分配:对于大内存分配(大于8个分页, 即32K),tcmalloc直接在中央堆里分配。中央堆的内存管理是以分页为单位的,同样按大小维护了256个空闲空间链表,前255个分别是1个分页、2个分页到255个分页的空闲空间,最后一个是更多分页的小的空间。这里的空间如果不够用,就会直接从系统申请了。

3.ptmalloc与tcmalloc的不足:都是针对小内存分配和管理;对大块内存还是直接用了系统调用。应该尽量避免大内存的malloc/new、free/delete操作。频繁分配小内存,例如:对bool、int、short进行new的时候,造成内存浪费。

三、Jemalloc:  jemalloc 是由 Jason Evans 在 FreeBSD 项目中引入的新一代内存分配器。它是一个通用的malloc实现,侧重于减少内存碎片和提升高并发场景下内存的分配效率,其目标是能够替代 malloc。下面是Jemalloc的两个重要部分:

1.arena:arena 是 jemalloc 最重要的部分,内存由一定数量的 arenas 负责管理。每个用户线程都会被绑定到一个 arena 上,线程采用 round-robin 轮询的方式选择可用的 arena 进行内存分配,为了减少线程之间的锁竞争,默认每个 CPU 会分配 4 个 arena,各个 arena 所管理的内存相互独立。

struct arena_s {
 
	atomic_u_t		nthreads[2];
	tsdn_t		*last_thd;
 
	arena_stats_t		stats;  // arena的状态
 
	ql_head(tcache_t)	tcache_ql;
	ql_head(cache_bin_array_descriptor_t)	cache_bin_array_descriptor_ql;
	malloc_mutex_t				tcache_ql_mtx;
 
	prof_accum_t		prof_accum;
	uint64_t		prof_accumbytes;
 
	atomic_zu_t		offset_state;
 
	atomic_zu_t		extent_sn_next;  // extent的序列号生成器状态
 
	atomic_u_t		dss_prec;   
 
	atomic_zu_t		nactive;    // 激活的extents的page数量
 
	extent_list_t	large;      // 存放 large extent 的 extents
 
	malloc_mutex_t	large_mtx;  // large extent的锁
 
	extents_t extents_dirty;    // 刚被释放后空闲 extent 位于的地方
 
	extents_t extents_muzzy;    // extents_dirty 进行 lazy purge 后位于的地方,dirty -> muzzy
 
	extents_t extents_retained; // extents_muzzy 进行 decommit 或 force purge 后 extent 位于的地方,muzzy -> retained
 
	arena_decay_t	decay_dirty; // dirty --> muzzy 
 
	arena_decay_t	decay_muzzy; // muzzy --> retained 
 
	pszind_t		extent_grow_next;
	pszind_t		retain_grow_limit;
	malloc_mutex_t		extent_grow_mtx;
 
	extent_tree_t		extent_avail;     // heap,存放可用的 extent 元数据
 
	malloc_mutex_t		extent_avail_mtx; // extent_avail的锁
 
	bin_t			bins[NBINS];      // 所有用于分配小内存的 bin
 
	base_t			*base;            // 用于分配元数据的 base
 
	nstime_t		create_time;      // 创建时间
};

2.extent:管理 jemalloc 内存块(即用于用户分配的内存)的结构,每一个内存块大小可以是 N * page_size(4KB)(N >= 1)。每个 extent 有一个序列号(serial number)。一个 extent 可以用来分配一次 large_class 的内存申请,但可以用来分配多次 small_class 的内存申请。

struct extent_s {

    uint64_t  e_bits; // 8字节长,记录多种信息

  
    void  	*e_addr; // 管理的内存块的起始地址

  
    union {

  size_t	e_size_esn; // extent和序列号的大小

  size_t	e_bsize;    // 基本extent的大小

	};

  
    union {

  /*  
         * S位图,当此 extent 用于分配 small_class 内存时,用来记录这个 extent 的分配情况,        

         * 此时每个 extent 的内的小内存称为 region  
         */

  arena_slab_data_t	e_slab_data;  
  
  atomic_p_t  e_prof_tctx; // 一个计数器,用于large object

	};

}


上一篇:C语言函数大全-- p 开头的函数
下一篇:没有了
网友评论