从系统的角度看,任务是竞争系统资源的最小运行单元。TencentOS tiny是一个支持多任务的操做系统,任务可使用或等待CPU、使用内存空间等系统资源,并独立于其它任务运行,理论上任何数量的任务均可以共享同一个优先级,这样子处于就绪态的多个相同优先级任务将会以时间片切换的方式共享处理器。centos
不过要注意的是:在TencentOS tiny中,不能建立与空闲任务相同优先级的任务
K_TASK_PRIO_IDLE
,相同优先级下的任务须要容许使用时间片调度,打开TOS_CFG_ROUND_ROBIN_EN
。数组
简而言之: TencentOS tiny的任务可认为是一系列独立任务的集合。每一个任务在本身的环境中运行。在任什么时候刻,只有一个任务获得运行,由TencentOS tiny调度器决定运行哪一个任务。从宏观
看上去全部的任务都在同时在执行。数据结构
TencentOS中的任务是抢占式调度机制,高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能获得调度。同时TencentOS也支持时间片轮转调度方式。函数
系统默承认以支持10个优先级,0~TOS_CFG_TASK_PRIO_MAX
,这个宏定义是能够修改的,优先级数值越大的任务优先级越低,(TOS_CFG_TASK_PRIO_MAX - (k_prio_t)1u)
为最低优先级,分配给空闲任务使用。测试
#define K_TASK_PRIO_IDLE (k_prio_t)(TOS_CFG_TASK_PRIO_MAX - (k_prio_t)1u) #define K_TASK_PRIO_INVALID (k_prio_t)(TOS_CFG_TASK_PRIO_MAX)
TencentOS tiny任务状态有如下几种。ui
// ready to schedule // a task's pend_list is in readyqueue #define K_TASK_STATE_READY (k_task_state_t)0x0000 // delayed, or pend for a timeout // a task's tick_list is in k_tick_list #define K_TASK_STATE_SLEEP (k_task_state_t)0x0001 // pend for something // a task's pend_list is in some pend object's list #define K_TASK_STATE_PEND (k_task_state_t)0x0002 // suspended #define K_TASK_STATE_SUSPENDED (k_task_state_t)0x0004 // deleted #define K_TASK_STATE_DELETED (k_task_state_t)0x0008 // actually we don't really need those TASK_STATE below, if you understand the task state deeply, the code can be much more elegant. // we are pending, also we are waitting for a timeout(eg. tos_sem_pend with a valid timeout, not TOS_TIME_FOREVER) // both a task's tick_list and pend_list is not empty #define K_TASK_STATE_PENDTIMEOUT (k_task_state_t)(K_TASK_STATE_PEND | K_TASK_STATE_SLEEP) // suspended when sleeping #define K_TASK_STATE_SLEEP_SUSPENDED (k_task_state_t)(K_TASK_STATE_SLEEP | K_TASK_STATE_SUSPENDED) // suspened when pending #define K_TASK_STATE_PEND_SUSPENDED (k_task_state_t)(K_TASK_STATE_PEND | K_TASK_STATE_SUSPENDED) // suspended when pendtimeout #define K_TASK_STATE_PENDTIMEOUT_SUSPENDED (k_task_state_t)(K_TASK_STATE_PENDTIMEOUT | K_TASK_STATE_SUSPENDED)
TencentOS tiny维护一条就绪列表,用于挂载系统中的全部处于就绪态的任务,他是readyqueue_t
类型的列表,其成员变量以下:this
readyqueue_t k_rdyq; typedef struct readyqueue_st { k_list_t task_list_head[TOS_CFG_TASK_PRIO_MAX]; uint32_t prio_mask[K_PRIO_TBL_SIZE]; k_prio_t highest_prio; } readyqueue_t;
task_list_head
是列表类型k_list_t
的数组,TencentOS tiny为每一个优先级的任务都分配一个列表,系统支持最大优先级为TOS_CFG_TASK_PRIO_MAX
prio_mask
则是优先级掩码数组,它是一个类型为32位变量的数组,数组成员个数由TOS_CFG_TASK_PRIO_MAX
决定:操作系统
#define K_PRIO_TBL_SIZE ((TOS_CFG_TASK_PRIO_MAX + 31) / 32)
当TOS_CFG_TASK_PRIO_MAX
不超过32时数组成员变量只有一个,就是32位的变量数值,那么该变量的每一位表明一个优先级。好比当TOS_CFG_TASK_PRIO_MAX
为64时,prio_mask[0]
变量的每一位(bit)表明0-31
优先级,而prio_mask[1]
变量的每一位表明32-63
优先级。指针
highest_prio
则是记录当前优先级列表的最高优先级,方便索引task_list_head
。code
与系统时间相关的任务都会被挂载到这个列表中,多是睡眠、有期限地等待信号量、事件、消息队列等状况~
k_list_t k_tick_list;
在多任务系统中,任务的执行是由系统调度的。系统为了顺利的调度任务,为每一个任务都额外定义了一个任务控制块,这个任务控制块就至关于任务的身份证,里面存有任务的全部信息,好比任务的栈指针,任务名称,任务的形参等。有了这个任务控制块以后,之后系统对任务的所有操做均可以经过这个任务控制块来实现。
TencentOS 任务控制块以下:
typedef struct k_task_st { k_stack_t *sp; /**< 任务栈指针,用于切换上下文*/ #if TOS_CFG_OBJECT_VERIFY_EN > 0u knl_obj_t knl_obj; /**< 只是为了验证,测试当前对象是否真的是一项任务。*/ #endif char *name; /**< 任务名称 */ k_task_entry_t entry; /**< 任务主体 */ void *arg; /**< 任务主体形参 */ k_task_state_t state; /**< 任务状态 */ k_prio_t prio; /**< 任务优先级 */ k_stack_t *stk_base; /**< 任务栈基地址 */ size_t stk_size; /**< 任务栈大小 */ k_tick_t tick_expires; /**< 任务阻塞的时间 */ k_list_t tick_list; /**< 延时列表 */ k_list_t pend_list; /**< 就绪、等待列表 */ #if TOS_CFG_MUTEX_EN > 0u k_list_t mutex_own_list; /**< 任务拥有的互斥量 */ k_prio_t prio_pending; /*< 用于记录持有互斥量的任务初始优先级,在优先级继承中使用 */ #endif pend_obj_t *pending_obj; /**< 记录任务此时挂载到的列表 */ pend_state_t pend_state; /**< 等待被唤醒的缘由(状态) */ #if TOS_CFG_ROUND_ROBIN_EN > 0u k_timeslice_t timeslice_reload; /**< 时间片初始值(重装载值) */ k_timeslice_t timeslice; /**< 剩余时间片 */ #endif #if TOS_CFG_MSG_EN > 0u void *msg_addr; /**< 保存接收到的消息 */ size_t msg_size; /**< 保存接收到的消息大小 */ #endif #if TOS_CFG_EVENT_EN > 0u k_opt_t opt_event_pend; /**< 等待事件的的操做类型:TOS_OPT_EVENT_PEND_ANY 、 TOS_OPT_EVENT_PEND_ALL */ k_event_flag_t flag_expect; /**< 期待发生的事件 */ k_event_flag_t *flag_match; /**< 等待到的事件 */ #endif } k_task_t;
在TencentOS tiny中,凡是使用__API__
修饰的函数都是提供给用户使用的,而使用__KERNEL__
修饰的代码则是给内核使用的。
TencentOS的建立任务函数有好几个参数:
|参数| 含义 |
|--|--|
|task | 任务控制块 |
|name | 任务名字 |
|entry | 任务主体 |
|arg | 任务形参 |
|prio | 优先级 |
|stk_base | 任务栈基地址 |
|stk_size | 任务栈大小 |
|timeslice | 时间片 |
参数详解(来源TencentOS tiny开发指南):
task
这是一个k_task_t类型的指针,k_task_t是内核的任务结构体类型。注意:task指针,应该指向生命周期大于待建立任务体生命周期的k_task_t类型变量,若是该指针指向的变量生命周期比待建立的任务体生命周期短,譬如多是一个生命周期极端的函数栈上变量,可能会出现任务体还在运行而k_task_t变量已被销毁,会致使系统调度出现不可预知问题。
name
指向任务名字符串的指针。注意:同task,该指针指向的字符串生命周期应该大于待建立的任务体生命周期,通常来讲,传入字符串常量指针便可。
entry
任务体运行的函数入口。当任务建立完毕进入运行状态后,entry是任务执行的入口,用户能够在此函数中编写业务逻辑。
arg
传递给任务入口函数的参数。
prio
任务优先级。prio的数值越小,优先级越高。用户能够在tos_config.h中,经过TOS_CFG_TASK_PRIO_MAX来配置任务优先级的最大数值,在内核的实现中,idle任务的优先级会被分配为TOS_CFG_TASK_PRIO_MAX - 1,此优先级只能被idle任务使用。所以对于一个用户建立的任务来讲,合理的优先级范围应该为[0, TOS_CFG_TASK_PRIO_MAX - 2]。另外TOS_CFG_TASK_PRIO_MAX的配置值必需大于等于8。
stk_base
任务在运行时使用的栈空间的起始地址。注意:同task,该指针指向的内存空间的生命周期应该大于待建立的任务体生命周期。stk_base是k_stack_t类型的数组起始地址。
stk_size
任务的栈空间大小。注意:由于stk_base是k_stack_t类型的数组指针,所以实际栈空间所占内存大小为stk_size * sizeof(k_stack_t)。
timeslice
时间片轮起色制下当前任务的时间片大小。当timeslice为0时,任务调度时间片会被设置为默认大小(TOS_CFG_CPU_TICK_PER_SECOND / 10),系统时钟滴答(systick)数 / 10。
建立任务的实现以下:首先对参数进行检查,还要再提一下:在TencentOS中,不能建立与空闲任务相同优先级的任务K_TASK_PRIO_IDLE
。而后调用cpu_task_stk_init
函数将任务栈进行初始化,而且将传入的参数记录到任务控制块中。若是打开了TOS_CFG_ROUND_ROBIN_EN
宏定义,则表示支持时间片调度,则须要配置时间片相关的信息timeslice
到任务控制块中。而后调用task_state_set_ready
函数将新建立的任务设置为就绪态K_TASK_STATE_READY
,再调用readyqueue_add_tail
函数将任务插入就绪列表k_rdyq
中。若是调度器运行起来了,则进行一次任务调度。
我的感受吧,没有从堆中动态分配仍是有点小小的遗憾,我更喜欢简单的函数接口~!
代码以下:
__API__ k_err_t tos_task_create(k_task_t *task, char *name, k_task_entry_t entry, void *arg, k_prio_t prio, k_stack_t *stk_base, size_t stk_size, k_timeslice_t timeslice) { TOS_CPU_CPSR_ALLOC(); TOS_IN_IRQ_CHECK(); TOS_PTR_SANITY_CHECK(task); TOS_PTR_SANITY_CHECK(entry); TOS_PTR_SANITY_CHECK(stk_base); if (unlikely(stk_size < sizeof(cpu_context_t))) { return K_ERR_TASK_STK_SIZE_INVALID; } if (unlikely(prio == K_TASK_PRIO_IDLE && !knl_is_idle(task))) { return K_ERR_TASK_PRIO_INVALID; } if (unlikely(prio > K_TASK_PRIO_IDLE)) { return K_ERR_TASK_PRIO_INVALID; } task_reset(task); #if TOS_CFG_OBJECT_VERIFY_EN > 0u knl_object_init(&task->knl_obj, KNL_OBJ_TYPE_TASK); #endif task->sp = cpu_task_stk_init((void *)entry, arg, (void *)task_exit, stk_base, stk_size); task->entry = entry; task->arg = arg; task->name = name; task->prio = prio; task->stk_base = stk_base; task->stk_size = stk_size; #if TOS_CFG_ROUND_ROBIN_EN > 0u task->timeslice_reload = timeslice; if (timeslice == (k_timeslice_t)0u) { task->timeslice = k_robin_default_timeslice; } else { task->timeslice = timeslice; } #endif TOS_CPU_INT_DISABLE(); task_state_set_ready(task); readyqueue_add_tail(task); TOS_CPU_INT_ENABLE(); if (tos_knl_is_running()) { knl_sched(); } return K_ERR_NONE; }
这个函数十分简单,根据传递进来的任务控制块销毁任务,也能够传递进NULL表示销毁当前运行的任务。可是不容许销毁空闲任务k_idle_task
,当调度器被锁住时不能销毁自身,会返回K_ERR_SCHED_LOCKED
错误代码。若是使用了互斥量,当任务被销毁时会释放掉互斥量,而且根据任务所处的状态进行销毁,好比任务处于就绪态、延时态、等待态,则会从对应的状态列表
中移除。
代码实现以下:
__API__ k_err_t tos_task_destroy(k_task_t *task) { TOS_CPU_CPSR_ALLOC(); TOS_IN_IRQ_CHECK(); if (unlikely(!task)) { task = k_curr_task; } #if TOS_CFG_OBJECT_VERIFY_EN > 0u if (!knl_object_verify(&task->knl_obj, KNL_OBJ_TYPE_TASK)) { return K_ERR_OBJ_INVALID; } #endif if (knl_is_idle(task)) { return K_ERR_TASK_DESTROY_IDLE; } if (knl_is_self(task) && knl_is_sched_locked()) { return K_ERR_SCHED_LOCKED; } TOS_CPU_INT_DISABLE(); #if TOS_CFG_MUTEX_EN > 0u // when we die, wakeup all the people in this land. if (!tos_list_empty(&task->mutex_own_list)) { task_mutex_release(task); } #endif if (task_state_is_ready(task)) { // that's simple, good kid readyqueue_remove(task); } if (task_state_is_sleeping(task)) { tick_list_remove(task); } if (task_state_is_pending(task)) { pend_list_remove(task); } task_reset(task); task_state_set_deleted(task); TOS_CPU_INT_ENABLE(); knl_sched(); return K_ERR_NONE; }
任务睡眠很是简单,主要的思路就是将任务从就绪列表移除,而后添加到延时列表中k_tick_list
,若是调度器被锁,直接返回错误代码K_ERR_SCHED_LOCKED
,若是睡眠时间为0,则调用tos_task_yield
函数发起一次任务调度;调用tick_list_add
函数将任务插入延时列表
中,睡眠的时间delay
是由用户指定的。不过须要注意的是若是任务睡眠的时间是永久睡眠TOS_TIME_FOREVER
,将返回错误代码K_ERR_DELAY_FOREVER
,这是由于任务睡眠是主动行为
,若是永久睡眠了,将无法主动唤醒,而任务等待事件、信号量、消息队列等行为是被动行为,能够是永久等待,一旦事件发生了、信号量呗释放、消息队列不为空时任务就会被唤醒,这是被动行为
,这两点须要区分开来。最后调用readyqueue_remove
函数将任务从就绪列表中移除,而后调用knl_sched
函数发起一次任务调度,就能切换另外一个任务。
任务睡眠的代码以下:
__API__ k_err_t tos_task_delay(k_tick_t delay) { TOS_CPU_CPSR_ALLOC(); TOS_IN_IRQ_CHECK(); if (knl_is_sched_locked()) { return K_ERR_SCHED_LOCKED; } if (unlikely(delay == (k_tick_t)0u)) { tos_task_yield(); return K_ERR_NONE; } TOS_CPU_INT_DISABLE(); if (tick_list_add(k_curr_task, delay) != K_ERR_NONE) { TOS_CPU_INT_ENABLE(); return K_ERR_DELAY_FOREVER; } readyqueue_remove(k_curr_task); TOS_CPU_INT_ENABLE(); knl_sched(); return K_ERR_NONE; }
相关代码能够在公众号后台获取。 更多资料欢迎关注“物联网IoT开发”公众号!