123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- (* Non-blocking heaps *)
- (* Copyright (C) Florian Negele *)
- (** The HeapManager module provides a lock-free data structure called Heap that handles memory management. *)
- (** A heap manages the allocation and deallocation of blocks of various sizes within a contiguous memory region. *)
- MODULE HeapManager;
- IMPORT CPU, Processors;
- (* The minimal size of a memory block *)
- CONST BlockSize = SIZE OF ADDRESS * 8;
- (* Representation of a memory block *)
- TYPE Block = POINTER {UNSAFE} TO RECORD index: SIZE; next {UNTRACED}: Block END;
- (** Represents a heap which manages a contiguous memory region. *)
- (** Heaps have to be initialised using the HeapManager.Initialize procedure before they are available for memory allocations. *)
- (* A heap is structured into a buddy system of memory blocks of various sizes. *)
- (* It maintains a free list for each different size of blocks. *)
- TYPE Heap* = RECORD
- free: ARRAY SIZE OF ADDRESS * 8 OF RECORD first {UNTRACED}, pooled {UNTRACED}: Block END;
- begin, end: ADDRESS; sentinel := 0: SIZE;
- END;
- VAR processors: ARRAY Processors.Maximum OF RECORD {ALIGNED (CPU.CacheLineSize)} hazard {UNTRACED}: Block END;
- (* Computes the index of a block that has the given size. *)
- PROCEDURE GetIndex (size: SIZE): SIZE;
- VAR result := 0: SIZE;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- size := (size - 1) DIV BlockSize;
- WHILE size # 0 DO INC (result); size := size DIV 2 END;
- RETURN result;
- END GetIndex;
- (** Initializes a heap that manages the memory region encompassed by the specified address range. *)
- (** The memory area must be owned by the caller and may not overlap with other heaps. *)
- (** This procedure must be called once before memory can be allocated from the corresponding heap. *)
- PROCEDURE Initialize- (VAR heap: Heap; begin, end: ADDRESS);
- VAR index: SIZE; next: ADDRESS; first {UNTRACED}: Block;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- (* check for valid arguments *)
- ASSERT (begin # NIL);
- ASSERT (end >= begin + BlockSize);
- (* setup heap size and align memory region *)
- begin := begin + (BlockSize - (begin + SIZE OF ADDRESS) MOD BlockSize) MOD BlockSize;
- end := begin + (end - begin) DIV BlockSize * BlockSize; heap.begin := begin; heap.end := end;
- (* setup all blocks and map the memory range as unallocated *)
- heap.sentinel := GetIndex (end - begin + BlockSize); index := heap.sentinel - 1;
- REPEAT
- LOOP
- next := begin + ASH (BlockSize, index);
- IF next <= end THEN EXIT END;
- DEC (index); heap.free[index].first := NIL;
- END;
- first := begin; first.next := heap.free[index].first;
- heap.free[index].first := first; begin := next;
- UNTIL begin = end;
- WHILE index # 0 DO DEC (index); heap.free[index].first := NIL END;
- REPEAT heap.free[index].pooled := NIL; INC (index) UNTIL index = heap.sentinel;
- END Initialize;
- PROCEDURE Access (VAR first {UNTRACED}: Block): {UNTRACED} Block;
- VAR index: SIZE; value {UNTRACED}: Block;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- value := CAS (first, NIL, NIL);
- index := Processors.GetCurrentIndex ();
- REPEAT
- processors[index].hazard := value;
- value := CAS (first, NIL, NIL);
- UNTIL processors[index].hazard = value;
- RETURN value;
- END Access;
- PROCEDURE Discard;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- processors[Processors.GetCurrentIndex ()].hazard := NIL;
- END Discard;
- PROCEDURE IsHazardous (block {UNTRACED}: Block): BOOLEAN;
- VAR index: SIZE;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- FOR index := 0 TO Processors.Maximum - 1 DO
- IF block = processors[index].hazard THEN RETURN TRUE END;
- END;
- RETURN FALSE;
- END IsHazardous;
- PROCEDURE Pool (block {UNTRACED}: Block; index: SIZE; VAR heap: Heap);
- VAR pooled {UNTRACED}: Block;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- LOOP
- pooled := CAS (heap.free[index].pooled, NIL, NIL); block.next := pooled;
- IF CAS (heap.free[index].pooled, pooled, block) = pooled THEN RETURN END;
- CPU.Backoff;
- END;
- END Pool;
- PROCEDURE Acquire (index: SIZE; VAR heap: Heap): {UNTRACED} Block;
- VAR first {UNTRACED}, value {UNTRACED}: Block;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- first := CAS (heap.free[index].pooled, NIL, NIL);
- IF (first # NIL) & (CAS (heap.free[index].pooled, first, NIL) = first) THEN
- REPEAT value := first.next; Release (first, index, heap); first := value UNTIL first = NIL;
- END;
- LOOP
- first := Access (heap.free[index].first);
- IF first = NIL THEN Discard; RETURN NIL END;
- value := CAS (heap.free[index].first, first, first.next);
- Discard; IF value = first THEN EXIT END;
- CPU.Backoff;
- END;
- RETURN first;
- END Acquire;
- PROCEDURE Release (block {UNTRACED}: Block; index: SIZE; VAR heap: Heap);
- VAR size: SIZE; buddy {UNTRACED}, first {UNTRACED}, value {UNTRACED}: Block;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- size := ASH (BlockSize, index);
- LOOP
- IF IsHazardous (block) THEN Pool (block, index, heap); RETURN END;
- LOOP
- first := Access (heap.free[index].first); IF first = NIL THEN Discard; EXIT END;
- IF ODD ((ADDRESS OF block^ - heap.begin) DIV size) THEN buddy := ADDRESS OF block^ - size ELSE buddy := ADDRESS OF block^ + size END;
- IF buddy # first THEN Discard; EXIT END; value := CAS (heap.free[index].first, first, first.next);
- Discard; IF value # first THEN first := value; EXIT END; INC (index); size := size * 2;
- IF ADDRESS OF buddy^ < ADDRESS OF block^ THEN block := buddy; IF IsHazardous (block) THEN Pool (block, index, heap); RETURN END END;
- END;
- block.next := first;
- IF CAS (heap.free[index].first, first, block) = first THEN EXIT END;
- CPU.Backoff;
- END;
- END Release;
- (** Allocates a block of memory with the requested size from the specified heap. *)
- (** The return value is the first address of the allocated memory, or NIL if the heap as no more free memory. *)
- PROCEDURE Allocate- (size: SIZE; VAR heap: Heap): ADDRESS;
- VAR index, current: SIZE; result: ADDRESS; block {UNTRACED}: Block;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- (* check for valid arguments *)
- ASSERT (size # 0);
- ASSERT (heap.sentinel # 0);
- index := GetIndex (size + SIZE OF ADDRESS);
- IF index >= heap.sentinel THEN RETURN NIL END;
- current := index; size := ASH (BlockSize, index);
- LOOP
- result := Acquire (current, heap);
- IF result # NIL THEN EXIT END;
- INC (current); size := size * 2;
- IF current = heap.sentinel THEN RETURN NIL END;
- END;
- WHILE current # index DO
- DEC (current); size := size DIV 2;
- Release (result + size, current, heap);
- END;
- block := result; block.index := index;
- RETURN result + SIZE OF ADDRESS;
- END Allocate;
- (** Deallocates a memory block that was previously allocated using a call to the HeapManager.Allocate procedure. *)
- PROCEDURE Deallocate- (address: ADDRESS; VAR heap: Heap);
- VAR block {UNTRACED}: Block; index: SIZE;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- ASSERT (heap.sentinel # 0);
- ASSERT (IsValid (address, heap));
- block := address - SIZE OF ADDRESS; index := block.index;
- ASSERT (index < heap.sentinel);
- Release (block, index, heap);
- END Deallocate;
- (** Checks whether an address is a valid heap address. *)
- PROCEDURE IsValid- (address: ADDRESS; CONST heap: Heap): BOOLEAN;
- BEGIN {UNCOOPERATIVE, UNCHECKED} RETURN (address >= heap.begin + SIZE OF ADDRESS) & (address < heap.end) & ((address MOD BlockSize) = 0);
- END IsValid;
- (** Returns the size of an allocated block of memory. *)
- PROCEDURE GetSize- (address: ADDRESS; CONST heap: Heap): SIZE;
- VAR block {UNTRACED}: Block; index: SIZE;
- BEGIN {UNCOOPERATIVE, UNCHECKED}
- ASSERT (heap.sentinel # 0);
- ASSERT (IsValid (address, heap));
- block := address - SIZE OF ADDRESS; index := block.index;
- ASSERT (index < heap.sentinel);
- RETURN ASH (BlockSize, index);
- END GetSize;
- END HeapManager.
|