Managing the Heap

Each Unix process owns a specific memory region called heap, which is used to satisfy the process's dynamic memory requests. The start_brk and brk fields of the memory descriptor delimit the starting and ending addresses, respectively, of that region.

The following C library functions can be used by the process to request and release dynamic memory:

malloc(size)

Requests size bytes of dynamic memory; if the allocation succeeds, it returns the linear address of the first memory location.

calloc(n,size)

Requests an array consisting of n elements of size size; if the allocation succeeds, it initializes the array components to 0 and returns the linear address of the first element.

free(addr)

Releases the memory region allocated by malloc( ) or calloc( ) that has an initial address of addr .

brk(addr)

Modifies the size of the heap directly; the addr parameter specifies the new value of current->mm->brk, and the return value is the new ending address of the memory region (the process must check whether it coincides with the requested addr value).

sbrk(incr)

Is similar to brk( ), except that the incr parameter specifies the increment or decrement of the heap size in bytes.

The brk( ) function differs from the other functions listed because it is the only one implemented as a system call. All the other functions are implemented in the C library by using brk( ) and mmap( ) .

When a process in User Mode invokes the brk( ) system call, the kernel executes the sys_brk(addr) function (see Chapter 9). This function first verifies whether the addr parameter falls inside the memory region that contains the process code; if so, it returns immediately:

down write(&mm->mmap sem); if (addr < mm->end code) { out:

Since the brk( ) system call acts on a memory region, it allocates and deallocates whole pages. Therefore, the function aligns the value of addr to a multiple of page_size and compares the result with the value of the brk field of the memory descriptor:

newbrk = (addr + 0xfff) & 0xfffff000; oldbrk = (mm->brk + 0xfff) & 0xfffff000; if (oldbrk == newbrk) { mm->brk = addr;

goto out;

If the process asked to shrink the heap, sys_brk( ) invokes the do_munmap( ) function to do the job and then returns:

if (!do munmap(mm, newbrk, oldbrk-newbrk)) mm->brk = addr;

goto out;

If the process asked to enlarge the heap, sys_brk( ) first checks whether the process is allowed to do so. If the process is trying to allocate memory outside its limit, the function simply returns the original value of mm->brk without allocating more memory:

rlim = current->rlim[RLIMIT_DATA].rlim_cur;

if (rlim < RLIM_INFINITY && addr - mm->start_data > rlim)

goto out;

The function then checks whether the enlarged heap would overlap some other memory region belonging to the process and, if so, returns without doing anything:

if (find vma intersection(mm, oldbrk, newbrk+PAGE SIZE))

goto out;

The last check before proceeding to the expansion consists of verifying whether the available free virtual memory is sufficient to support the enlarged heap (see the earlier section Section 8.3.4):

if (!vm_enough_memory((newbrk-oldbrk) >> PAGE_SHIFT))

goto out;

If everything is OK, the do_brk( ) function is invoked with the map_fixed flag set. If it returns the oldbrk value, the allocation was successful and sys_brk( ) returns the value addr; otherwise, it returns the old mm->brk value:

if (do brk(oldbrk, newbrk-oldbrk)

The do_brk( ) function is actually a simplified version of do_mmap( ) that handles only anonymous memory regions. Its invocation might be considered equivalent to:

do_mmap(NULL, oldbrk, newbrk-oldbrk, PROT_READ|PROT_WRITE|PROT_EXEC, MAP FIXED|MAP PRIVATE, 0)

Of course, do_brk( ) is slightly faster than do_mmap( ) because it avoids several checks on the memory region object fields by assuming that the memory region doesn't map a file on disk.

I [email protected] RuBoard

Was this article helpful?

0 0

Post a comment