diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6f65c48 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +./build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..69b31d2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.26) + +add_subdirectory("protothreads") + +# cd ./build && make test +enable_testing() + +set( LIB_NAME pt_os_lib ) + +file( GLOB SRCS + "*.cpp") + +add_library(${LIB_NAME} STATIC ${SRCS}) + +include_directories(${PROJECT_SOURCE_DIR}) + +target_include_directories(${LIB_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +target_link_libraries(${LIB_NAME} protothreads) + +set( OS_LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}" PARENT_SCOPE ) + +add_subdirectory( "./tests" ) diff --git a/protothreads/CMakeLists.txt b/protothreads/CMakeLists.txt new file mode 100644 index 0000000..2ec6a41 --- /dev/null +++ b/protothreads/CMakeLists.txt @@ -0,0 +1,9 @@ +include(FetchContent) + +fetchcontent_declare( + pt + GIT_REPOSITORY https://github.com/ZhuLingQing/protothreads.git + GIT_TAG main +) + +FetchContent_MakeAvailable(pt) diff --git a/pt-os.cpp b/pt-os.cpp new file mode 100644 index 0000000..b204fd8 --- /dev/null +++ b/pt-os.cpp @@ -0,0 +1,206 @@ +#include "pt-os.h" +#include + +static inline size_t min_(size_t a, size_t b) { return a < b ? a : b; } + +#define OsTaskId2Idx(id) (int)(id - kPt_) + +class TaskControlBlock +{ + public: + TaskControlBlock() { Reset(); } + + void Reset() + { + #if defined(osMaxNameLen) && osMaxNameLen > 0 + memset(kName_, 0, sizeof(kName_)); + #endif + kTask_ = nullptr; + kPtStatus_ = OsTaskNotExist; + } + + void Register(const char *name, TaskFunction task, void *param) + { + #if defined(osMaxNameLen) && osMaxNameLen > 0 + memcpy(kName_, name, min_(sizeof(kName_) - 1, strlen(name))); + #endif + kTask_ = task; + kParam_ = param; + kEntered_ = false; + kPtStatus_ = OsTaskWaiting; + } + + bool Yield(OsTaskId taskId) + { + if (kPtStatus_ == OsTaskExit) return false; + if (!kEntered_ && kPtStatus_ != OsTaskSuspend) + { + kEntered_ = true; + kPtStatus_ = (OsTaskStatus)kTask_(taskId, kParam_); + kEntered_ = false; + } + return (kPtStatus_ == OsTaskExit)?true:false; + } + + OsTaskStatus Status() { return kPtStatus_; } + int Suspend() + { + if (kPtStatus_ == OsTaskNotExist) return INVALID_TASK_ID; + if (kPtStatus_ == OsTaskExit) return INVALID_TASK_STATUS; + kPtStatus_ = OsTaskSuspend; + return TASK_OP_SUCCESS; + } + int Resume() + { + if (kPtStatus_ == OsTaskNotExist) return INVALID_TASK_ID; + if (kPtStatus_ == OsTaskExit) return INVALID_TASK_STATUS; + kPtStatus_ = OsTaskWaiting; + return TASK_OP_SUCCESS; + } + + int Delete() + { + if (kPtStatus_ == OsTaskNotExist) return INVALID_TASK_ID; + if (kPtStatus_ == OsTaskExit) return INVALID_TASK_STATUS; + kPtStatus_ = OsTaskExit; + return TASK_OP_SUCCESS; + } + + const char *Name() + { + #if defined(osMaxNameLen) && osMaxNameLen > 0 + return kName_; + #else + return nullptr; + #endif + } + + protected: + TaskFunction kTask_; + #if defined(osMaxNameLen) && osMaxNameLen > 0 + char kName_[osMaxNameLen]; + #endif + void *kParam_; + bool kEntered_; + OsTaskStatus kPtStatus_; +}; + +class PtOs +{ + public: + PtOs() { Reset(); } + + OsTaskId RegisterTask(const char *name, TaskFunction task, void *param) + { + if (kIdx_ >= osMaxThreads) return OsInvlidTaskId; + kTaskCb_[kIdx_].Register(name, task, param); + kIdx_++; + kNumLiveTasks_++; + PT_INIT(&kPt_[kIdx_ - 1]); + return &kPt_[kIdx_ - 1]; + } + + void Yield() + { + for (int i = 0; i < kIdx_; i++) + { + if (true == kTaskCb_[i].Yield(kPt_ + i)) kNumLiveTasks_--; + } + } + + const char *Name(OsTaskId taskId) + { + OS_ASSERT((taskId >= &kPt_[0] && taskId < &kPt_[osMaxThreads])); + return kTaskCb_[OsTaskId2Idx(taskId)].Name(); + } + + int Delete(OsTaskId taskId) + { + OS_ASSERT((taskId >= &kPt_[0] && taskId < &kPt_[osMaxThreads])); + auto rc = kTaskCb_[OsTaskId2Idx(taskId)].Delete(); + if (rc == TASK_OP_SUCCESS) kNumLiveTasks_--; + return rc; + } + + int Suspend(OsTaskId taskId) + { + OS_ASSERT((taskId >= &kPt_[0] && taskId < &kPt_[osMaxThreads])); + return kTaskCb_[OsTaskId2Idx(taskId)].Suspend(); + } + + int Resume(OsTaskId taskId) + { + OS_ASSERT((taskId >= &kPt_[0] && taskId < &kPt_[osMaxThreads])); + return kTaskCb_[OsTaskId2Idx(taskId)].Resume(); + } + + OsTaskStatus Status(OsTaskId taskId) + { + OS_ASSERT((taskId >= &kPt_[0] && taskId < &kPt_[osMaxThreads])); + return kTaskCb_[OsTaskId2Idx(taskId)].Status(); + } + + int NumLiveTasks() { return kNumLiveTasks_; } + + void Reset() + { + for (int i = 0; i < osMaxThreads; i++) + kTaskCb_[i].Reset(); + kIdx_ = 0; + kNumLiveTasks_ = 0; + } + + protected: + TaskControlBlock kTaskCb_[osMaxThreads]; + struct pt kPt_[osMaxThreads]; + int kIdx_; + int kNumLiveTasks_; +}; + +static PtOs sOsControlBlock_; + +OsTaskId RegisterTask(const char *name, TaskFunction task, void *param) { return sOsControlBlock_.RegisterTask(name, task, param); } + +// Cause Registered Service get scheduled +void TaskYield(void) +{ + sOsControlBlock_.Yield(); +} + +const char *TaskName(OsTaskId taskId) +{ + return sOsControlBlock_.Name(taskId); +} + +int TaskDelete(OsTaskId taskId) +{ + return sOsControlBlock_.Delete(taskId); +} + +int TaskSuspend(OsTaskId taskId) +{ + return sOsControlBlock_.Suspend(taskId); +} + +int TaskResume(OsTaskId taskId) +{ + return sOsControlBlock_.Resume(taskId); +} + +OsTaskStatus TaskStatus(OsTaskId taskId) +{ + return sOsControlBlock_.Status(taskId); +} + +void OsInit(void) +{ + //make sure + OS_ASSERT(0 == sOsControlBlock_.NumLiveTasks()); + sOsControlBlock_.Reset(); +} + +void OsStart(void) +{ + while (sOsControlBlock_.NumLiveTasks() > 0) + sOsControlBlock_.Yield(); +} \ No newline at end of file diff --git a/pt-os.h b/pt-os.h new file mode 100644 index 0000000..f09f90c --- /dev/null +++ b/pt-os.h @@ -0,0 +1,63 @@ +#ifndef _PT_OS_H_ +#define _PT_OS_H_ + +#include "pt-osConfig.h" +#include + +#define TASK_BEGIN(id) PT_BEGIN(id) +#define TASK_YIELD(id) PT_YIELD(id) +#define TASK_WAIT_UNTIL(id,cond) PT_WAIT_UNTIL(id, cond) +#define TASK_SUSPEND(id) do { if (taskId == id) PT_EXIT(id); else TaskSuspend(id); } while(0) +#define TASK_DELETE(id) do { if (taskId == id) PT_END(id); else TaskDelete(id); } while(0) +#define TASK_EXIT(id) return PT_ENDED +#define TASK_END(id) PT_END(id) +#define TASK_DECLARE(thread_declare) PT_THREAD(thread_declare) + +#if __cplusplus +extern "C" +{ +#endif + typedef struct pt * OsTaskId; + #define OsInvlidTaskId ((OsTaskId)INVALID_TASK_ID) + + #define TASK_OP_SUCCESS (0) + #define INVALID_TASK_ID (-1) + #define INVALID_TASK_STATUS (-2) + + typedef enum + { + OsTaskWaiting = PT_WAITING, + OsTaskYield = PT_YIELDED, + OsTaskSuspend = PT_EXITED, + OsTaskExit = PT_ENDED, + OsTaskNotExist = -1, + }OsTaskStatus; + + typedef char (*TaskFunction)(OsTaskId, void *); + + OsTaskId RegisterTask(const char *name, TaskFunction task, void *param); + + // Cause Registered Service get scheduled + void TaskYield(void); + + const char *TaskName(OsTaskId taskId); + + // Call TASK_DELETE, it will identify this task or not. + int TaskDelete(OsTaskId taskId); + + // Call TASK_SUSPEND, it will identify this task or not. + int TaskSuspend(OsTaskId taskId); + + int TaskResume(OsTaskId taskId); + + OsTaskStatus TaskStatus(OsTaskId taskId); + + void OsInit(void); + + void OsStart(void); + +#if __cplusplus +} +#endif + +#endif diff --git a/pt-osConfig.h b/pt-osConfig.h new file mode 100644 index 0000000..19cbe4b --- /dev/null +++ b/pt-osConfig.h @@ -0,0 +1,11 @@ +#ifndef _OS_CONFIG_H_ +#define _OS_CONFIG_H_ + + +#define osMaxNameLen (16) +#define osMaxThreads (64) + +#include +#define OS_ASSERT assert + +#endif diff --git a/tests/1p1c.cpp b/tests/1p1c.cpp new file mode 100644 index 0000000..c09f9e1 --- /dev/null +++ b/tests/1p1c.cpp @@ -0,0 +1,90 @@ +#include "tz_ringbuf.hpp" +#include "pt-os.h" +#include +#include +#include "test.h" + +static constexpr int kThreadNum = 1; + +static tzhu::ringbuf rb_; +static volatile bool testFailed; + +static OsTaskId kProdId_[kThreadNum]; +static OsTaskId kConsId_[kThreadNum]; + +static tzhu::ringbuf kProdData_[kThreadNum]; +static tzhu::ringbuf kConsData_[kThreadNum]; + +static inline void TestInit() +{ + testFailed = false; +} + +static inline int TestCheck() +{ + char name[16]; + for(int i = 0; i < kThreadNum; i++) + { + sprintf(name, "RPOD%d", i); + DumpData(name, kProdData_[i]); + } + for(int i = 0; i < kThreadNum; i++) + { + sprintf(name, "CONS%d", i); + DumpData(name, kConsData_[i]); + } + + if (testFailed) printf("Test failed.\n"); + return testFailed ? 1 : 0; +} + +static TASK_DECLARE(prodTask(OsTaskId taskId, void *param)) +{ + static int i = 1; + TASK_BEGIN(taskId); + printf("%s Begin\n", TaskName(taskId)); + while (1) + { + TASK_WAIT_UNTIL(taskId, !rb_.full()); + rb_.push(i); + kProdData_[(long)param].push(i); + i++; + if (i > testCount) TASK_EXIT(taskId); + } + TASK_END(taskId); +} + +static TASK_DECLARE(consTask(OsTaskId taskId, void *param)) +{ + static int i = 1; + int v; + TASK_BEGIN(taskId); + printf("%s Begin\n", TaskName(taskId)); + while (1) + { + TASK_WAIT_UNTIL(taskId, !rb_.empty()); + rb_.pop(v); + kConsData_[(long)param].push(v); + if (v != i) + { + testFailed = true; + TASK_EXIT(taskId); + } + i++; + if (i > testCount) TASK_EXIT(taskId); + } + TASK_END(taskId); +} + +int Test1p1c() +{ + printf("======== %s ========\n", __FUNCTION__); + TestInit(); + + OsInit(); + kProdId_[0] = RegisterTask("prodTask", prodTask, (void *)0); + kConsId_[0] = RegisterTask("consTask", consTask, (void *)0); + OsStart(); + + return TestCheck(); +} \ No newline at end of file diff --git a/tests/1p2c.cpp b/tests/1p2c.cpp new file mode 100644 index 0000000..7f1e3eb --- /dev/null +++ b/tests/1p2c.cpp @@ -0,0 +1,95 @@ +#include "tz_ringbuf.hpp" +#include "pt-os.h" +#include +#include +#include "test.h" + +static constexpr int kThreadNum = 2; + +static tzhu::ringbuf rb_; +static volatile bool testFailed; + +static OsTaskId kProdId_[kThreadNum]; +static OsTaskId kConsId_[kThreadNum]; + +static tzhu::ringbuf kProdData_[kThreadNum]; +static tzhu::ringbuf kConsData_[kThreadNum]; + +static inline void TestInit() +{ + testFailed = false; +} +static inline int TestCheck() +{ + char name[16]; + for(int i = 0; i < kThreadNum; i++) + { + sprintf(name, "RPOD%d", i); + DumpData(name, kProdData_[i]); + } + for(int i = 0; i < kThreadNum; i++) + { + sprintf(name, "CONS%d", i); + DumpData(name, kConsData_[i]); + } + if (testFailed) printf("Test failed.\n"); + return testFailed ? 1 : 0; +} + +static TASK_DECLARE(prodTask(OsTaskId taskId, void *param)) +{ + static int i = 1; + TASK_BEGIN(taskId); + printf("%s Begin\n", TaskName(taskId)); + while (1) + { + TASK_WAIT_UNTIL(taskId, !rb_.full()); + rb_.push(i); + kProdData_[(long)param].push(i); + i++; + if (i > testCount) TASK_EXIT(taskId); + } + TASK_END(taskId); +} + +static TASK_DECLARE(consTask(OsTaskId taskId, void *param)) +{ + static int i = 1; + int v; + TASK_BEGIN(taskId); + printf("%s Begin\n", TaskName(taskId)); + while (1) + { + TASK_WAIT_UNTIL(taskId, !rb_.empty()); + rb_.pop(v); + kConsData_[(long)param].push(v); + if (v != i) + { + testFailed = true; + TASK_EXIT(taskId); + } + i++; + if (i > testCount) + { + printf("%s delete %s\n", TaskName(taskId), TaskName(kConsId_[(long)param ^ 1])); + TaskDelete(kConsId_[(long)param ^ 1]); + TASK_EXIT(taskId); + } + TASK_YIELD(taskId); + } + TASK_END(taskId); +} + +int Test1p2c() +{ + printf("======== %s ========\n", __FUNCTION__); + TestInit(); + + OsInit(); + kProdId_[0] = RegisterTask("prodTask", prodTask, (void *)0); + kConsId_[0] = RegisterTask("consTask1", consTask, (void *)0); + kConsId_[1] = RegisterTask("consTask2", consTask, (void *)1); + OsStart(); + + return TestCheck(); +} \ No newline at end of file diff --git a/tests/2p1c.cpp b/tests/2p1c.cpp new file mode 100644 index 0000000..7ae808a --- /dev/null +++ b/tests/2p1c.cpp @@ -0,0 +1,99 @@ +#include "tz_ringbuf.hpp" +#include "pt-os.h" +#include +#include +#include "test.h" + +static constexpr int kThreadNum = 2; + +static tzhu::ringbuf rb_; +static volatile bool testFailed; + +static OsTaskId kProdId_[kThreadNum]; +static OsTaskId kConsId_[kThreadNum]; + +static tzhu::ringbuf kProdData_[kThreadNum]; +static tzhu::ringbuf kConsData_[kThreadNum]; + +static inline void TestInit() +{ + testFailed = false; +} +static inline int TestCheck() +{ + char name[16]; + for(int i = 0; i < kThreadNum; i++) + { + sprintf(name, "RPOD%d", i); + DumpData(name, kProdData_[i]); + } + for(int i = 0; i < kThreadNum; i++) + { + sprintf(name, "CONS%d", i); + DumpData(name, kConsData_[i]); + } + if (testFailed) printf("Test failed.\n"); + return testFailed ? 1 : 0; +} + +static TASK_DECLARE(prodTask(OsTaskId taskId, void *param)) +{ + static int i = 1; + TASK_BEGIN(taskId); + printf("%s Begin\n", TaskName(taskId)); + while (1) + { + TASK_WAIT_UNTIL(taskId, !rb_.full()); + if (i > testCount) + { + printf("%s delete %s.\n", TaskName(taskId), TaskName(kProdId_[0])); + TaskDelete(kProdId_[0]); + TASK_EXIT(taskId); + } + rb_.push(i); + kProdData_[(long)param].push(i); + i++; + if (taskId == kProdId_[0] && i > testCount/2) + { + printf("%s suspend.\n", TaskName(taskId)); + TASK_SUSPEND(taskId); + } + } + TASK_END(taskId); +} + +static TASK_DECLARE(consTask(OsTaskId taskId, void *param)) +{ + static int i = 1; + int v; + TASK_BEGIN(taskId); + printf("%s Begin\n", TaskName(taskId)); + while (1) + { + TASK_WAIT_UNTIL(taskId, !rb_.empty()); + rb_.pop(v); + kConsData_[(long)param].push(v); + if (v != i) + { + testFailed = true; + TASK_EXIT(taskId); + } + i++; + if (i > testCount) TASK_EXIT(taskId); + } + TASK_END(taskId); +} + +int Test2p1c() +{ + printf("======== %s ========\n", __FUNCTION__); + TestInit(); + + OsInit(); + kProdId_[0] = RegisterTask("prodTask1", prodTask, (void *)0); + kProdId_[1] = RegisterTask("prodTask2", prodTask, (void *)1); + kConsId_[0] = RegisterTask("consTask", consTask, (void *)0); + OsStart(); + + return TestCheck(); +} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..3b611e4 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,15 @@ +set( TEST test_pt_os) + +project(${TEST} CXX) + +file( GLOB TEST_SRC "*.cpp") +add_executable( ${TEST} ${TEST_SRC}) + +target_link_libraries(${TEST} + pt_os_lib + protothreads +) + +target_include_directories(${TEST} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} ) + +add_test( NAME ${TEST} COMMAND ./${TEST}) diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..787607c --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,33 @@ +#include "tz_ringbuf.hpp" +#include "pt-os.h" +#include +#include +#include "test.h" + +void DumpData(const char *name, tzhu::ringbuf &kData) +{ + int v; + printf("%s: %ld\n",name,kData.size()); + for (int j = 0; kData.size() > 1; j++) + { + kData.pop(v); + printf("%d, ", v); + } + if( true == kData.pop(v)) + printf("%d\n", v); +} + +int Test1p1c(); +int Test2p1c(); +int Test1p2c(); +int TestResume(); + +int main() +{ + int rc = 0; + rc += Test1p1c(); + rc += Test1p2c(); + rc += Test2p1c(); + rc += TestResume(); + return rc; +} \ No newline at end of file diff --git a/tests/resume.cpp b/tests/resume.cpp new file mode 100644 index 0000000..c1dcdd5 --- /dev/null +++ b/tests/resume.cpp @@ -0,0 +1,109 @@ +#include "tz_ringbuf.hpp" +#include "pt-os.h" +#include +#include +#include "test.h" + +static constexpr int kThreadNum = 2; + +static tzhu::ringbuf rb_; +static volatile bool testFailed; + +static OsTaskId kProdId_[kThreadNum]; +static OsTaskId kConsId_[kThreadNum]; + +static tzhu::ringbuf kProdData_[kThreadNum]; +static tzhu::ringbuf kConsData_[kThreadNum]; + +static inline void TestInit() +{ + testFailed = false; +} +static inline int TestCheck() +{ + char name[16]; + for(int i = 0; i < kThreadNum; i++) + { + sprintf(name, "RPOD%d", i); + DumpData(name, kProdData_[i]); + } + for(int i = 0; i < kThreadNum; i++) + { + sprintf(name, "CONS%d", i); + DumpData(name, kConsData_[i]); + } + if (testFailed) printf("Test failed.\n"); + return testFailed ? 1 : 0; +} + +static TASK_DECLARE(prodTask(OsTaskId taskId, void *param)) +{ + static int i = 1; + TASK_BEGIN(taskId); + printf("%s Begin\n", TaskName(taskId)); + while (1) + { + TASK_WAIT_UNTIL(taskId, !rb_.full()); + rb_.push(i); + kProdData_[(long)param].push(i); + i++; + } + TASK_END(taskId); +} + +static TASK_DECLARE(consTask(OsTaskId taskId, void *param)) +{ + static int i = 1; + int v; + TASK_BEGIN(taskId); + printf("%s Begin\n", TaskName(taskId)); + while (1) + { + TASK_WAIT_UNTIL(taskId, !rb_.empty()); + rb_.pop(v); + kConsData_[(long)param].push(v); + if (v != i) + { + testFailed = true; + TASK_EXIT(taskId); + } + if (i == testCount/4*1) + { + printf("%s suspend %s\n", TaskName(taskId), TaskName(kProdId_[0])); + TaskSuspend(kProdId_[0]); + } + else if (i == testCount/4*2) + { + printf("%s resume %s\n", TaskName(taskId), TaskName(kProdId_[0])); + TaskResume(kProdId_[0]); + } + else if (i == testCount/4*3) + { + printf("%s suspend %s\n", TaskName(taskId), TaskName(kProdId_[1])); + TaskSuspend(kProdId_[1]); + } + i++; + if (i > testCount) + { + printf("%s Kill %s, %s\n", TaskName(taskId), TaskName(kProdId_[0]), TaskName(kProdId_[1])); + TaskDelete(kProdId_[0]); + TaskDelete(kProdId_[1]); + TASK_EXIT(taskId); + }; + } + TASK_END(taskId); +} + +int TestResume() +{ + printf("======== %s ========\n", __FUNCTION__); + TestInit(); + + OsInit(); + kProdId_[0] = RegisterTask("prodTask1", prodTask, (void *)0); + kProdId_[1] = RegisterTask("prodTask2", prodTask, (void *)1); + kConsId_[0] = RegisterTask("consTask", consTask, (void *)0); + OsStart(); + + return TestCheck(); +} \ No newline at end of file diff --git a/tests/test.h b/tests/test.h new file mode 100644 index 0000000..79092f0 --- /dev/null +++ b/tests/test.h @@ -0,0 +1,8 @@ +#ifndef _TEST_H_ +#define _TEST_H_ + +#define testCount 100 + +void DumpData(const char *name, tzhu::ringbuf &kData); + +#endif // _TEST_H_ \ No newline at end of file diff --git a/tests/tz_ringbuf.hpp b/tests/tz_ringbuf.hpp new file mode 100644 index 0000000..6d8c5f7 --- /dev/null +++ b/tests/tz_ringbuf.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +namespace tzhu +{ + +template +class ringbuf +{ + public: + ringbuf() + { + in_ = 0; + out_ = 0; + capacity_ = sizeof(buf) / sizeof(buf[0]); + } + bool push(T &v) + { + if (true == full()) return false; + buf[in_] = v; + if (++in_ >= capacity_) in_ = 0; + return true; + } + bool pop(T &v) + { + if (true == empty()) return false; + v = buf[out_]; + if (++out_ >= capacity_) out_ = 0; + return true; + } + size_t size() + { + if (in_ >= out_) + return in_ - out_; + else + return (capacity_ + in_ - out_); + } + bool full() { return (size() >= capacity_ - 1) ? true : false; } + bool empty() { return (size() == 0) ? true : false; } + + protected: + T buf[szBuf + 1]; + volatile uint32_t in_; + volatile uint32_t out_; + uint32_t capacity_; +}; +}; // namespace tzhu