Mark3 Realtime Kernel
thread.cpp
Go to the documentation of this file.
1 /*===========================================================================
2  _____ _____ _____ _____
3  ___| _|__ __|_ |__ __|__ |__ __| __ |__ ______
4 | \ / | || \ || | || |/ / ||___ |
5 | \/ | || \ || \ || \ ||___ |
6 |__/\__/|__|_||__|\__\ __||__|\__\ __||__|\__\ __||______|
7  |_____| |_____| |_____| |_____|
8 
9 --[Mark3 Realtime Platform]--------------------------------------------------
10 
11 Copyright (c) 2012 - 2019 m0slevin, all rights reserved.
12 See license.txt for more information
13 ===========================================================================*/
22 #include "kerneltypes.h"
23 #include "mark3cfg.h"
24 
25 #include "mark3.h"
26 
27 namespace Mark3
28 {
29 //---------------------------------------------------------------------------
31 {
32  // On destruction of a thread located on a stack,
33  // ensure that the thread is either stopped, or exited.
34  // If the thread is stopped, move it to the exit state.
35  // If not in the exit state, kernel panic -- it's catastrophic to have
36  // running threads on stack suddenly disappear.
37  if (ThreadState::Stop == m_eState) {
38  const auto cs = CriticalGuard{};
39  m_pclCurrent->Remove(this);
40  m_pclCurrent = nullptr;
41  m_pclOwner = nullptr;
43  } else if (ThreadState::Exit != m_eState) {
45  }
46 }
47 
48 //---------------------------------------------------------------------------
50  K_WORD* pwStack_, uint16_t u16StackSize_, PORT_PRIO_TYPE uXPriority_, ThreadEntryFunc pfEntryPoint_, void* pvArg_)
51 {
52  static auto u8ThreadID = uint8_t { 0 };
53 
54  KERNEL_ASSERT(pwStack_);
55  KERNEL_ASSERT(pfEntryPoint_);
56 
57  ClearNode();
58 
59  m_u8ThreadID = u8ThreadID++;
60 
61  // Initialize the thread parameters to their initial values.
62  m_pwStack = pwStack_;
63  m_pwStackTop = PORT_TOP_OF_STACK(pwStack_, u16StackSize_);
64 
65  m_u16StackSize = u16StackSize_;
66  m_uXPriority = uXPriority_;
68  m_pfEntryPoint = pfEntryPoint_;
69  m_pvArg = pvArg_;
70 
71 #if KERNEL_NAMED_THREADS
72  m_szName = nullptr;
73 #endif
74 #if KERNEL_ROUND_ROBIN
76 #endif
77 
78  m_clTimer.Init();
79 
80  // Call CPU-specific stack initialization
82 
83  // Add to the global "stop" list.
84  { // Begin critical section
85  const auto cs = CriticalGuard{};
89  m_pclCurrent->Add(this);
90  } // End critical section
91 
92 #if KERNEL_THREAD_CREATE_CALLOUT
94  if (nullptr != pfCallout) {
95  pfCallout(this);
96  }
97 #endif
98 }
99 
100 //---------------------------------------------------------------------------
101 Thread* Thread::Init(uint16_t u16StackSize_, PORT_PRIO_TYPE uXPriority_, ThreadEntryFunc pfEntryPoint_, void* pvArg_)
102 {
103  auto* pclNew = AutoAlloc::NewObject<Thread, AutoAllocType::Thread>();
104  auto* pwStack = static_cast<K_WORD*>(AutoAlloc::NewRawData(u16StackSize_));
105  pclNew->Init(pwStack, u16StackSize_, uXPriority_, pfEntryPoint_, pvArg_);
106  return pclNew;
107 }
108 
109 //---------------------------------------------------------------------------
110 void Thread::Start(void)
111 {
113 
114  // Remove the thread from the scheduler's "stopped" list, and add it
115  // to the scheduler's ready list at the proper priority.
116 
117  const auto cs = CriticalGuard{};
119  Scheduler::Add(this);
123 
124 #if KERNEL_ROUND_ROBIN
125  if (Kernel::IsStarted()) {
127  // Deal with the thread Quantum
128  Quantum::Update(this);
129  }
130  }
131 #endif
132 
133  if (Kernel::IsStarted()) {
135  Thread::Yield();
136  }
137  }
138 }
139 
140 //---------------------------------------------------------------------------
142 {
144 
145  auto bReschedule = false;
146  if (ThreadState::Stop == m_eState) {
147  return;
148  }
149 
150  { // Begin critical section
151  const auto cs = CriticalGuard{};
152 
153  // If a thread is attempting to stop itself, ensure we call the scheduler
154  if (this == Scheduler::GetCurrentThread()) {
155  bReschedule = true;
156 #if KERNEL_ROUND_ROBIN
157  // Cancel RR scheduling
158  Quantum::Cancel();
159 #endif
160  }
161 
162  // Add this thread to the stop-list (removing it from active scheduling)
163  // Remove the thread from scheduling
164  if (ThreadState::Ready == m_eState) {
165  Scheduler::Remove(this);
166  } else if (ThreadState::Blocked == m_eState) {
167  m_pclCurrent->Remove(this);
168  }
169 
172  m_pclOwner->Add(this);
174 
175  // Just to be safe - attempt to remove the thread's timer
176  // from the timer-scheduler (does no harm if it isn't
177  // in the timer-list)
179  } // End Critical Section
180 
181  if (bReschedule) {
182  Thread::Yield();
183  }
184 }
185 
186 //---------------------------------------------------------------------------
188 {
190 
191  auto bReschedule = false;
192 
193  if (ThreadState::Exit == m_eState) {
194  return;
195  }
196 
197  { // Begin critical section
198  const auto cs = CriticalGuard{};
199 
200  // If this thread is the actively-running thread, make sure we run the
201  // scheduler again.
202  if (this == Scheduler::GetCurrentThread()) {
203  bReschedule = true;
204 #if KERNEL_ROUND_ROBIN
205  // Cancel RR scheduling
206  Quantum::Cancel();
207 #endif
208  }
209 
210  // Remove the thread from scheduling
211  if (ThreadState::Ready == m_eState) {
212  Scheduler::Remove(this);
213  } else if ((ThreadState::Blocked == m_eState) || (ThreadState::Stop == m_eState)) {
214  m_pclCurrent->Remove(this);
215  }
216 
217  m_pclCurrent = nullptr;
218  m_pclOwner = nullptr;
220 
221  // We've removed the thread from scheduling, but interrupts might
222  // trigger checks against this thread's currently priority before
223  // we get around to scheduling new threads. As a result, set the
224  // priority to idle to ensure that we always wind up scheduling
225  // new threads.
226  m_uXCurPriority = 0;
227  m_uXPriority = 0;
228 
229  // Just to be safe - attempt to remove the thread's timer
230  // from the timer-scheduler (does no harm if it isn't
231  // in the timer-list)
233  } // End Critical Section
234 
235 #if KERNEL_THREAD_EXIT_CALLOUT
237  if (nullptr != pfCallout) {
238  pfCallout(this);
239  }
240 #endif
241 
242  if (bReschedule) {
243  // Choose a new "next" thread if we must
244  Thread::Yield();
245  }
246 }
247 
248 //---------------------------------------------------------------------------
249 void Thread::Sleep(uint32_t u32TimeMs_)
250 {
251  auto clSemaphore = Semaphore {};
252  auto* pclTimer = g_pclCurrent->GetTimer();
253  auto lTimerCallback = [](Thread* /*pclOwner*/, void* pvData_) {
254  auto* pclSemaphore = static_cast<Semaphore*>(pvData_);
255  pclSemaphore->Post();
256  };
257 
258  // Create a semaphore that this thread will block on
259  clSemaphore.Init(0, 1);
260 
261  // Create a one-shot timer that will call a callback that posts the
262  // semaphore, waking our thread.
263  pclTimer->Init();
264  pclTimer->Start(false, u32TimeMs_, lTimerCallback, &clSemaphore);
265 
266  clSemaphore.Pend();
267 }
268 
269 #if KERNEL_STACK_CHECK
270 //---------------------------------------------------------------------------
271 uint16_t Thread::GetStackSlack()
272 {
274 
275  auto wBottom = uint16_t { 0 };
276  auto wTop = static_cast<uint16_t>((m_u16StackSize - 1) / sizeof(K_WORD));
277  auto wMid = static_cast<uint16_t>(((wTop + wBottom) + 1) / 2);
278 
279  { // Begin critical section
280  const auto cs = CriticalGuard{};
281 
282  // Logarithmic bisection - find the point where the contents of the
283  // stack go from 0xFF's to non 0xFF. Not Definitive, but accurate enough
284  while ((wTop - wBottom) > 1) {
285 #if PORT_STACK_GROWS_DOWN
286  if (m_pwStack[wMid] != static_cast<K_WORD>(-1))
287 #else
288  if (m_pwStack[wMid] == static_cast<K_WORD>(-1))
289 #endif
290  {
291  wTop = wMid;
292  } else {
293  wBottom = wMid;
294  }
295  wMid = (wTop + wBottom + 1) / 2;
296  }
297  } // End Critical Section
298 
299  return wMid * sizeof(K_WORD);
300 }
301 #endif
302 
303 //---------------------------------------------------------------------------
305 {
306  const auto cs = CriticalGuard{};
307  // Run the scheduler
308  if (Scheduler::IsEnabled()) {
310 
311  // Only switch contexts if the new task is different than the old task
312  if (g_pclCurrent != g_pclNext) {
313 #if KERNEL_ROUND_ROBIN
315 #endif
317  }
318  } else {
320  }
321 }
322 
323 //---------------------------------------------------------------------------
325 {
327  Yield();
328 }
329 
330 //---------------------------------------------------------------------------
332 {
334 
335  GetCurrent()->Remove(this);
337  GetCurrent()->Add(this);
338 }
339 
340 //---------------------------------------------------------------------------
342 {
344  auto bSchedule = false;
345 
346  { // Begin critical section
347  const auto cs = CriticalGuard{};
348 
349  // If this is the currently running thread, it's a good idea to reschedule
350  // Or, if the new priority is a higher priority than the current thread's.
351  if ((this == g_pclCurrent) || (uXPriority_ > g_pclCurrent->GetPriority())) {
352  bSchedule = true;
353 #if KERNEL_ROUND_ROBIN
354  Quantum::Cancel();
355 #endif
356  }
357  Scheduler::Remove(this);
358 
359  m_uXCurPriority = uXPriority_;
360  m_uXPriority = uXPriority_;
361 
362  Scheduler::Add(this);
363  } // End critical section
364 
365  if (bSchedule) {
366  if (Scheduler::IsEnabled()) {
367  { // Begin critical section
368  const auto cs = CriticalGuard{};
370 #if KERNEL_ROUND_ROBIN
372 #endif
373  } // End critical sectin
375  } else {
377  }
378  }
379 }
380 
381 //---------------------------------------------------------------------------
383 {
385 
386  SetOwner(Scheduler::GetThreadList(uXPriority_));
387  m_uXCurPriority = uXPriority_;
388 }
389 
390 //---------------------------------------------------------------------------
392 {
393  // Call the context switch interrupt if the scheduler is enabled.
394  if (Scheduler::IsEnabled()) {
395 #if KERNEL_STACK_CHECK
398  }
399 #endif
400 #if KERNEL_CONTEXT_SWITCH_CALLOUT
401  auto pfCallout = Kernel::GetThreadContextSwitchCallout();
402  if (nullptr != pfCallout) {
403  pfCallout(g_pclCurrent);
404  }
405 #endif
407  }
408 }
409 
410 //---------------------------------------------------------------------------
412 {
414  return &m_clTimer;
415 }
416 //---------------------------------------------------------------------------
417 void Thread::SetExpired(bool bExpired_)
418 {
420  m_bExpired = bExpired_;
421 }
422 
423 //---------------------------------------------------------------------------
425 {
427  return m_bExpired;
428 }
429 } // namespace Mark3
uint16_t m_u16Quantum
Thread quantum (in milliseconds)
Definition: thread.h:493
K_WORD * m_pwStack
Pointer to the thread&#39;s stack.
Definition: thread.h:452
static void Remove(Timer *pclListNode_)
Remove Remove a timer from the timer scheduler. May implicitly stop the timer if this is the only act...
static void Update(Thread *pclTargetThread_)
Update Update the current thread being tracked for round-robing scheduling. Note - this has no effect...
#define K_WORD
Size of a data word.
Definition: portcfg.h:62
Basic data type primatives used throughout the OS.
static bool IsEnabled()
IsEnabled Return the current state of the scheduler - whether or not scheudling is enabled or disable...
Definition: scheduler.h:150
static ThreadList * GetStopList()
GetStopList Return the pointer to the list of threads that are in the scheduler&#39;s stopped state...
Definition: scheduler.h:142
static void Add(Thread *pclThread_)
Add Add a thread to the scheduler at its current priority level.
Definition: scheduler.cpp:59
void Exit()
Exit. Remove the thread from being scheduled again. The thread is effectively destroyed when this occ...
Definition: thread.cpp:187
void(*)(Thread *pclThread_) ThreadExitCallout
Definition: thread.h:53
void(*)(Thread *pclThread_) ThreadCreateCallout
Definition: thread.h:52
PORT_PRIO_TYPE m_uXCurPriority
Current priority of the thread (priority inheritence)
Definition: thread.h:461
PORT_PRIO_TYPE m_uXPriority
Default priority of the thread.
Definition: thread.h:458
void Init(K_WORD *pwStack_, uint16_t u16StackSize_, PORT_PRIO_TYPE uXPriority_, ThreadEntryFunc pfEntryPoint_, void *pvArg_)
Init Initialize a thread prior to its use. Initialized threads are placed in the stopped state...
Definition: thread.cpp:49
void Init()
Init Re-initialize the Timer to default values.
Definition: timer.cpp:36
static void CoopYield(void)
CoopYield Cooperative yield - This forces the system to not only call the scheduler, but also move the currently executing thread to the back of the current thread list, allowing other same-priority threads the opportunity to run. This is used primarily for cooperative scheduling between threads in the same priority level.
Definition: thread.cpp:324
ThreadList * m_pclCurrent
Pointer to the thread-list where the thread currently resides.
Definition: thread.h:480
uint16_t m_u16StackSize
Size of the stack (in bytes)
Definition: thread.h:477
#define PORT_PRIO_TYPE
Type used for bitmap in the PriorityMap class.
Definition: portcfg.h:73
the Semaphore class provides Binary & Counting semaphore objects, based on BlockingObject base class...
Definition: ksemaphore.h:36
#define KERNEL_ASSERT(x)
Definition: kerneldebug.h:36
bool m_bExpired
Indicate whether or not a blocking-object timeout has occurred.
Definition: thread.h:505
K_WORD * m_pwStackTop
Pointer to the top of the thread&#39;s stack.
Definition: thread.h:449
static void ContextSwitchSWI(void)
ContextSwitchSWI This code is used to trigger the context switch interrupt. Called whenever the kerne...
Definition: thread.cpp:391
ThreadEntryFunc m_pfEntryPoint
The entry-point function called when the thread starts.
Definition: thread.h:486
bool IsInitialized()
IsInitialized Used to check whether or not a thread has been initialized prior to use...
Definition: thread.h:77
void InheritPriority(PORT_PRIO_TYPE uXPriority_)
InheritPriority Allow the thread to run at a different priority level (temporarily) for the purpose o...
Definition: thread.cpp:382
static ThreadList * GetThreadList(PORT_PRIO_TYPE uXPriority_)
GetThreadList Return the pointer to the active list of threads that are at the given priority level i...
Definition: scheduler.h:134
#define PANIC_RUNNING_THREAD_DESCOPED
Definition: paniccodes.h:28
static void Trigger(void)
Trigger Call the software interrupt.
Definition: kernelswi.cpp:46
void * m_pvArg
Pointer to the argument passed into the thread&#39;s entrypoint.
Definition: thread.h:489
uint16_t GetStackSlack()
GetStackSlack Performs a (somewhat lengthy) check on the thread stack to check the amount of stack ma...
static ThreadCreateCallout GetThreadCreateCallout()
GetThreadCreateCallout Return the current function called on every Thread::Init();.
Definition: kernel.h:178
void Remove(Thread *node_)
Remove Remove the specified thread from the threadlist.
Definition: threadlist.cpp:111
Definition: atomic.cpp:23
Mark3 Kernel Configuration This file is used to configure the kernel for your specific application in...
Timer m_clTimer
Timer used for blocking-object timeouts.
Definition: thread.h:511
Mark3::Thread * g_pclCurrent
Definition: scheduler.cpp:25
PORT_PRIO_TYPE GetCurPriority(void)
GetCurPriority Return the priority of the current thread.
Definition: thread.h:181
static void QueueScheduler()
QueueScheduler Tell the kernel to perform a scheduling operation as soon as the scheduler is re-enabl...
Definition: scheduler.h:156
Single include file given to users of the Mark3 Kernel API.
The Thread Class. This object providing the fundamental thread control data structures and functions ...
Definition: thread.h:64
const char * m_szName
Thread name.
Definition: thread.h:473
void SetExpired(bool bExpired_)
SetExpired Set the status of the current blocking call on the thread.
Definition: thread.cpp:417
void SetCurrent(ThreadList *pclNewList_)
SetCurrent. Set the thread&#39;s current to the specified thread list.
Definition: thread.h:206
#define PANIC_STACK_SLACK_VIOLATED
Definition: paniccodes.h:24
Mark3::Thread * g_pclNext
Definition: scheduler.cpp:24
static ThreadContextCallout GetThreadContextSwitchCallout()
GetThreadContextSwitchCallout Return the current function called on every Thread::ContextSwitchSWI() ...
Definition: kernel.h:198
static void InitStack(Thread *pstThread_)
InitStack Initialize the thread&#39;s stack.
Definition: threadport.cpp:43
void Start()
Start Start the thread - remove it from the stopped list, add it to the scheduler&#39;s list of threads (...
Definition: thread.cpp:110
ThreadState m_eState
Enum indicating the thread&#39;s current state.
Definition: thread.h:464
static void Yield(void)
Yield Yield the thread - this forces the system to call the scheduler and determine what thread shoul...
Definition: thread.cpp:304
static void Cancel()
void SetOwner(ThreadList *pclNewList_)
SetOwner. Set the thread&#39;s owner to the specified thread list.
Definition: thread.h:213
#define THREAD_QUANTUM_DEFAULT
Definition: portcfg.h:39
void Stop()
Stop Stop a thread that&#39;s actively scheduled without destroying its stacks. Stopped threads can be re...
Definition: thread.cpp:141
static void Schedule()
Schedule Run the scheduler, determines the next thread to run based on the current state of the threa...
Definition: scheduler.cpp:45
static bool IsStarted()
IsStarted.
Definition: kernel.h:86
The CriticalGuard class. This class provides an implemention of RAII for critical sections...
Definition: criticalguard.h:38
void(*)(void *pvArg_) ThreadEntryFunc
Definition: kerneltypes.h:43
static void Sleep(uint32_t u32TimeMs_)
Sleep Put the thread to sleep for the specified time (in milliseconds). Actual time slept may be long...
Definition: thread.cpp:249
Timer * GetTimer()
Definition: thread.cpp:411
bool GetExpired()
GetExpired Return the status of the most-recent blocking call on the thread.
Definition: thread.cpp:424
void Add(Thread *node_)
Add Add a thread to the threadlist.
Definition: threadlist.cpp:47
The Timer Class. This class provides kernel-managed timers, used to provide high-precision delays...
Definition: timer.h:68
static void * NewRawData(size_t sSize_)
NewRawData Attempt to allocate a blob of raw data from the heap.
Definition: autoalloc.cpp:100
ThreadList * GetCurrent(void)
GetCurrent Return the ThreadList where the thread is currently located.
Definition: thread.h:166
bool Post()
Increment the semaphore count. If the semaphore count is zero at the time this is called...
Definition: ksemaphore.cpp:108
static void Remove(Thread *pclThread_)
Remove Remove a thread from the scheduler at its current priority level.
Definition: scheduler.cpp:67
static uint16_t GetStackGuardThreshold()
Definition: kernel.h:202
void SetPriorityBase(PORT_PRIO_TYPE uXPriority_)
SetPriorityBase.
Definition: thread.cpp:331
#define PORT_TOP_OF_STACK(x, y)
Macro to find the top of a stack given its size and top address.
Definition: threadport.h:36
uint8_t m_u8ThreadID
Thread ID.
Definition: thread.h:455
static ThreadExitCallout GetThreadExitCallout()
GetThreadExitCallout Return the current function called on every Thread::Exit();. ...
Definition: kernel.h:188
PORT_PRIO_TYPE GetPriority(void)
GetPriority Return the priority of the current thread.
Definition: thread.h:174
static Thread * GetCurrentThread()
GetCurrentThread Return the pointer to the currently-running thread.
Definition: scheduler.h:116
ThreadList * m_pclOwner
Pointer to the thread-list where the thread resides when active.
Definition: thread.h:483
void SetPriority(PORT_PRIO_TYPE uXPriority_)
SetPriority. Set the priority of the Thread (running or otherwise) to a different level...
Definition: thread.cpp:341
static void Panic(uint16_t u16Cause_)
Panic Cause the kernel to enter its panic state.
Definition: kernel.cpp:70