硬件:STM32F103ZET6、ST-LINK、usb转串口工具、4个LED灯、1个蜂鸣器、4个1k电阻、2个按键、面包板、杜邦线
文章目录
- 前言
- 一、RT-Thread相关接口函数
- 1. 获取当前运行的线程
- 2. 设置调度器钩子函数
- 二、程序设计
- 1. 头文件包含及宏定义
- 2. 线程入口函数定义
- 3. main函数设计
- 三、程序测试
- 总结
前言
本章进一步研究多线程的运行机制。要求实现功能如下:创建2个线程,线程名称分别为LED和BEEP。两个线程的任务是连续5次打印本线程的名字后退出线程(注意:线程不执行控制LED和蜂鸣器动作)。
设计本任务的目的是观察LED和BEEP线程在操作系统中是如何同时运行的。
一、RT-Thread相关接口函数
1. 获取当前运行的线程
在程序的运行过程中,相同的一段代码可能会被多个线程执行,在执行的时候可以通过下面的函数接口获得当前执行的线程句柄:
rt_thread_t rt_thread_self(void);
该接口的返回值见下表:
返回 | 描述 |
---|---|
thread | 当前运行的线程句柄 |
返回 | RT_NULL:失败,调度器还未启动 |
2. 设置调度器钩子函数
在整个系统的运行时,系统都处于线程运行、中断触发和响应中断、切换到其他线程,甚至是线程间的切换过程中,或者说系统的上下文切换是系统中最普遍的事件。有时用户可能会想知道在一个时刻发生了什么样的线程切换,可以通过调用下面的函数接口设置一个相应的钩子函数。在系统线程切换时,这个钩子函数将被调用:
void rt_scheduler_sethook(void (*hook)(struct rt_thread* from, struct rt_thread* to));
设置调度器钩子函数的输入参数如下表所示:
参数 | 描述 |
---|---|
hook | 表示用户定义的钩子函数指针 |
钩子函数 hook() 的声明如下:
void hook(struct rt_thread* from, struct rt_thread* to);
调度器钩子函数 hook() 的输入参数如下表所示:
函数参数 | 描述 |
---|---|
from | 表示系统所要切换出的线程控制块指针 |
to | 表示系统所要切换到的线程控制块指针 |
注:请仔细编写你的钩子函数,稍有不慎将很可能导致整个系统运行不正常(在这个钩子函数中,基本上不允许调用系统 API,更不应该导致当前运行的上下文挂起)。
二、程序设计
使用rt_thread_t rt_thread_self()函数获取本线程的线程句柄,然后通过线程句柄,可以方便地获得线程地名称。对main.c进行如下程序设计
1. 头文件包含及宏定义
本任务代码中,我们使用预编译宏进行选择编译,使代码可以兼容两个版本,提高代码利用率
#include <rtthread.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
//#include "car_led.h" //包含LED控制模块头文件
//#include "car_beep.h" //包含蜂鸣器控制模块头文件
#define THREAD_STACK_SIZE 1024 //定义线程栈大小
//两个线程优先级分别定义
#define THREAD_PRIORITY_LED 20
#define THREAD_PRIORITY_BEEP 20
#define THREAD_TIMESLICE 10 //定义线程时间片
/*使用预编译宏进行选择编译,当定义以下宏时,讲开启调度器钩子功能*/
//#define SCHEDULER_HOOK
#ifdef SCHEDULER_HOOK
//定义调度钩子函数
static void hook_of_scheduler(struct rt_thread* from,struct rt_thread* to)
{
//打印调度信息:从一个线程切换到另一个线程运行
rt_kprintf("from: %s --> to: %s \n",from->name,to->name);
}
#endif
2. 线程入口函数定义
本任务需要创建两个线程,所以要编写两个线程入口函数,分别为beep_thread_entry和led_thread_entry。
void beep_thread_entry(void * parameter)
{
rt_thread_t tid;
int count = 0; //打印出前5个调度过程
while(1){
tid =rt_thread_self(); //获取本线程地句柄
//打印线程的名字和当前计数变量地值
LOG_D("thread name: %s count = %d\n",tid->name,count);
if (count++ ==5) //线程循环5次后退出
break;
}
//线程退出时打印退出信息
LOG_D("thread %s exit\n",tid->name);
}
void led_thread_entry()
{
rt_thread_t tid;
int count = 0; //打印出前5个调度过程
while(1){
tid =rt_thread_self(); //获取本线程地句柄
//打印线程的名字和当前计数变量地值
LOG_D("thread name: %s count = %d\n",tid->name,count);
if (count++ ==5) //线程循环5次后退出
break;
}
//线程退出时打印退出信息
LOG_D("thread %s exit\n",tid->name);
}
3. main函数设计
main只负责线程的创建,用动态的方法创建LED线程,静态方法创建beep线程。静态方法需要用户自定义线程栈空间和线程控制块。
/* 栈首地址必须系统对齐 */
ALIGN(RT_ALIGN_SIZE)
static char beep_stack[THREAD_STACK_SIZE]; //定义栈空间
static struct rt_thread beepThread; //静态方式定义beep线程控制块
rt_thread_t TidLed = RT_NULL; //动态方式定义LED线程句柄
int main(void)
{
int ret;
#ifdef SCHEDULER_HOOK
//设置调度钩子
rt_scheduler_sethook(hook_of_scheduler);
#endif
/* 动态方式创建线程 */
TidLed = rt_thread_create("LED",
led_thread_entry,
RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY_LED,
THREAD_TIMESLICE);
if (TidLed != RT_NULL)//判断线程是否成功创建
rt_thread_startup(TidLed);//成功则启动线程
else {//否则打印日志并即出
LOG_D("can not create LED thread!");
return -1;
}
/* 采用静态方式初始化线程 */
ret = rt_thread_init(&beepThread,
"BEEP",
beep_thread_entry,
RT_NULL,
&beep_stack[0],
sizeof(beep_stack),
THREAD_PRIORITY_BEEP,
THREAD_TIMESLICE);
if (ret == RT_EOK) //判断线程是否成功创建
rt_thread_startup(&beepThread); //成功则启动线程
else { //否则打印日志并即出
LOG_D("can not init beep thread!");
return -1;
}
return RT_EOK;
}
三、程序测试
(1)使用终端连接开发板,然后按开发板reset键重启系统,终端调试信息如下图,发现两个线程轮流输出信息,可以间接说明两个线程时轮流执行的。
(2)把BEEP优先级改为19,修改后按照(1)进行测试,如图所示,即使LED线程先于BEEP线程创建,由于BEEP线程的优先级高于LED线程,因此BEEP线程被执行,LED要等BEEP执行完后再执行。
(3)打开预处理宏定义#define SCHEDULER_HOOK,把LED和BEEP优先级都设置为20,重新构建并下载。由下图可以看到,系统先运行main线程,再运行tshell线程,这是因为系统中main线程优先级默认为10,比tshell默认优先级20高,所以系统先运行main线程。
tshell运行后LED线程和BEEP线程接着轮流运行,由于这3个线程的优先级都是20,所以他们在属于同一个优先级的队列中,并且按启动先后顺序排列(注意:是启动顺序,即rt_thread_startup()函数的执行顺序,而不是创建程序),调度顺序也是按照启动的先后顺序进行的。
LED线程和BEEP线程退出后,进入tidle0线程运行,tidle0优先级在系统中最低,当所有高优先级的线程退出或者睡眠时,会进入tidle0线程运行。
(4)打开预处理宏定义#define SCHEDULER_HOOK,把LED和BEEP优先级分别设置为20、19。
修改后重新构建并下载程序,观察终端如图所示。系统运行顺序为:
main线程→BEEP线程→tshell线程→LED线程
总结
在操作系统中,所有线程各自独立运行,所有线程看起来是同时工作的,但在只有一个CPU核的情况下,在同一时刻只能有一个线程在CPU上运行,操作系统为每个线程分配一定的运行时间片,当线程的运行时间耗尽时,操作系统会调度下一个线程到CPU运行。由于时间片很小,使得我们觉得线程是在同时运行的。