背景
原生的protothreads是一个具备的任务调度的系统,但当和RTOS的进行对标时,就会显示出明显差距,例如无sleep功能,消息同步手段匮乏,无超时控制等。为了弥补protothreads的这些差距,拓展了protothreads 并命名为PTOS的(protothreads operating system)
PTOS 综述
PTOS是一个非抢式无堆栈操作系统,所有的线程具备相同优先级,内置(event)事件旗标,(sem)信号量、(queue)消息队列、(box)广播式邮箱等消息同步机制。内置timer server线程,支持轻量级软件定时器仅需8字节,创建一个新线程开销仅需32字节。集成线程监控和管理API,低功耗组件,无任何CPU专属代码,移植仅需实现system tick接口即可。以下是PTOS线程结构图1:
- Main 指的是裸机的Main,在PTOS中充当root thread
- 一级节点,表示通过pt_thread_register 接口注册root字节下的,绿色的表示可运行态的。
- 二级节点,表示通过PT_SPAWN_EX 接口挂载在某一级节点下的子线程, 夫线程和由其衍生出来的子线程(孙子线程)有且仅有一个处于可运行态
- 三级节点、及其以下,和其父节点关系,等同二级节点和其夫节点的关系
其数据结构如下:
typedef char (*pt_thread_t)(struct pt *);
typedef struct
{
uint32_t status:4;
uint32_t timeout_tick:28;
uint32_t start_tick;
} pt_timer_t;
typedef struct
{
uint32_t begin : 1;
uint32_t idle : 1;
uint32_t time_out : 1;
uint32_t super : 1;
uint32_t yield_cnt : 8;
uint32_t yield_run_cnt : 8;
uint32_t monitor_enbale : 1;
uint32_t monitor_view : 1;
uint32_t no_use : 10;
} pt_thread_status;
typedef struct
{
slist_t list;
#if PT_MONITOR_FUNC_ENABLE
const uint8_t *pt_monitor;
#endif
struct pt pt;
slist_t sub_head;
pt_thread_t pt_thread;
pt_thread_status status;
pt_timer_t timer;
} pt_thread_info;
由于PTOS属于非抢占式操作系统,因此每一个线程设计时必须具备主动释放cpu的使用权。(注: PTOS核心是原理上时轮询式状态机系统,轮询式状态机系统详情阅读《序章:Protothreads的原生特性》
PTOS 的组成文件
PTOS 由原生的protothreads的三个文件:lc-switch.h、lc.h、pt.h和PTOS本身的pt_box_extern.c、pt_box_extern.h、pt_queue_extern.c、pt_queue_extern.h、pt_sem_extern.c、pt_sem_extern.h、pt_event_extern.c、pt_event_extern.h、pt_timers.c、pt_timers.h、pt_threads.c、pt_threads.h、pt_monitor.c、pt_monitor.h文件组成。
文件 | 功能 |
---|---|
lc-switch.h、lc.h、pt.h | protothreads原生文件,提供任务调度功能 |
pt_timers.* | 提供时钟管理功能,内嵌soft_timers_thread |
pt_threads.* | 提供时提供线程管理功能,例如线程注册,运行等 |
pt_queue* | 提供线程间使用消息队列同步功能 |
pt_sem* | 提供线程间使用信号量同步功能 |
pt_event* | 提供线程间使用事件旗标同步功能 |
pt_box* | 提供线程间使用广播式邮箱同步功能 |
pt_monitor.* | debug时提供确认线程运行状态功能 |
PTOS 的外部接口简介
PTOS提供了丰富的接口,用以下表格进行简单介绍。在本栏目的其他文件再做详细的介绍
系统接口
pt_threads和pt_timers.h 属于PTOS的最核心的部分,具体原理会在《第二章:PTOS的时钟管理》和《第三章:PTOS的线程管理》描述。
- pt_threads.h 主要提供以下, 部分可参考作用请参考《序章:Protothreads的原生特性》
线程接口名称 | 含义 |
---|---|
void pt_threads_init(void); | PTOS初始化函数,main函数初始化调用 |
int pt_threads_run(void) | PTOS运行函数,main函数轮询 |
uint32_t pt_threads_get_block_time_ms(void); | 获取图1的root线程的最小阻塞时间 |
uint32_t pt_thread_get_block_time_ms(pt_thread_info *pti); | 获取某一节点最小阻塞时间 |
void pt_thread_register(pt_thread_info *pti, pt_thread_t ptt, const uint8_t *pt_monitor); | 注册线程 |
void pt_thread_unregister(pt_thread_info *pti) ; | 注销线程 |
PT_BEGIN_EX(pt); | 继承并拓展PT_BEGIN, |
PT_END_EX(pt) | 继承并拓展PT_END |
PT_EXIT_EX(pt) | 继承并拓展PT_END |
PT_WAIT_UNTIL_EX(pt) | 继承并拓展PT_WAIT_UNTIL_EX |
PT_SPAWN_EX(pt) | 继承并拓展PT_SPAWN_EX |
PT_SLEEP_MS(pt, ms) | 线程休眠函数,单位毫秒 |
PT_IS_TIMEOUT(pt) | 线程唤醒原因是否是超时 |
- pt_timers.h 主要提供以下功能。
定时器接口名称 | 含义 |
---|---|
int pt_timers_server_init(void); | 软件定时器服务初始化 |
int pt_timer_register(timer_extern_t *t, timers_callback_t cb); | 注册软件定时回调函数 |
int pt_timer_start(pt_timer_t *t, uint32_t ms); | 开启定时器 |
int pt_timer_stop(pt_timer_t *t); | 关闭定时器 |
uint32_t timers_server_get_block_time_ms(void); | 软件定时器服务挂起的最小时间 |
同步接口
event、sem、queue都属于PTOS的同步接口,具体原理会在《第四章:PTOS的同步机制 》描述,其中box比较特殊,具体原理将放到《第五章:PTOS的广播式邮箱》描述
- pt_sem_extern.h 主要提供以下接口
信号量接口名称 | 含义 |
---|---|
void pt_sem_init(pt_sem_t *sem, uint8_t bin); | 初始化信号量 |
void pt_sem_post(pt_sem_t *sem); | 发送信号量 |
PT_SEM_WAIT_BLOCK(pt, s, ms); | 等待信号量,单位毫秒s |
PT_SEM_WAIT_YEILD(pt, s, cnt); | 等待信号量,单位轮询次数 |
- pt_event_extern.h 主要提供以下接口
事件旗标接口名称 | 含义 |
---|---|
void pt_event_init(pt_event_t *event); | 初始化事件旗标量 |
void pt_event_set_bit(pt_event_t *event, uint32_t bit_mark); | 事件旗标发送信号 |
int pt_event_clear_bit(pt_event_t *event, uint32_t bit_mark); | 事件旗标清空信号 |
PT_EVNET_WAIT_BLOCK(pt, e, ms); | 等待事件变真,超时单位毫秒 |
PT_EVNET_WAIT_YEILD(pt, e, cnt); | 等待事件变真,超时单位轮询次数 |
- pt_queue_extern.h 主要提供以下接口
事件旗标接口名称 | 含义 |
---|---|
void pt_queue_init(pt_queue_t *queue, uint8_t *addr, uint16_t memery_size, uint16_t item_size); | 初始化消息队列 |
int pt_queue_write(pt_queue_t *queue, void *item); | 写消息 |
int pt_queue_read(pt_queue_t *queue, void *item); | 读消息 |
PT_QUEUE_WRITE_BLOCK(pt, q, item, ms) | 等待写入消息成功,超时单位毫秒 |
PT_QUEUE_READ_BLOCK(pt, q, item, ms) | 等待读取消息成功,超时单位毫秒 |
PT_QUEUE_WRITE_YEILD(pt, q, item, cnt) | 等待写入消息成功,超时单位轮询次数 |
PT_QUEUE_READ_BLOCK(pt, q, item, cnt) | 等待读取消息成功,超时单位轮询次数 |
- pt_box_extern.h 主要提供以下接口
事件旗标接口名称 | 含义 |
---|---|
void pt_box_init(pt_box_t *box); | 初始化广播邮箱 |
void pt_box_get_msg(pt_box_t *box, void **msg) | 获取广播邮箱里面的消息 |
PT_BOX_SEND_BLOCK(pt, b, target, msg, ms) | 等待发送消息成功,超时单位毫秒 |
PT_BOX_SEND_YIELD(pt, b, target, msg, cnt); | 等待发送消息成功,超时单位轮询次数 |
PT_BOX_ACCEPT_BLOCK(pt, b, cb, para, ms); | 等待接受消息成功,超时单位毫秒 |
PT_BOX_ACCEPT_YEILD(pt, b, cb, para, cnt); | 等待接受消息成功,超时单位轮询次数 |
PTOS 优缺点分析
由于PTOS的调度原理使用了Protothreads,所以PTOS也继承了Protothreads的优点和缺点。以下对其优点和缺点进行分析
优点一:OS移植方便
PTOS 唯一使用硬件相关的只有System Tick,我们只需要外部实现System Tick的就可以成功移植PTOS!!!
优点二:OS对MCU资源要求低
PTOS的移植(不开monitor,什么都不做) 需要ram 44(4+4+4+4+28)字节, rom 1156 (80+108+544+424)。后面新增加一个thread,仅需要开销28字节,若新增软件定时器需要8字节。
缺点一:无堆栈
PTOS是无堆栈的操作系统,所有线程公用一个堆(PTOS使用堆容易出错,线程内部不建议使用),且线程无栈。无堆低资源的MCU影响不大,但线程无栈,意味着局部变量在线程调度的时候会释放,局部变量的使用变得谨慎。我们只需要在使用的时候遵循以下原则,也可以在代码编程时进行规避
- 局部变量在程序调度之后不要使用,如果需要使用调度之前的变量,在函数内使用static变量
- 引发任务调度的函数一般都是大写的宏
- 线程内部不要使用malloc和free,如果确实需要使用,可参考以下方式
{
static uint8_t *prt = NULL;
static uint8_t prt_size = 0;
if(prt == NULL && prt_size != size)
{
prt = malloc(size);
prt_size = size;
}
....
if(prt)
{
free(prt);
prt= NULL;
prt_size = 0;
}
}
缺点二:非实时
PTOS不是实时系统,所有线程同属一个有限等级。每个线程需要自己主动释放出cpu使用权限,否则其他线程将会饿死。用户创建的任务,都会影响实时性的高低。但PTOS作为一个低资源的MCU的OS来说,线程数目本来不多,如果用户合理遵循不使用硬延迟的方式(系统提供了PT_SLEEP_MS函数),都可以满足用户的使用需求的.