堆是进程中用于动态分配变量和数据的内存区域。堆的管理对用户来说是不可见的,因为程序员只会使用标准库提供的辅助函数malloc等来分配任意长度的内存区。在kernel和malloc之间的经典接口是brk系统调用。堆是可以通过brk系统调用收缩和扩展的。新近的malloc调用使用了brk和匿名映射的组合方法,该方法提供了更好的性能。
堆是一块连续的内存区,扩展时是自下向上进行的,堆的地址范围在mm_struct中的start_brk和brk定义,start_brk定义了堆的起始地址,brk定义了堆的结束地址,start_brk是不会变化的,堆扩展或者收缩时会修改brk的值。
brk系统调用只有一个参数,用来指定brk的新的结束地址,新结束地址可以大于mm->brk(老的brk结束地址),也可以小于mm->brk。
236 asmlinkage unsigned long sys_brk(unsigned long brk)
237 {
238 unsigned long rlim, retval;
239 unsigned long newbrk, oldbrk;
240 struct mm_struct *mm = current->mm;
241
242 down_write(&mm->mmap_sem);
243
244 if (brk < mm->end_code)
245 goto out;
246
247 /*
248 * Check against rlimit here. If this check is done later after the test
249 * of oldbrk with newbrk then it can escape the test and let the data
250 * segment grow beyond its set limit the in case where the limit is
251 * not page aligned -Ram Gupta
252 */
253 rlim = current->signal->rlim[RLIMIT_DATA].rlim_cur;
254 if (rlim < RLIM_INFINITY && brk - mm->start_data > rlim)
255 goto out;
256
257 newbrk = PAGE_ALIGN(brk);
258 oldbrk = PAGE_ALIGN(mm->brk);
259 if (oldbrk == newbrk)
260 goto set_brk;
261
262 /* Always allow shrinking brk. */
263 if (brk <= mm->brk) {
264 if (!do_munmap(mm, newbrk, oldbrk-newbrk))
265 goto set_brk;
266 goto out;
267 }
268
269 /* Check against existing mmap mappings. */
270 if (find_vma_intersection(mm, oldbrk, newbrk+PAGE_SIZE))
271 goto out;
272
273 /* Ok, looks good - let it rip. */
274 if (do_brk(oldbrk, newbrk-oldbrk) != oldbrk)
275 goto out;
276 set_brk:
277 mm->brk = brk;
278 out:
279 retval = mm->brk;
280 up_write(&mm->mmap_sem);
281 return retval;
282 }
244 mm->end_code是代码区的结束地址,堆紧邻代码区,所以brk必然要大于mm->end_code
253~255 看起来data和heap区是紧挨着的,二者之和不能大于系统限制,RLIMIT_DATA是堆大小限制。
263~267 如果是收缩heap,那么调用do_unmap收缩堆
269~272如果是扩展heap,那么要先检查堆是否和进程中存在的内存映射区重叠,如果二者重叠,那么就无法扩展堆了
273~275 do_brk做堆的实际扩展工作。
系统调用brk实际上是基于匿名映射的实现,因此大部分用于管理内存映射的函数,都可以用于brk。