1 // Copyright 2007 Google Inc. All Rights Reserved. 2 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 7 // http://www.apache.org/licenses/LICENSE-2.0 8 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // This is an interface to a simple thread safe container with fine-grain locks, 16 // used to hold data blocks and patterns. 17 18 // This file must work with autoconf on its public version, 19 // so these includes are correct. 20 #include "finelock_queue.h" 21 #include "os.h" 22 23 // Page entry queue implementation follows. 24 // Push and Get functions are analogous to lock and unlock operations on a given 25 // page entry, while preserving queue semantics. 26 // 27 // The actual 'queue' implementation is actually just an array. The entries are 28 // never shuffled or re-ordered like that of a real queue. Instead, Get 29 // functions return a random page entry of a given type and lock that particular 30 // page entry until it is unlocked by corresponding Put functions. 31 // 32 // In this implementation, a free page is those page entries where pattern is 33 // null (pe->pattern == 0) 34 35 36 // Constructor: Allocates memory and initialize locks. 37 FineLockPEQueue::FineLockPEQueue( 38 uint64 queuesize, int64 pagesize) { 39 q_size_ = queuesize; 40 pages_ = new struct page_entry[q_size_]; 41 pagelocks_ = new pthread_mutex_t[q_size_]; 42 page_size_ = pagesize; 43 44 // What metric should we measure this run. 45 queue_metric_ = kTouch; 46 47 { // Init all the page locks. 48 for (uint64 i = 0; i < q_size_; i++) { 49 pthread_mutex_init(&(pagelocks_[i]), NULL); 50 // Pages start out owned (locked) by Sat::InitializePages. 51 // A locked state indicates that the page state is unknown, 52 // and the lock should not be aquired. As InitializePages creates 53 // the page records, they will be inserted and unlocked, at which point 54 // they are ready to be aquired and filled by worker threads. 55 sat_assert(pthread_mutex_lock(&(pagelocks_[i])) == 0); 56 } 57 } 58 59 { // Init the random number generator. 60 for (int i = 0; i < 4; i++) { 61 rand_seed_[i] = i + 0xbeef; 62 pthread_mutex_init(&(randlocks_[i]), NULL); 63 } 64 } 65 66 // Try to make a linear congruential generator with our queue size. 67 // We need this to deterministically search all the queue (being able to find 68 // a single available element is a design requirement), but we don't want to 69 // cause any page to be more likley chosen than another. The previous 70 // sequential retry heavily biased pages at the beginning of a bunch, or 71 // isolated pages surrounded by unqualified ones. 72 int64 length = queuesize; 73 int64 modlength = length; 74 int64 a; 75 int64 c; 76 77 if (length < 3) { 78 a = 1; 79 c = 1; 80 } else { 81 // Search for a nontrivial generator. 82 a = getA(length) % length; 83 // If this queue size doesn't have a nontrivial generator (where the 84 // multiplier is greater then one) we'll check increasing queue sizes, 85 // and discard out of bounds results. 86 while (a == 1) { 87 modlength++; 88 a = getA(modlength) % modlength; 89 } 90 c = getC(modlength); 91 } 92 93 // This is our final generator. 94 a_ = a; 95 c_ = c; 96 modlength_ = modlength; 97 } 98 99 // Part of building a linear congruential generator n1 = (a * n0 + c) % m 100 // Get 'a', where a - 1 must be divisible by all prime 101 // factors of 'm', our queue size. 102 int64 FineLockPEQueue::getA(int64 m) { 103 int64 remaining = m; 104 int64 a = 1; 105 if ((((remaining / 4) * 4) == remaining)) { 106 a = 2; 107 } 108 // For each number, let's see if it's divisible, 109 // then divide it out. 110 for (int64 i = 2; i <= m; i++) { 111 if (((remaining / i) * i) == remaining) { 112 remaining /= i; 113 // Keep dividing it out until there's no more. 114 while (((remaining / i) * i) == remaining) 115 remaining /= i; 116 a *= i; 117 } 118 } 119 120 // Return 'a' as specified. 121 return (a + 1) % m; 122 } 123 124 125 // Part of building a linear congruential generator n1 = (a * n0 + c) % m 126 // Get a prime number approx 3/4 the size of our queue. 127 int64 FineLockPEQueue::getC(int64 m) { 128 // Start here at 3/4. 129 int64 start = (3 * m) / 4 + 1; 130 int64 possible_prime = start; 131 // Keep trying until we find a prime. 132 for (possible_prime = start; possible_prime > 1; possible_prime--) { 133 bool failed = false; 134 for (int64 i = 2; i < possible_prime; i++) { 135 if (((possible_prime / i) * i) == possible_prime) { 136 failed = true; 137 break; 138 } 139 } 140 if (!failed) { 141 return possible_prime; 142 } 143 } 144 // One is prime enough. 145 return 1; 146 } 147 148 // Destructor: Clean-up allocated memory and destroy pthread locks. 149 FineLockPEQueue::~FineLockPEQueue() { 150 uint64 i; 151 for (i = 0; i < q_size_; i++) 152 pthread_mutex_destroy(&(pagelocks_[i])); 153 delete[] pagelocks_; 154 delete[] pages_; 155 for (i = 0; i < 4; i++) { 156 pthread_mutex_destroy(&(randlocks_[i])); 157 } 158 } 159 160 161 bool FineLockPEQueue::QueueAnalysis() { 162 const char *measurement = "Error"; 163 uint64 buckets[32]; 164 165 if (queue_metric_ == kTries) 166 measurement = "Failed retrievals"; 167 else if (queue_metric_ == kTouch) 168 measurement = "Reads per page"; 169 170 // Buckets for each log2 access counts. 171 for (int b = 0; b < 32; b++) { 172 buckets[b] = 0; 173 } 174 175 // Bucketize the page counts by highest bit set. 176 for (uint64 i = 0; i < q_size_; i++) { 177 uint32 readcount = pages_[i].touch; 178 int b = 0; 179 for (b = 0; b < 31; b++) { 180 if (readcount < (1u << b)) 181 break; 182 } 183 184 buckets[b]++; 185 } 186 187 logprintf(12, "Log: %s histogram\n", measurement); 188 for (int b = 0; b < 32; b++) { 189 if (buckets[b]) 190 logprintf(12, "Log: %12d - %12d: %12d\n", 191 ((1 << b) >> 1), 1 << b, buckets[b]); 192 } 193 194 return true; 195 } 196 197 namespace { 198 // Callback mechanism for exporting last action. 199 OsLayer *g_os; 200 FineLockPEQueue *g_fpqueue = 0; 201 202 // Global callback to hook into Os object. 203 bool err_log_callback(uint64 paddr, string *buf) { 204 if (g_fpqueue) { 205 return g_fpqueue->ErrorLogCallback(paddr, buf); 206 } 207 return false; 208 } 209 } 210 211 // Setup global state for exporting callback. 212 void FineLockPEQueue::set_os(OsLayer *os) { 213 g_os = os; 214 g_fpqueue = this; 215 } 216 217 OsLayer::ErrCallback FineLockPEQueue::get_err_log_callback() { 218 return err_log_callback; 219 } 220 221 // This call is used to export last transaction info on a particular physical 222 // address. 223 bool FineLockPEQueue::ErrorLogCallback(uint64 paddr, string *message) { 224 struct page_entry pe; 225 OsLayer *os = g_os; 226 sat_assert(g_os); 227 char buf[256]; 228 229 // Find the page of this paddr. 230 int gotpage = GetPageFromPhysical(paddr, &pe); 231 if (!gotpage) { 232 return false; 233 } 234 235 // Find offset into the page. 236 uint64 addr_diff = paddr - pe.paddr; 237 238 // Find vaddr of this paddr. Make sure it matches, 239 // as sometimes virtual memory is not contiguous. 240 char *vaddr = 241 reinterpret_cast<char*>(os->PrepareTestMem(pe.offset, page_size_)); 242 uint64 new_paddr = os->VirtualToPhysical(vaddr + addr_diff); 243 os->ReleaseTestMem(vaddr, pe.offset, page_size_); 244 245 // Is the physical address at this page offset the same as 246 // the physical address we were given? 247 if (new_paddr != paddr) { 248 return false; 249 } 250 251 // Print all the info associated with this page. 252 message->assign(" (Last Transaction:"); 253 254 if (pe.lastpattern) { 255 int offset = addr_diff / 8; 256 datacast_t data; 257 258 data.l32.l = pe.lastpattern->pattern(offset << 1); 259 data.l32.h = pe.lastpattern->pattern((offset << 1) + 1); 260 261 snprintf(buf, sizeof(buf), " %s data=%#016llx", 262 pe.lastpattern->name(), data.l64); 263 message->append(buf); 264 } 265 snprintf(buf, sizeof(buf), " tsc=%#llx)", pe.ts); 266 message->append(buf); 267 return true; 268 } 269 270 bool FineLockPEQueue::GetPageFromPhysical(uint64 paddr, 271 struct page_entry *pe) { 272 // Traverse through array until finding a page 273 // that contains the address we want.. 274 for (uint64 i = 0; i < q_size_; i++) { 275 uint64 page_addr = pages_[i].paddr; 276 // This assumes linear vaddr. 277 if ((page_addr <= paddr) && (page_addr + page_size_ > paddr)) { 278 *pe = pages_[i]; 279 return true; 280 } 281 } 282 return false; 283 } 284 285 286 // Get a random number from the slot we locked. 287 uint64 FineLockPEQueue::GetRandom64FromSlot(int slot) { 288 // 64 bit LCG numbers suggested on the internets by 289 // http://nuclear.llnl.gov/CNP/rng/rngman/node4.html and others. 290 uint64 result = 2862933555777941757ULL * rand_seed_[slot] + 3037000493ULL; 291 rand_seed_[slot] = result; 292 return result; 293 } 294 295 // Get a random number, we have 4 generators to choose from so hopefully we 296 // won't be blocking on this. 297 uint64 FineLockPEQueue::GetRandom64() { 298 // Try each available slot. 299 for (int i = 0; i < 4; i++) { 300 if (pthread_mutex_trylock(&(randlocks_[i])) == 0) { 301 uint64 result = GetRandom64FromSlot(i); 302 pthread_mutex_unlock(&(randlocks_[i])); 303 return result; 304 } 305 } 306 // Forget it, just wait. 307 int i = 0; 308 if (pthread_mutex_lock(&(randlocks_[i])) == 0) { 309 uint64 result = GetRandom64FromSlot(i); 310 pthread_mutex_unlock(&(randlocks_[i])); 311 return result; 312 } 313 314 logprintf(0, "Process Error: Could not acquire random lock.\n"); 315 sat_assert(0); 316 return 0; 317 } 318 319 320 // Helper function to get a random page entry with given predicate, 321 // ie, page_is_valid() or page_is_empty() as defined in finelock_queue.h. 322 // 323 // Setting tag to a value other than kDontCareTag (-1) 324 // indicates that we need a tag match, otherwise any tag will do. 325 // 326 // Returns true on success, false on failure. 327 bool FineLockPEQueue::GetRandomWithPredicateTag(struct page_entry *pe, 328 bool (*pred_func)(struct page_entry*), 329 int32 tag) { 330 if (!pe || !q_size_) 331 return false; 332 333 // Randomly index into page entry array. 334 uint64 first_try = GetRandom64() % q_size_; 335 uint64 next_try = 1; 336 337 // Traverse through array until finding a page meeting given predicate. 338 for (uint64 i = 0; i < q_size_; i++) { 339 uint64 index = (next_try + first_try) % q_size_; 340 // Go through the loop linear conguentially. We are offsetting by 341 // 'first_try' so this path will be a different sequence for every 342 // initioal value chosen. 343 next_try = (a_ * next_try + c_) % (modlength_); 344 while (next_try >= q_size_) { 345 // If we have chosen a modlength greater than the queue size, 346 // discard out of bounds results. 347 next_try = (a_ * next_try + c_) % (modlength_); 348 } 349 350 // If page does not meet predicate, don't trylock (expensive). 351 if (!(pred_func)(&pages_[index])) 352 continue; 353 354 // If page does not meet tag predicate, don't trylock (expensive). 355 if ((tag != kDontCareTag) && !(pages_[index].tag & tag)) 356 continue; 357 358 if (pthread_mutex_trylock(&(pagelocks_[index])) == 0) { 359 // If page property (valid/empty) changes before successfully locking, 360 // release page and move on. 361 if (!(pred_func)(&pages_[index])) { 362 pthread_mutex_unlock(&(pagelocks_[index])); 363 continue; 364 } else { 365 // A page entry with given predicate is locked, returns success. 366 *pe = pages_[index]; 367 368 // Add metrics as necessary. 369 if (pred_func == page_is_valid) { 370 // Measure time to fetch valid page. 371 if (queue_metric_ == kTries) 372 pe->touch = i; 373 // Measure number of times each page is read. 374 if (queue_metric_ == kTouch) 375 pe->touch++; 376 } 377 378 return true; 379 } 380 } 381 } 382 383 return false; 384 } 385 386 // Without tag hint. 387 bool FineLockPEQueue::GetRandomWithPredicate(struct page_entry *pe, 388 bool (*pred_func)(struct page_entry*)) { 389 return GetRandomWithPredicateTag(pe, pred_func, kDontCareTag); 390 } 391 392 393 // GetValid() randomly finds a valid page, locks it and returns page entry by 394 // pointer. 395 // 396 // Returns true on success, false on failure. 397 bool FineLockPEQueue::GetValid(struct page_entry *pe) { 398 return GetRandomWithPredicate(pe, page_is_valid); 399 } 400 401 bool FineLockPEQueue::GetValid(struct page_entry *pe, int32 mask) { 402 return GetRandomWithPredicateTag(pe, page_is_valid, mask); 403 } 404 405 // GetEmpty() randomly finds an empty page, locks it and returns page entry by 406 // pointer. 407 // 408 // Returns true on success, false on failure. 409 bool FineLockPEQueue::GetEmpty(struct page_entry *pe, int32 mask) { 410 return GetRandomWithPredicateTag(pe, page_is_empty, mask); 411 } 412 bool FineLockPEQueue::GetEmpty(struct page_entry *pe) { 413 return GetRandomWithPredicate(pe, page_is_empty); 414 } 415 416 // PutEmpty puts an empty page back into the queue, making it available by 417 // releasing the per-page-entry lock. 418 // 419 // Returns true on success, false on failure. 420 bool FineLockPEQueue::PutEmpty(struct page_entry *pe) { 421 if (!pe || !q_size_) 422 return false; 423 424 int64 index = pe->offset / page_size_; 425 if (!valid_index(index)) 426 return false; 427 428 pages_[index] = *pe; 429 // Enforce that page entry is indeed empty. 430 pages_[index].pattern = 0; 431 return (pthread_mutex_unlock(&(pagelocks_[index])) == 0); 432 } 433 434 // PutValid puts a valid page back into the queue, making it available by 435 // releasing the per-page-entry lock. 436 // 437 // Returns true on success, false on failure. 438 bool FineLockPEQueue::PutValid(struct page_entry *pe) { 439 if (!pe || !page_is_valid(pe) || !q_size_) 440 return false; 441 442 int64 index = pe->offset / page_size_; 443 if (!valid_index(index)) 444 return false; 445 446 pages_[index] = *pe; 447 return (pthread_mutex_unlock(&(pagelocks_[index])) == 0); 448 } 449