├── README.md ├── index.html ├── script.js └── style.css /README.md: -------------------------------------------------------------------------------- 1 | # Memory Management Simulator 2 | An online simulation of operating system memory management to help computer science students understand the algorithm in a visual way. 3 | 4 | * Users can input processes of a given size and length of time. 5 | * Each process is automatically allocated to a ‘chunk’ in memory (as would be 6 | done by an operating system). 7 | * Memory chunks are partitioned for efficient process allocation (using the 8 | ‘Best-Fit’ strategy to reduce fragmentation). 9 | * As the program runs, a clock ticks in the background, decrementing the time 10 | remaining for each process. 11 | * When a process terminates, chunks are automatically deallocated (freeing 12 | memory). 13 | * Memory is simulated using a doubly linked list data structure. 14 | 15 | ![Doubly linked list diagram](http://i.imgur.com/muMyi6A.jpg) 16 | 17 | ### Demo 18 | A live version can be found here: 19 | [Memory Management Simulator](http://jamiegoodson.uk/projects/memory-management-simulator/) 20 | 21 | ![Screenshot](https://i.imgur.com/9FnvI0n.png) 22 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Jamie Goodson's Website 5 | 6 | 7 | 8 | 9 |
10 |
11 |

Memory Management

An Online Simulation

12 |
13 | 14 |
15 |
16 |
17 |
18 |
19 | 20 |
21 |

Add Process

22 |
23 | 24 | 25 | 26 |
27 |

28 |

Process Queue

