RT-THREAD 内核快速入门(一)线程

系列文章

RT-THREAD 内核快速入门(二)定时器

RT-THREAD 内核快速入门(三) 信号量,互斥量,事件

RT-THREAD 内核快速入门(四)邮箱,消息队列,信号

RT-THREAD 内核快速入门(五)内存管理与中断管理

基于STM32Cubemx移植Rt-thread-nano

前言

这个系列将会从RT-thread内核将会框架和总体下手,同时给出需要注意的核心,以及内核之间的联系(官方文档没有对这些做出比较综合的解释)。函数具体用法不做讨论,自己看文档就可以了,更重要的是理解RTOS的运行方式与运行原理,以及编程需要注意的,这些重难点才是本文讨论的。理解好运行的方式与原理,其他的细节能够很快速的攻破。这里所谓的快速上手就是,理解RTOS的运行方式与运行原理,知道怎么运行了,什么运行方式会出现什么结果,RTOS优劣势,裸机与之相比又有什么优势,也就能够明确与裸机编程的区别,以及如何进行运用编程,将RTOS运行到自己的项目来。那系好安全带,我们进入第一章。

文章的内容是相关联的层层递进的,如果零基础的同学可以先从第一章开始了解。

注意:
这个系列并不会对用到的函数进行介绍,想要了解函数的具体用法,可以先了解运行方式与运行原理之后,之后详细再具体看用法。想要比较详细地了解细节,自己看文档。链接就放在这里啦。

官方文档


一、RT-THREAD 内核快速入门(一)

本文的环境keil,用Keil 模拟器 STM32F103 ,例程可以直接从官方文档下载,也会用到这个例程的一些源码。
下载例程

一、线程重要概念

解释:

线程也可以说是任务,举个煮饭例子,做饭分为三步:拿米,洗米,煮饭。这三个可以是三个任务,也可以是三个线程。将煮饭这一大步分为三个动作去做(线程)去做,顺序执行。线程任务的载体,具有一些属性,我们做饭只是通过划分为三个线程的这种方式去做饭,可以直接在main中做,一个大的线程中做。

线程的作用:

从上面的例子知道,线程只是达成任务的一种方式。利用多线程编程,将任务划分,使得思维更清晰,开发更迅速。多线程具有他自己的优势,在某种情况下,能加快开发进度。相应的,调试起来也更难。

线程的五个重要状态:

初始,就绪,运行,挂起,关闭。

初始状态:刚创建线程,还未放入队列中就是初始状态

就绪状态:线程(任务)以及准备好了,等到某些条件,线程就进入运行状态。

挂起状态:线程运行需要的一些条件,如果没有这些条件,就进入挂起。

关闭状态:线程从队列中脱离/删除。

在这五个状态中,最核心的就是就绪和挂起,线程的状态只要在队列中,一般来说,不是就绪,运行就是挂起。我们控制线程的状态一般控制线程从就绪到挂起,控制的这个过程就通过线程间的通信或者同步。下面的一些函数就是控制线程的状态的,就绪与挂起的函数是最多的,核心就这两个家伙。

在这里插入图片描述

二、创建线程

创建线程先前,还需要大概了解启动流程(大概从哪里开始运行,我们的代码实际是在哪运行的)和自处初始化(调用函数的一种方式)

启动流程:

这里仅仅做简单讨论,讨论仅仅与main启动,常规裸机启动的一些区别。

裸机启动流程:启动文件 - >中间省略 - > main
rt-thread启动:启动文件 - >中间省略 - > rtthread_startup -> main

rt-thread启动和裸机启动的初略区别在,rtt启动是从这个函数rtthread_startup开始对内核进行初始化,最后到main线程,一般我们写函数初始化,比如线程创建是在main中的。

自初始化:

调用宏定义:

MSH_CMD_EXPORT(command, desc)

进行我们的函数初始化,传入函数的地址即可,与字符串(串口1命令)。调用这个宏是在main之前进行初始化,这样的隐性调用,不必想在写裸机那样进行直接调用,也能进行调用,我们在这里进行线程的创建。

线程重要属性:

优先级,时间片,栈大小,入口函数,线程结构体。

优先级:线程优先级,线程按照优先级进行执行,优先级等级越低优先级越高,0最高。如果有两个相同优先级的线程,就轮流执行。

时间片:每个线程执行的时间,仅仅在优先级相同的情况有用

