我们常说,空闲任务的优先级是最低的,仿佛任何任务都比它重要。但是实际上空闲任务,是作为所有RTOS中不可或缺的一部分。不管有那个任务被执行,总需要在必要时间切换到其他任务,满足系统的需要。所以,它最神奇,同时也最深奥。不过,由于知识能力所限,本人现在还不能清楚其中的重要核心部分;为了满足开发的需求,需要简单理解空闲任务及回调函数(钩子函数)的使用现象和简单的思考。
1.钩子函数的创建
钩子函数的初始化 rt_thread_idle_sethook(xx)
删除 rt_thread_idle_delhook(xx)
xx内填写钩子函数的名称
这里创建了两个线程是作为打印用途的。钩子函数的创建只需要一个函数。
rt_err_t result;
/* 设置空闲线程钩子 */
rt_thread_idle_sethook(idle_hook);
rt_thread_idle_sethook(idle1_hook);
/* 创建线程 */
tid = rt_thread_create("thread1",
thread_entry, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
if (tid != RT_NULL)
rt_thread_startup(tid);
result = rt_thread_init(&tid1,
"CPU occupancy rate",
tid1_entry,
RT_NULL,
&tid1_stack[0],
sizeof(tid1_stack),
THREAD_PRIORITY -1,
THREAD_TIMESLICE);
if(RT_EOK == result)
rt_thread_startup(&tid1);
这里是具体内容
static rt_uint8_t cpu_usage_major = 0, cpu_usage_minor = 0;
static rt_uint32_t count_d;
/* 空闲函数钩子函数执行次数 */
volatile static int hook_times = 0;
/* 空闲任务钩子函数 */
static void idle_hook()
{
if (0 == (hook_times % 10))
{
rt_kprintf("enter idle hook %d times.\n", hook_times);
}
//rt_kprintf("hook_time ++\n");
rt_enter_critical();
hook_times++;
rt_exit_critical();
}
/*打印CPU占有率*/
static void idle1_hook()
{
rt_tick_t tick_buf;
//存放系统TICK
rt_uint32_t count_n;
//存放计数信息
volatile uint32_t loop;
//存放CPU空转计数信息
//rt_kprintf("calc again\n");
//进入CPU空转模式-关闭任务调度
if(count_d == 0) //CPU不做调度的数据,获取一次就好
{
//rt_kprintf("entry calc count_d\n");
rt_enter_critical(); //进入临界区,关闭调度
tick_buf = rt_tick_get();
while( rt_tick_get() - tick_buf < USING_CPU_TICK )//等待系统度过10个TICK时钟
{
count_d ++; //空转模式下的CPU计数
loop = 0;
while(loop < USING_CPU_LOOP)
loop++;
}
rt_exit_critical(); //退出临界区
}
count_n = 0;
tick_buf = rt_tick_get();
while( rt_tick_get() - tick_buf < USING_CPU_TICK ) //等待系统度过10个TICK时钟
{
count_n ++; //正常模式下的CPU计数-未关闭调度
loop = 0;
while(loop < USING_CPU_LOOP)
loop++;
}
/* 计算整数百分比整数部分和小数部分 */
if (count_n < count_d)
{
count_n = count_d - count_n;
cpu_usage_major = (count_n * 100) / count_d; //整数部分
cpu_usage_minor = ((count_n * 100) % count_d) * 100 / count_d;//小数部分
}
else
{
count_d = count_n;
/* CPU使用率为0 */
cpu_usage_major = 0;
cpu_usage_minor = 0;
}
}
rt_tick_get(); 获取系统当前TICK值
这是创建的两个线程
/* 线程入口 */
static void thread_entry(void *parameter)
{
rt_uint8_t i = 210,j=2,k=6;
while (i--)
{
k=j+j;
j=k-j;
k=j*k;
k=k/j;
rt_thread_delay(1);
}
rt_kprintf("delete idle hook.\n");
/* 删除空闲钩子函数 */
rt_thread_idle_delhook(idle_hook);
rt_kprintf("thread1 finish.\n");
}
static void tid1_entry(void *parameter)
{
//删除CPU occupancy rate thread
rt_uint8_t major, minor,i = 200;
while(i--)
{
cpu_usage_get(&major, &minor);
rt_kprintf("cpu usage: %d.%d%\n", major, minor);
/* 休眠50个OS Tick */
/* 手动修改此处休眠 tick 时间,可以模拟实现不同的CPU使用率 */
rt_thread_delay(1);
}
rt_kprintf("delete idle1 hook\n");
rt_thread_idle_delhook(idle1_hook);
rt_kprintf("tid1 finished\n");
}
钩子函数的创建:
1除了初始化函数外
2.设置钩子函数本身。
2.CPU占用率测量思想
CPU只有一个,因为任务是切换的,不同的任务自然会导致占有率的不同。有的任务,执行完一次会挂起很长时间;有的任务需要频繁唤醒,这些任务必然会影响CPU的使用。占用率的测量思想就是依靠空闲函数执行时带起的钩子函数来实现的。在相同时间内,关闭和开启任务调度的情况下,测量钩子函数的执行次数,就可以了解到CPU进入空闲函数的空闲率。利用1-空闲率=占用率就可以获得占用率。代码在上面的第二段,完整文件贴在文末。由于停止任务调度的CPU速率,通常是相同的,所以只用执行一次就可以得到CPU空转时的执行次数。
3.对于钩子函数的思考
rt_tick_get()获取当前系统的TICK值
static rt_tick_t rt_tick = 0;
typedef rt_uint32_t rt_tick_t; /**< Type for tick count */
/**
* This function will return current tick from operating system startup
*
* @return current tick
*/
rt_tick_t rt_tick_get(void)
{
/* return the global tick */
return rt_tick;
}
/**
* This function will notify kernel there is one tick passed. Normally,
* this function is invoked by clock ISR.
*/
void rt_tick_increase(void)
{
struct rt_thread *thread;
/* increase the global tick */
++ rt_tick;
/* check time slice */
thread = rt_thread_self();
-- thread->remaining_tick;
if (thread->remaining_tick == 0)
{
/* change to initialized tick */
thread->remaining_tick = thread->init_tick;
/* yield */
rt_thread_yield();
}
/* check timer */
rt_timer_check();
}
/**
* This is the timer interrupt service routine.
*
*/
void SysTick_Handler(void)
{
/* enter interrupt */
rt_interrupt_enter();
HAL_IncTick();
rt_tick_increase();
/* leave interrupt */
rt_interrupt_leave();
}
理论上,任务处于空闲状态时候,开始调度。但是,我在尝试过将创建得两个线程的任务挂起函数rt_thread_delay()修改为1时,系统仍然是可以运行的。我这么写函数,是为了让系统尽可能得执行我们写得两个任务函数,而不是空闲任务。所以我只给这两个任务函数1个OS的时间,这样的函数我写了两个。但是系统也没有因此崩溃,反而忙里偷闲得执行了很多次钩子函数。
3.临界区
访问公共资源的区域成为临界区。在临界区内,不允许其他线程使用临界区的资源。
rt_enter_critical(); 进入临界区,中断未关闭
rt_exit_critical(); 退出临界区,中断未关闭
未成对出现,则任务调度异常
xx = rt_hw_interrupt_disable(); 关闭中断,因此进入临界区
rt_hw_interrupt_enable(xx); 打开中断,因此退出临界区
未成对出现,系统崩溃
临界区资源的访问,不能在其中添加任务挂起的delay类函数,不然会导致临界区资源仍然是不安全的。
#include <rthw.h>
#include <rtthread.h>
#define THREAD_PRIORITY 20
#define THREAD_STACK_SIZE 512
#define THREAD_TIMESLICE 5
/* 同时访问的全局变量,临界资源 */
static rt_uint32_t cnt;
void thread_entry(void *parameter)
{
rt_uint32_t no;
rt_uint32_t level;
no = (rt_uint32_t) parameter;
while (1)
{
/* 关闭中断 */
level = rt_hw_interrupt_disable();
// rt_enter_critical();
cnt += no;
// rt_thread_mdelay(1000);
/* 恢复中断 */
rt_hw_interrupt_enable(level);
// rt_exit_critical();
rt_kprintf("protect thread[%d]'s counter is %d\n", no, cnt);
rt_thread_mdelay(no * 100);
}
}
/* 用户应用程序入口 */
int interrupt_sample(void)
{
rt_thread_t thread;
/* 创建t1线程 */
thread = rt_thread_create("thread1", thread_entry, (void *)10,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
if (thread != RT_NULL)
rt_thread_startup(thread);
/* 创建t2线程 */
thread = rt_thread_create("thread2", thread_entry, (void *)20,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
if (thread != RT_NULL)
rt_thread_startup(thread);
return 0;
}
在这个例子里,我没有看出来临界区的作用。在中间加上delay类函数,也没有出现无法调度的情况,应该是我对内核的理解还不够,以后再慢慢深入。不过编程习惯还是要养成的,不理解得先模仿,理解后再回来补充。现在用得是keil里的仿真,等全部学完上板子时再编写外部中断函数验证一下临界区的内容。
4.中断函数的编写注意事项
rt_interrupt_enter(); 告知RT-Thread,现在处于中断状态
rt_interrupt_leave(); 告知RT-Thread,现在离开中断状态
/**
* This is the timer interrupt service routine.
*
*/
void SysTick_Handler(void)
{
/* enter interrupt */
rt_interrupt_enter();
HAL_IncTick();
rt_tick_increase();
/* leave interrupt */
rt_interrupt_leave();
}
原则上,函数体内没有用到RT_Thread的程序,可以不设置这两个函数rt_inerrupt_enter()和rt_interrupt_leave()