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: 2151 $ 14 *---------------------------------------------------------------------------*/ 15 16 #ifndef NN_FND_FND_TORIABUDDYHEAP_H_ 17 #define NN_FND_FND_TORIABUDDYHEAP_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 ToriaBuddyHeapBase : public nn::fnd::HeapBase 28 { 29 protected: 30 template <s32 align, typename T> DivUp(T x)31 static inline 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 inline uptr RoundDown(uptr addr) 38 { 39 return (addr / alignment) * alignment; 40 } 41 42 template<size_t alignment> RoundUp(uptr addr)43 static inline uptr RoundUp(uptr addr) 44 { 45 return RoundDown<alignment>(addr + alignment - 1); 46 } 47 48 public: 49 static const size_t ALIGN = 4; 50 static const size_t MIN_SIZE = 8; 51 52 /*! 53 @brief 総ページ数を取得します。 54 */ GetNumMaxPages(void)55 static inline size_t GetNumMaxPages(void) 56 { 57 return 1 << (MAX_ORDER - 1); 58 } 59 60 /*! 61 @brief 総データサイズをバイト単位で取得します。 62 */ GetMaxSize(void)63 static inline size_t GetMaxSize(void) 64 { 65 return GetNumMaxPages() * PAGE_SIZE; 66 } 67 68 /*! 69 @brief バディヒープを初期化します。 70 71 @param[in] addr ヒープの開始アドレス 72 @param[in] numPages ページ数 73 */ Initialize(uptr addr,size_t numPages)74 void Initialize(uptr addr, size_t numPages) 75 { 76 NN_ASSERT((addr % ALIGN) == 0); 77 78 m_HeapStart = addr; 79 m_HeapSize = PAGE_SIZE * numPages; 80 81 //NN_LOG_DEBUG("start %p\n", m_HeapStart); 82 //NN_LOG_DEBUG("size %p\n", m_HeapSize); 83 84 const size_t numMaxPages = GetNumMaxPages(); 85 //NN_LOG_DEBUG("numMaxPages %d\n", numMaxPages); 86 const size_t maxSize = GetMaxSize(); 87 //NN_LOG_DEBUG("maxSize %d\n", maxSize); 88 const size_t numMaxOrders = m_HeapSize / maxSize; 89 //NN_LOG_DEBUG("numMaxOrders %d\n", numMaxOrders); 90 91 NN_ASSERT(numMaxOrders > 0); 92 93 // 管理領域の仮初期化 94 // ページごとに1つ管理領域を割り当てします。 95 m_pPages = reinterpret_cast<Page*>(m_HeapStart); 96 97 for (size_t i = 0; i < numPages; i++) 98 { 99 m_pPages[i].pNext = NULL; 100 } 101 102 for (size_t i = 0; i < MAX_ORDER; ++i ) 103 { 104 m_FreeArea[i].Initialize(); 105 } 106 for (size_t i = 0; i < numMaxOrders; i++) 107 { 108 // 各ページの開始アドレスをフリーリストに登録します。 109 m_FreeArea[MAX_ORDER - 1].PushBack(&(m_pPages[i * numMaxPages])); 110 } 111 112 // 管理領域を再配置(普通は同じところに配置されるはず) 113 const size_t manageAreaSize = sizeof(Page) * numPages; 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)-1)) == 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 inline 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 inline 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 inline bool IsEmpty() const 365 { 366 return m_pFirstPage ? false : true; 367 } 368 }; 369 370 PageList m_FreeArea[MAX_ORDER]; 371 372 uptr m_HeapStart; 373 size_t m_HeapSize; 374 375 /* 376 :private 377 378 @brief Order(2^order)に対応したサイズのフリーページを取得します。 379 380 @param[in] order オーダー(2のべき乗) 381 382 @return 割り当てられたページエントリー 383 */ GetFreePage(s32 order)384 Page* GetFreePage(s32 order) 385 { 386 for(s32 i = order; i < MAX_ORDER; i++) 387 { 388 // 2の order 乗(べき乗)に対応したページのリストを探索します。 389 // もしそのページに空きページが登録さていない場合は、ひとつ上の order+1 乗の 390 // ページを探索します。 391 PageList &freeArea = m_FreeArea[i]; 392 if(!(freeArea.IsEmpty())) 393 { 394 // 空きメモリを見つけました。 395 Page* pPage = freeArea.PopFront(); 396 397 NN_NULL_TASSERT_(pPage); 398 399 // 要求に対して大きすぎるメモリを割り当てする場合 400 // 使用しない部分を分割し、空きページリストへ登録しなおします 401 DivideBuddies(pPage, order, i); 402 403 return pPage; 404 } 405 } 406 407 //NN_LOG_WARN("No free pages order %d\n", order); 408 return NULL; 409 } 410 411 /* 412 @brief 大きなページから必要な分だけを切り出します。 413 414 @param[in] pPage 分割対象のページ 415 @param[in] demandedOrder 割り当てを必要としているorder(2のべき乗) 416 @param[in] freeOrder 分割対象のページのOrder(2のべき乗) 417 */ DivideBuddies(Page * pPage,s32 demandedOrder,s32 freeOrder)418 void DivideBuddies(Page* pPage, s32 demandedOrder, s32 freeOrder) 419 { 420 for(s32 i = freeOrder; demandedOrder < i; i--) 421 { 422 Page* pDividedPage = &pPage[(1 << (i - 1))]; 423 424 // 細切れにして空きページリストへ登録します。 425 // 4KB の要求に対して 16KB の大きな空きページが見つかった場合、差し引きして割り当てを必要としない 426 // 12KB(16KB-4KB) を 4KB, 8KB の二つのページリストに分け、フリーリストへ追加します。 427 428 //NN_LOG_DEBUG("divides buddy address 0x%08X and 0x%08X, order%d\n", GetAddressFromPage(*pPage), GetAddressFromPage(*pDividedPage), i); 429 m_FreeArea[i - 1].PushBack(pDividedPage); 430 } 431 } 432 433 /* 434 :private 435 436 @brief 空きページリストへ登録します。あわせてメモリの連結処理を行います。 437 438 @param[in] pPage 追加対象のページ 439 @param[in] order ページのサイズ 440 */ JointBuddies(Page * pPage,s32 order)441 void JointBuddies(Page* pPage, s32 order) 442 { 443 // 最上位のオーダーの 1つ前まで 444 while(order < MAX_ORDER - 1) 445 { 446 // バディ(相方)となるページを求めます。 447 Page* pBuddyPage = GetBuddy(pPage, order); 448 449 // フリーリストにバディがいるか 450 if(m_FreeArea[order].Remove(pBuddyPage)) 451 { 452 // いたら、フリーリストから削除して(=ジョイントした)1つ上のオーダーでジョイントを試みる。 453 454 // ページが1つ上のオーダーでアラインしていなかったら、アドレスを Buddy に。 455 if(!IsAlignedToOrder(pPage, order + 1)) 456 { 457 pPage = pBuddyPage; 458 } 459 460 //NN_LOG_DEBUG("joints buddy address 0x%08X and 0x%08X, order %d", GetAddressFromPage(*pPage), GetAddressFromPage(*GetBuddy(pPage, order)), order); 461 462 463 order++; 464 } 465 else 466 { 467 // いなかったら、ジョイントできなかったので、ページをフリーリストに追加する。 468 break; 469 } 470 } 471 472 // Buddy が利用中だったか、最上位オーダーまでジョイントした 473 //NN_LOG_DEBUG("adding a free page, address 0x%08X, order %d", GetAddressFromPage(*pPage), order ); 474 m_FreeArea[order].PushBack(pPage); 475 } 476 477 /*! 478 @brief ページが示すアドレスを取得します。 479 480 @param[in] page ページエントリー 481 482 @return アドレス 483 */ GetAddressFromPage(const Page & page)484 inline uptr GetAddressFromPage(const Page& page) const 485 { 486 return m_HeapStart + GetIndexFromPage(page) * PAGE_SIZE; 487 } 488 489 /*! 490 @brief 指定したアドレスを含むページエントリーを取得します。 491 492 @param[in] addr アドレス 493 494 @return ページエントリー 495 */ GetPageFromAddress(uptr addr)496 inline Page* GetPageFromAddress(uptr addr) 497 { 498 // TODO: 範囲チェック 499 return &m_pPages[(addr - m_HeapStart) / PAGE_SIZE]; 500 } 501 502 /* 503 @brief 指定したページエントリーが先頭から何番目のページかを取得します。 504 505 @param[in] page ページエントリー 506 507 @return 先頭から何番目か 508 */ GetIndexFromPage(const Page & page)509 inline s32 GetIndexFromPage(const Page& page) const 510 { 511 return &page - m_pPages; 512 } 513 GetBuddy(Page * pPage,s32 order)514 inline Page* GetBuddy(Page* pPage, s32 order) 515 { 516 // ページのインデックスが上のオーダーで割り切れるなら、右隣が Buddy 517 if(IsAlignedToOrder(pPage, order + 1)) 518 { 519 return pPage + GetNumPagesByOrder(order); 520 } 521 else // でなければ、左隣が Buddy 522 { 523 return pPage - GetNumPagesByOrder(order); 524 } 525 } 526 527 /* 528 @brief ページエントリーはorderの境界か? 529 530 @param[in] pPage 探索対象のページエントリー 531 @param[in] order ページの order (2のべき乗) 532 533 @return ページエントリーが境界にある場合は true 534 */ IsAlignedToOrder(Page * pPage,s32 order)535 inline bool IsAlignedToOrder(Page* pPage, s32 order) const 536 { 537 return (GetIndexFromPage(*pPage) & (GetNumPagesByOrder(order) - 1)) == 0; 538 } 539 540 /* 541 @brief Orderからページ数をもとめます 542 543 @param[in] order ページの order (2のべき乗) 544 545 @return ページ数 546 */ GetNumPagesByOrder(s32 order)547 static inline int GetNumPagesByOrder(s32 order) 548 { 549 return (1 << order); 550 } 551 GetBytesByOrder(s32 order)552 inline size_t GetBytesByOrder(s32 order) const 553 { 554 return (1 << order); 555 } 556 GetNearestHigherOrder(size_t numPages)557 static inline s32 GetNearestHigherOrder(size_t numPages) 558 { 559 for(int order = 0; order <= MAX_ORDER; order++) 560 { 561 if( numPages <= (1U << order) ) { 562 return order; 563 } 564 } 565 566 //NN_LOG_WARN("No nearest higher order: numPages=%d, MAX_ORDER=%d", numPages, MAX_ORDER); 567 return -1; 568 } 569 GetNearestLowerOrder(size_t numPages)570 static inline s32 GetNearestLowerOrder(size_t numPages) 571 { 572 for(int order = 0; order <= MAX_ORDER; order++) 573 { 574 if( numPages < (1 << order) ) { 575 return order - 1; 576 } 577 } 578 579 //NN_LOG_WARN("No nearest lower order: numPages=%d, MAX_ORDER=%d", numPages, MAX_ORDER); 580 return -1; 581 } 582 583 Page* m_pPages; 584 }; 585 586 template <size_t PageSize, s32 MaxOrder, class LockPolicy> 587 class ToriaBuddyHeapTemplate : public ToriaBuddyHeapBase<PageSize, MaxOrder>, private LockPolicy::LockObject 588 { 589 private: 590 typedef ToriaBuddyHeapBase<PageSize, MaxOrder> Base; 591 typedef typename LockPolicy::LockObject LockObject; 592 typedef typename LockPolicy::ScopedLock ScopedLock; 593 594 public: ToriaBuddyHeapTemplate()595 ToriaBuddyHeapTemplate() {} 596 597 template <class MemoryBlockT> ToriaBuddyHeapTemplate(const MemoryBlockT & block)598 explicit ToriaBuddyHeapTemplate(const MemoryBlockT& block) 599 { 600 Initialize(block.GetAddress(), block.GetSize()); 601 } 602 603 /* 604 @brief バディヒープを初期化します。 605 606 @param[in] addr ヒープに割り当てる領域のアドレス 607 @param[in] size ヒープに割り当てる領域のサイズ 608 */ Initialize(uptr addr,size_t size)609 void Initialize(uptr addr, size_t size) 610 { 611 NN_TASSERT_(size >= Base::MIN_SIZE); 612 Base::Initialize(addr, size / Base::PAGE_SIZE); 613 LockObject::Initialize(); 614 } 615 616 /* 617 @brief バディヒープを破棄します。 618 */ Finalize()619 void Finalize() 620 { 621 LockObject::Finalize(); 622 Base::Finalize(); 623 } 624 625 /*! 626 @brief バディヒープからメモリを確保します。 627 628 @param[in] order サイズ(2のるい乗) 629 630 @return 確保したメモリへのポインタ 631 */ AllocateByOrder(s32 order)632 inline void* AllocateByOrder(s32 order) 633 { 634 ScopedLock lk(*this); 635 return Base::AllocateByOrder(order); 636 } 637 638 /*! 639 @brief バディヒープから確保したメモリブロックを解放します。 640 641 @param[in] p 解放するメモリブロックの先頭アドレスを指定します。 642 @param[in] order サイズ(2のるい乗) 643 */ Free(void * p,s32 order)644 void Free(void* p, s32 order) 645 { 646 ScopedLock lk(*this); 647 Base::Free(p, order); 648 } 649 650 class Allocator; 651 }; 652 653 template <size_t PageSize, s32 MaxOrder, class LockPolicy> 654 class ToriaBuddyHeapTemplate<PageSize, MaxOrder, LockPolicy>::Allocator /*: public nn::fnd::IAllocator*/ 655 { 656 private: 657 typedef ToriaBuddyHeapTemplate<PageSize, MaxOrder, LockPolicy> BuddyHeap; 658 659 public: Allocator(BuddyHeap & heap)660 Allocator(BuddyHeap& heap) : m_Heap(0) { Initialize(heap); } 661 662 /*! 663 @brief 初期化をしないコンストラクタです。 664 */ Allocator()665 Allocator() : m_Heap(0) {} 666 667 /*! 668 @breif アロケータを初期化します。 669 670 @param[in] heap バディヒープ 671 */ Initialize(BuddyHeap & heap)672 void Initialize(BuddyHeap& heap) 673 { 674 NN_TASSERT_(!this->m_Heap); 675 this->m_Heap = &heap; 676 } 677 678 /*! 679 @brief アロケータにセットされているヒープを返します。 680 681 @return アロケータにセットされているヒープ 682 */ GetHeap()683 inline BuddyHeap* GetHeap() { return m_Heap; } 684 685 /*! 686 @brief アロケータにセットされている拡張ヒープを返します。 687 688 @return アロケータにセットされている拡張ヒープ 689 */ GetHeap()690 inline const BuddyHeap* GetHeap() const { return m_Heap; } 691 692 /*! 693 @brief 指定したサイズとアラインメントでメモリ領域を確保します。 694 695 @param[in] size 確保するメモリのサイズ 696 @param[in] alignment 確保するメモリのアラインメント 697 698 @return 確保したメモリ領域の先頭へのポインタ 699 */ Allocate(s32 * pOutOrder,size_t size)700 void* Allocate(s32 *pOutOrder, size_t size) 701 { 702 s32 order = m_Heap->GetOrder(size); 703 void* p = m_Heap->AllocateByOrder(order); 704 if (!p) 705 { 706 return false; 707 } 708 //NN_LOG_DEBUG("Alloc size %d, order %d\n", size, order); 709 *pOutOrder = order; 710 return reinterpret_cast<void*>(reinterpret_cast<uptr>(p)); 711 } 712 713 /*! 714 @brief メモリ領域を解放します。 715 716 @param[in] p 確保されているメモリ領域の先頭へのポインタ 717 */ Free(void * p,s32 order)718 inline void Free(void* p, s32 order) 719 { 720 m_Heap->Free(p, order); 721 } 722 GetBytesByOrder(s32 order)723 inline size_t GetBytesByOrder(s32 order) const 724 { 725 return m_Heap->GetBytesByOrder(order); 726 } 727 728 729 private: 730 BuddyHeap* m_Heap; //!< バディヒープ 731 }; 732 733 }} 734 735 #endif /* NN_FND_FND_TORIABUDDYHEAP_H_ */ 736