12. 支持时间片¶
在RT-Thread中,当同一个优先级下有两个或两个以上线程的时候,线程支持时间片功能,即我们可以指定线程持续运行一次的时间,单位为tick。假如有两个线程分别为线程2和线程3,他们的优先级都为3,线程2的时间片为2,线程3的时间片为3。当执行到优先级为3的线程时,会先执行线程2,直到线程2的时间片 耗完,然后再执行线程3,具体的实验波形图看本章最后的实验现象即可。
12.1. 实现时间片¶
12.1.1. 在线程控制块中添加时间片相关成员¶
在线程控制块中添加时间片相关的成员,init_tick表示初始时间片,remaining_tick表示还剩下多少时间片,具体见 代码清单12-1 的高亮部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | struct rt_thread
{
/* rt 对象 */
char name[RT_NAME_MAX]; /* 对象的名字 */
rt_uint8_t type; /* 对象类型 */
rt_uint8_t flags; /* 对象的状态 */
rt_list_t list; /* 对象的列表节点 */
rt_list_t tlist; /* 线程链表节点 */
void *sp; /* 线程栈指针 */
void *entry; /* 线程入口地址 */
void *parameter; /* 线程形参 */
void *stack_addr; /* 线程起始地址 */
rt_uint32_t stack_size; /* 线程栈大小,单位为字节 */
rt_ubase_t init_tick; /* 初始时间片 */
rt_ubase_t remaining_tick; /* 剩余时间片 */
rt_uint8_t current_priority; /* 当前优先级 */
rt_uint8_t init_priority; /* 初始优先级 */
rt_uint32_t number_mask; /* 当前优先级掩码 */
rt_err_t error; /* 错误码 */
rt_uint8_t stat; /* 线程的状态 */
struct rt_timer thread_timer; /* 内置的线程定时器 */
};
|
12.1.2. 修改线程初始化函数¶
在线程初始化函数rt_thread_init中添加时间片相关形参,并将线程控制块中与时间片相关的成员初始化好,具体见 代码清单12-2 的高亮部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | rt_err_t rt_thread_init(struct rt_thread *thread,
const char *name,
void (*entry)(void *parameter),
void *parameter,
void *stack_start,
rt_uint32_t stack_size,
rt_uint8_t priority,
rt_uint32_t tick)
{
/* 线程对象初始化 */
/* 线程结构体开头部分的成员就是rt_object_t类型 */
rt_object_init((rt_object_t)thread, RT_Object_Class_Thread, name);
rt_list_init(&(thread->tlist));
thread->entry = (void *)entry;
thread->parameter = parameter;
thread->stack_addr = stack_start;
thread->stack_size = stack_size;
/* 初始化线程栈,并返回线程栈指针 */
thread->sp = (void *)rt_hw_stack_init( thread->entry,
thread->parameter,
(void *)((char *)thread->stack_addr + thread->stack_size - 4) );
thread->init_priority = priority;
thread->current_priority = priority;
thread->number_mask = 0;
/* 错误码和状态 */
thread->error = RT_EOK;
thread->stat = RT_THREAD_INIT;
/* 时间片相关 */
thread->init_tick = tick;
thread->remaining_tick = tick;
/* 初始化线程定时器 */
rt_timer_init(&(thread->thread_timer), /* 静态定时器对象 */
thread->name, /* 定时器的名字,直接使用的是线程的名字 */
rt_thread_timeout, /* 超时函数 */
thread, /* 超时函数形参 */
0, /* 延时时间 */
RT_TIMER_FLAG_ONE_SHOT); /* 定时器的标志 */
return RT_EOK;
}
|
12.1.2.1. 修改空闲线程初始化函数¶
在空闲线。程初始化函数中指定空闲线程的时间片,通常很少线程的优先级会与空闲线程的优先级一样, 时间片我们可以随便设置,这里我们示意性的设置为2,具体见 代码清单12-3 的高亮部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | void rt_thread_idle_init(void)
{
/* 初始化线程 */
rt_thread_init(&idle,
"idle",
rt_thread_idle_entry,
RT_NULL,
&rt_thread_stack[0],
sizeof(rt_thread_stack),
RT_THREAD_PRIORITY_MAX-1,
2);/* 时间片 */
/* 启动空闲线程 */
rt_thread_startup(&idle);
}
|
12.1.3. 修改系统时基更新函数¶
在系统时基更新函数中添加与时间片相关的代码,具体见 代码清单12-4 的高亮部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | void rt_tick_increase(void)
{
struct rt_thread *thread;
/* 系统时基计数器加1操作,rt_tick是一个全局变量 */
++ rt_tick;
/* 获取当前线程线程控制块 */
thread = rt_thread_self(); (1)
/* 时间片递减 */
-- thread->remaining_tick; (2)
/* 如果时间片用完,则重置时间片,然后让出处理器 */
if (thread->remaining_tick == 0) (3)
{
/* 重置时间片 */
thread->remaining_tick = thread->init_tick; (4)
/* 让出处理器 */
rt_thread_yield(); (5)
}
/* 扫描系统定时器列表 */
rt_timer_check();
}
|
代码清单12-4(1):获取当前线程线程控制块。
代码清单12-4(2):递减当前线程的时间片。
代码清单12-4(3):如果时间片用完,则重置时间片,然后让出处理器,具体是否真正的要让出处理器还要看当前线程下是否有两个以上的线程。
代码清单12-4(4):如果时间片耗完,则重置时间片。
代码清单12-4(5):调用rt_thread_yield让出处理器,该函数在thread.c中定义,具体实现见 代码清单12-5。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | /**
* 该函数将让当前线程让出处理器,调度器选择最高优先级的线程运行。当前让出处理器之后,
* 当前线程还是在就绪态。
*
* @return RT_EOK
*/
rt_err_t rt_thread_yield(void)
{
register rt_base_t level;
struct rt_thread *thread;
/* 关中断 */
level = rt_hw_interrupt_disable();
/* 获取当前线程的线程控制块 */
thread = rt_current_thread; (1)
/* 如果线程在就绪态,且同一个优先级下不止一个线程 */
if ((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_READY && (2)
thread->tlist.next != thread->tlist.prev)
{
/* 将时间片耗完的线程从就绪列表移除 */
rt_list_remove(&(thread->tlist)); (3)
/* 将线程插入到该优先级下的链表的尾部 */ (4)
rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]),
&(thread->tlist));
/* 开中断 */
rt_hw_interrupt_enable(level); (5)
/* 执行调度 */
rt_schedule();
return RT_EOK;
}
/* 开中断 */
rt_hw_interrupt_enable(level);
return RT_EOK;
}
|
代码清单12-5(1):获取当前线程线程控制块。
代码清单12-5(2):如果线程在就绪态,且同一个优先级下不止一个线程,则执行if里面的代码,否则函数返回。
代码清单12-5(3):将时间片耗完的线程从就绪列表移除。
代码清单12-5(4):将时间片耗完的线程插入到该优先级下的链表的尾部,把机会让给下一个线程。
代码清单12-5(5):执行调度。
12.2. 修改main.c文件¶
main.c文件的修改内容具体见 代码清单12-6 的高亮部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | /*
*************************************************************************
* 包含的头文件
*************************************************************************
*/
#include <rtthread.h>
#include <rthw.h>
#include "ARMCM7.h"
/*
*************************************************************************
* 全局变量
*************************************************************************
*/
rt_uint8_t flag1;
rt_uint8_t flag2;
rt_uint8_t flag3;
extern rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];
/*
*************************************************************************
* 线程控制块 & STACK & 线程声明
*************************************************************************
*/
/* 定义线程控制块 */
struct rt_thread rt_flag1_thread;
struct rt_thread rt_flag2_thread;
struct rt_thread rt_flag3_thread;
ALIGN(RT_ALIGN_SIZE)
/* 定义线程栈 */
rt_uint8_t rt_flag1_thread_stack[512];
rt_uint8_t rt_flag2_thread_stack[512];
rt_uint8_t rt_flag3_thread_stack[512];
/* 线程声明 */
void flag1_thread_entry(void *p_arg);
void flag2_thread_entry(void *p_arg);
void flag3_thread_entry(void *p_arg);
/*
*************************************************************************
* 函数声明
*************************************************************************
*/
void delay(uint32_t count);
/************************************************************************
* @brief main函数
* @param 无
* @retval 无
*
* @attention
***********************************************************************
*/
int main(void)
{
/* 硬件初始化 */
/* 将硬件相关的初始化放在这里,如果是软件仿真则没有相关初始化代码 */
/* 关中断 */
rt_hw_interrupt_disable();
/* SysTick中断频率设置 */
SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND );
/* 系统定时器列表初始化 */
rt_system_timer_init();
/* 调度器初始化 */
rt_system_scheduler_init();
/* 初始化空闲线程 */
rt_thread_idle_init();
/* 初始化线程 */
rt_thread_init( &rt_flag1_thread, /* 线程控制块 */
"rt_flag1_thread", /* 线程名字,字符串形式 */
flag1_thread_entry, /* 线程入口地址 */
RT_NULL, /* 线程形参 */
&rt_flag1_thread_stack[0], /* 线程栈起始地址 */
sizeof(rt_flag1_thread_stack), /* 线程栈大小,单位为字节 */
2, /* 优先级 */
4); /* 时间片 */
/* 将线程插入到就绪列表 */
rt_thread_startup(&rt_flag1_thread);
/* 初始化线程 */
rt_thread_init( &rt_flag2_thread, /* 线程控制块 */
"rt_flag2_thread", /* 线程名字,字符串形式 */
flag2_thread_entry, /* 线程入口地址 */
RT_NULL, /* 线程形参 */
&rt_flag2_thread_stack[0], /* 线程栈起始地址 */
sizeof(rt_flag2_thread_stack), /* 线程栈大小,单位为字节 */
3, /* 优先级 */
2); /* 时间片 */
/* 将线程插入到就绪列表 */
rt_thread_startup(&rt_flag2_thread);
/* 初始化线程 */
rt_thread_init( &rt_flag3_thread, /* 线程控制块 */
"rt_flag3_thread", /* 线程名字,字符串形式 */
flag3_thread_entry, /* 线程入口地址 */
RT_NULL, /* 线程形参 */
&rt_flag3_thread_stack[0], /* 线程栈起始地址 */
sizeof(rt_flag3_thread_stack), /* 线程栈大小,单位为字节 */
3, /* 优先级 */
3); /* 时间片 */
/* 将线程插入到就绪列表 */
rt_thread_startup(&rt_flag3_thread);
/* 启动系统调度器 */
rt_system_scheduler_start();
}
/*
*************************************************************************
* 函数实现
*************************************************************************
*/
/* 软件延时 */
void delay (uint32_t count)
{
for(; count!=0; count--);
}
/* 线程1 */
void flag1_thread_entry( void *p_arg )
{
for( ;; )
{
flag1 = 1;
rt_thread_delay(3); /*(阻塞延时)*/
flag1 = 0;
rt_thread_delay(3);
}
}
/* 线程2 */
void flag2_thread_entry( void *p_arg )
{
for( ;; )
{
flag2 = 1;
//rt_thread_delay(2);
delay( 100 ); /*(软件延时)*/
flag2 = 0;
//rt_thread_delay(2);
delay( 100 );
}
}
/* 线程3 */
void flag3_thread_entry( void *p_arg )
{
for( ;; )
{
flag3 = 1;
//rt_thread_delay(3);
delay( 100 ); /*(软件延时)*/
flag3 = 0;
//rt_thread_delay(3);
delay( 100 );
}
}
void SysTick_Handler(void)
{
/* 进入中断 */
rt_interrupt_enter();
/* 更新时基 */
rt_tick_increase();
/* 离开中断 */
rt_interrupt_leave();
}
|
代码清单12-6(优先级):线程1的优先级修改为2,线程2和线程3的优先级修改为3。
代码清单12-6(时间片):线程1的时间片设置为4(可是与线程1同优先级的线程没有, 这里设置了时间片也没有什么鸟用,不信等下看实验现象),线程2和线程3的时间片设置为3。
代码清单12-6(阻塞延时):设置线程1高低电平的时间为3个tick,且延时要使用阻塞延时。
代码清单12-6(软件延时):将线程2和线程3的延时改成软件延时,因为这两个线程的优先级是相同的, 当他们的时间片耗完的时候让出处理器进行系统调度,不会一直的占有CPU,所以可以使用软件延时,但是线程1却不可以,因为与线程1同优先级的线程没有,时间片功能不起作用,当时间片耗完的时候不 会让出CPU,会一直的占有CPU,所以不能使用软件延时。