栈大小:存放线程的局部变量以及函数入口和出口的内存区,这个在切换线程的时候,将原先执行的状态保存下来,直到下一个执行这个线程,读取线程的保存状态,比如局部变量等。这样就不会像执行函数那样,跳出函数就丢失了变量以及跳出时候执行的位置。

入口函数:线程实际执行的函数

入口函数参数:传递到入口函数的参数,通常是RT_NULL

线程结构体:内核通过线程结构体管理线程。

创建静态线程:

例程1:优先级相同线程

/*
 * 程序清单:创建/删除、初始化线程
 *以轮流的方式运行
 * 这个例子会创建两个线程,两个静态线程。
 * 一个线程在运行完毕后自动被系统删除,另一个线程一直打印计数。
 */
#include <rtthread.h>

#define THREAD_PRIORITY         25
#define THREAD_STACK_SIZE       512
#define THREAD_TIMESLICE        5


/* 线程1的入口函数 */
static void thread1_entry(void *parameter)
{
    rt_uint32_t count = 0;
		while(1)
		{
			count++;
			rt_kprintf("thread1 is ruing count is: %d\n", count);
			rt_thread_mdelay(300);
		}
}
//对齐方式
ALIGN(RT_ALIGN_SIZE)

static char thread1_stack[1024];
static struct rt_thread thread1;

static char thread2_stack[1024];
static struct rt_thread thread2;
/* 线程2入口 */
static void thread2_entry(void *param)
{
    rt_uint32_t count = 0;
		while(1)
		{
			count++;
			rt_kprintf("thread2 is ruing count is: %d\n", count);
			rt_thread_mdelay(300);//线程挂起,让出CPU持有权,知道300ms后,线程唤醒,参与调度
			if(count >5) break;
		}
    rt_kprintf("thread2 exit\n");
    /* 线程2运行结束后也将自动被系统删除
    (线程控制块和线程栈依然在idle线程中释放) */
}

/* 删除线程示例的初始化 */
int thread_sample(void)
{
    /* 初始化线程1,名称是thread1,入口是thread1_entry */
    rt_thread_init(&thread1,
                   "threa1",
                   thread1_entry,
                   RT_NULL,
                   &thread1_stack[0],
                   sizeof(thread1_stack),
                   THREAD_PRIORITY - 1, THREAD_TIMESLICE);
    rt_thread_startup(&thread1);
									 
    /* 初始化线程2,名称是thread2,入口是thread2_entry */
    rt_thread_init(&thread2,
                   "thread2",
                   thread2_entry,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack),
                   THREAD_PRIORITY - 1, THREAD_TIMESLICE);
    rt_thread_startup(&thread2);

    return 0;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(thread_sample, thread sample);


运行结果:线程间轮流执行
在这里插入图片描述

例程2:优先级不同线程。

优先级高的先执行,优先级低的不能执行实验,线程无线程让出CPU

/*
 * 程序清单:创建/删除、初始化线程
 *以轮流的方式运行
 * 这个例子会创建两个线程,两个静态线程。
 * 一个线程在运行完毕后自动被系统删除,另一个线程一直打印计数。
 */
#include <rtthread.h>

#define THREAD_PRIORITY         25
#define THREAD_STACK_SIZE       512
#define THREAD_TIMESLICE        5


/* 线程1的入口函数 */
static void thread1_entry(void *parameter)
{
    rt_uint32_t count = 0;
		while(1)
		{
			count++;
			rt_kprintf("thread1 is ruing count is: %d\n", count);
			rt_thread_mdelay(300);
		}
}
//对齐方式
ALIGN(RT_ALIGN_SIZE)

static char thread1_stack[1024];
static struct rt_thread thread1;

static char thread2_stack[1024];
static struct rt_thread thread2;
/* 线程2入口 */
static void thread2_entry(void *param)
{
    rt_uint32_t count = 0;
		while(1)
		{
			count++;
			rt_kprintf("thread2 is ruing count is: %d\n", count);
			rt_thread_mdelay(300);
			if(count >5) break;
		}
    rt_kprintf("thread2 exit\n");
    /* 线程2运行结束后也将自动被系统删除
    (线程控制块和线程栈依然在idle线程中释放) */
}

