1 /*---------------------------------------------------------------------------*
2   Project:  Horizon
3   File:     os_CriticalSection.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: 35573 $
14  *---------------------------------------------------------------------------*/
15 
16 /*! @file
17     @brief      CriticalSection に関する API の宣言
18 
19     :include nn/os.h
20 */
21 
22 #ifndef NN_OS_OS_CRITICALSECTION_H_
23 #define NN_OS_OS_CRITICALSECTION_H_
24 
25 #include <nn/os/os_WaitableCounter.h>
26 #include <nn/assert.h>
27 #include <nn/WithInitialize.h>
28 #include <nn/util/detail/util_ScopedLockImpl.h>
29 #include <nn/hw/ARM/reg_access.h>
30 
31 #ifdef __cplusplus
32 
33 namespace nn { namespace os {
34 
35 /*!
36     @brief クリティカルセクションを扱う為のクラスです。クリティカルセクションは排他制御を行うための同期オブジェクトです。
37 
38     プログラムの特定の個所について複数のスレッドからの同時実行を抑制し、
39     リソースが同時に複数のスレッドからアクセスされないようにします。
40 
41     ミューテックス(@ref nn::os::Mutex)と同じような役割を持ちますが、以下のような制限があります。
42 
43     @li 一つのアプリケーション内で排他制御を行う場合にのみ使用できます。
44     @li 優先度継承は行いません。優先度逆転などの問題が生じる可能性があります。
45     @li ミューテックスよりクリティカルセクションの方がオブジェクトを保持するためのメモリ使用量は多くなります。
46 
47     スレッド間でロックが衝突しない場合のパフォーマンスでは
48     @ref nn::os::CriticalSection の方が @ref nn::os::Mutex より
49     圧倒的に高速なため、
50     通常は @ref nn::os::Mutex ではなく、@ref nn::os::CriticalSection を使用すべきです。
51 
52     @ref nn::os::CriticalSection::ScopedLock を用いるとオブジェクトの生成からスコープから出るまでの間、クリティカルセクションに入ることができます。
53 */
54 
55 class CriticalSection : private nn::util::NonCopyable<CriticalSection>
56 {
57 private:
58     struct ReverseIfPositiveUpdater
59     {
operatorReverseIfPositiveUpdater60         bool operator()(s32& x)
61         {
62             if( x > 0 )
63             {
64                 x = -x;
65                 return true;
66             }
67             else
68             {
69                 return false;
70             }
71         }
72     };
73 
74     struct ReverseUpdater
75     {
76         s32 afterUpdate;
operatorReverseUpdater77         bool operator()(s32& x)
78         {
79             x = -x;
80             afterUpdate = x;
81             return true;
82         }
83     };
84 
85 public:
86 
87     static const bool CAN_LOCK_RECURSIVELY = true;
88 
89     /*!
90       @brief        オブジェクトを構築します。
91 
92                     初期化しないコンストラクタと、初期化するコンストラクタが用意されています。
93 
94                     初期化しない場合、使用する前に、@ref nn::os::CriticalSection::Initialize を呼んで明示的に初期化する必要があります。
95     */
CriticalSection()96     CriticalSection() : m_ThreadUniqueValue(GetInvalidThreadUniqueValue()), m_LockCount(-1) {}
97 
98     /*!
99       @brief        オブジェクトを構築し、初期化を行います。
100     */
CriticalSection(const nn::WithInitialize &)101     CriticalSection(const nn::WithInitialize&) { Initialize(); }
102 
103     /*!
104       @brief        オブジェクトを初期化します。
105 
106       @return       無し。
107     */
Initialize()108     void Initialize()
109     {
110         *m_Counter          = 1;
111         m_ThreadUniqueValue = GetInvalidThreadUniqueValue();
112         m_LockCount         = 0;
113     }
114 
115     /*!
116       @brief        オブジェクトの初期化を試みます。
117 
118       @return       処理結果を返します。
119     */
TryInitialize()120     nn::Result TryInitialize()
121     {
122         Initialize();
123         return ResultSuccess();
124     }
125 
126     /*!
127       @brief        クリティカルセクションを破棄します。
128 
129                     デストラクタから自動的に呼び出されますが、明示的に呼ぶこともできます。
130 
131       @return       無し。
132     */
Finalize()133     void Finalize() { m_LockCount = -1; }
134 
135     /*!
136       @brief        デストラクタです。
137     */
~CriticalSection()138     ~CriticalSection() {}
139 
140     /*!
141       @brief        ロックして他のスレッドがクリティカルセクションに進入するのを防ぎます。ブロックします。
142 
143       @return       無し。
144     */
Enter()145     void Enter()
146     {
147         NN_TASSERT_(IsInitialized());
148         // 自スレッドがクリティカルセクションに進入していなければ
149         // クリティカルセクションに進入を試みます。
150         if (!LockedByCurrentThread() && !TryEnterImpl())
151         {
152             EnterImpl();
153         }
154         ++this->m_LockCount;
155     }
156 
157     /*!
158       @brief        ロックして他のスレッドがクリティカルセクションに進入するのを防ぎます。ブロックしません。
159 
160       @return       ロックに成功したかを返します。
161     */
TryEnter()162     bool TryEnter()
163     {
164         NN_TASSERT_(IsInitialized());
165         // 自スレッドがクリティカルセクションに進入していなければ
166         // クリティカルセクションに進入を試みます。
167         if (LockedByCurrentThread() || TryEnterImpl())
168         {
169             ++this->m_LockCount;
170             return true;
171         }
172         else
173         {
174             return false;
175         }
176     }
177 
178     /*!
179       @brief        アンロックして他のスレッドがクリティカルセクションに進入できるようにします。
180 
181       @return       無し。
182     */
Leave()183     void Leave()
184     {
185         NN_TASSERT_(IsInitialized());
186         NN_TASSERTMSG_(LockedByCurrentThread() && m_LockCount > 0, "CriticalSection is not entered on the current thread.");
187         if (--this->m_LockCount == 0)
188         {
189             NN_TASSERTMSG_( *m_Counter < 0 , "CriticalSection is not entered.");
190 
191             // クリティカルセクションを取得中のスレッド ID をクリアします。
192             m_ThreadUniqueValue = GetInvalidThreadUniqueValue();
193 
194             // カウンタの符号を正に設定することでロックを解除します。
195             ReverseUpdater updater;
196             m_Counter->AtomicUpdateConditional(updater);
197 
198             // 待機している最も優先度の高いスレッドを起床します。
199             if( updater.afterUpdate > 1 )
200             {
201                 m_Counter.Signal(1);
202             }
203         }
204     }
205 
206     /*!
207         @class nn::os::CriticalSection::ScopedLock
208 
209         @brief オブジェクトの生成時からオブジェクトの存在するスコープを抜けるまでの間
210                クリティカルセクションに入ります。
211     */
212     class ScopedLock;
213 
IsLocked()214     bool IsLocked() const
215     {
216         return (*m_Counter < 0);
217     }
218 
219 private:
220 
221     void EnterImpl();
222 
TryEnterImpl()223     bool TryEnterImpl()
224     {
225         ReverseIfPositiveUpdater updater;
226         if (m_Counter->AtomicUpdateConditional(updater))
227         {
228             // クリティカルセクションの進入に成功すれば
229             // カウンタの符号が正から負に変わります。
230 
231             NN_TASSERT_(m_LockCount == 0);
232 
233             // クリティカルセクションを取得したスレッド ID を保存します。
234             this->m_ThreadUniqueValue = GetThreadUniqueValue();
235             return true;
236         }
237         else
238         {
239             return false;
240         }
241     }
242 
243     // TODO: ARM に移すことを推奨します。
244 #ifdef NN_PROCESSOR_ARM946ES
GetThreadUniqueValue()245     static uptr GetThreadUniqueValue()
246     {
247         return nn::os::CTR::ARM946ES::GetThreadId();
248     }
GetInvalidThreadUniqueValue()249     static uptr GetInvalidThreadUniqueValue()
250     {
251         return static_cast<uptr>(-1);
252     }
253 #else
GetThreadUniqueValue()254     static uptr GetThreadUniqueValue()
255     {
256         uptr v;
257         HW_GET_CP15_THREAD_ID_USER_READ_ONLY(v);
258         return v;
259     }
GetInvalidThreadUniqueValue()260     static uptr GetInvalidThreadUniqueValue()
261     {
262         return 0;
263     }
264 #endif
265 
LockedByCurrentThread()266     bool LockedByCurrentThread() const
267     {
268         return GetThreadUniqueValue() == m_ThreadUniqueValue;
269     }
270 
271 private:
272     nn::os::WaitableCounter m_Counter;
273     uptr                    m_ThreadUniqueValue;
274     s32                     m_LockCount;
IsInitialized()275     bool IsInitialized() const { return m_LockCount >= 0; }
276 };
277 
278 NN_UTIL_DETAIL_DEFINE_SCOPED_LOCK(CriticalSection, Enter(), Leave());
279 
280 }} // namespace nn::os
281 
282 #endif // __cplusplus
283 
284 // 以下、C 用宣言
285 
286 #include <nn/util/detail/util_CLibImpl.h>
287 
288 #define NN_OS_CRITICALSECTION_SIZE 12
289 
290 /*!
291   @addtogroup   nn_os                   os
292   @{
293 
294   @defgroup     nn_os_CriticalSection_c CriticalSection (C)
295 
296   @brief        @ref nn::os::CriticalSection の C インタフェースモジュールです。
297 
298   @{
299 */
300 
301 /*!
302   @struct       nnosCriticalSection
303   @brief        クリティカルセクションを表す C の構造体です。
304 
305   @brief 対応するクラス @ref nn::os::CriticalSection を参照してください。
306 */
307 NN_UTIL_DETAIL_CLIBIMPL_DEFINE_BUFFER_CLASS(nnosCriticalSection, nn::os::CriticalSection, NN_OS_CRITICALSECTION_SIZE, u32);
308 
309 /*!
310   @brief 対応する C++ 関数を参照してください。@ref nn::os::CriticalSection::Initialize
311 */
312 NN_EXTERN_C void nnosCriticalSectionInitialize(nnosCriticalSection* this_);
313 
314 /*!
315   @brief 対応する C++ 関数を参照してください。@ref nn::os::CriticalSection::TryInitialize
316 */
317 NN_EXTERN_C bool nnosCriticalSectionTryInitialize(nnosCriticalSection* this_);
318 
319 /*!
320   @brief 対応する C++ 関数を参照してください。@ref nn::os::CriticalSection::Enter
321 */
322 NN_EXTERN_C void nnosCriticalSectionEnter(nnosCriticalSection* this_);
323 
324 /*!
325   @brief 対応する C++ 関数を参照してください。@ref nn::os::CriticalSection::TryEnter
326 */
327 NN_EXTERN_C bool nnosCriticalSectionTryEnter(nnosCriticalSection* this_);
328 
329 /*!
330   @brief 対応する C++ 関数を参照してください。@ref nn::os::CriticalSection::Leave
331 */
332 NN_EXTERN_C void nnosCriticalSectionLeave(nnosCriticalSection* this_);
333 
334 /*!
335   @brief 対応する C++ 関数を参照してください。@ref nn::os::CriticalSection::Finalize
336 */
337 NN_EXTERN_C void nnosCriticalSectionFinalize(nnosCriticalSection* this_);
338 
339 /*!
340   @}
341 
342   @}
343 */
344 
345 #endif
346