Memory management Review Outline -- explicit management (malloc/free) -- basic idea -- goals/trade-offs -- simple implementation -- malloc -- challenges/tools -- garbage collection -- reference counting -- mark and sweep -- 3-color http://www.cs.princeton.edu/courses/archive/spr08/cos217/lectures/13Memory.ppt http://www.cs.princeton.edu/courses/archive/spr08/cos217/lectures/14Memory.ppt ftp://ftp.cs.utexas.edu/pub/garbage/gcsurvey.ps Intro Basic problem: Allocating/deallocating variable-sized storage - mostly in context of process malloc/free new/gc -- many of same principles apply in other contexts --- free space on disk --- OS internals --- OS segmentation --- user-level allocator - example -- process address space ----------------- | stack | | \|/ | | | | | | | | | | | | | | | | | | | | | | | | | | /|\ | | heap | | | | globals | | | | code | | free (why?) | ------------------ - heap allows creation of on-demand data structures -- e.g., when programmer doesn't know right size or how many until run time - naive allocator <-- break ------------------ <-- top | | | | | | | heap | | | | | | | ------------------ malloc(size){ ret = top; while(top + size >= break){ sbrk(CHUNK_SIZE); break += CHUNK_SIZE); } top = top + size; // and/or sbrk() } free(void *ptr){ return; } - problems with naive allocator -- inefficient/memory leak -- alignment of allocated data structures - other requirements -- fast -- general (any workload) -- allocate all dynamic data structures on heap - better allocator add header and footer (for simplicity, I'll put lots of info in it DA: space overhead) ------------- | free/used | header | size | | next free | <-- only valid if free == 1 | prev free | <-- only valid if free == 1 ............. | | | | | [user] | body | | | | | [padding] | ............. | free/used | footer | size | ------------- -- Keep doubly linked list of free blocks sorted by address [picture] -- malloc() scan list from current pointer to find a big enough block --- if too big, split it; insert remainder to free list (first fit) [picture] --- if none, sbrk; add big new chunk to free list; then split it and add remainder to free list [picture] --- return pointer -- free(usrptr) calculate block pointer from usrptr find next free block (how? -- size pointer gets us to next header; etc.) find prev free block (how? -- footer) insert to free list if next or prev is adjacent, coalesce blocks How is this approach? -- time allocate O(n) free O(n) -- space --- issue 1: big headers and footers [[optimization: next/prev in body]] --- issue 2: first fit --> fragmentation [[optimization: best fit; but how to do best fit efficiently?]] [[QUESTION]] how to do better? -- segregated free lists -- different lists of different approx sizes [picture] --> Allocation is fast O(1)-ish [[but coalesce gets more complex and slower]] --> need to find by size and by neighbor... solution? -- two free lists -- global sorted by address size sorted by address -- tree of free blocks sorted by size -- tree of free blocks sorted by address allocate O(log(N)) free O(log(N)) space (put extra pointers in [user] --> min alloc size is 6 pointers, but otherwise pretty good...) Garbage collection avoid (too few frees) memory leaks avoid double-free (too many frees) (related to safe language v. unsafe unsafe allows pointer arithmatic, overflow end of array, etc. Not strictly speaking garbage collection v. explicit issues. But related) -- solution 1: Reference counting often used in C programs for subset of objects e.g., memory pages in kernel (core map) other uses -- files/inodes in file system ... basic idea: -- object has extra field -- reference count -- add work on pointer manipulation --- remove pointer from existing object decrement ref count; free if zero (on free: examine all pointers in object; decrement their objects' ref counts) --- add pointer to existing object increment reference count problem: cycles problem: efficiency (adds cost to pointer manipulation) Assume safe language (java, scripting) basic idea -- find all reachable data structures; others are free e.g., Tri-color [[ picture -- a bunch of objects; some with pointers to each other ]] 3 sets: white -- condemned set (candidates for gc) black -- reachable from root (must stay) grey -- reachable from root (must stay) but children not scanned initially: all objects white (except root (grey)) objects move from white->gray->black (never in other direction) while(grey set is not empty) e = select a grey element foreach pointer p in e if p is white, make it grey if p is black or grey do nothing finally put all white elements on the free list cost: O(# reachable + # free) optimizations: (1) Incremental instead of "stop and copy" (which introduces "the awkward pause") -- let program run a bit, then garbage collect a bit, then run program a bit... --> if program mutator writes to a black object's pointer, we could miss reachable object (since we won't traverse black object again) --> on update of black object pointer to point to white object either make black object gray or make destination gray (2) Copying garbage collectors (address external fragmentation; reduce cost) e.g., semispace allocate in space 0; when space 0 is full move all live objects to "space 1"; continue allocating in space 1 when space 1 full; move all live objects to space 0 etc. --> cost O(# reachable) (3) Generational reduce cost most objects live a very short time some objects live a long time --> segregate objects by age into multiple areas [picture] --> garbage collect "new" areas more frequently than "old" ones --> survivors promoted to "older" area --> cost O(# reachable in target generation) (Challenge: tracking pointers that extend across generations --> indirection table or list of outbound pointers from each generation...) Memory bugs Memory bugs and concurrency bugs are what keep you up at night. Bryant and O'Halloran -- "Common Memory-Related Bugs in C Programs" I'll only comment on a few... (1) Dereferencing bad pointers (2) Reading uninitialized memory (3) Allowing stack buffer overflows void bufoverflow() { char buf[64]; gets(buf); /* Possible buffer overflow */ return; } Big source of security vulnerabilities -- Any time you receive input from outside your program, treat it as coming from an adversary (4) Assuming pointers and the objects they point to are the same size (5) Making off-by-one errors (6) Referencing a pointer instead of the object it points to (7) Misunderstanding pointer arithmatic int *search(int *p, int val) { while(*p && *p != val){ p += sizeof(int); /* Should be p++ */ return p } or int arith() { int p[100]; char *c = p; p++; c++; assert((char*)p == (char *)c); /* Nope */ } (8) Referencing nonexistant variables int *stackref(){ int val; return &val; } (9) Referencing data in free heap blocks (10) Introducing memory leaks One solution: safe, managed languages (e.g., Java) which of these is Java still vulnerable to? Another aid for C/C++: Memory debugging tools debugger (e.g., gdb) gdb1.c gdb2.c debugging versions of malloc (e.g., dmalloc) runtime tools (e.g., valgrind)