Using Templates to Statically Allocate Thread Working Area in ChibiOS
On both 8-bit AVR and 32-bit ARM (AT91SAM7X and SAM3S), I've been using ChibiOS. It's a nifty little OS that supports fully-static operation. That is to say, it's possible to allocate all OS structures statically, at compile time, so none need be allocated dynamically at run-time, an operation that can possibly fail. This also allows the exact memory requirements to be known before loading the code onto the target.
I wrote a CThread
class (so named to avoid conflict with the OS Thread
object) that wraps the allocation of the thread working area and OS thread creation. To do this, CThread
is a template class, parameterizing the stack size. Clients subclass CThread
and implement the virtual msg_t entry()
method.
Note: If you're looking for the description of what goes wrong when the compiler fails to properly initialize static C++ object instances, I moved that material to a new post, Trouble with Static C++ Constructors in ChibiOS/ARM.
ChibiOS Threads
In ChibiOS, to create a thread, you allocate the thread working area with a macro provided by the OS, and call chThdCreateStatic()
:
[cpp]
WORKING_AREA(sMyThreadWA, 512);
msg_t
MyThreadEntry(void* inArg)
{
// …do work
return 0;
}
void
MyProgram::startAThread()
{
Thread* t = chThdCreateStatic(sThreadWA, sizeof (sMyThreadWA), NORMALPRIO, MyThreadEntry, NULL);
}
[/cpp]
The last parameter to chThdCreateStatic()
is passed into the thread's entry point. We use it later to pass a reference to the thread class.
As soon as chThdCreateStatic()
is called, the thread begins executing. ChibiOS provides numerous synchronization primitives, but we won't get into those here.
CThread
Class
The idea with the CThread
wrapper is to provide a class to be subclassed to tidy up the creation of a thread. It would be used like this:
[cpp]
class
MyThread : public CThread<512>
{
protected:
virtual msg_t entry();
};
[/cpp]
And the implementation:
[cpp]
msg_t
MyThread::entry()
{
// …do work
return 0;
}
[/cpp]
Finally, the thread is allocated as a global (as before), and started:
[cpp]
MyThread sMyThread;
void
MyProgram::startAThread()
{
sMyThread.start(NORMALPRIO);
}
[/cpp]
Considerably tidier, isn't it?
Pulling this off requires two classes: A non-template BaseThread
class that provides the basic thread functionality, and the CThread
template class that derives from it. Note that I do this to try to avoid redundant code generation, which can probably be done using partial specialization or a smart compiler, but I wasn't sure how much luck I would have. The approach does result in a an extra member variable in the base class: the working area size from construction to be used when the thread is started.
BaseThread::entry()
should be pure virtual, but I had link errors on AVR with that.
Here's the complete implementation.
CThread.h:
[cpp]
/**
CThread.h
Created by Roderick Mann on 2/3/11.
Copyright 2011 Latency: Zero. All rights reserved.
*/
#ifndef __CThread_h__
#define __CThread_h__
#include "ch.h"
/**
*/
class
BaseThread
{
public:
BaseThread(void* inWorkingArea, size_t inWorkingAreaSize);
void start(tprio_t inPriority = NORMALPRIO);
msg_t sendMessage(msg_t inMsg, void* inContext);
Thread* getSysThread() { return mSysThread; }
protected:
virtual msg_t entry();
private:
static msg_t ThreadEntry(void* inArg);
void* mWorkingArea;
uint32_t mWorkingAreaSize;
Thread* mSysThread;
};
inline
msg_t
BaseThread::ThreadEntry(void* inArg)
{
BaseThread* self = reinterpret_cast<BaseThread*> (inArg);
return self->entry();
}
/**
*/
template<size_t inStackSize>
class
CThread : public BaseThread
{
public:
CThread()
:
BaseThread(mWorkingArea, sizeof(mWorkingArea))
{
}
protected:
virtual stkalign_t* getWorkingArea() { return mWorkingArea; }
private:
WORKING_AREA(mWorkingArea, inStackSize);
};
#endif // __CThread_h__
[/cpp]
And the implementation:
[cpp]
/**
CThread.cpp
Created by Roderick Mann on 2/3/11.
Copyright 2011 Latency: Zero. All rights reserved.
*/
#include "CThread.h"
#include "ch.h"
BaseThread::BaseThread(void* inWorkingArea, size_t inWorkingAreaSize)
:
mWorkingArea(inWorkingArea),
mWorkingAreaSize(inWorkingAreaSize),
mSysThread(NULL)
{
}
msg_t
BaseThread::entry()
{
return 0;
}
void
BaseThread::start(tprio_t inPriority)
{
mSysThread = chThdCreateStatic(mWorkingArea,
mWorkingAreaSize,
inPriority,
ThreadEntry,
this);
}
[/cpp]
What you see above is a little messier than it could be, given a number of issues I ran into while developing it, and concerns about code bloat. But it works reasonably well.