1 /*---------------------------------------------------------------------------* 2 Project: Horizon 3 File: fnd_BuddyHeap.h 4 5 Copyright (C)2009 Nintendo Co., Ltd. All rights reserved. 6 7 These coded instructions, statements, and computer programs contain 8 proprietary information of Nintendo of America Inc. and/or Nintendo 9 Company Ltd., and are protected by Federal copyright law. They may 10 not be disclosed to third parties or copied or duplicated in any form, 11 in whole or in part, without the prior written consent of Nintendo. 12 13 $Rev: 23169 $ 14 *---------------------------------------------------------------------------*/ 15 16 #ifndef NN_FND_FND_BUDDYHEAP_H_ 17 #define NN_FND_FND_BUDDYHEAP_H_ 18 19 #include <nn/types.h> 20 #include <nn/fnd/fnd_HeapBase.h> 21 #include <nn/fnd/fnd_Allocator.h> 22 23 namespace nn { 24 namespace fnd { 25 26 template<size_t PageSize, s32 MaxOrder> 27 class BuddyHeapBase : public nn::fnd::HeapBase 28 { 29 protected: 30 template <s32 align, typename T> DivUp(T x)31 static T DivUp(T x) 32 { 33 return static_cast<T>( (x + (align - 1)) / align ); 34 } 35 36 template<size_t alignment> RoundDown(uptr addr)37 static uptr RoundDown(uptr addr) 38 { 39 return (addr / alignment) * alignment; 40 } 41 42 template<size_t alignment> RoundUp(uptr addr)43 static uptr RoundUp(uptr addr) 44 { 45 return RoundDown<alignment>(addr + alignment - 1); 46 } 47 48 public: 49 static const size_t ALIGN = 8; 50 51 /*! 52 @brief 総ページ数を取得します。 53 */ GetNumMaxPages(void)54 static size_t GetNumMaxPages(void) 55 { 56 return 1 << (MAX_ORDER - 1); 57 } 58 59 /*! 60 @brief 総データサイズをバイト単位で取得します。 61 */ GetMaxSize(void)62 static size_t GetMaxSize(void) 63 { 64 return GetNumMaxPages() * PAGE_SIZE; 65 } 66 67 /*! 68 @brief バディヒープを初期化します。 69 70 @param[in] addr ヒープの開始アドレス 71 @param[in] numPages ページ数 72 */ Initialize(uptr addr,size_t numPages)73 void Initialize(uptr addr, size_t numPages) 74 { 75 NN_ASSERT((addr % ALIGN) == 0); 76 77 m_HeapStart = addr; 78 m_HeapSize = PAGE_SIZE * numPages; 79 80 NN_LOG_DEBUG("start %p\n", m_HeapStart); 81 NN_LOG_DEBUG("size %p\n", m_HeapSize); 82 83 const size_t numMaxPages = GetNumMaxPages(); 84 NN_LOG_DEBUG("numMaxPages %d\n", numMaxPages); 85 const size_t maxSize = GetMaxSize(); 86 NN_LOG_DEBUG("maxSize %d\n", maxSize); 87 const size_t numMaxOrders = m_HeapSize / maxSize; 88 NN_LOG_DEBUG("numMaxOrders %d\n", numMaxOrders); 89 90 NN_ASSERT(numMaxOrders > 0); 91 92 // 管理領域の仮初期化 93 // ページごとに1つ管理領域を割り当てします。 94 m_pPages = reinterpret_cast<Page*>(m_HeapStart); 95 96 for (size_t i = 0; i < numPages; i++) 97 { 98 m_pPages[i].pNext = NULL; 99 } 100 101 for (size_t i = 0; i < MAX_ORDER; ++i ) 102 { 103 m_FreeArea[i].Initialize(); 104 } 105 for (size_t i = 0; i < numMaxOrders; i++) 106 { 107 // 各ページの開始アドレスをフリーリストに登録します。 108 m_FreeArea[MAX_ORDER - 1].PushBack(&(m_pPages[i * numMaxPages])); 109 } 110 111 // 管理領域を再配置(普通は同じところに配置されるはず) 112 const size_t manageAreaSize = sizeof(Page) * numPages; 113 114 if(m_pPages != reinterpret_cast<Page*>(AllocateByOrder(GetNearestHigherOrder(DivUp<PAGE_SIZE>(manageAreaSize))))) 115 { 116 NN_LOG_WARN("failed to allocate page managing area."); 117 } 118 } 119 120 /*! 121 @brief バディヒープを破棄します。 122 */ Finalize(void)123 void Finalize(void){} 124 125 /*! 126 @brief オーダーを指定してメモリを確保します。 127 128 @param[in] order サイズ(2のべき乗) 129 130 @return 確保したメモリへのポインタ 131 */ AllocateByOrder(s32 order)132 void* AllocateByOrder(s32 order) 133 { 134 NN_ASSERT(order >= 0); 135 136 NN_LOG_DEBUG("demands order %d\n", order); 137 138 139 Page* pPage = GetFreePage(order); 140 NN_LOG_DEBUG("pPage = %08x\n", pPage); 141 142 if(pPage) 143 { 144 uptr addr = GetAddressFromPage(*pPage); 145 146 NN_LOG_DEBUG("returns order %d, address 0x%08X\n", order, addr); 147 148 return reinterpret_cast<void*>(addr); 149 } 150 else 151 { 152 return NULL; 153 } 154 } 155 156 /*! 157 @brief メモリを解放します。 158 159 (Free の引数が違うので HeapBase 準拠は無理) 160 161 @param[in] p 解放するメモリへのポインタ 162 @param[in] order オーダー 163 */ Free(void * p,s32 order)164 void Free(void* p, s32 order) 165 { 166 if(!p) 167 { 168 return; 169 } 170 171 NN_ASSERT((reinterpret_cast<uptr>(p) - m_HeapStart) % PAGE_SIZE == 0); 172 173 Page* pPage = GetPageFromAddress(reinterpret_cast<uptr>(p)); 174 175 // ページのインデックスがオーダーで割り切れるか。 176 NN_ASSERT(GetIndexFromPage(*pPage) % (1 << order) == 0); 177 178 NN_LOG_DEBUG("addr = 0x%08x, order = %d, pPage = 0x%08x\n", p, order, pPage); 179 180 JointBuddies(pPage, order); 181 } 182 FreeV(void *)183 virtual void FreeV(void*) { NN_ASSERT(false); } 184 185 /*! 186 @brief このヒープが利用しているメモリ領域の先頭アドレスを返します。 187 */ GetStartAddress()188 virtual void* GetStartAddress() const { return reinterpret_cast<void*>(m_HeapStart); } 189 190 /*! 191 @brief このヒープが利用しているメモリ領域のサイズを返します。 192 */ GetTotalSize()193 virtual size_t GetTotalSize() const { return m_HeapSize; } 194 195 /*! 196 @brief デバッグ用にヒープの内容をダンプします。 197 */ Dump()198 virtual void Dump() const{}; 199 200 /*! 201 @brief 指定したアドレスがヒープ領域に含まれているかどうかを判定します。 202 */ HasAddress(const void * addr)203 virtual bool HasAddress(const void* addr) const 204 { 205 return m_HeapStart <= reinterpret_cast<uptr>(addr) 206 && reinterpret_cast<uptr>(addr) < m_HeapStart + m_HeapSize; 207 } 208 209 /*! 210 @brief バイト単位のサイズからオーダー数を計算します。 211 212 @param[in] size サイズ(バイト単位) 213 214 @return オーダー数 215 */ GetOrder(size_t size)216 static s32 GetOrder(size_t size) 217 { 218 return GetNearestHigherOrder(DivUp<PAGE_SIZE>(size)); 219 } 220 221 static const size_t PAGE_SIZE = PageSize; 222 static const s32 MAX_ORDER = MaxOrder; 223 224 private: 225 // ページエントリーです。 226 struct Page 227 { 228 Page* pNext; 229 }; 230 231 // 同一サイズのメモリブロック毎にリンクリストを構成します。 232 class PageList 233 { 234 private: 235 Page* m_pFirstPage; // ページリストの先頭要素 236 Page* m_pLastPage; // ページリストの最後尾要素 237 238 public: 239 // ページリストを初期化します。 Initialize()240 void Initialize() 241 { 242 m_pFirstPage = NULL; 243 m_pLastPage = NULL; 244 } 245 246 /* 247 @brief ページリストの先頭から1ページ取得します。 248 249 @return ページリストの先頭要素。リストに何も登録されていなかったら NULL を返します。 250 */ PopFront()251 Page* PopFront() 252 { 253 NN_ASSERT(m_pFirstPage); 254 255 // ページリストの最初の1ページ分を取得します。 256 Page* pPage = m_pFirstPage; 257 258 // ページリストをひとつ進めます。 259 m_pFirstPage = pPage->pNext; 260 pPage->pNext = NULL; 261 262 // もしページリストの先頭と末端が一緒の場合は 263 // ページリストの末尾に到達しています。 264 // (ページリストの要素が空になりました) 265 if(pPage == m_pLastPage) 266 { 267 m_pLastPage = NULL; 268 } 269 270 return pPage; 271 } 272 273 /* 274 @brief ページリストの最後尾に 1 ページ追加します。 275 276 @param[in] pPage 追加対象のページ 277 */ PushBack(Page * pPage)278 void PushBack(Page* pPage) 279 { 280 NN_ASSERT(!pPage->pNext); 281 282 // ページリストは空か? 283 if(!m_pFirstPage) 284 { 285 // 先頭要素として追加します。 286 m_pFirstPage = pPage; 287 } 288 else 289 { 290 // リストの末尾に追加します。 291 m_pLastPage->pNext = pPage; 292 } 293 294 m_pLastPage = pPage; 295 } 296 297 /* 298 @brief ページリストから 1 ページ削除します。 299 300 @param[in] pPage 削除対象のページ 301 302 @return 正常に削除できたら true、ページが存在しない場合は false 303 */ Remove(Page * pPage)304 bool Remove(Page* pPage) 305 { 306 NN_NULL_TASSERT_(pPage); 307 308 // ページリストは空か? 309 if(m_pFirstPage == NULL) 310 { 311 return false; 312 } 313 314 Page* pPrevPage = NULL; 315 Page* page = m_pFirstPage; 316 317 for(;;) 318 { 319 if(page == pPage) 320 { 321 // 削除対象のページが見つかった場合 322 if(page == m_pFirstPage) 323 { 324 // 削除対象のページがページリストの先頭だったため 325 // リストをひとつ進めつつ要素を削除します。 326 m_pFirstPage = page->pNext; 327 } 328 else if(page == m_pLastPage) 329 { 330 // 削除対象のページがページリストの末尾だったため 331 // 末尾の要素をひとつ戻します。 332 m_pLastPage = pPrevPage; 333 m_pLastPage->pNext = NULL; 334 } 335 else 336 { 337 // 先頭~末尾の間にあるページのため 338 // リンクリストをつなげなおします。 339 pPrevPage->pNext = page->pNext; 340 } 341 342 page->pNext = NULL; 343 344 return true; 345 } 346 347 if(page->pNext == NULL) 348 { 349 return false; 350 } 351 352 pPrevPage = page; 353 page = page->pNext; 354 } 355 356 // not reached 357 } 358 359 /* 360 @brief ページリストは空か? 361 362 @return ページリストに何も登録されていなければ true 363 */ IsEmpty()364 bool IsEmpty() const 365 { 366 if(m_pFirstPage) 367 { 368 return false; 369 } 370 else 371 { 372 return true; 373 } 374 } 375 }; 376 377 PageList m_FreeArea[MAX_ORDER]; 378 379 uptr m_HeapStart; 380 size_t m_HeapSize; 381 382 /* 383 :private 384 385 @brief Order(2^order)に対応したサイズのフリーページを取得します。 386 387 @param[in] order オーダー(2のべき乗) 388 389 @return 割り当てられたページエントリー 390 */ GetFreePage(s32 order)391 Page* GetFreePage(s32 order) 392 { 393 for(s32 i = order; i < MAX_ORDER; i++) 394 { 395 // 2の order 乗(べき乗)に対応したページのリストを探索します。 396 // もしそのページに空きページが登録さていない場合は、ひとつ上の order+1 乗の 397 // ページを探索します。 398 if(!(m_FreeArea[i].IsEmpty())) 399 { 400 // 空きメモリを見つけました。 401 Page* pPage = m_FreeArea[i].PopFront(); 402 403 NN_NULL_TASSERT_(pPage); 404 405 // 要求に対して大きすぎるメモリを割り当てする場合 406 // 使用しない部分を分割し、空きページリストへ登録しなおします 407 DivideBuddies(pPage, order, i); 408 409 return pPage; 410 } 411 } 412 413 NN_LOG_WARN("No free pages order %d\n", order); 414 return NULL; 415 } 416 417 /* 418 @brief 大きなページから必要な分だけを切り出します。 419 420 @param[in] pPage 分割対象のページ 421 @param[in] demandedOrder 割り当てを必要としているorder(2のべき乗) 422 @param[in] freeOrder 分割対象のページのOrder(2のべき乗) 423 */ DivideBuddies(Page * pPage,s32 demandedOrder,s32 freeOrder)424 void DivideBuddies(Page* pPage, s32 demandedOrder, s32 freeOrder) 425 { 426 for(s32 i = freeOrder; demandedOrder < i; i--) 427 { 428 Page* pDividedPage = &pPage[(1 << (i - 1))];; 429 430 // 細切れにして空きページリストへ登録します。 431 // 4KB の要求に対して 16KB の大きな空きページが見つかった場合、差し引きして割り当てを必要としない 432 // 12KB(16KB-4KB) を 4KB, 8KB の二つのページリストに分け、フリーリストへ追加します。 433 434 NN_LOG_DEBUG("divides buddy address 0x%08X and 0x%08X, order%d\n", GetAddressFromPage(*pPage), GetAddressFromPage(*pDividedPage), i); 435 m_FreeArea[i - 1].PushBack(pDividedPage); 436 } 437 } 438 439 /* 440 :private 441 442 @brief 空きページリストへ登録します。あわせてメモリの連結処理を行います。 443 444 @param[in] pPage 追加対象のページ 445 @param[in] order ページのサイズ 446 */ JointBuddies(Page * pPage,s32 order)447 void JointBuddies(Page* pPage, s32 order) 448 { 449 // 最上位のオーダーの 1つ前まで 450 while(order < MAX_ORDER - 1) 451 { 452 // バディ(相方)となるページを求めます。 453 Page* pBuddyPage = GetBuddy(pPage, order); 454 455 // フリーリストにバディがいるか 456 if(m_FreeArea[order].Remove(pBuddyPage)) 457 { 458 // いたら、フリーリストから削除して(=ジョイントした)1つ上のオーダーでジョイントを試みる。 459 460 // ページが1つ上のオーダーでアラインしていなかったら、アドレスを Buddy に。 461 if(!IsAlignedToOrder(pPage, order + 1)) 462 { 463 pPage = pBuddyPage; 464 } 465 466 NN_LOG_DEBUG("joints buddy address 0x%08X and 0x%08X, order %d", GetAddressFromPage(*pPage), GetAddressFromPage(*GetBuddy(pPage, order)), order); 467 468 469 order++; 470 } 471 else 472 { 473 // いなかったら、ジョイントできなかったので、ページをフリーリストに追加する。 474 break; 475 } 476 } 477 478 // Buddy が利用中だったか、最上位オーダーまでジョイントした 479 NN_LOG_DEBUG("adding a free page, address 0x%08X, order %d", GetAddressFromPage(*pPage), order ); 480 m_FreeArea[order].PushBack(pPage); 481 } 482 483 /*! 484 @brief ページが示すアドレスを取得します。 485 486 @param[in] page ページエントリー 487 488 @return アドレス 489 */ GetAddressFromPage(const Page & page)490 inline uptr GetAddressFromPage(const Page& page) const 491 { 492 return m_HeapStart + GetIndexFromPage(page) * PAGE_SIZE; 493 } 494 495 /*! 496 @brief 指定したアドレスを含むページエントリーを取得します。 497 498 @param[in] addr アドレス 499 500 @return ページエントリー 501 */ GetPageFromAddress(uptr addr)502 inline Page* GetPageFromAddress(uptr addr) 503 { 504 // TODO: 範囲チェック 505 return &m_pPages[(addr - m_HeapStart) / PAGE_SIZE]; 506 } 507 508 /* 509 @brief 指定したページエントリーが先頭から何番目のページかを取得します。 510 511 @param[in] page ページエントリー 512 513 @return 先頭から何番目か 514 */ GetIndexFromPage(const Page & page)515 inline s32 GetIndexFromPage(const Page& page) const 516 { 517 return &page - m_pPages; 518 } 519 GetBuddy(Page * pPage,s32 order)520 inline Page* GetBuddy(Page* pPage, s32 order) 521 { 522 // ページのインデックスが上のオーダーで割り切れるなら、右隣が Buddy 523 if(IsAlignedToOrder(pPage, order + 1)) 524 { 525 return pPage + GetNumPagesByOrder(order); 526 } 527 else // でなければ、左隣が Buddy 528 { 529 return pPage - GetNumPagesByOrder(order); 530 } 531 } 532 533 /* 534 @brief ページエントリーはorderの境界か? 535 536 @param[in] pPage 探索対象のページエントリー 537 @param[in] order ページの order (2のべき乗) 538 539 @return ページエントリーが境界にある場合は true 540 */ IsAlignedToOrder(Page * pPage,s32 order)541 inline bool IsAlignedToOrder(Page* pPage, s32 order) const 542 { 543 return GetIndexFromPage(*pPage) % GetNumPagesByOrder(order) == 0; 544 } 545 546 /* 547 @brief Orderからページ数をもとめます 548 549 @param[in] order ページの order (2のべき乗) 550 551 @return ページ数 552 */ GetNumPagesByOrder(s32 order)553 inline int GetNumPagesByOrder(s32 order) const 554 { 555 return (1 << order); 556 } 557 GetNearestHigherOrder(size_t numPages)558 static s32 GetNearestHigherOrder(size_t numPages) 559 { 560 for(int order = 0; order <= MAX_ORDER; order++) 561 { 562 if( numPages <= (1U << order) ) { 563 return order; 564 } 565 } 566 567 NN_LOG_WARN("No nearest higher order: numPages=%d, MAX_ORDER=%d", numPages, MAX_ORDER); 568 return -1; 569 } 570 GetNearestLowerOrder(size_t numPages)571 static s32 GetNearestLowerOrder(size_t numPages) 572 { 573 for(int order = 0; order <= MAX_ORDER; order++) 574 { 575 if( numPages < (1 << order) ) { 576 return order - 1; 577 } 578 } 579 580 NN_LOG_WARN("No nearest lower order: numPages=%d, MAX_ORDER=%d", numPages, MAX_ORDER); 581 return -1; 582 } 583 584 Page* m_pPages; 585 }; 586 587 template <size_t PageSize, s32 MaxOrder, class LockPolicy> 588 class BuddyHeapTemplate : public BuddyHeapBase<PageSize, MaxOrder>, private LockPolicy::LockObject 589 { 590 private: 591 typedef BuddyHeapBase<PageSize, MaxOrder> Base; 592 typedef typename LockPolicy::LockObject LockObject; 593 typedef typename LockPolicy::ScopedLock ScopedLock; 594 595 public: BuddyHeapTemplate()596 BuddyHeapTemplate() {} 597 598 template <class MemoryBlockT> BuddyHeapTemplate(const MemoryBlockT & block)599 explicit BuddyHeapTemplate(const MemoryBlockT& block) 600 { 601 Initialize(block.GetAddress(), block.GetSize()); 602 } 603 604 /* 605 @brief バディヒープを初期化します。 606 607 @param[in] addr ヒープに割り当てる領域のアドレス 608 @param[in] size ヒープに割り当てる領域のサイズ 609 */ Initialize(uptr addr,size_t size)610 void Initialize(uptr addr, size_t size) 611 { 612 uptr addrAligned = Base::RoundUp<Base::ALIGN>(addr); 613 size -= addrAligned - addr; 614 615 Base::Initialize(addrAligned, size / Base::PAGE_SIZE); 616 LockObject::Initialize(); 617 } 618 619 /* 620 @brief バディヒープを破棄します。 621 */ Finalize()622 void Finalize() 623 { 624 LockObject::Finalize(); 625 Base::Finalize(); 626 } 627 628 /*! 629 @brief バディヒープからメモリを確保します。 630 631 @param[in] order サイズ(2のるい乗) 632 633 @return 確保したメモリへのポインタ 634 */ AllocateByOrder(s32 order)635 void* AllocateByOrder(s32 order) 636 { 637 ScopedLock lk(*this); 638 return Base::AllocateByOrder(order); 639 } 640 641 /*! 642 @brief バディヒープから確保したメモリブロックを解放します。 643 644 @param[in] p 解放するメモリブロックの先頭アドレスを指定します。 645 @param[in] order サイズ(2のるい乗) 646 */ Free(void * p,s32 order)647 void Free(void* p, s32 order) 648 { 649 ScopedLock lk(*this); 650 Base::Free(p, order); 651 } 652 653 class Allocator; 654 }; 655 656 template <size_t PageSize, s32 MaxOrder, class LockPolicy> 657 class BuddyHeapTemplate<PageSize, MaxOrder, LockPolicy>::Allocator : public nn::fnd::IAllocator 658 { 659 private: 660 typedef BuddyHeapTemplate<PageSize, MaxOrder, LockPolicy> BuddyHeap; 661 static const size_t ALIGN = BuddyHeap::ALIGN; 662 663 public: Allocator(BuddyHeap & heap)664 Allocator(BuddyHeap& heap) : m_Heap(0) { Initialize(heap); } 665 666 /*! 667 @brief 初期化をしないコンストラクタです。 668 */ Allocator()669 Allocator() : m_Heap(0) {} 670 671 /*! 672 @breif アロケータを初期化します。 673 674 @param[in] heap バディヒープ 675 */ Initialize(BuddyHeap & heap)676 void Initialize(BuddyHeap& heap) 677 { 678 NN_TASSERT_(!this->m_Heap); 679 this->m_Heap = &heap; 680 } 681 682 /*! 683 @brief アロケータにセットされているヒープを返します。 684 685 @return アロケータにセットされているヒープ 686 */ GetHeap()687 BuddyHeap* GetHeap() { return m_Heap; } 688 689 /*! 690 @brief アロケータにセットされている拡張ヒープを返します。 691 692 @return アロケータにセットされている拡張ヒープ 693 */ GetHeap()694 const BuddyHeap* GetHeap() const { return m_Heap; } 695 696 /*! 697 @brief 指定したサイズとアラインメントでメモリ領域を確保します。 698 699 @param[in] size 確保するメモリのサイズ 700 @param[in] alignment 確保するメモリのアラインメント 701 702 @return 確保したメモリ領域の先頭へのポインタ 703 */ 704 virtual void* Allocate(size_t size, s32 alignment = nn::fnd::HeapBase::DEFAULT_ALIGNMENT) 705 { 706 NN_ASSERT(alignment <= ALIGN); 707 if (alignment > ALIGN) 708 { 709 return NULL; 710 } 711 712 // 確保したメモリの先頭にオーダーを保存しておくために 713 // 余分にメモリを確保します。 714 s32 order = m_Heap->GetOrder(size + ALIGN); 715 //NN_TLOG_("size: %d => order: %d\n", size, order); 716 void* p = m_Heap->AllocateByOrder(order); 717 if (!p) 718 { 719 return NULL; 720 } 721 NN_LOG_DEBUG("Alloc size %d, order %d\n", size + ALIGN, order); 722 *reinterpret_cast<s32*>(p) = order; 723 return reinterpret_cast<void*>(reinterpret_cast<uptr>(p) + ALIGN); 724 } 725 726 /*! 727 @brief メモリ領域を解放します。 728 729 @param[in] p 確保されているメモリ領域の先頭へのポインタ 730 */ Free(void * p)731 virtual void Free(void* p) 732 { 733 p = reinterpret_cast<void*>(reinterpret_cast<uptr>(p) - ALIGN); 734 s32 order = *reinterpret_cast<s32*>(p); 735 m_Heap->Free(p, order); 736 } 737 738 private: 739 BuddyHeap* m_Heap; //!< バディヒープ 740 }; 741 742 }} 743 744 #endif /* NN_FND_FND_BUDDYHEAP_H_ */ 745