OpenRTX/lib/miosix-kernel/miosix/kernel/queue.h

555 wiersze
17 KiB
C++

/***************************************************************************
* Copyright (C) 2014 by Terraneo Federico *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* As a special exception, if other files instantiate templates or use *
* macros or inline functions from this file, or you compile this file *
* and link it with other works to produce a work based on this file, *
* this file does not by itself cause the resulting work to be covered *
* by the GNU General Public License. However the source code for this *
* file must still be made available in accordance with the GNU General *
* Public License. This exception does not invalidate any other reasons *
* why a work based on this file might be covered by the GNU General *
* Public License. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, see <http://www.gnu.org/licenses/> *
***************************************************************************/
#ifndef QUEUE_H
#define QUEUE_H
#include "kernel.h"
#include "error.h"
namespace miosix {
/**
* \addtogroup Sync
* \{
*/
/**
* A queue, used to transfer data between TWO threads, or between ONE thread
* and an IRQ.<br>
* If you need to tranfer data between more than two threads, you need to
* use mutexes to ensure that only one thread at a time calls get, and only one
* thread at a time calls put.<br>
* Dynamically creating a queue with new or on the stack must be done with care,
* to avoid deleting a queue with a waiting thread, and to avoid situations
* where a thread tries to access a deleted queue.
* \tparam T the type of elements in the queue
* \tparam len the length of the Queue. Value 0 is forbidden
*/
template <typename T, unsigned int len>
class Queue
{
public:
/**
* Constructor, create a new empty queue.
*/
Queue() : waiting(0), numElem(0), putPos(0), getPos(0) {}
/**
* \return true if the queue is empty
*/
bool isEmpty() const { return numElem==0; }
/**
* \return true if the queue is full
*/
bool isFull() const { return numElem==len; }
/**
* \return the number of elements currently in the queue
*/
unsigned int size() const { return numElem; }
/**
* \return the maximum number of elements the queue can hold
*/
unsigned int capacity() const { return len; }
/**
* If a queue is empty, waits until the queue is not empty.
*/
void waitUntilNotEmpty();
/**
* If a queue is full, waits until the queue is not full
*/
void waitUntilNotFull();
/**
* Get an element from the queue. If the queue is empty, then sleep until
* an element becomes available.
* \param elem an element from the queue
*/
void get(T& elem);
/**
* Put an element to the queue. If the queue is full, then sleep until a
* place becomes available.
* \param elem element to add to the queue
*/
void put(const T& elem);
/**
* Get an element from the queue, only if the queue is not empty.<br>
* Can ONLY be used inside an IRQ, or when interrupts are disabled.
* \param elem an element from the queue. The element is valid only if the
* return value is true
* \return true if the queue was not empty
*/
bool IRQget(T& elem);
/**
* Get an element from the queue, only if the queue is not empty.<br>
* Can ONLY be used inside an IRQ, or when interrupts are disabled.
* \param elem an element from the queue. The element is valid only if the
* return value is true
* \param hppw is not modified if no thread is woken or if the woken thread
* has a lower or equal priority than the currently running thread, else is
* set to true
* \return true if the queue was not empty
*/
bool IRQget(T& elem, bool& hppw);
/**
* Put an element to the queue, only if th queue is not full.<br>
* Can ONLY be used inside an IRQ, or when interrupts are disabled.
* \param elem element to add. The element has been added only if the
* return value is true
* \return true if the queue was not full.
*/
bool IRQput(const T& elem);
/**
* Put an element to the queue, only if th queue is not full.<br>
* Can ONLY be used inside an IRQ, or when interrupts are disabled.
* \param elem element to add. The element has been added only if the
* return value is true
* \param hppw is not modified if no thread is woken or if the woken thread
* has a lower or equal priority than the currently running thread, else is
* set to true
* \return true if the queue was not full.
*/
bool IRQput(const T& elem, bool& hppw);
/**
* Clear all items in the queue.<br>
* Cannot be used inside an IRQ
*/
void reset()
{
FastInterruptDisableLock lock;
IRQreset();
}
/**
* Same as reset(), but to be used only inside IRQs or when interrupts are
* disabled.
*/
void IRQreset()
{
IRQwakeWaitingThread();
putPos=getPos=numElem=0;
}
private:
//Unwanted methods
Queue(const Queue& s);///< No public copy constructor
Queue& operator = (const Queue& s);///< No publc operator =
/**
* Wake an eventual waiting thread.
* Must be called when interrupts are disabled
*/
void IRQwakeWaitingThread()
{
if(!waiting) return;
waiting->IRQwakeup();//Wakeup eventual waiting thread
waiting=0;
}
//Queue data
T buffer[len];///< queued elements are put here. Used as a ring buffer
Thread *waiting;///< If not null holds the thread waiting
volatile unsigned int numElem;///< nuber of elements in the queue
volatile unsigned int putPos; ///< index of buffer where to get next element
volatile unsigned int getPos; ///< index of buffer where to put next element
};
template <typename T, unsigned int len>
void Queue<T,len>::waitUntilNotEmpty()
{
FastInterruptDisableLock dLock;
IRQwakeWaitingThread();
while(isEmpty())
{
waiting=Thread::IRQgetCurrentThread();
Thread::IRQwait();
{
FastInterruptEnableLock eLock(dLock);
Thread::yield();
}
IRQwakeWaitingThread();
}
}
template <typename T, unsigned int len>
void Queue<T,len>::waitUntilNotFull()
{
FastInterruptDisableLock dLock;
IRQwakeWaitingThread();
while(isFull())
{
waiting=Thread::IRQgetCurrentThread();
Thread::IRQwait();
{
FastInterruptEnableLock eLock(dLock);
Thread::yield();
}
IRQwakeWaitingThread();
}
}
template <typename T, unsigned int len>
void Queue<T,len>::get(T& elem)
{
FastInterruptDisableLock dLock;
IRQwakeWaitingThread();
while(isEmpty())
{
waiting=Thread::IRQgetCurrentThread();
Thread::IRQwait();
{
FastInterruptEnableLock eLock(dLock);
Thread::yield();
}
IRQwakeWaitingThread();
}
numElem--;
elem=buffer[getPos];
if(++getPos==len) getPos=0;
}
template <typename T, unsigned int len>
void Queue<T,len>::put(const T& elem)
{
FastInterruptDisableLock dLock;
IRQwakeWaitingThread();
while(isFull())
{
waiting=Thread::IRQgetCurrentThread();
Thread::IRQwait();
{
FastInterruptEnableLock eLock(dLock);
Thread::yield();
}
IRQwakeWaitingThread();
}
numElem++;
buffer[putPos]=elem;
if(++putPos==len) putPos=0;
}
template <typename T, unsigned int len>
bool Queue<T,len>::IRQget(T& elem)
{
IRQwakeWaitingThread();
if(isEmpty()) return false;
numElem--;
elem=buffer[getPos];
if(++getPos==len) getPos=0;
return true;
}
template <typename T, unsigned int len>
bool Queue<T,len>::IRQget(T& elem, bool& hppw)
{
if(waiting && (waiting->IRQgetPriority() >
Thread::IRQgetCurrentThread()->IRQgetPriority())) hppw=true;
IRQwakeWaitingThread();
if(isEmpty()) return false;
numElem--;
elem=buffer[getPos];
if(++getPos==len) getPos=0;
return true;
}
template <typename T, unsigned int len>
bool Queue<T,len>::IRQput(const T& elem)
{
IRQwakeWaitingThread();
if(isFull()) return false;
numElem++;
buffer[putPos]=elem;
if(++putPos==len) putPos=0;
return true;
}
template <typename T, unsigned int len>
bool Queue<T,len>::IRQput(const T& elem, bool& hppw)
{
if(waiting && (waiting->IRQgetPriority() >
Thread::IRQgetCurrentThread()->IRQgetPriority())) hppw=true;
IRQwakeWaitingThread();
if(isFull()) return false;
numElem++;
buffer[putPos]=elem;
if(++putPos==len) putPos=0;
return true;
}
//This partial specialization is meant to to produce compiler errors in case an
//attempt is made to instantiate a Queue with zero size, as it is forbidden
template<typename T> class Queue<T,0> {};
/**
* An unsynchronized circular buffer data structure with the storage dynamically
* allocated on the heap.
* Note that unlike Queue, this class is only a data structure and not a
* synchronization primitive. The synchronization between the thread and
* the IRQ (or the other thread) must be done by the caller.
*/
template<typename T>
class DynUnsyncQueue
{
public:
/**
* Constructor
* \param elem number of elements of the circular buffer
*/
DynUnsyncQueue(unsigned int elem) : data(new T[elem]),
putPos(0), getPos(0), queueSize(0), queueCapacity(elem) {}
/**
* \return true if the queue is empty
*/
bool isEmpty() const { return queueSize==0; }
/**
* \return true if the queue is full
*/
bool isFull() const { return queueSize==queueCapacity; }
/**
* \return the number of elements currently in the queue
*/
unsigned int size() const { return queueSize; }
/**
* \return the maximum number of elements the queue can hold
*/
unsigned int capacity() const { return queueCapacity; }
/**
* Try to put an element in the circular buffer
* \param elem element to put
* \return true if the queue was not full
*/
bool tryPut(const T& elem);
/**
* Try to get an element from the circular buffer
* \param elem element to get will be stored here
* \return true if the queue was not empty
*/
bool tryGet(T& elem);
/**
* Erase all elements in the queue
*/
void reset() { putPos=getPos=queueSize=0; }
/**
* Destructor
*/
~DynUnsyncQueue() { delete[] data; }
private:
DynUnsyncQueue(const DynUnsyncQueue&);
DynUnsyncQueue& operator=(const DynUnsyncQueue&);
T *data;
unsigned int putPos,getPos;
volatile unsigned int queueSize;
const unsigned int queueCapacity;
};
template<typename T>
bool DynUnsyncQueue<T>::tryPut(const T& elem)
{
if(isFull()) return false;
queueSize++;
data[putPos++]=elem;
if(putPos>=queueCapacity) putPos=0;
return true;
}
template<typename T>
bool DynUnsyncQueue<T>::tryGet(T& elem)
{
if(isEmpty()) return false;
queueSize--;
elem=data[getPos++];
if(getPos>=queueCapacity) getPos=0;
return true;
}
/**
* A class to handle double buffering, but also triple buffering and in general
* N-buffering. Works between two threads but is especially suited to
* synchronize between a thread and an interrupt routine.<br>
* Note that unlike Queue, this class is only a data structure and not a
* synchronization primitive. The synchronization between the thread and
* the IRQ (or the other thread) must be done by the caller. <br>
* The internal implementation treats the buffers as a circular queue of N
* elements, hence the name.
* \tparam T type of elements of the buffer, usually char or unsigned char
* \tparam size maximum size of a buffer
* \tparam numbuf number of buffers, the default is two resulting in a
* double buffering scheme. Values 0 and 1 are forbidden
*/
template<typename T, unsigned int size, unsigned char numbuf=2>
class BufferQueue
{
public:
/**
* Constructor, all buffers are empty
*/
BufferQueue() : put(0), get(0), cnt(0) {}
/**
* \return true if no buffer is available for reading
*/
bool isEmpty() const { return cnt==0; }
/**
* \return true if no buffer is available for writing
*/
bool isFull() const { return cnt==numbuf; }
/**
* \return the maximum size of a buffer
*/
unsigned int bufferMaxSize() const { return size; }
/**
* \return the maximum number of buffers
*/
unsigned int numberOfBuffers() const { return numbuf; }
/**
* This member function allows to retrieve a buffer ready to be written,
* if available.
* \param buffer the available buffer will be assigned here if available
* \return true if a writable buffer has been found, false otherwise.
* In this case the buffer parameter is not modified
*/
bool tryGetWritableBuffer(T *&buffer)
{
if(isFull()) return false;
buffer=buf[put];
return true;
}
/**
* After having called tryGetWritableBuffer() to retrieve a buffer and
* having filled it, this member function allows to mark the buffer as
* available on the reader side.
* \param actualSize actual size of buffer. It usually equals bufferMaxSize
* but can be a lower value in case there is less available data
*/
void bufferFilled(unsigned int actualSize)
{
if(isFull()) errorHandler(UNEXPECTED);
cnt++;
bufSize[put++]=actualSize;
if(put>=numbuf) put=0;
}
/**
* \return the number of buffers available for writing (0 to numbuf)
*/
unsigned char availableForWriting() const { return numbuf-cnt; }
/**
* This member function allows to retrieve a buffer ready to be read,
* if available.
* \param buffer the available buffer will be assigned here if available
* \param actualSize the actual size of the buffer, as reported by the
* writer side
* \return true if a readable buffer has been found, false otherwise.
* In this case the buffer and actualSize parameters are not modified
*/
bool tryGetReadableBuffer(const T *&buffer, unsigned int& actualSize)
{
if(isEmpty()) return false;
buffer=buf[get];
actualSize=bufSize[get];
return true;
}
/**
* After having called tryGetReadableBuffer() to retrieve a buffer and
* having read it, this member function allows to mark the buffer as
* available on the writer side.
*/
void bufferEmptied()
{
if(isEmpty()) errorHandler(UNEXPECTED);
cnt--;
get++;
if(get>=numbuf) get=0;
}
/**
* \return The number of buffers available for reading (0, to numbuf)
*/
unsigned char availableForReading() const { return cnt; }
/**
* Reset the buffers. As a consequence, the queue becomes empty.
*/
void reset()
{
put=get=cnt=0;
}
private:
//Unwanted methods
BufferQueue(const BufferQueue&);
BufferQueue& operator=(const BufferQueue&);
T buf[numbuf][size]; // The buffers
unsigned int bufSize[numbuf]; //To handle partially empty buffers
unsigned char put; //Put pointer
unsigned char get; //Get pointer
volatile unsigned char cnt; //Number of filled buffers, either (0 to numbuf)
};
//These two partial specialization are meant to produce compiler errors in case
//an attempt is made to allocate a BufferQueue with zero or one buffer, as it
//is forbidden
template<typename T, unsigned int size> class BufferQueue<T,size,0> {};
template<typename T, unsigned int size> class BufferQueue<T,size,1> {};
/**
* \}
*/
} //namespace miosix
#endif //QUEUE_H