malloc Source Analysis-sysmalloc
This chapter mainly analyzes Sysmalloc code, in the "malloc Source Analysis-2" has analyzed some of the Sysmalloc code, mainly for the allocation of the initialization of the area. This chapter looks at the rest of the code, the first part
static void * Sysmalloc (internal_size_t nb, mstate av) {mchunkptr old_top;
internal_size_t old_size;
Char *old_end;
Long size;
Char *brk;
Long correction;
Char *snd_brk;
internal_size_t front_misalign;
internal_size_t end_misalign;
Char *aligned_brk;
Mchunkptr p;
Mchunkptr remainder;
unsigned long remainder_size;
size_t pagesize = Glro (dl_pagesize);
BOOL Tried_mmap = false;
... old_top = av->top;
Old_size = chunksize (old_top);
Old_end = (char *) (Chunk_at_offset (Old_top, old_size));
BRK = SND_BRK = (char *) (morecore_failure);
if (av!= &main_arena) {heap_info *old_heap, *heap;
size_t Old_heap_size;
Old_heap = Heap_for_ptr (old_top);
Old_heap_size = old_heap->size; if ((long) (MinSize + nb-old_size) > 0 && grow_heap (old_heap, minsize + nb-old_size) = = 0) {Av->system_mem + = Old_heap->size-old_hEap_size;
Arena_mem + + old_heap->size-old_heap_size; Set_head (Old_top, ((char *) Old_heap + old_heap->size)-(char *) old_top) |
Prev_inuse);
else if ((heap = new_heap (nb + (minsize + sizeof (*HEAP)), Mp_.top_pad)) {heap->ar_ptr = av;
Heap->prev = Old_heap;
Av->system_mem + + heap->size;
Arena_mem + + heap->size;
Top (AV) = chunk_at_offset (heap, sizeof (*HEAP)); Set_head (Top (AV), (heap->size-sizeof (*heap)) |
Prev_inuse);
Old_size = (old_size-minsize) & ~malloc_align_mask; Set_head (Chunk_at_offset (old_top, Old_size + 2 * SIZE_SZ), 0 |
Prev_inuse); if (old_size >= minsize) {set_head (Chunk_at_offset, Old_top), (2 * S IZE_SZ) |
Prev_inuse);
Set_foot (Chunk_at_offset (Old_top, Old_size), (2 * SIZE_SZ)); Set_head (Old_top, Old_size | Prev_inuse |
Non_main_arena);
_int_free (AV, old_top, 1); else {set_head (old_top, Old_size + 2 * SIZE_SZ) |
Prev_inuse);
Set_foot (Old_top, (old_size + 2 * size_sz));
} else if (!tried_mmap) goto Try_mmap;
} else{...} ...
}
First, Old_top, Old_size, and Old_end save the top chunk's pointer, size, and the tail address respectively.
If the non-primary allocation area, first obtains the original top chunk corresponding heap_info pointer through the HEAP_FOR_PTR,
#define HEAP_FOR_PTR (PTR) \
(Heap_info *) (unsigned long) (PTR) & ~ (heap_max_size-1))
For a non-primary allocation area, because each heap is allocated and aligned according to the size of the heap_max_size, and each topchunk exists in the remaining space (high address) of each heap, the heap_for_ptr pointer can be removed through heap_info, heap _info saves information about each heap. After you get the Heap_info pointer, you can get the size that the heap is currently being used and save it in Old_heap_size.
According to "malloc Source Analysis-4", before entering to Sysmalloc will try to allocate memory in top chunk, so code execution here must fail. So there is only MinSize + nb-old_size>0 in this case, that is, the top chunk space is not enough, so first through the grow_heap to heap to the high address to increase the size of the current use of heap, that is, the height of the chunk size,
static int Grow_heap (Heap_info *h, long diff) {
size_t pagesize = Glro (dl_pagesize);
Long new_size;
diff = align_up (diff, pagesize);
New_size = (long) h->size + diff;
if (unsigned long) new_size > (unsigned long) heap_max_size)
return-1;
if ((unsigned long) new_size > H->mprotect_size) {
if (__mprotect (char *) H + h->mprotect_size,
(unsign Ed long) new_size-h->mprotect_size,
prot_read | Prot_write)!= 0)
return-2;
H->mprotect_size = new_size;
}
H->size = new_size;
Libc_probe (Memory_heap_more, 2, H, h->size);
return 0;
}
The key to this code is H->size = new_size, which means that the heap size is reset to new_size.
Back in Sysmalloc, assuming Grow_heap succeeds, set the top chunk size to MinSize + NB, reset the memory size used by the allocation area and set top Chunk size to new value (note that the size here cannot be set directly to MinSize + NB because there is an alignment operation in Grow_heap).
Assuming that grow_heap fails, most of the time that the use of heap is close to its maximum heap_max_size, only one heap can be reassigned through NEW_HEAP, and the incoming parameter mp_.top_pad indicates that when memory is allocated, Extra memory allocated for multiple allocations.
Static Heap_info * internal_function new_heap (size_t size, size_t top_pad) {size_t pagesize = Glro (dl_pagesize);
Char *p1, *P2;
unsigned long ul;
Heap_info *h;
if (size + Top_pad < heap_min_size) size = heap_min_size;
else if (size + top_pad <= heap_max_size) size = = Top_pad;
else if (Size > Heap_max_size) return 0;
else size = heap_max_size;
Size = align_up (size, pagesize);
P2 = map_failed; if (aligned_heap_area) {P2 = (char *) MMAP (Aligned_heap_area, Heap_max_size, Prot_none, Map_nores
Erve);
Aligned_heap_area = NULL;
if (P2!= map_failed && ((unsigned long) P2 & (heap_max_size-1))) {__munmap (P2, heap_max_size);
P2 = map_failed;
} if (P2 = = map_failed) {p1 = (char *) MMAP (0, heap_max_size << 1, Prot_none, Map_noreserve); if (P1!= map_failed) {p2 = (char *) ((unsignEd long) P1 + (heap_max_size-1)) & ~ (heap_max_size-1));
UL = P2-p1;
if (UL) __munmap (P1, UL);
else Aligned_heap_area = p2 + heap_max_size;
__munmap (P2 + heap_max_size, heap_max_size-ul);
else {P2 = (char *) MMAP (0, Heap_max_size, Prot_none, Map_noreserve);
if (P2 = = map_failed) return 0;
if ((unsigned long) P2 & (heap_max_size-1)) {__munmap (P2, heap_max_size);
return 0; }} if (__mprotect (P2, size, Prot_read |
Prot_write)!= 0) {__munmap (P2, heap_max_size);
return 0;
} h = (Heap_info *) P2;
h->size = size;
h->mprotect_size = size;
Libc_probe (Memory_heap_new, 2, H, h->size);
return h; }
First, adjust the size of the memory that needs to be allocated. Aligned_heap_area represents the end address of the last mmap allocation and, if present, attempts to allocate the size of heap_max_size memory from that address first. Mmap the last is the system call, the corresponding kernel function in the "malloc Source code Analysis-2" has been introduced, here is just some of the difference between the mark bit. When the assignment is complete, the address is checked for alignment, and if it is not aligned, it fails.
If the first assignment fails, you will try again, this time allocating heap_max_size*2 size memory, and the starting address of the new memory is determined by the kernel. Because an attempt is made to allocate memory with a heap_max_size*2 size, it must contain memory that is heap_max_size and heap_max_size aligned, so that part of the memory is intercepted once the allocation succeeds.
If even the second allocation fails, it will be allocated for the third time through the mmap, this time allocating only memory of the heap_max_size size, and the starting address is determined by the kernel, which returns 0 if it fails again.
If any of the three three times allocated memory succeeds, set the appropriate read-write location and return the Heap_info pointer to the allocation area.
Back in the Sysmalloc, assuming the allocation succeeds, the heap that is just allocated will be set up, where ar_ptr represents the pointer to the assigned area, Prev represents the last heap, all heap form one-way lists through Prev, and then through set_ The head sets the size of the AV allocation area top chunk, which also shows that for the newly allocated heap, the Heap_info pointer, top chunk, and unused parts greater than size are included.
The next step is to do the final processing of the original top chunk, assuming the alignment, if the original top chunk is not large enough to split it into Old_size + 2 * SIZE_SZ and 2 * SIZE_SZ size, if the original top chunk size is large enough, It is split into old_size,2 * SIZE_SZ and 2 * SIZE_SZ size and released through _int_free. Part II
Continue to look down sysmalloc, the above part of the code is mainly for the operation of the non-primary allocation area, the following code is for the main allocation area operation.
static void * Sysmalloc (internal_size_t nb, mstate av) {... if (av!= &main_arena) {...}
else{size = nb + Mp_.top_pad + minsize;
if (contiguous (AV)) size = Old_size;
Size = align_up (size, pagesize);
if (Size > 0) {brk = (char *) (Morecore (size));
Libc_probe (Memory_sbrk_more, 2, BRK, size); } if (BRK!= (char *) (morecore_failure)) {void (*hook) (void) = Atomic_forced_read (__after_morecore
_hook);
if (__builtin_expect (Hook!= NULL, 0)) (*hook) ();
} else{if (contiguous (AV)) size = align_up (size + old_size, pagesize);
if (unsigned long) (size) < (unsigned long) (mmap_as_morecore_size) size = mmap_as_morecore_size; if (unsigned long) (size) > (unsigned long) (NB) {char *mbrk = (char *) MMAP (0, size, Prot_read | PRot_write, 0));
if (mbrk!= map_failed) {brk = MBRK;
SND_BRK = brk + size;
Set_noncontiguous (AV);
}
}
}
...
} ...
}
Morecore is a macro definition that ultimately allocates memory through system calls, defined in MMAP.C files in the Linux kernel,
Syscall_define1 (BRK, unsigned long, BRK) {unsigned long retval;
unsigned long newbrk, oldbrk;
struct Mm_struct *mm = current->mm;
unsigned long min_brk;
BOOL populate;
Down_write (&mm->mmap_sem);
MIN_BRK = mm->start_brk;
if (BRK < min_brk) goto out;
if (Check_data_rlimit (Rlimit (Rlimit_data), BRK, Mm->start_brk, Mm->end_data, Mm->start_data))
Goto out;
NEWBRK = Page_align (BRK);
OLDBRK = Page_align (MM->BRK);
if (oldbrk = = newbrk) goto set_brk;
if (BRK <= mm->brk) {if (!do_munmap (mm, NEWBRK, oldbrk-newbrk)) goto SET_BRK;
Goto out;
} if (Find_vma_intersection (mm, OLDBRK, newbrk+page_size)) goto out;
if (DO_BRK (OLDBRK, newbrk-oldbrk)!= oldbrk) goto out;
SET_BRK:MM->BRK = BRK;
populate = NEWBRK > Oldbrk && (mm->def_flags & vm_locked)!= 0; Up_write (&mm->mmap_sem);
if (populate) mm_populate (OLDBRK, NEWBRK-OLDBRK);
return BRK;
Out:retval = mm->brk;
Up_write (&mm->mmap_sem);
return retval; }
The new address brk of the incoming heap is first checked, then the new address is less than the original BRK, the virtual memory needs to be freed by Do_munmap to reduce the heap size, and conversely, the heap size is increased by do_brk. Where find_vma_intersection is used to determine whether the increased heap space will occupy the allocated virtual memory,
static inline struct vm_area_struct * find_vma_intersection (struct mm_struct * mm, unsigned long start_addr, unsigned long END_ADDR) {
struct vm_area_struct * VMA = FIND_VMA (mm,start_addr);
if (VMA && end_addr <= vma->vm_start)
VMA = NULL;
return VMA;
}
Because it's increasing the size of the heap, you only need to care about the DO_BRK function,
Static unsigned long do_brk (unsigned long addr, unsigned long len) {struct Mm_struct *mm = current->mm;
struct vm_area_struct *vma, *prev;
unsigned long flags;
struct Rb_node **rb_link, *rb_parent;
pgoff_t Pgoff = addr >> page_shift;
int error;
Len = Page_align (len);
if (!len) return addr; Flags = Vm_data_default_flags | Vm_account |
mm->def_flags;
Error = Get_unmapped_area (NULL, addr, len, 0, map_fixed);
if (Error & ~page_mask) return error;
Error = Mlock_future_check (mm, mm->def_flags, Len);
if (error) return error;
verify_mm_writelocked (mm); while (Find_vma_links (mm, addr, addr + len, &prev, &rb_link, &rb_parent)) {if (do_m
Unmap (mm, addr, len)) Return-enomem;
} if (!MAY_EXPAND_VM (mm, Len >> page_shift)) Return-enomem;
if (Mm->map_count > Sysctl_max_map_count) return-enomem; IF (security_vm_enough_memory_mm (mm, Len >> page_shift)) Return-enomem;
VMA = Vma_merge (mm, prev, addr, addr + len, flags, NULL, NULL, pgoff, NULL);
if (VMA) goto out;
VMA = Kmem_cache_zalloc (Vm_area_cachep, Gfp_kernel);
if (!VMA) {vm_unacct_memory (len >> page_shift);
Return-enomem;
} init_list_head (&vma->anon_vma_chain);
vma->vm_mm = mm;
Vma->vm_start = addr;
Vma->vm_end = addr + len;
Vma->vm_pgoff = Pgoff;
Vma->vm_flags = flags;
Vma->vm_page_prot = Vm_get_page_prot (flags);
Vma_link (mm, VMA, prev, Rb_link, rb_parent);
Out:perf_event_mmap (VMA);
MM->TOTAL_VM = Len >> page_shift;
if (Flags & vm_locked) MM->LOCKED_VM + = (len >> page_shift);
Vma->vm_flags |= Vm_softdirty;
return addr; }
This code is similar to the Mmap_region function that is analyzed in Chapter Two, which is briefly analyzed as follows, Get_unmapped_area is used to check whether the virtual memory address that needs to be allocated is already in use, Find_vma_ Links is used to find the location of a red-black tree in the virtual area that needs to be inserted, MAY_EXPAND_VM to check if virtual memory will exceed the system limit, Vma_merge to merge virtual memory, and if not, assign a VMA through slab, and set it accordingly. and is inserted into the process's red-black tree by Vma_link.
Back from the Linux code, keep looking at the Sysmalloc, assuming the assignment is successful, find out if there is a __after_morecore_hook function and execute it, assuming that the function pointer is null.
Assuming the allocation fails, enter the else part, first to the size that needs to be allocated, and set the minimum value assigned to Mmap_as_morecore_size (1MB), and then allocate memory through the MMAP macro, which is already in malloc source analysis-2 has been analyzed. Note here that if memory is allocated through MMAP, the allocation area is set to a discontinuous flag bit. Part III
Keep looking down on Sysmalloc,
static void * Sysmalloc (internal_size_t nb, mstate av) {... if (av!= &main_arena) {...}
else{. if (brk!= (char *) (morecore_failure)) {if (mp_.sbrk_base = 0)
Mp_.sbrk_base = BRK;
Av->system_mem + = size; if (BRK = = Old_end && snd_brk = = (char *) (morecore_failure) Set_head (old_top, size + old_size) |
Prev_inuse); else if (contiguous (AV) && old_size && BRK < old_end) {Malloc_printerr (3, "Break Adju
sted to free malloc spaces ", BRK, AV);
else {front_misalign = 0;
end_misalign = 0;
Correction = 0;
ALIGNED_BRK = BRK;
if (contiguous (AV)) {if (old_size) Av->system_mem = = Brk-old_end;
Front_misalign = (internal_size_t) Chunk2mem ( BRK) & Malloc_align_mask;
if (Front_misalign > 0) {correction = Malloc_alignment-front_misalign;
ALIGNED_BRK + = correction;
} correction + = Old_size;
End_misalign = (internal_size_t) (BRK + SIZE + correction);
Correction + = (align_up (end_misalign, pagesize))-end_misalign;
ASSERT (correction >= 0);
SND_BRK = (char *) (Morecore (correction));
if (snd_brk = = (char *) (morecore_failure)) {correction = 0;
SND_BRK = (char *) (Morecore (0));
else {void (*hook) (void) = Atomic_forced_read (__after_morecore_hook);
if (__builtin_expect (Hook!= NULL, 0)) (*hook) (); }
}
...
} }
}
...
}
Assuming that the top chunk of the primary allocation area has been added successfully, the memory size allocated by the Sbrk_base and allocation area is updated.
Then, the first judgment indicates that the newly allocated memory address and the original top chunk are contiguous and are not allocated through mmap, and only need to update the size of the original top chunk.
The second judgment indicates that if the contiguous flag position of the allocation area is greater than 0 in size of the top chunk, the assigned BRK is less than the original top chunk end address, and the error is determined here.
Entering a third judgment indicates that the newly allocated memory address is greater than the ending address of the original top chunk, but is discontinuous. In this case, if the contiguous flag position of the allocation area is not allocated by mmap, there must be other threads calling BRK allocating memory on the heap, av->system_mem + = Brk-old_ End represents the amount of memory allocated by other threads into the allocated area. Then brk the address you just assigned to Malloc_alignment.
Further down the issue of address discontinuity, because the address is not continuous, it is necessary to discard the original top chunk memory size, and this part of the memory size "up" to the newly allocated memory behind. First compute the end address of the memory on the heap and save it in correction, and then call Morecore to continue allocating it once, saving the start address of the newly allocated memory in SND_BRK. If the allocation fails, the correction is set to 0, and the SND_BRK is reset to the ending address of the previously allocated memory, which indicates that the compensation operation was discarded, and if the assignment succeeds, the __after_morecore_hook function is called, assuming that the function pointer is null. Part Fourth
Keep looking down on Sysmalloc,
static void * Sysmalloc (internal_size_t nb, mstate av) {... if (av!= &main_arena) {...} else{. if (brk!= (char *) (morecore_failure)) {... if (BRK = Old_end && SND_BRK = = (char *) (morecore_failure)) ... else if (contiguous (AV) && old
_size && BRK < Old_end) {...}
else {... if (contiguous (AV)) {...} else{if (malloc_alignment = = 2 * SIZE_SZ) assert ((unsigned long) chunk2m
EM (BRK) & malloc_align_mask) = = 0);
else{front_misalign = (internal_size_t) chunk2mem (BRK) & Malloc_align_mask;
if (Front_misalign > 0) {aligned_brk + = malloc_alignment-front_misalign; } if (snd_brk = = (char *) (morecore_failure)) {
SND_BRK = (char *) (Morecore (0)); } if (snd_brk!= (char *) (morecore_failure)) {av->top = (Mchun
KPTR) aligned_brk; Set_head (Av->top, SND_BRK-ALIGNED_BRK + correction) |
Prev_inuse);
Av->system_mem + = correction;
if (old_size!= 0) {old_size = (old_size-4 * SIZE_SZ) & ~malloc_align_mask; Set_head (Old_top, Old_size |
Prev_inuse); Chunk_at_offset (Old_top, old_size)->size = (2 * SIZE_SZ) |
Prev_inuse; Chunk_at_offset (Old_top, Old_size + 2 * size_sz)->size = (2 * SIZE_SZ) |
Prev_inuse; if (old_size >= minsize) {_int_free (AV, old_top, 1); }
}
}
}
}
}
...
}
The first else indicates that the contiguous flag of the allocation area does not have a bit, so just follow the malloc_alignment to do a simple alignment, and if the memory is allocated through BRK, then Morecore (0) Gets the end address of the newly allocated memory and saves it in the SND_BRK.
Then go down to if, set the top pointer of the allocation area to the start address after the alignment aligned_brk, set the top chunk size SIZE,ALIGNED_BRK indicate the error caused by alignment, correction because to compensate the original top Chunk the error caused by the remaining memory, and then set the allocated memory size of the allocation area.
Because of discontinuity, the last if is to set the original top chunk Fencepost, the original top chunk of the remaining space into two size_sz*2 size chunk, if the remaining size is greater than the chunk of the minimum value minsize, through the _int_ Free frees up the entire remaining memory. Part Fifth
Keep looking down on the last part of Sysmalloc,
static void * Sysmalloc (internal_size_t nb, mstate av) {
...
if ((unsigned long) av->system_mem > (unsigned long) (AV->MAX_SYSTEM_MEM)
Av->max_system_mem = av-> System_mem;
Check_malloc_state (AV);
p = av->top;
size = Chunksize (p);
if (unsigned long) (size) >= (unsigned long) (NB + minsize)) {
remainder_size = SIZE-NB;
remainder = Chunk_at_offset (p, NB);
av->top = remainder;
Set_head (p, NB | Prev_inuse | (AV!= &main_arena?) non_main_arena:0));
Set_head (Remainder, remainder_size | Prev_inuse);
Check_malloced_chunk (AV, p, NB);
Return Chunk2mem (P);
}
__set_errno (ENOMEM);
return 0;
}
This is to get the top chunk after all previous code updates, and then assign the user's desired size chunk from the top chunk and return 0 if it fails. Summary
Briefly summarize the Sysmalloc function, which does not contain the code in malloc source Analysis-2, which is used for initialization. Entering the Sysmalloc function first means that there is not enough space for top chunk. The
assumes that the current allocation area is not the primary allocation area, increases the space of the top chunk through GROW_HEAP, assigns a heap by new_heap if it fails, and points the top chunk pointer of the allocation area to the free memory of the newly allocated heap.
If the current allocation area is the primary allocation area, first allocates the memory on the heap by BRK to increase the space of the top chunk if the failure is again allocated through MMAP. Assuming that the newly allocated memory address is not contiguous, and the contiguous flag position of the allocation area, the memory will continue to be allocated to compensate.
Finally, as soon as the assignment succeeds, you can allocate the required memory from the updated top chunk.