介绍
在嵌入式开发中,现在很多设备都会都会跑专门的操作系统,比如linux、rt_thread、freertos、ucos等,但还是会有不少设备用不上的操作系统,就会采取裸跑的形式(又叫前后台系统)。像在这种不带操作系统的软件开发中,很常见的形式就是把所有要执行的函数全部放在循环当中,按顺序循环的执行每一个函数,然后加入对函数执行的周期有要求还可以采用定时器来确定多久来执行某个函数。但是这对于我们要来管理多个这样的函数时候,在编程上就显得很冗余繁琐了,也不方便我们管理。
本文就写了一个方便我们管理定时任务管理内核。纯C语言,需要用到的硬件资源也就一个定时器(用于得到时间片),当然也可以在电脑上运行,只要能获取到时间片就可以。这个内核最主要的功能就是实现函数的周期调用,比如想要LED闪烁,就写个LED取反的函数,把这个函数添加到这个内核进行管理就可以实现特定周期调用这个取反函数来使LED闪烁,比如又想写个函数来检测扫描按键,同样也是把这个函数添加到内核进行管理就行。说白了,这个内核就是实现软件定时器的功能。
原理
原理其实也很简单,前面说到了所需要用到的硬件资源就只有一个定时器,这个定时器就为这个内核提供了时间片,而内核就根据时间片的值来判断到某个时刻要去执行特定函数。
// 任务结构体
typedef struct{
uint32_t id; // 任务ID,唯一,自然数
uint8_t prio; // 任务优先级,自然数,越小优先级越高
TASK_STATE state; // 任务状态
uint16_t period; // 任务运行周期
uint16_t time; // 任务倒计时
void (*task_done)(void *); // 任务执行函数
}TCB_T;
每一个需要周期执行的函数放在了这个名为“TCB_T”的结构体里面,就叫这个结构体为任务控制块吧,“task_done”即为函数指针。其中还包含了任务id(用来识别任务的唯一特征),任务优先级(当某个时刻有多个任务在等待执行时,会按优先级从高到低来逐个执行,同等优先级的话就是任务id小的先执行),任务运行周期(以毫秒为单位),任务倒计时(剩余等待的时间片数),以及任务当前状态(如下)。
// ------ 任务状态 ---@def TASK_STATE
typedef enum {
TASK_STATE_RUNNING = 0, // 正在运行
TASK_STATE_READY, // 准备运行
TASK_STATE_WAIT, // 等待计时
TASK_STATE_SUSPEND, // 任务挂起
}TASK_STATE;
每个任务都需要一个任务控制块来维护,这些任务控制块又通过链表连接起来,这里采用链表的存储形式(之前也写过数组形式的,原理很类似),也就是这些任务是可以动态创建的(也可以采用静态链接的方式把任务添加到内核管理),创建的任务ID是唯一的,作为识别任务的特征,这些任务按照id从小到大有序排列。除了需要总的任务链表,还需要一个就绪的任务链表,也就是当某个时刻有多个任务需要执行了,这些任务就都会放在这个就绪链表当中,按照优先级高的优先,同等优先级ID小的优先,逐个执行。
实现
前面讲了这个内核概念性的东西,接下来就聊聊代码。
/* 函数声明 */
void task_list(unsigned char list);
void increase_kern_time(void);
uint32_t get_kern_time(void);
KE_RET kern_init(uint16_t time_slice);
KE_RET create_task(uint32_t id, uint8_t prio, TASK_STATE state, uint16_t period, void (*task_done)(void *));
uint32_t create_task_dynamic(uint8_t prio, TASK_STATE state, uint16_t period, void (*task_done)(void*));
KE_RET delete_task(uint32_t id);
KE_RET attach_task(TCB_T* task);
KE_RET dettach_task(TCB_T* task);
KE_RET set_task_state(uint32_t id, TASK_STATE task_state);
KE_RET suspend_task(uint32_t id);
KE_RET resume_task(uint32_t id);
uint32_t get_task_num(void);
uint32_t get_runnning_task_id(void);
void schedule(void);
上面这些函数就是留给外部去调用的。
task_list:就是打印当前任务链表中的任务,包括就绪的任务链表
increase_kern_time:就是需要在硬件定时器那块,回调函数中调用此函数来增加时间片值
get_kern_time:获取当前内核时间片
kern_init:就是内核的初始化,需要传入一个时间片的周期值,单位毫秒
create_task:这个就是创建任务的函数,需要传入唯一的id、优先级、创建后任务的状态、回调的函数
create_task_dynamic:对比create_task函数,这个函数不需要手动传入任务id,任务id由内核自动分配,就不会存在因为id重复出现任务创建失败的情况
delete_task:根据id来删除任务
attach_task:把静态定义好的任务控制块链接到内核
dettach_task:把用attach_task函数链接到内核的任务断开链接
set_task_state:设置任务的状态
suspend_task:挂起任务,挂起后就不会再执行
resume_task:把任务从挂起状态恢复
get_task_num:获取当前任务的总数
get_runnning_task_id:获取正在执行的任务id
schedule:任务调度,在调度函数里面决定要执行哪个任务的函数,这个函数需要放在死循环中运行
看到这些函数里面很多的返回值类型都是“KE_RET”型的,这个返回值可以使用“K_EEROR”宏来运行能在函数返回错误时候抛出错误,这个返回值类型定义如下:
//------ 错误返回值 ---@def FAIL_RETURN
typedef enum {
KE_OK = 0, // 成功返回
KE_ALLOC_FAIL, // 内存空间分配失败
KE_TASK_LIST_NULL, // 任务列表为空
KE_NO_THIS_TASK, // 没有此任务
KE_TASK_NUM_OVER, // 任务数超限
KE_TASK_ID_ERR, // 任务ID错误
KE_TASK_STATE_ERR, // 任务状态错误
KE_TASK_REPEAT, // 任务重复
KE_NODE_ERR, // 节点错误
}KE_RET;
任务调度
任务调度里面就做两件事,其一是遍历任务给任务倒计时,到时就添加到就绪链表,另一件事是读取就绪链表中的任务来执行。


创建任务
/**************************************************************
* 函数名: create_task
* 函数功能: 任务创建函数
* 输入值: id: 任务ID
prio: 任务优先级,越小优先级越高
state: 任务状态 @ref TASK_STATE
TASK_STATE_WAIT -- 任务创建后等待一个周期再运行
TASK_STATE_READY -- 任务创建后立刻运行
period: 任务执行周期,每隔period毫秒运行一次任务函数
*task_done: 具体的任务函数
* 返回值: KE_RET
*/
KE_RET create_task(
uint32_t id,
uint8_t prio,
TASK_STATE state,

最低0.47元/天 解锁文章
269

被折叠的 条评论
为什么被折叠?



