堆是进程中用于动态分配变量和数据的内存区域。堆的管理对用户来说是不可见的,因为程序员只会使用标准库提供的辅助函数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。