/* 删除线程示例的初始化 */
int thread_sample(void)
{
    /* 初始化线程1,名称是thread1,入口是thread1_entry */
    rt_thread_init(&thread1,
                   "threa1",
                   thread1_entry,
                   RT_NULL,
                   &thread1_stack[0],
                   sizeof(thread1_stack),
                   THREAD_PRIORITY - 1, THREAD_TIMESLICE);
    rt_thread_startup(&thread1);
									 
    /* 初始化线程2,名称是thread2,入口是thread2_entry */
    rt_thread_init(&thread2,
                   "thread2",
                   thread2_entry,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack),
                   THREAD_PRIORITY, THREAD_TIMESLICE);
    rt_thread_startup(&thread2);

    return 0;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(thread_sample, thread sample);

运行结果:
在这里插入图片描述

例程三:栈溢出实验
常出现的错误

/*
 * 程序清单:栈溢出实验
 * 这个例子会创建两个线程,两个静态线程。
 * 一个线程在运行完毕后自动被系统删除,另一个线程一直打印计数。
 */
#include <rtthread.h>

#define THREAD_PRIORITY         25
#define THREAD_STACK_SIZE       512
#define THREAD_TIMESLICE        5


/* 线程1的入口函数 */
static void thread1_entry(void *parameter)
{
    rt_uint32_t count = 0;
		while(1)
		{
			count++;
			rt_kprintf("thread1 is ruing count is: %d\n", count);
			rt_thread_mdelay(500);
		}
}
//对齐方式
ALIGN(RT_ALIGN_SIZE)

static char thread1_stack[1024];
static struct rt_thread thread1;

static char thread2_stack[50];
static struct rt_thread thread2;
/* 线程2入口 */
static void thread2_entry(void *param)
{
    rt_uint32_t count = 0;
		while(1)
		{
			count++;
			rt_kprintf("thread2 is ruing count is: %d\n", count);
			if(count >5) break;
		}
    rt_kprintf("thread2 exit\n");
    /* 线程2运行结束后也将自动被系统删除
    (线程控制块和线程栈依然在idle线程中释放) */
}

/* 删除线程示例的初始化 */
int thread_sample(void)
{
    /* 初始化线程1,名称是thread1,入口是thread1_entry */
    rt_thread_init(&thread1,
                   "threa1",
                   thread1_entry,
                   RT_NULL,
                   &thread1_stack[0],
                   sizeof(thread1_stack),
                   THREAD_PRIORITY - 1, THREAD_TIMESLICE);
    rt_thread_startup(&thread1);
									 
    /* 初始化线程2,名称是thread2,入口是thread2_entry */
    rt_thread_init(&thread2,
                   "thread2",
                   thread2_entry,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack),
                   THREAD_PRIORITY, THREAD_TIMESLICE);
    rt_thread_startup(&thread2);

    return 0;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(thread_sample, thread sample);

在这里插入图片描述

三、例程分析

rt-thread的运行调度方式是基于优先级的全抢占式多线程调度算法,即在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢占的,包括线程调度器自身。

根据定义对运行调度进行分析:

  • 优先级高的得到优先执行,如果优先级高的一直在运行状态,不挂起,那么优先级低的得不到运行。
  • 中断和调度器上锁部分以及禁止中断代码不可抢占(为什么后面文章会分析)
  • 相同优先级按照时间片运行,线程1时间片运行完毕就轮到线程2

运行调度与线程状态的关系:唤醒与挂起是最重要的关系,这里是需要理解非常清楚,才能基本运用多线程线程进行编程。

  • 线程调度是对就绪状态的线程进行调度,对于就绪状态的线程,按照优先级进行优先运行
  • 挂起线程不参与调度
  • 线程可以通过某种方式唤醒线程,使之进入就绪状态

核心通过挂起与就绪控制线程使得低优先级线程得以运行,高优先级线程让出CPU控制权。

分析如下:
例程1:
创建两个线程,启动进入就绪状态,线程1先执行,经历一些时间片,然后线程休眠(调用rt_thread_mdelay)函数,进入挂起状态。线程2从就绪状态进入运行状态,然后休眠。经过500ms后线程1唤醒,从挂起转换到就绪状态,又经历休眠。

例程2:
两个优先级,线程优先级比线程2优先级高,线程1一直运行,线程2得不到运行。常常出现的问题,线程一直拥有CPU的执行权,导致低优先级的不能运行。

例程3:
容易出现的错误,线程栈溢出,导致不可程序运行。

运用重点

  • 线程优先级:做完任务需要让出CPU控制权,使得低优先级得到运行
  • 线程栈容易溢出,这个超级容易犯错
  • 理解好线程的就绪与挂起的状态,以及能够线程调度的方式以及记得什么时候不能进行线程调度。很好理解后面的线程间的通信与同步。
  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值