29 | 30 | 31 |
Process IDSize (K)Time Units Remaining
32 |
33 |
34 |
35 |
DEBUG LOG
36 |
37 |
38 |
39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | function Process(size, time) { 2 | this.size = size; 3 | this.timeLeft = time; 4 | this.allocatedBlock = null; 5 | this.id = processID; 6 | 7 | processID += 1; 8 | 9 | this.isAllocated = function() { 10 | return this.allocatedBlock != null; 11 | }; 12 | 13 | this.tick = function() { 14 | this.timeLeft -=1; 15 | }; 16 | }; 17 | 18 | function MemControlBlock(size) { 19 | this.size = size; 20 | this.process = null; 21 | this.available = true; 22 | this.next = null; 23 | this.prev = null; 24 | this.fromPartition = false; // Used to determine whether height of a MemControlBlock needs to be added 25 | 26 | this.setProcess = function(process) { 27 | if (process == null) { 28 | this.process = null; 29 | this.available = true; 30 | } else { 31 | this.process = process; 32 | this.available = false; 33 | }; 34 | }; 35 | }; 36 | 37 | // Simulates memory 38 | function Heap() { 39 | this.head = null; 40 | this.size = 0; 41 | 42 | // Allocate process to memory. 43 | // Use best-fit method: from the list of holes, choose the smallest hole 44 | this.requestAllocation = function(process) { 45 | blockBestFit = this.head; 46 | 47 | // Make sure our initial best block is valid 48 | while ((blockBestFit.size < process.size) || (!blockBestFit.available)) { 49 | blockBestFit = blockBestFit.next; 50 | if (blockBestFit == null) {return false}; // Means we couldn't even find an initial valid block 51 | }; 52 | //log("Initial best block: " + blockBestFit.size); 53 | 54 | // See if there's an even better block 55 | block = blockBestFit.next; 56 | while (block != null) { 57 | //log("Testing block: " + block.size); 58 | if ((block.size >= process.size) && (block.available) && (block.size < blockBestFit.size)) { 59 | blockBestFit = block; 60 | //log("New best block: " + blockBestFit.size); 61 | }; 62 | block = block.next; 63 | }; 64 | 65 | spaceLeftover = blockBestFit.size - (process.size + memControlBlockSize); // Space leftover if block was divided 66 | 67 | // Partition block if needed 68 | if (spaceLeftover > 0) { 69 | newBlock = new MemControlBlock(spaceLeftover); 70 | 71 | nextBlock = blockBestFit.next; 72 | if (nextBlock != null) { 73 | nextBlock.prev = newBlock; 74 | newBlock.next = nextBlock; 75 | }; 76 | 77 | blockBestFit.next = newBlock; 78 | newBlock.prev = blockBestFit; 79 | 80 | blockBestFit.size = process.size; 81 | 82 | newBlock.fromPartition = true; 83 | }; 84 | 85 | blockBestFit.setProcess(process); 86 | process.allocatedBlock = blockBestFit; 87 | return true; 88 | }; 89 | 90 | this.deallocateProcess = function(process) { 91 | process.allocatedBlock.setProcess(null); 92 | process.allocatedBlock = null; 93 | }; 94 | 95 | this.add = function(block) { 96 | if (this.head == null) { 97 | this.head = block; 98 | } else { 99 | block.next = this.head; 100 | this.head.prev = block; 101 | this.head = block; 102 | }; 103 | 104 | this.size += block.size; 105 | } 106 | 107 | this.toString = function() { 108 | string = "[|"; 109 | block = this.head; 110 | 111 | prefix = ""; 112 | suffix = " |"; 113 | while (block != null) { 114 | if (block.available) {prefix = " "} else {prefix = " "}; 115 | string += (prefix + block.size + suffix); 116 | block = block.next; 117 | }; 118 | 119 | string += "]" 120 | return string; 121 | }; 122 | 123 | this.repaint = function() { 124 | block = this.head; 125 | memoryDiv.innerHTML = ""; 126 | 127 | while (block != null) { 128 | height = ((block.size/heap.size)*100); 129 | if (block.fromPartition) { 130 | height += (memControlBlockSize/heap.size)*100; 131 | }; 132 | 133 | // Create div block element 134 | divBlock = document.createElement("div"); 135 | divBlock.style.height = (height + "%"); 136 | divBlock.setAttribute("id", "block"); 137 | if (block.available) {divBlock.className = "available"} else {divBlock.className = "unavailable"}; 138 | memoryDiv.appendChild(divBlock); 139 | 140 | // Add size label 141 | // TODO: Show process details on mouse over 142 | blockLabel = document.createElement("div"); 143 | blockLabel.setAttribute("id", "blockLabel"); 144 | blockLabel.style.height = (height + "%"); 145 | blockLabel.innerHTML = block.size + "K"; 146 | if (height <= 2) { 147 | blockLabel.style.display = "none"; 148 | }; 149 | divBlock.appendChild(blockLabel); 150 | 151 | block = block.next; 152 | }; 153 | }; 154 | }; 155 | 156 | // Handle front-end process submission 157 | document.getElementById("processForm").onsubmit = function () { 158 | elements = this.elements; // Form elements 159 | 160 | inProcessSize = elements.namedItem("processSize"); 161 | inProcessTime = elements.namedItem("processTime"); 162 | 163 | process = new Process(parseInt(inProcessSize.value), parseInt(inProcessTime.value)); 164 | 165 | /* heap.requestAllocation(process); 166 | heap.repaint();*/ 167 | processes.push(process); 168 | addProcessToTable(process); 169 | 170 | 171 | // Debug log 172 | log("Requesting: " + process.size); 173 | log(heap.toString() + "
"); 174 | 175 | // Clear form 176 | inProcessSize.value = ""; 177 | inProcessTime.value = ""; 178 | 179 | return false; 180 | }; 181 | 182 | function log(string) { 183 | logBox.innerHTML += (string + "
"); 184 | } 185 | 186 | function addProcessToTable(process) { 187 | row = document.createElement("tr"); 188 | row.setAttribute("id", "process" + process.id); 189 | 190 | colName = document.createElement("td"); 191 | colName.innerHTML = process.id; 192 | 193 | colSize = document.createElement("td"); 194 | colSize.innerHTML = process.size; 195 | 196 | colTime = document.createElement("td"); 197 | colTime.setAttribute("id", "process" + process.id + "timeLeft"); 198 | colTime.innerHTML = process.timeLeft; 199 | 200 | row.appendChild(colName); 201 | row.appendChild(colSize); 202 | row.appendChild(colTime); 203 | 204 | processTable.appendChild(row); 205 | }; 206 | 207 | function removeProcessFromTable(process) { 208 | processTable.removeChild(document.getElementById("process" + process.id)); 209 | }; 210 | 211 | // TODO: Update 'time left' for each row in table 212 | function refreshTable() { 213 | for (i=0; i -1) { 254 | processes.splice(index, 1); 255 | }; 256 | 257 | // Remove process from table 258 | removeProcessFromTable(process); 259 | }; 260 | }; 261 | }; 262 | 263 | refreshTable(); 264 | heap.repaint(); 265 | }, 1000); 266 | 267 | 268 | 269 | 270 | /*processSizes = [150, 383, 400, 800, 2000, 10, 50, 20, 300, 830]; 271 | var processIndex = 0; 272 | var allocateProcesss = setInterval(function() { 273 | process = new Process(processSizes[processIndex]); 274 | //log("Requesting allocation for process: " + process.size); 275 | 276 | if (heap.requestAllocation(process)) { 277 | //log("Process successfully allocated."); 278 | //log("New heap: " + heap.toString() + "
"); 279 | heap.repaint(); 280 | } else { 281 | //log("Process failed to be allocated." + "
"); 282 | }; 283 | 284 | processIndex += 1; 285 | if (processIndex == (processSizes.length-1)) { 286 | clearInterval(allocateProcesss); 287 | } 288 | }, 1000);*/ 289 | 290 | /*for (i=0; i"); 297 | heap.repaint(); 298 | } else { 299 | log("Process failed to be allocated." + "
"); 300 | }; 301 | };*/ 302 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | /* https://flatuicolors.com */ 2 | body { 3 | font-family: 'Open Sans', sans-serif; 4 | background-color: #2c3e50; 5 | color: #FFFFFF; 6 | padding: 0; 7 | margin: 0; 8 | font-size: 12pt; 9 | line-height: 1.4; 10 | } 11 | 12 | h1 { 13 | font-size: 40pt; 14 | margin: 0; 15 | margin-bottom: 20px; 16 | line-height: 1; 17 | } 18 | 19 | h2 { 20 | margin: 0; 21 | margin-top: 10px; 22 | margin-bottom: 10px; 23 | line-height: 1; 24 | } 25 | 26 | h3 { 27 | font-size: 16pt; 28 | color: #95A5A6; 29 | margin: 0; 30 | } 31 | 32 | #title { 33 | text-align: center; 34 | margin-bottom: 40px; 35 | } 36 | 37 | #mainContainer { 38 | width: 70%; 39 | margin-top: 30px; 40 | margin-bottom: 20px; 41 | margin-right: auto; 42 | margin-left: auto; 43 | } 44 | 45 | #leftContainer { 46 | float: left; 47 | width: 50%; 48 | } 49 | 50 | #rightContainer { 51 | float: right; 52 | width: 50%; 53 | } 54 | 55 | #memoryContainer { 56 | float: left; 57 | margin-left: auto; 58 | margin-right: auto; 59 | height: 80vh; 60 | width: 150px; 61 | } 62 | 63 | #memory { 64 | float: right; 65 | width: 150px; 66 | height: 100%; 67 | } 68 | 69 | #block { 70 | box-sizing: border-box; 71 | border: 1px solid #2C3E50; 72 | width: 100%; 73 | } 74 | 75 | #blockLabel { 76 | text-align: center; 77 | font-weight: bold; 78 | padding: 2px; 79 | } 80 | 81 | #logBoxContainer { 82 | width: 100%; 83 | float: left; 84 | font-family: Consolas, monospace; 85 | height: 400px; 86 | overflow: hidden; 87 | 88 | /* box-sizing: border-box; 89 | border: 1px solid #FFFFFF;*/ 90 | } 91 | 92 | #logBox { 93 | height: 100%; 94 | width: 100%; 95 | overflow: auto; 96 | padding-right: 20px; /* Hides scrollbar */ 97 | display: none; 98 | } 99 | 100 | .available { 101 | background-color: #27ae60; 102 | } 103 | 104 | .unavailable { 105 | background-color: #c0392b; 106 | } 107 | 108 | #processTable { 109 | width: 400px; 110 | } 111 | 112 | #processTable th { 113 | text-align: left; 114 | padding-right: 20px; 115 | } 116 | 117 | #processTable td { 118 | color: #BDC3C7; 119 | } 120 | 121 | input { 122 | height: 30px; 123 | padding-left: 10px; 124 | padding-right: 10px; 125 | border: 2px solid #95A5A6; 126 | border-radius: 6px; 127 | width: 200px; 128 | margin: 10px; 129 | margin-left: 0; 130 | } --------------------------------------------------------------------------------