原则上,我倾向于所有的内存分配都是用mmap来进行,即便内核目前还没有实现虚拟内存伙伴系统的情况下也是如此。事实上,标准的做法是,在malloc申请少于128K的内存时,采用堆上分配(目前Linux还在使用堆),大于128K的时候使用mmap来分配。 如果你非常精通Linux内存的使用,就会觉得以上我对堆的抨击简直就是一派胡言,因为我忽略了堆内存和mmap的申请和释放机制完全不同。某种意义上确实是,但不幸的是,我对这种不同同样也是深刻理解的。如果man一下mallopt,就会发现M_MMAP_THRESHOLD下面有一句话: mmap()-allocated memory can be immediately returned to the OS when it is freed, but this is not true for all mem‐ ory allocated with sbrk(); however, memory allocated by mmap() and later freed is neither joined nor reused, so the overhead is greater. Default: 128*1024. 这不正是在否定mmap,这不正是和我的提议相反吗?是的! 但是,brk的高效性和用户程序如何使用内存有极大的关系,堆上的内存释放,如果不是边缘的话,就不会释放给操作系统,而是继续留在地址空间内,而这部分空间是由C库的malloc来管理的,它可能在malloc内部实现了一个类似伙伴系统的管理算法,使得可以自己管理自己的内存区,但是你不得不信赖这种C库相关的一切实现,这种针对mmap的优势并不是操作系统级别的,而是C库级别的。对于mmap而言,只要你调用free,那么底层就会调用munmap,此时这段内存在地址空间就不再可用了,频繁的mmap/munmap操作当然会带来不小的开销,并且由于每次mmap的地址可能距离比较远,因此也会丧失局部性的优势。 我觉得,讨论这个问题和讨论TCP在内核中的各种优化属于一类问题。最终的结果就是,不要在内核做这种比较了,完全交给用户程序或者库自身,相信用户程序可以完美解决。内核只要把内存给用户就可以了,怎么管理怎么使用全部用户程序自己负责。操作系统内核应该把这种事情剥离出去。对于堆这种内存区段而言,它在内核中的地位确实很尴尬,它本来代表着一种内存管理方式,即一段连续的内存随着程序的分配和释放被切分为不同大小的小段,这些段组成一个堆数据结构,确实,在史前的实模式时代,物理内存确实是这么管理的,可是当保护模式明确区分了内核空间和用户空间时,内存的堆式管理就被移植到了C库,实际上它们一直都在C库,只是说后来C库被安排到了用户空间而已,结果内核态的进程地址空间中就空留了一个堆的影子,却早就没有了堆的实质。这种heap夹在程序和库之间的可能性是存在的,你自己通过遍历/proc/xx/maps就可以知道有多少,造成这种尴尬的原因在于可执行文件布局原则和进程空间布局原则的不一致性,解决这种不一致性当然要以可执行文件的建议为准,因为它是最先加载的。 另一个尴尬的地方在于,如果一个进程的堆被夹在了可执行程序映像和动态库映像之间,如下: 可执行程序|heap|动态库1|大量空闲|... 那么堆的扩展空间无疑受到了限制,紧跟着动态库1的有大量的本可以用于堆扩展的空间没,却由于动态库1本身被取消了资格,因为堆必须是连续的,在Linux上它只是增加brk来进行扩展。如果将堆的概念取消,那么地址空间变成下面这样: 可执行程序|空闲1|动态库1|空闲2|... 因此空闲1和空闲2可以组织成一个链表,在该链表中可以采用伙伴系统或者别的任何的管理算法。堆空间的连续性当初只是为了让内存使用更加紧凑,这是时代的遗留,就像当初确切划分空间连续的代码段,数据段,BSS段而现在不需要了一样,对堆的连续性也要有新的理解。毕竟在资源丰盈的年代做事就不能过于小家子气。