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