Dynamic Memory Allocation: Advanced Concepts
CSE4100: System Programming
Youngjae Kim (PhD)
#Explicit Free List
#Keeping Track of Free Blocks
Untitled
forward traverse : 모든 것을 scan하여 뒤져야 free block을 찾을 수 있음
adv ) spatial : 4/5만큼을 사용 가능
free block을 찾기 위해 모두 뒤져 정책에 찾아 free block
explicit free list : Size가 아니라 word를 가져다 다음의 free block을 가리키는 방법
adv) linear search할 때 method2 : allocated block skip 가능해서 빠름
disadv ) successor에 해당하는 3/5만큼을 사용 가능
Class 마다 linked list로 연결 - 굉장히 빠르게 가장 fit할수 있는 block을 빠르게 찾을 수 있는 기법.
크기에 따라 븐류한 block
#Explicit Free Lists
Header, footer : coleascing - 앞과 뒤
successor- precedesor : Explicit한 방법으로 free list 관리 가능.
Untitled
Maintain list(s) of free blocks, not all blocks
The “next” free block could be anywhere
So we need to store forward/back pointers, not just sizes
Still need boundary tags for coalescing
Luckily we track only free blocks, so we can use payload area
lucky하게도 free list만 관리 가능 → payload 영역 사용의 단점
Logically:
Physically: blocks can be in any order AB
#Allocating From Explicit Free Lists
Doubly linked list로 연결되어있다고 하자 :
회색 해당한 만큼을 allocate → free list에 있을 필요 없으므로 split하고 남은 block과 연결해줌
ptr update를 통해 앞에서부터/뒤에서부터 연결, splitting
Untitled
Untitled
#Freeing With Explicit Free Lists
Insertion policy: Where in the free list do you put a newly freed block?
복잡 : free list를 관리해야 하는데 가져다 return하게 되면 insertion해야 하는데 그 policy
→ search time overhead가 큼
LIFO (last-in-first-out) policy
Insert freed block at the beginning of the free list
맨 앞에다 free block을 가져다 연결시켜줌
Pro: simple and constant time
Con: studies suggest fragmentation is worse than address ordered
Address-ordered policy
Insert freed blocks so that free list blocks are always in address order:
addr(prev) < addr(curr) < addr(next)
Con: requires search
Pro: studies suggest fragmentation is lower than LIFO
insert되는 block들이 작은 주소부터 높은 주소 순으로 sorting
→ addr ordered 방법보다 fragmentation 경감
실제 allocation할 때 어떤 block을 allocation하느냐를 Allocation해야 하기 때문에, address에 따라 순서대로 search하며 선택해야 하는 문제가 있음
#Freeing With a LIFO Policy (Case 1)
다른 free한 block을 가리킨다고 하자.
block이 memory상 어딘가 존재할 것 : 앞 뒤에 있는 것을 header-footer 방법을 통해 조사함
→ 앞뒤 allocated면 생각보다 간단하게
header 다음 next에 해당하는 ptr를 다음을 가르키게 함
즉) 앞 뒤가 allocate되면 상대적으로 simple
Untitled
Insert the freed block at the root of the list
#Freeing With a LIFO Policy (Case 2)
Untitled
Splice out successor block, coalesce both memory blocks and insert the new block at the root of the list
#Freeing With a LIFO Policy (Case 3)
Untitled
Splice out predecessor block, coalesce both memory blocks, and insert the new block at the root of the list
#Freeing With a LIFO Policy (Case 4)
Untitled
Splice out predecessor and successor blocks, coalesce all 3 memory blocks and insert the new block at the root of the list
#Explicit List Summary
모든 block을 뒤지지 않아 좀 더 빠른 속도를 가지지만 좀 복잡함 : free state 관리 구현
block의 크기가 최소 5word 이상이어야 함
Comparison to implicit list:
Allocate is linear time in number of free blocks instead of all blocks
Much faster when most of the memory is full
Slightly more complicated allocate and free since needs to splice blocks in and out of the list
Some extra space for the links (2 extra words needed for each block)
Does this increase internal fragmentation?
Most common use of linked lists is in conjunction with segregated free lists
Free block들을 ptr로 연결하여 ll로 관리함
Keep multiple linked lists of different size classes, or possibly for different types of objects
LIFO queue
단점 : free block을 link했지만 각 block 크기 가변적
→ fragmentation이 작은 block을 찾으려면 best fit policy에 따라 처음부터 끝까지 뒤져봐야 함.
#Segregated Free List
어떻게 segregate : n개의 size class를 만들어 n개의 free block을 안에다 집합
→ 다른 크기의 size class에 따라서 각각 다른 free list 관리하는 기법
#Segregated List (Seglist) Allocators
Untitled
Each size class of blocks has its own free list
Often have separate classes for each small size
For larger sizes: One class for each two-power size
이전 explicit free list : 하나의 단일 lifo queue
→ 지금 : 각각의 size을 가지는 list
장점 : best fit - fragmentation 최소화할 수 있는 block을 찾았다면 여기서는 class 별 로 나누었기에 내게 맞는 class를 찾을 수 있는 기법
#Seglist Allocator
malloc(n)
새롭게 요청된 메모리로부터 n개의 byte block 할당.
Untitled
Given an array of free lists, each one for some size class
To allocate a block of size n:
Search appropriate free list for block of size m > n
처음부터 찾는다 : m>n인 m의 크기를 가진 free list를 찾는다
ex. malloc(4)라고 하면 4를 수용하는 block을 가진 class를 찾음
→ block이 있는 경우는 block할당, 나머지 frag는 집어넣음
malloc(6)을 요청했다면 5-8 class에서 할당받는다.
→ 8 size block 중 나머지 2개는 1-2class로 이동한다.
(구현 별 문제 : 나머지 fragment를 어떻게 처리할 것인가. 앞에다 붙이는게 빠를수도 있다 (LIFO니까))
m payload, n 실제 class의 size
Payload를 고려하는 것에 따라 m>=n
If an appropriate block is found:
Split block and place fragment on appropriate list (optional)
If no block is found, try next larger class
만일 block size가 없다면 더 큰 class로 이동
Repeat until block is found
If no block is found:
Request additional heap memory from OS (using sbrk())
Allocate block of n bytes from this new memory
Place remainder as a single free block in largest size class.
To free a block:
Coalesce and place on appropriate list
block의 free : 앞, 뒤를 보고 coalesce
Advantages of seglist allocators
Higher throughput) malloc을 할 때 성능이 좋다
log time for power-of-two size classes
size class로 나뉘어져 있기에 fragmentation을 최소화하면서 적합한 class를 빨리 찾을 수 있다.
Better memory utilization
First-fit search of segregated free list approximates a best-fit search of entire heap.
앞에서부터 찾더라도 나보다도 큰 공간이지만 나중에 보니 fragmentation을 최소화할 수 있는 block이 있을 수도 있다.
Extreme case: Giving each block its own size class is equivalent to best-fit.
모든 각각의 block마다 size class를 두고 나서 best fit 해당할수도 있으나 class를 너무 많이 뽑아야 함.
#More Info on Allocators
Knuth, “The Art of Computer Programming”, 2nd edition, Addison Wesley, 1973
The classic reference on dynamic storage allocation
Wilson et al, “Dynamic Storage Allocation: A Survey and Critical Review”, Proc. 1995 Int’l Workshop on Memory Management, Kinross, Scotland, Sept, 1995.
Comprehensive survey
#Garbage collection
#Implicit Memory Management: Garbage Collection
integer pointer
int type 128 byte (32 * 4byte) (heap에 할당)
stack에 할당 (local variable)
address space를 보면
실제 사용하지 않는 공간임에도 불구하고 자리를 차지함
garbage collection : 자동으로 heap에 할당된 storage들에 대한 reclamation
Garbage collection: automatic reclamation of heap-allocated storage—application never has to free
void foo() {
int *p = malloc(128);
return; /** p block is now garbage */
}
Common in many dynamic languages:
Python, Ruby, Java, Perl, ML, Lisp, Mathematica
Variants (“conservative” garbage collectors) exist for C and C++
However, cannot necessarily collect all garbage
#Garbage Collection
c에서의 garbage collection 구현의 어려움
How does the memory manager know when memory can be freed?
내가 할당한 memory allocation이 runtime시에 언제 free될 것이냐
In general we cannot know what is going to be used in the future since it depends on conditionals
But we can tell that certain blocks cannot be used if there are no pointers to them
= program이 어떻게 실행될지 execution order를 정확히 모르면 모른다.
그러나 적어도, heap에 할당된 memory object를 point하는 ptr가 없다면 해당 allocated block은 사용되지 않는다.
(point하는 ptr가 없으니까 나중에 garbage collect해도 되는구나)
Must make certain assumptions about pointers
memory int type에 있는 변수 안의 값이 ptr 일수도 아닐수도 있다.
Memory manager can distinguish pointers from non-pointers
ptr 는 다른 memory obj를 가리키는 address일수도, int일수도 있으나 이를 구분할 방법은 c에서는 없음.
memory manager가 할 수 있다고 가정
어떤 block이든간에 시작을 point함
All pointers point to the start of a block
type casting같이 ptr 숨김을 못한다.
Cannot hide pointers
(e.g., by coercing them to an int, and then back again)
#Classical GC Algorithms
Mark-and-sweep collection (McCarthy, 1960)
Does not move blocks (unless you also “compact”)
Reference counting (Collins, 1960)
Does not move blocks (not discussed)
Copying collection (Minsky, 1963)
Moves blocks (not discussed)
Generational Collectors (Lieberman and Hewitt, 1983)
Collection based on lifetimes
Most allocations become garbage very soon
So focus reclamation work on zones of memory recently allocated
For more information:
Jones and Lin, “Garbage Collection: Algorithms for Automatic Dynamic Memory”, John Wiley & Sons, 1996. Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
#Memory as a Graph
grapy로 표현 : heap memory object로 만든 direct graph
어떤 ds를 만들었는데 heap 안에 있는 object를 stack밖에서 point하는 경우
root node는 heap 안에 있는 것을 가리키는 형상
direct graph :
모종의 자료구조로 인해 binary tree를 heap 안에서 만들었다고 하면 Root로부터 시작하여 heap 안의 object를 point하는 direct graph
어느 순간에 가게 되면 외부에서 가리키는 ptr이 없어지는 경우도 존재.
ex. heap에는 이미 할당되어 있지만 함수가 return되며 point하는 local var이 사라짐
→ not-reachable garbage 생성
heap node는 이런식으로 tree로 표현되지만 실제로는 1dim array : 어떤 부분은 free하고 어떤 부분은 allocated, 어떤 부분은 reachable-not reachable
식별할 수 있다면 reachable하지 않는 놈들을 free하면 됨 : mark and sweep algorithm
We view memory as a directed graph
Each block is a node in the graph / Each pointer is an edge in the graph
Locations not in the heap that contain pointers into the heap are called root nodes (e.g. registers, locations on the stack, global variables)
A node (block) is reachable if there is a path from any root to that node.
Non-reachable nodes are garbage (cannot be needed by the application)
#Mark and Sweep Collecting
Can build on top of malloc/free package
Allocate using malloc until you “run out of space”
malloc을 지속한다 : 공간이 없을 때까지
공간이 없다 = sbrk를 써서 계속 확장하더라도 malloc이 잘 안되는 경우 memory 공간이 없음
When out of space:
Use extra mark bit in the head of each block
Header에다가 extra bit를 가져가 mark한다
mark : 앞서 root에 해당하는 녀석을 인자로 돌려 reachable한 녀석들을 dfs하여 mark bit set
Mark: Start at roots and set mark bit on each reachable block
Recursively Mark를 수행하며 각 block에 reachable한 경우에 한하여 mark해줌
→ not reachable은 allocated되어 있지만 mark bit이 set되어있지 않음
Sweep: Scan all blocks and free blocks that are not marked root
1dim 평면의 array의 처음부터 끝 brk까지 scan하며 mark되지 않은 block에 대하여 free
Before mark - free list를 pt하는게 아닌 application 관점에서 앞서의 tree를 구현
after mark - rechable한 경우 mark bit을 setting해줌
after sweep - not rechable 들은 free를 해줌 (allocated but not marked)
#Assumptions For a Simple Implementation
Application
new(n): returns pointer to new block with all locations cleared
New(n) : heap memory obj를 할당하고 ptr를 return
read(b,i): read location i of block b into register
Read(), block b에서 i번째에 해당하는 값을 읽어 register에 집어넣음
write(b,i,v): write v into location i of block b
write(b,i,v) : block b의 i번째에 v를 쓴다.
Each block will have a header word
앞에는 header word가 있다고 가정하고 collector를 구현하자.
addressed as b[-1], for a block b
Used for different purposes in different collectors
functions/Instructions used by the Garbage Collector
is_ptr(p): determines whether p is a pointer
p가 ptr인지 int인지 구분할 수 없지만 구분된다고 가정하자.
length(b): returns the length of block b, not including the header
block b에서의 header를 제외한 word개수
get_roots(): returns all the roots
#Mark and Sweep (cont.)
Mark using depth-first traversal of the memory graph
P의 크기만큼을 보면서 p[i]를 recursive하게 돌아감 → 초록tordls reachable obj에 대해 mark해줌
ptr mark(ptr p)
{
if (!is_ptr(p))
return; // do nothing if not pointer
if (markBitSet(p))
return; // check if already marked
setMarkBit(p); // set the mark bit
for (i = 0; i < length(p); i++) // call mark on all words
}
mark(p[i]); // in the block return;
Sweep using lengths to find next block
P (시작) end (breakpoint)
ptr sweep(ptr p, ptr end)
{
while (p < end)
{
if markBitSet (p)
clearMarkBit();
else if (allocateBitSet(p))
free(p);
p += length(p);
}
}
#Conservative Mark & Sweep in C
c 언어 상 p가 가리키는 것이 memory block 중 ptr block, int block일 수 있음
memory value type를 저장하지 않기에 우리는 구분 불가능했던 것
구현 방법 : block에서 left/right해서 있는 값은 무조건 ptr라고 하고
balenced bt를 만들어 point하여 allocate block을 따라가 mark and sweep한다.
→ conservative하게 정확하게 구분할 수는 없지만 어느정도 determine 한다라고 가정
실제 가리키는게 not allocated이지만 allocated라고 가정해놓고 point
A “conservative garbage collector” for C programs
is_ptr() determines if a word is a pointer by checking if it points to an allocated block of memory
But, in C pointers can point to the middle of a block
So how to find the beginning of the block?
Can use a balanced binary tree to keep track of all allocated blocks (key is start-of-block)
Balanced-tree pointers can be stored in header (use two additional words)
Left: smaller addresses / Right: larger addresses
(Left / right)를 통해 balanced bt를 구축하게 됏을 때 left가 alloc 안되어 있지만 되어 있다는 block이라고 하면 garbage collection할 때 memory leak이 발생하기는 하나 구현할 수는 있다.
#Memory-related perils and pitfalls
#Memory-Related Perils and Pitfalls
Dereferencing bad pointers
Reading uninitialized memory
Overwriting memory
Referencing nonexistent variables
Freeing blocks multiple times
Referencing freed blocks
Failing to free blocks
#Dereferencing Bad Pointers
The classic scanf bug
int val;
...
scanf(“%d”, val);
#Reading Uninitialized Memory
Assuming that heap data is initialized to zero
/* return y = Ax */
int *matvec(int **A, int *x)
{
int y = malloc(Nsizeof(int));
int i, j;
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
y[i] += A[i][j] * x[j];
return y;
}
#Overwriting Memory
Allocating the (possibly) wrong sized object
int **p;
p = malloc(Nsizeof(int));
for (i = 0; i < N; i++)
{
p[i] = malloc(Msizeof(int));
}
Off-by-one error
int **p;
p = malloc(N * sizeof(int));
for (i = 0; i <= N; i++)
{
p[i] = malloc(Msizeof(int));
}
Not checking the max string size
char s[8];
int i;
gets(s); /* reads “123456789” from stdin */
Basis for classic buffer overflow attacks
Misunderstanding pointer arithmetic
int *search(int *p, int val)
{
while (*p && *p != val)
p += sizeof(int);
return p;
}
Referencing a pointer instead of the object it points to
int *BinheapDelete(int **binheap, int *size)
{
int *packet;
packet = binheap[0];
binheap[0] = binheap[*size - 1];
*size--;
Heapify(binheap, *size, 0);
return (packet);
}
#Referencing Nonexistent Variables
Forgetting that local variables disappear when a function returns
int *foo ()
{
int val;
return &val;
}
#Freeing Blocks Multiple Times
Nasty!
x = malloc(N*sizeof(int));
// <manipulate x>
free(x);
y = malloc(M*sizeof(int));
// <manipulate y>
free(x);
#Referencing Freed Blocks
Evil!
x = malloc(Nsizeof(int));
// <manipulate x>
free(x);
...
y = malloc(Msizeof(int));
for (i = 0; i < M; i++)
y[i] = x[i]++;
#Failing to Free Blocks (Memory Leaks)
Slow, long-term killer!
foo()
{
int x = malloc(Nsizeof(int));
... return;
}
Freeing only part of a data structure
struct list
{
int val;
struct list *next;
};
foo()
{
struct list *head = malloc(sizeof(struct list));
head->val = 0;
head->next = NULL;
// <create and manipulate the rest of the list>
... free(head);
return;
}
#Dealing With Memory Bugs
Debugger : gdb
Good for finding bad pointer dereferences Hard to detect the other memory bugs
Data structure consistency checker
Runs silently, prints message only on error Use as a probe to zero in on error
Binary translator: valgrind
Powerful debugging and analysis technique
Rewrites text section of executable object file
Checks each individual reference at runtime
Bad pointers, overwrites, refs outside of allocated block