Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • avn/swd/miosix-kernel
  • emilio.corigliano/miosix-kernel
2 results
Select Git revision
Show changes
Commits on Source (7)
......@@ -68,6 +68,7 @@ foreach(OPT_BOARD ${BOARDS})
kernel/process_pool.cpp
kernel/timeconversion.cpp
kernel/SystemMap.cpp
kernel/intrusive.cpp
kernel/scheduler/priority/priority_scheduler.cpp
kernel/scheduler/control/control_scheduler.cpp
kernel/scheduler/edf/edf_scheduler.cpp
......
......@@ -23,6 +23,7 @@ kernel/process.cpp \
kernel/process_pool.cpp \
kernel/timeconversion.cpp \
kernel/SystemMap.cpp \
kernel/intrusive.cpp \
kernel/scheduler/priority/priority_scheduler.cpp \
kernel/scheduler/control/control_scheduler.cpp \
kernel/scheduler/edf/edf_scheduler.cpp \
......
/***************************************************************************
* Copyright (C) 2023 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 TEST_ALGORITHM
#include "intrusive.h"
#else //TEST_ALGORITHM
#include <iostream>
#include <cassert>
// Unused stubs as the test code only tests IntrusiveList
inline int atomicSwap(volatile int*, int) { return 0; }
void *atomicFetchAndIncrement(void *const volatile*, int, int) { return nullptr; }
//C++ glassbox testing trick
#define private public
#define protected public
#include "intrusive.h"
#undef private
#undef public
using namespace std;
using namespace miosix;
#endif //TEST_ALGORITHM
namespace miosix {
//
// class IntrusiveListBase
//
void IntrusiveListBase::push_back(IntrusiveListItem *item)
{
#ifdef INTRUSIVE_LIST_ERROR_CHECK
if((head!=nullptr) ^ (tail!=nullptr)) fail();
if(!empty() && head==tail && (head->prev || head->next)) fail();
if(item->prev!=nullptr || item->next!=nullptr) fail();
#endif //INTRUSIVE_LIST_ERROR_CHECK
if(empty()) head=item;
else {
item->prev=tail;
tail->next=item;
}
tail=item;
}
void IntrusiveListBase::pop_back()
{
#ifdef INTRUSIVE_LIST_ERROR_CHECK
if(head==nullptr || tail==nullptr) fail();
if(!empty() && head==tail && (head->prev || head->next)) fail();
#endif //INTRUSIVE_LIST_ERROR_CHECK
IntrusiveListItem *removedItem=tail;
tail=removedItem->prev;
if(tail!=nullptr)
{
tail->next=nullptr;
removedItem->prev=nullptr;
} else head=nullptr;
}
void IntrusiveListBase::push_front(IntrusiveListItem *item)
{
#ifdef INTRUSIVE_LIST_ERROR_CHECK
if((head!=nullptr) ^ (tail!=nullptr)) fail();
if(!empty() && head==tail && (head->prev || head->next)) fail();
if(item->prev!=nullptr || item->next!=nullptr) fail();
#endif //INTRUSIVE_LIST_ERROR_CHECK
if(empty()) tail=item;
else {
head->prev=item;
item->next=head;
}
head=item;
}
void IntrusiveListBase::pop_front()
{
#ifdef INTRUSIVE_LIST_ERROR_CHECK
if(head==nullptr || tail==nullptr) fail();
if(!empty() && head==tail && (head->prev || head->next)) fail();
#endif //INTRUSIVE_LIST_ERROR_CHECK
IntrusiveListItem *removedItem=head;
head=removedItem->next;
if(head!=nullptr)
{
head->prev=nullptr;
removedItem->next=nullptr;
} else tail=nullptr;
}
void IntrusiveListBase::insert(IntrusiveListItem *cur, IntrusiveListItem *item)
{
#ifdef INTRUSIVE_LIST_ERROR_CHECK
if((head!=nullptr) ^ (tail!=nullptr)) fail();
if(!empty() && head==tail && (head->prev || head->next)) fail();
if(cur!=nullptr)
{
if(cur->prev==nullptr && cur!=head) fail();
if(cur->next==nullptr && cur!=tail) fail();
}
if(item->prev!=nullptr || item->next!=nullptr) fail();
#endif //INTRUSIVE_LIST_ERROR_CHECK
item->next=cur;
if(cur!=nullptr)
{
item->prev=cur->prev;
cur->prev=item;
} else {
item->prev=tail;
tail=item;
}
if(item->prev!=nullptr) item->prev->next=item;
else head=item;
}
IntrusiveListItem *IntrusiveListBase::erase(IntrusiveListItem *cur)
{
#ifdef INTRUSIVE_LIST_ERROR_CHECK
if(head==nullptr || tail==nullptr) fail();
if(!empty() && head==tail && (head->prev || head->next)) fail();
if(cur==nullptr) fail();
if(cur->prev==nullptr && cur!=head) fail();
if(cur->next==nullptr && cur!=tail) fail();
#endif //INTRUSIVE_LIST_ERROR_CHECK
if(cur->prev!=nullptr) cur->prev->next=cur->next;
else head=cur->next;
if(cur->next!=nullptr) cur->next->prev=cur->prev;
else tail=cur->prev;
auto result=cur->next;
cur->prev=nullptr;
cur->next=nullptr;
return result;
}
#ifdef INTRUSIVE_LIST_ERROR_CHECK
#warning "INTRUSIVE_LIST_ERROR_CHECK should not be enabled in release builds"
void IntrusiveListBase::fail()
{
#ifndef TEST_ALGORITHM
errorHandler(UNEXPECTED);
#else //TEST_ALGORITHM
assert(false);
#endif //TEST_ALGORITHM
}
#endif //INTRUSIVE_LIST_ERROR_CHECK
} //namespace miosix
//Testsuite for IntrusiveList. Compile with:
//g++ -DTEST_ALGORITHM -DINTRUSIVE_LIST_ERROR_CHECK -fsanitize=address -m32
// -std=c++14 -Wall -O2 -o test intrusive.cpp; ./test
#ifdef TEST_ALGORITHM
void emptyCheck(IntrusiveListItem& x)
{
//Glass box check
assert(x.next==nullptr); assert(x.prev==nullptr);
}
void emptyCheck(IntrusiveList<IntrusiveListItem>& list)
{
//Black box check
assert(list.empty());
assert(list.begin()==list.end());
//Glass box check
assert(list.head==nullptr);
assert(list.tail==nullptr);
}
void oneItemCheck(IntrusiveList<IntrusiveListItem>& list, IntrusiveListItem& a)
{
IntrusiveList<IntrusiveListItem>::iterator it;
//Black box check
assert(list.empty()==false);
assert(list.front()==&a);
assert(list.back()==&a);
assert(list.begin()!=list.end());
assert(*list.begin()==&a);
assert(++list.begin()==list.end());
it=list.begin(); it++; assert(it==list.end());
assert(--list.end()==list.begin());
it=list.end(); it--; assert(it==list.begin());
//Glass box check
assert(list.head==&a);
assert(list.tail==&a);
assert(a.prev==nullptr);
assert(a.next==nullptr);
}
void twoItemCheck(IntrusiveList<IntrusiveListItem>& list, IntrusiveListItem& a,
IntrusiveListItem& b)
{
IntrusiveList<IntrusiveListItem>::iterator it;
//Black box check
assert(list.empty()==false);
assert(list.front()==&a);
assert(list.back()==&b);
assert(list.begin()!=list.end());
it=list.begin();
assert(*it++==&a);
assert(*it++==&b);
assert(it==list.end());
it=list.begin();
assert(*it==&a);
++it;
assert(*it==&b);
++it;
assert(it==list.end());
it=list.end();
it--;
assert(*it==&b);
it--;
assert(*it==&a);
assert(it==list.begin());
it=list.end();
assert(*--it==&b);
assert(*--it==&a);
assert(it==list.begin());
//Glass box check
assert(list.head==&a);
assert(list.tail==&b);
assert(a.prev==nullptr);
assert(a.next==&b);
assert(b.prev==&a);
assert(b.next==nullptr);
}
void threeItemCheck(IntrusiveList<IntrusiveListItem>& list, IntrusiveListItem& a,
IntrusiveListItem& b, IntrusiveListItem& c)
{
IntrusiveList<IntrusiveListItem>::iterator it;
//Black box check
assert(list.empty()==false);
assert(list.front()==&a);
assert(list.back()==&c);
assert(list.begin()!=list.end());
it=list.begin();
assert(*it++==&a);
assert(*it++==&b);
assert(*it++==&c);
assert(it==list.end());
it=list.begin();
assert(*it==&a);
++it;
assert(*it==&b);
++it;
assert(*it==&c);
++it;
assert(it==list.end());
it=list.end();
it--;
assert(*it==&c);
it--;
assert(*it==&b);
it--;
assert(*it==&a);
assert(it==list.begin());
it=list.end();
assert(*--it==&c);
assert(*--it==&b);
assert(*--it==&a);
assert(it==list.begin());
//Glass box check
assert(list.head==&a);
assert(list.tail==&c);
assert(a.prev==nullptr);
assert(a.next==&b);
assert(b.prev==&a);
assert(b.next==&c);
assert(c.prev==&b);
assert(c.next==nullptr);
}
int main()
{
IntrusiveListItem a,b,c;
IntrusiveList<IntrusiveListItem> list;
emptyCheck(a);
emptyCheck(b);
emptyCheck(c);
emptyCheck(list);
//
// Testing push_back / pop_back
//
list.push_back(&a);
oneItemCheck(list,a);
list.push_back(&b);
twoItemCheck(list,a,b);
list.pop_back();
oneItemCheck(list,a);
emptyCheck(b);
list.pop_back();
emptyCheck(list);
emptyCheck(a);
//
// Testing push_front / pop_front
//
list.push_front(&a);
oneItemCheck(list,a);
list.push_front(&b);
twoItemCheck(list,b,a);
list.pop_front();
oneItemCheck(list,a);
emptyCheck(b);
list.pop_front();
emptyCheck(list);
emptyCheck(a);
//
// Testing insert / erase
//
list.insert(list.end(),&a);
oneItemCheck(list,a);
list.insert(list.end(),&b);
twoItemCheck(list,a,b);
list.erase(++list.begin()); //Erase second item first
oneItemCheck(list,a);
emptyCheck(b);
list.erase(list.begin()); //Erase only item
emptyCheck(list);
emptyCheck(a);
list.insert(list.begin(),&a);
oneItemCheck(list,a);
list.insert(list.begin(),&b);
twoItemCheck(list,b,a);
list.erase(list.begin()); //Erase first item first
oneItemCheck(list,a);
emptyCheck(b);
list.erase(list.begin()); //Erase only item
emptyCheck(list);
emptyCheck(a);
list.insert(list.end(),&a);
oneItemCheck(list,a);
list.insert(list.end(),&c);
twoItemCheck(list,a,c);
list.insert(++list.begin(),&b); //Insert in the middle
threeItemCheck(list,a,b,c);
list.erase(++list.begin()); //Erase in the middle
twoItemCheck(list,a,c);
emptyCheck(b);
list.erase(list.begin());
oneItemCheck(list,c);
emptyCheck(a);
list.erase(list.begin());
emptyCheck(list);
emptyCheck(c);
//
// Testing removeFast
//
assert(list.removeFast(&a)==false); //Not present, list empty
emptyCheck(list);
emptyCheck(a);
list.push_front(&a);
assert(list.removeFast(&b)==false); //Not present, list not empty
oneItemCheck(list,a);
emptyCheck(b);
assert(list.removeFast(&a)==true); //Present, only element
emptyCheck(list);
emptyCheck(a);
list.push_front(&c);
list.push_front(&b);
list.push_front(&a);
assert(list.removeFast(&a)==true); //Present, at list head
twoItemCheck(list,b,c);
emptyCheck(a);
list.push_front(&a);
assert(list.removeFast(&b)==true); //Present, at in the middle
twoItemCheck(list,a,c);
emptyCheck(b);
assert(list.removeFast(&c)==true); //Present, at list tail
oneItemCheck(list,a);
emptyCheck(c);
list.pop_front(); //Just to end with empty list
emptyCheck(list);
emptyCheck(a);
cout<<"Test passed"<<endl;
return 0;
}
#endif //TEST_ALGORITHM
/***************************************************************************
* Copyright (C) 2013 by Terraneo Federico *
* Copyright (C) 2013-2023 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 *
......@@ -25,17 +25,19 @@
* along with this program; if not, see <http://www.gnu.org/licenses/> *
***************************************************************************/
#ifndef INTRUSIVE_H
#define INTRUSIVE_H
#pragma once
#include <ostream>
#include <cstddef>
#include <cassert>
#include <type_traits>
#ifndef TEST_ALGORITHM
#include "interfaces/atomic_ops.h"
#include "error.h"
#endif //TEST_ALGORITHM
#if __cplusplus > 199711L
#include <type_traits>
#endif // c++11
//Only enable when testing code that uses IntrusiveList
//#define INTRUSIVE_LIST_ERROR_CHECK
namespace miosix {
......@@ -181,7 +183,7 @@ public:
/**
* Default constructor
*/
intrusive_ref_ptr() : object(0) {}
intrusive_ref_ptr() : object(nullptr) {}
/**
* Constructor, with raw pointer
......@@ -280,6 +282,14 @@ public:
return object==0 ? 0 : &SafeBoolStruct::b;
}
/**
* \return true if the object contains a callback
*/
explicit operator bool() const
{
return object!=nullptr;
}
/**
* Swap the managed object with another intrusive_ref_ptr
* \param rhs the other smart pointer
......@@ -294,9 +304,9 @@ public:
void reset()
{
if(decrementRefCount()) delete object;
// Object needs to be set to 0 regardless
// Object needs to be set to nullptr regardless
// of whether the object is deleted
object=0;
object=nullptr;
}
/**
......@@ -349,7 +359,7 @@ private:
*/
bool decrementRefCount()
{
if(object==0) return false;
if(object==nullptr) return false;
return atomicAddExchange(&object->intrusive.referenceCount,-1)==1;
}
......@@ -395,7 +405,7 @@ intrusive_ref_ptr<T>& intrusive_ref_ptr<T>::operator= (T* o)
template<typename T>
intrusive_ref_ptr<T> intrusive_ref_ptr<T>::atomic_load() const
{
intrusive_ref_ptr<T> result; // This gets initialized with 0
intrusive_ref_ptr<T> result; // This gets initialized with nullptr
// According to the C++ standard, this causes undefined behaviour if
// T has virtual functions, but GCC (and clang) have an implementation
......@@ -442,7 +452,7 @@ intrusive_ref_ptr<T> intrusive_ref_ptr<T>::atomic_exchange(
volatile int *objectAddrInt=reinterpret_cast<volatile int*>(&object);
temp=reinterpret_cast<T*>(atomicSwap(objectAddrInt,tempInt));
intrusive_ref_ptr<T> result; // This gets initialized with 0
intrusive_ref_ptr<T> result; // This gets initialized with nullptr
// This does not increment referenceCount, as the pointer was swapped
result.object=temp;
return result;
......@@ -595,7 +605,7 @@ intrusive_ref_ptr<T> const_pointer_cast(const intrusive_ref_ptr<U>& r)
template<typename T>
intrusive_ref_ptr<T> atomic_load(const intrusive_ref_ptr<T> *p)
{
if(p==0) return intrusive_ref_ptr<T>();
if(p==nullptr) return intrusive_ref_ptr<T>();
return p->atomic_load();
}
......@@ -630,10 +640,271 @@ template<typename T>
intrusive_ref_ptr<T> atomic_exchange(intrusive_ref_ptr<T> *p,
intrusive_ref_ptr<T> r)
{
if(p==0) return intrusive_ref_ptr<T>();
if(p==nullptr) return intrusive_ref_ptr<T>();
return p->atomic_exchange(r);
}
} //namenpace miosix
//Forward declarations
class IntrusiveListBase;
template<typename T>
class IntrusiveList;
/**
* Base class from which all items to be put in an IntrusiveList must derive,
* contains the next and prev pointer that create the list
*/
class IntrusiveListItem
{
private:
IntrusiveListItem *next=nullptr;
IntrusiveListItem *prev=nullptr;
friend class IntrusiveListBase;
template<typename T>
friend class IntrusiveList;
};
/**
* \internal
* Base class of IntrusiveList with the non-template-dependent part to improve
* code size when instantiationg multiple IntrusiveLists
*/
class IntrusiveListBase
{
protected:
IntrusiveListBase() : head(nullptr), tail(nullptr) {}
void push_back(IntrusiveListItem *item);
void pop_back();
void push_front(IntrusiveListItem *item);
void pop_front();
void insert(IntrusiveListItem *cur, IntrusiveListItem *item);
IntrusiveListItem *erase(IntrusiveListItem *cur);
IntrusiveListItem* front() { return head; }
IntrusiveListItem* back() { return tail; }
bool empty() const { return head==nullptr; }
#ifdef INTRUSIVE_LIST_ERROR_CHECK
static void fail();
#endif //INTRUSIVE_LIST_ERROR_CHECK
private:
IntrusiveListItem *head;
IntrusiveListItem *tail;
};
/**
* A doubly linked list that only accepts objects that derive from
* IntrusiveListItem.
*
* Compared to std::list, this class offers the guarantee that no dynamic memory
* allocation is performed. Differently from std::list, objects are not copied
* when put in the list, so this is a non-owning container. For this reason,
* this class unlike std::list accepts objects by pointer instead of reference
* in member functions like insert() or push_front(), and returns objects by
* pointer when dereferncing iterators or in member functions like front().
* The caller is thus responsible for managing the lifetime of objects put in
* this list.
*/
template<typename T>
class IntrusiveList : private IntrusiveListBase
{
public:
/**
* Intrusive list iterator type
*/
class iterator
{
public:
iterator() : list(nullptr), cur(nullptr) {}
T* operator*()
{
#ifdef INTRUSIVE_LIST_ERROR_CHECK
if(list==nullptr || cur==nullptr) IntrusiveListBase::fail();
#endif //INTRUSIVE_LIST_ERROR_CHECK
return static_cast<T*>(cur);
}
iterator operator++()
{
#ifdef INTRUSIVE_LIST_ERROR_CHECK
if(list==nullptr || cur==nullptr) IntrusiveListBase::fail();
#endif //INTRUSIVE_LIST_ERROR_CHECK
cur=cur->next; return *this;
}
iterator operator--()
{
#ifdef INTRUSIVE_LIST_ERROR_CHECK
if(list==nullptr || list->empty()) IntrusiveListBase::fail();
#endif //INTRUSIVE_LIST_ERROR_CHECK
if(cur!=nullptr) cur=cur->prev;
else cur=list->IntrusiveListBase::back(); //Special case: decrementing end()
return *this;
}
iterator operator++(int)
{
#ifdef INTRUSIVE_LIST_ERROR_CHECK
if(list==nullptr || cur==nullptr) IntrusiveListBase::fail();
#endif //INTRUSIVE_LIST_ERROR_CHECK
iterator result=*this;
cur=cur->next;
return result;
}
iterator operator--(int)
{
#ifdef INTRUSIVE_LIST_ERROR_CHECK
if(list==nullptr || list->empty()) IntrusiveListBase::fail();
#endif //INTRUSIVE_LIST_ERROR_CHECK
iterator result=*this;
if(cur!=nullptr) cur=cur->prev;
else cur=list->IntrusiveListBase::back(); //Special case: decrementing end()
return result;
}
bool operator==(const iterator& rhs) { return cur==rhs.cur; }
bool operator!=(const iterator& rhs) { return cur!=rhs.cur; }
private:
iterator(IntrusiveList<T> *list, IntrusiveListItem *cur)
: list(list), cur(cur) {}
IntrusiveList<T> *list;
IntrusiveListItem *cur;
friend class IntrusiveList<T>;
};
/**
* Constructor, produces an empty list
*/
IntrusiveList() {}
/**
* Disabled copy constructor and operator=
* Since intrusive lists do not store objects by value, and an item can
* only belong to at most one list, intrusive lists are not copyable.
*/
IntrusiveList(const IntrusiveList&)=delete;
IntrusiveList& operator=(const IntrusiveList&)=delete;
/**
* Adds item to the end of the list
* \param item item to add
*/
void push_back(T *item) { IntrusiveListBase::push_back(item); }
/**
* Removes the last element in the list
*/
void pop_back() { IntrusiveListBase::pop_back(); }
/**
* Adds item to the front of the list
* \param item item to add
*/
void push_front(T *item) { IntrusiveListBase::push_front(item); }
/**
* Removes the first item of the list
*/
void pop_front() { IntrusiveListBase::pop_front(); }
/**
* Inserts the given item before the position indicated by the iterator
* \param it position where to insert the item
* \param item item to insert
*/
void insert(iterator it, T *item)
{
#ifdef INTRUSIVE_LIST_ERROR_CHECK
if(it.list!=this) fail();
#endif //INTRUSIVE_LIST_ERROR_CHECK
IntrusiveListItem *cur=it.cur; //Safe even if it==end() -> cur=nullptr
IntrusiveListBase::insert(cur,item);
}
/**
* Removes the specified item from the list
* \param it iterator to the item to remove
* \return an iterator to the next item
*/
iterator erase(iterator it)
{
#ifdef INTRUSIVE_LIST_ERROR_CHECK
if(it.list!=this) fail();
#endif //INTRUSIVE_LIST_ERROR_CHECK
IntrusiveListItem *cur=it.cur;
return iterator(this,IntrusiveListBase::erase(cur));
}
/**
* Nonportable version of std::list::remove that is O(1) since it relies on
* the list being intrusive
* NOTE: can ONLY be called if you are sure the item to remove is either not
* in any list (in this case, nothing is done) or is in the list it is being
* removed from. Trying to remove an item that is present in another list
* produces undefined bahavior.
* \param item item to remove, must not be nullptr
* \return true if the item was removed, false if the item was not present
* in the list
*/
bool removeFast(T *item)
{
if(item->prev==nullptr && IntrusiveListBase::front()!=item) return false;
IntrusiveListBase::erase(item);
return true;
}
/**
* \return an iterator to the first item
*/
iterator begin() { return iterator(this,IntrusiveListBase::front()); }
/**
* \return an iterator to the last item
*/
iterator end() { return iterator(this,nullptr); }
/**
* \return a pointer to the first item. List must not be empty
*/
T* front()
{
auto result=IntrusiveListBase::front();
#ifdef INTRUSIVE_LIST_ERROR_CHECK
if(result==nullptr) fail();
#endif //INTRUSIVE_LIST_ERROR_CHECK
return static_cast<T*>(result);
}
/**
* \return a pointer to the last item. List must not be empty
*/
T* back()
{
auto result=IntrusiveListBase::back();
#ifdef INTRUSIVE_LIST_ERROR_CHECK
if(result==nullptr) fail();
#endif //INTRUSIVE_LIST_ERROR_CHECK
return static_cast<T*>(result);
}
/**
* \return true if the list is empty
*/
bool empty() const { return IntrusiveListBase::empty(); }
};
#endif //INTRUSIVE_H
} //namespace miosix
......@@ -62,7 +62,7 @@ volatile Thread *cur=NULL;///<\internal Thread currently running
///\internal True if there are threads in the DELETED status. Used by idle thread
static volatile bool exist_deleted=false;
static SleepData *sleeping_list=NULL;///<\internal list of sleeping threads
static IntrusiveList<SleepData> sleeping_list;///<\internal list of sleeping threads
static volatile long long tick=0;///<\internal Kernel tick
......@@ -228,38 +228,43 @@ long long getTick()
/**
* \internal
* Used by Thread::sleep() to add a thread to sleeping list. The list is sorted
* by the wakeup_time field to reduce time required to wake threads during
* context switch.
* Used by Thread::sleep() to add a thread to
* sleeping list. The list is sorted by the wakeupTime field to reduce time
* required to wake threads during context switch.
* Also sets thread SLEEP_FLAG. It is labeled IRQ not because it is meant to be
* used inside an IRQ, but because interrupts must be disabled prior to calling
* this function.
*/
void IRQaddToSleepingList(SleepData *x)
{
x->p->flags.IRQsetSleep(true);
if((sleeping_list==NULL)||(x->wakeup_time <= sleeping_list->wakeup_time))
x->thread->flags.IRQsetSleep(true);
if(sleeping_list.empty() || sleeping_list.front()->wakeupTime>=x->wakeupTime)
{
x->next=sleeping_list;
sleeping_list=x;
sleeping_list.push_front(x);
} else {
SleepData *cur=sleeping_list;
for(;;)
{
if((cur->next==NULL)||(x->wakeup_time <= cur->next->wakeup_time))
{
x->next=cur->next;
cur->next=x;
break;
}
cur=cur->next;
auto it=sleeping_list.begin();
while(it!=sleeping_list.end() && (*it)->wakeupTime<x->wakeupTime) ++it;
sleeping_list.insert(it,x);
}
}
/**
* \internal
* Used by condvar timed waits to remove a thread from sleeping list in case that it
* is woke up by a signal or broadcast.
* It is labeled IRQ not because it is meant to be
* used inside an IRQ, but because interrupts must be disabled prior to calling
* this function.
* \return true if the item was removed from the sleeping list, false otherwise
*/
bool IRQremoveFromSleepingList(SleepData *x)
{
return sleeping_list.removeFast(x);
}
/**
* \internal
* Called @ every tick to check if it's time to wake some thread.
* Called to check if it's time to wake some thread.
* Also increases the system tick.
* Takes care of clearing SLEEP_FLAG.
* It is used by the kernel, and should not be used by end users.
......@@ -268,15 +273,21 @@ void IRQaddToSleepingList(SleepData *x)
bool IRQwakeThreads()
{
tick++;//Increment tick
if(sleeping_list.empty()) return false; //If no item in list, return
bool result=false;
for(;;)
{
if(sleeping_list==NULL) break;//If no item in list, return
//Since list is sorted, if we don't need to wake the first element
//we don't need to wake the other too
if(tick != sleeping_list->wakeup_time) break;
sleeping_list->p->flags.IRQsetSleep(false);//Wake thread
sleeping_list=sleeping_list->next;//Remove from list
for(auto it=sleeping_list.begin();it!=sleeping_list.end();)
{
if(tick<(*it)->wakeupTime) break;
(*it)->thread->flags.IRQsetSleep(false); //Wake thread
//Reset cond wait flag to wakeup threads in condvar timed waits too
(*it)->thread->flags.IRQsetCondWait(false);
// Reset wait flag to wakeup threads in timed waits too
(*it)->thread->flags.IRQsetWait(false);
it=sleeping_list.erase(it);
result=true;
}
return result;
......@@ -353,10 +364,10 @@ void Thread::sleep(unsigned int ms)
//the tick isr will wake threads, modifying the sleeping_list
{
FastInterruptDisableLock lock;
d.p=const_cast<Thread*>(cur);
if(((ms*TICK_FREQ)/1000)>0) d.wakeup_time=getTick()+(ms*TICK_FREQ)/1000;
d.thread=const_cast<Thread*>(cur);
if(((ms*TICK_FREQ)/1000)>0) d.wakeupTime=getTick()+(ms*TICK_FREQ)/1000;
//If tick resolution is too low, wait one tick
else d.wakeup_time=getTick()+1;
else d.wakeupTime=getTick()+1;
IRQaddToSleepingList(&d);//Also sets SLEEP_FLAG
}
Thread::yield();
......@@ -373,8 +384,8 @@ void Thread::sleepUntil(long long absoluteTime)
{
FastInterruptDisableLock lock;
if(absoluteTime<=getTick()) return; //Wakeup time in the past, return
d.p=const_cast<Thread*>(cur);
d.wakeup_time=absoluteTime;
d.thread=const_cast<Thread*>(cur);
d.wakeupTime=absoluteTime;
IRQaddToSleepingList(&d);//Also sets SLEEP_FLAG
}
Thread::yield();
......@@ -453,12 +464,19 @@ void Thread::wait()
//Return here after wakeup
}
TimedWaitResult Thread::timedWaitFor(long long ms)
{
FastInterruptDisableLock dLock;
return Thread::IRQtimedWaitFor(dLock, ms);
}
void Thread::wakeup()
{
//pausing the kernel is not enough because of IRQwait and IRQwakeup
{
FastInterruptDisableLock lock;
this->flags.IRQsetWait(false);
this->flags.IRQsetSleep(false);
}
#ifdef SCHED_TYPE_EDF
yield();//The other thread might have a closer deadline
......@@ -470,6 +488,7 @@ void Thread::PKwakeup()
//pausing the kernel is not enough because of IRQwait and IRQwakeup
FastInterruptDisableLock lock;
this->flags.IRQsetWait(false);
this->flags.IRQsetSleep(false);
}
void Thread::detach()
......@@ -564,9 +583,71 @@ void Thread::IRQwait()
const_cast<Thread*>(cur)->flags.IRQsetWait(true);
}
template<typename InterruptDisableType>
void Thread::IRQyield(InterruptDisableType& dLock)
{
(void)dLock; // Unused by the implementation
// Since this function is called with interrupts disabled, the interrupt
// nesting level is unknown, but always >= 1. We need to save it, reset it
// to 0 before the yield, and restore it after the yield. If we yielded
// unconditionally, the next thread might be started with interrupts
// disabled.
// Save the current interrupt nesting level to restore it after the yield
auto savedNesting = interruptDisableNesting; //For InterruptDisableLock
// Reset the interrupt nesting level to 0
interruptDisableNesting = 0;
miosix_private::doEnableInterrupts();
Thread::yield();
miosix_private::doDisableInterrupts();
if (interruptDisableNesting != 0)
errorHandler(UNEXPECTED);
// Restore the interrupt nesting level to the previous value
interruptDisableNesting = savedNesting;
}
template<typename InterruptDisableType>
TimedWaitResult Thread::IRQtimedWaitFor(InterruptDisableType& dLock, long long ms)
{
if (ms <= 0)
return TimedWaitResult::Timeout;
Thread *thread = Thread::IRQgetCurrentThread();
SleepData sleepData;
sleepData.thread = thread;
if (((ms * TICK_FREQ) / 1000) > 0)
{
sleepData.wakeupTime = getTick() + (ms * TICK_FREQ) / 1000;
}
else
{
// If tick resolution is too low, wait one tick
sleepData.wakeupTime = getTick() + 1;
}
IRQaddToSleepingList(&sleepData); // Also sets SLEEP_FLAG
thread->flags.IRQsetWait(true);
Thread::IRQyield(dLock);
// Ensure that the thread is removed from the sleeping list, as it may have
// been woken up by a call to wakeup()
bool removed_from_sleep_list = IRQremoveFromSleepingList(&sleepData);
// If the thread was still in the sleeping list, it was woken up by a call
// to wakeup()
return removed_from_sleep_list ? TimedWaitResult::NoTimeout
: TimedWaitResult::Timeout;
}
void Thread::IRQwakeup()
{
this->flags.IRQsetWait(false);
this->flags.IRQsetSleep(false);
}
bool Thread::IRQexists(Thread* p)
......
......@@ -34,6 +34,7 @@
#include "interfaces/portability.h"
#include "kernel/scheduler/sched_types.h"
#include "stdlib_integration/libstdcpp_integration.h"
#include "intrusive.h"
#include <cstdlib>
#include <new>
#include <functional>
......@@ -393,6 +394,7 @@ struct SleepData;
class MemoryProfiling;
class Mutex;
class ConditionVariable;
enum class TimedWaitResult;
#ifdef WITH_PROCESSES
class ProcessBase;
#endif //WITH_PROCESSES
......@@ -587,6 +589,17 @@ public:
*/
static void wait();
/**
* This method stops the thread until another thread calls wakeup() on this
* thread, or the given number of milliseconds have passed. <br>Calls to
* wait are not cumulative. If wait() is called two times, only one call to
* wakeup() is needed to wake the thread.<br>CANNOT be called when the
* kernel is paused.
* \param ms the number of millisecond. If it is ==0 this method will
* return immediately
*/
static TimedWaitResult timedWaitFor(long long ms);
/**
* Wakeup a thread.
* <br>CANNOT be called when the kernel is paused.
......@@ -643,6 +656,14 @@ public:
*/
Priority IRQgetPriority();
/**
* Same as yield(), but meant to be used when interrupts are disabled.
* Internally, this function re-enables interrupts before yielding.
* <br>CANNOT be called when the kernel is paused.
*/
template<typename InterruptDisableType>
static void IRQyield(InterruptDisableType& dLock);
/**
* Same as wait(), but is meant to be used only inside an IRQ or when
* interrupts are disabled.<br>
......@@ -661,6 +682,24 @@ public:
*/
static void IRQwait();
/**
* Same as timedWait(), but is meant to be used only inside an IRQ or when
* interrupts are disabled.<br>
* Note: this method is meant to put the current thread in wait status in a
* piece of code where interrupts are disabled.
* Note: as opposed to IRQwait(), it does not return immediately! Any action
* that must be done before putting the thread in wait status must be done
* before calling this method.
*
* \code
* disableInterrupts();
* ...
* Thread::IRQtimedWait(disableInterruptLock, 1000);
* \endcode
*/
template<typename InterruptDisableType>
static TimedWaitResult IRQtimedWaitFor(InterruptDisableType& dLock, long long ms);
/**
* Same as wakeup(), but is meant to be used only inside an IRQ or when
* interrupts are disabled.
......@@ -998,6 +1037,8 @@ private:
friend void miosix_private::IRQstackOverflowCheck();
//Need access to status
friend void IRQaddToSleepingList(SleepData *x);
//Need access to status
friend bool IRQremoveFromSleepingList(SleepData *);
//Needs access to status
friend bool IRQwakeThreads();
//Needs access to watermark, status, next
......@@ -1052,20 +1093,24 @@ public:
/**
* \internal
* \struct Sleep_data
* This struct is used to make a list of sleeping threads.
* This class is used to make a list of sleeping threads.
* It is used by the kernel, and should not be used by end users.
*/
struct SleepData
class SleepData : public IntrusiveListItem
{
public:
// Default constructor declared for compatibility with the old SleepData class usages
SleepData() = default;
SleepData(Thread *thread, long long wakeupTime)
: thread(thread), wakeupTime(wakeupTime) {}
///\internal Thread that is sleeping
Thread *p;
Thread *thread;
///\internal When this number becomes equal to the kernel tick,
///the thread will wake
long long wakeup_time;
SleepData *next;///<\internal Next thread in the list
long long wakeupTime;
};
/**
......
......@@ -37,6 +37,9 @@ using namespace std;
namespace miosix {
void IRQaddToSleepingList(SleepData *x);
bool IRQremoveFromSleepingList(SleepData *x);
//
// class Mutex
//
......@@ -361,27 +364,20 @@ unsigned int Mutex::PKunlockAllDepthLevels(PauseKernelLock& dLock)
// class ConditionVariable
//
ConditionVariable::ConditionVariable(): first(0), last(0) {}
ConditionVariable::ConditionVariable(): condList() {}
void ConditionVariable::wait(Mutex& m)
{
PauseKernelLock dLock;
Thread *t=Thread::IRQgetCurrentThread();
WaitToken listItem(t);
condList.push_back(&listItem); //Add entry to tail of list
WaitingData w;
w.p=Thread::getCurrentThread();
w.next=0;
//Add entry to tail of list
if(first==0)
{
first=last=&w;
} else {
last->next=&w;
last=&w;
}
//Unlock mutex and wait
{
FastInterruptDisableLock l;
w.p->flags.IRQsetCondWait(true);
t->flags.IRQsetCondWait(true);
}
unsigned int depth=m.PKunlockAllDepthLevels(dLock);
......@@ -395,27 +391,92 @@ void ConditionVariable::wait(Mutex& m)
void ConditionVariable::wait(FastMutex& m)
{
FastInterruptDisableLock dLock;
Thread *t=Thread::IRQgetCurrentThread();
WaitToken listItem(t);
condList.push_back(&listItem); //Add entry to tail of list
t->flags.IRQsetCondWait(true);
WaitingData w;
w.p=Thread::getCurrentThread();
w.next=0;
//Add entry to tail of list
if(first==0)
//Unlock mutex and wait
unsigned int depth=IRQdoMutexUnlockAllDepthLevels(m.get());
{
first=last=&w;
} else {
last->next=&w;
last=&w;
FastInterruptEnableLock eLock(dLock);
Thread::yield(); //Here the wait becomes effective
}
IRQdoMutexLockToDepth(m.get(),dLock,depth);
}
TimedWaitResult ConditionVariable::timedWait(Mutex& m, long long absTime)
{
//Disallow absolute sleeps with negative or too low values (< 1ms)
absTime=std::max(absTime,1LL);
PauseKernelLock dLock;
Thread *t=Thread::getCurrentThread();
WaitToken listItem(t);
condList.push_back(&listItem); //Add entry to tail of list
SleepData sleepData(t, absTime);
{
FastInterruptDisableLock l;
IRQaddToSleepingList(&sleepData); //Putting this thread on the sleeping list too
t->flags.IRQsetCondWait(true);
}
//Unlock mutex and wait
w.p->flags.IRQsetCondWait(true);
unsigned int depth=m.PKunlockAllDepthLevels(dLock);
{
RestartKernelLock eLock(dLock);
Thread::yield(); //Here the wait becomes effective
}
//Ensure that the thread is removed from both list, as it can be woken by
//either a signal/broadcast (that removes it from condList) or by
//IRQwakeThreads (that removes it from sleeping list).
bool removed = condList.removeFast(&listItem);
{
FastInterruptDisableLock l;
IRQremoveFromSleepingList(&sleepData);
}
m.PKlockToDepth(dLock,depth);
//If the thread was still in the cond variable list, it was woken up by a timeout
return removed ? TimedWaitResult::Timeout : TimedWaitResult::NoTimeout;
}
TimedWaitResult ConditionVariable::timedWait(FastMutex& m, long long absTime)
{
//Disallow absolute sleeps with negative or too low values (< 1ms)
absTime=std::max(absTime,1LL);
FastInterruptDisableLock dLock;
Thread *t=Thread::IRQgetCurrentThread();
WaitToken listItem(t);
condList.push_back(&listItem); //Putting this thread last on the list (lifo policy)
SleepData sleepData(t,absTime);
IRQaddToSleepingList(&sleepData); //Putting this thread on the sleeping list too
t->flags.IRQsetCondWait(true);
//Unlock mutex and wait
unsigned int depth=IRQdoMutexUnlockAllDepthLevels(m.get());
{
FastInterruptEnableLock eLock(dLock);
Thread::yield(); //Here the wait becomes effective
}
//Ensure that the thread is removed from both list, as it can be woken by
//either a signal/broadcast (that removes it from condList) or by
//IRQwakeThreads (that removes it from sleeping list).
bool removed=condList.removeFast(&listItem);
IRQremoveFromSleepingList(&sleepData);
IRQdoMutexLockToDepth(m.get(),dLock,depth);
//If the thread was still in the cond variable list, it was woken up by a timeout
return removed ? TimedWaitResult::Timeout : TimedWaitResult::NoTimeout;
}
void ConditionVariable::signal()
......@@ -423,17 +484,18 @@ void ConditionVariable::signal()
bool hppw=false;
{
//Using interruptDisableLock because we need to call IRQsetCondWait
//that can only be called with irq disabled, othrwise we would use
//that can only be called with irq disabled, otherwise we would use
//PauseKernelLock
FastInterruptDisableLock lock;
if(first==0) return;
//Wakeup
first->p->flags.IRQsetCondWait(false);
if(condList.empty()) return;
//Remove from list and wakeup
Thread *t=condList.front()->thread;
condList.pop_front();
t->flags.IRQsetCondWait(false);
t->flags.IRQsetSleep(false); //Needed due to timedwait
//Check for priority issues
if(first->p->IRQgetPriority() >
Thread::IRQgetCurrentThread()->IRQgetPriority()) hppw=true;
//Remove from list
first=first->next;
if(t->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority())
hppw=true;
}
//If the woken thread has higher priority than our priority, yield
if(hppw) Thread::yield();
......@@ -447,15 +509,16 @@ void ConditionVariable::broadcast()
//that can only be called with irq disabled, othrwise we would use
//PauseKernelLock
FastInterruptDisableLock lock;
while(first!=0)
while(!condList.empty())
{
//Wakeup
first->p->flags.IRQsetCondWait(false);
//Remove from list and wakeup
Thread *t=condList.front()->thread;
condList.pop_front();
t->flags.IRQsetCondWait(false);
t->flags.IRQsetSleep(false); //Needed due to timedwait
//Check for priority issues
if(first->p->IRQgetPriority() >
Thread::IRQgetCurrentThread()->IRQgetPriority()) hppw=true;
//Remove from list
first=first->next;
if(t->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority())
hppw=true;
}
}
//If at least one of the woken thread has higher priority than our priority,
......
......@@ -386,6 +386,27 @@ private:
T& mutex;///< Reference to locked mutex
};
/**
* \internal
* This class is used to make a list of threads that are waiting on a condition
* variable. It is used by the kernel, and should not be used by end users.
*/
class WaitToken : public IntrusiveListItem
{
public:
WaitToken(Thread *thread) : thread(thread) {}
Thread *thread; ///<\internal Thread that is waiting
};
/**
* Possible return values of timedWait
*/
enum class TimedWaitResult
{
NoTimeout,
Timeout
};
/**
* A condition variable class for thread synchronization, available from
* Miosix 1.53.<br>
......@@ -416,6 +437,42 @@ public:
wait(l.get());
}
/**
* Unlock the mutex and wait until woken up or timeout occurs.
* If more threads call wait() they must do so specifying the same mutex,
* otherwise the behaviour is undefined.
* \param l A Lock instance that locked a Mutex
* \param absTime absolute timeout time in milliseconds
* \return whether the return was due to a timout or wakeup
*/
template<typename T>
TimedWaitResult timedWait(Lock<T>& l, long long absTime)
{
return timedWait(l.get(),absTime);
}
/**
* Unlock the mutex and wait until woken up or the given number of
* milliseconds have passed.
* If more threads call wait() they must do so specifying the same mutex,
* otherwise the behaviour is undefined.
* \param l A Lock instance that locked a Mutex
* \param ms the number of milliseconds to wait
* \return whether the return was due to a timout or wakeup
*/
template<typename T>
TimedWaitResult timedWaitFor(Lock<T>& l, long long ms)
{
if (ms <= 0)
return TimedWaitResult::Timeout;
long long tickDelay = ((ms * TICK_FREQ) / 1000);
// If tick resolution is too low, wait one tick
tickDelay = std::max(tickDelay, 1LL);
return timedWait(l.get(),getTick()+tickDelay);
}
/**
* Unlock the Mutex and wait.
* If more threads call wait() they must do so specifying the same mutex,
......@@ -432,9 +489,29 @@ public:
*/
void wait(FastMutex& m);
/**
* Unlock the Mutex and wait until woken up or timeout occurs.
* If more threads call wait() they must do so specifying the same mutex,
* otherwise the behaviour is undefined.
* \param m a locked Mutex
* \param absTime absolute timeout time in milliseconds
* \return whether the return was due to a timout or wakeup
*/
TimedWaitResult timedWait(Mutex& m, long long absTime);
/**
* Unlock the FastMutex and wait until woken up or timeout occurs.
* If more threads call wait() they must do so specifying the same mutex,
* otherwise the behaviour is undefined.
* \param m a locked Mutex
* \param absTime absolute timeout time in milliseconds
* \return whether the return was due to a timout or wakeup
*/
TimedWaitResult timedWait(FastMutex& m, long long absTime);
/**
* Wakeup one waiting thread.
* Currently implemented policy is fifo.
* Currently implemented policy is fifo.C
*/
void signal();
......@@ -448,19 +525,8 @@ private:
ConditionVariable(const ConditionVariable& );
ConditionVariable& operator= (const ConditionVariable& );
/**
* \internal
* \struct WaitingData
* This struct is used to make a list of waiting threads.
*/
struct WaitingData
{
Thread *p;///<\internal Thread that is waiting
WaitingData *next;///<\internal Next thread in the list
};
WaitingData *first;///<Pointer to first element of waiting fifo
WaitingData *last;///<Pointer to last element of waiting fifo
// The list of threads waiting on this condition variable
IntrusiveList<WaitToken> condList;
};
/**
......