【STM32F4】【银杏科技ARM+FPGA】iCore3移植RT-Thread--内核之线程的创建

  RT-Thread系统中线程是调度的最小单位,后面的内核讲解都是以线程为单位展开的,线程的本质就是我们平时跑的裸机函数,但是它添加了实时性的元素,可以函数级的抢占,但不存在中断嵌套导致栈溢出的问题,因为每个线程都有自己的栈,使得RT-Thread具有实时性。初学者对操作系统的线程不太了解,先从我们接触比较多的引入,以进程开始。

一、线程的引出

进程:当一个程序进入内存运行,即变成一个进程,进程是正在运行的程序,并且具有一定独立功能。例如我们的电脑同时打开了word、QQ、迅雷等,每个运行中的程序就是一个进程。

线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程,一个进程中可以有多个线程。例如我们打开多个QQ的聊天室,每个聊天室就是一个线程。

  我们知道,在跑裸机系统时,程序是放在main函数中while(1)中不断的循环,有顺序地跑里面的功能函数,例如LED的闪烁。如果加入了按键功能去检测外部的事件,且按键功能函数运行时间较长,就会影响到下一个功能函数的实时执行,例如此时另外一个按键按下,但是上一个按键功能还未执行完,即使按键松开后也未被执行,导致整个系统的实时响应能力变差,甚至导致事件丢失。由此中断响应可以将事件的响应和处理分割开来,大大提高了程序的实时响应能力,但是事件的处理还是在外部函数中顺序执行。从而引出多线程系统,虽然多线程系统的事件也是在中断中完成的,但是事件的处理是在线程中完成的,线程之间的切换所需要操作系统做的工作很少,占用的资源较少。

二、线程的重要属性

1.RT-Thread线程栈

  在多线程系统中,每个线程是独立互不干扰的,为此为每个线程分配独立的栈空间。在任何时刻,只有一个线程执行,RT-thread调度器决定运行哪个线程,调度器会不断的开启或关闭每一个线程,从表面上看所有的线程是同时执行的。RT-Thread具有独立的栈,用于存放线程的上下文环境(寄存器值、堆栈内容),当线程切换时,用于保存或读取上下文信息,恢复上次的执行环境。

2.RT-Thread线程状态

系统运行时同一时间只能允许一个线程运行,每个线程有五种运行状态,系统会自动根据它们的运行情况做调整:

初始状态(RT_THREAD_INIT):创建线程即为初始状态,此时不参与系统调度。

就绪状态(RT_THREAD_READY):该线程在就绪列表中,根据优先级等待CPU。

运行状态(RT_THREAD_RUNNING):线程当前正在运行。

挂起状态(RT_THREAD_SUSPEND):若线程正在等待某个时序或外部中断,线程被挂起,此时线程不参与调度。

关闭状态(RT_THREAD_CLOSE):该线程运行结束时将出于关闭状态。

3.线程调度

  线程根据线程的优先级调度,RT-Thread最大支持256个线程优先级(0~255),数值越小优先级越高,对于 ARM Cortex-M 系列,普遍采用 32 个优先级。最低优先级默认分配给空闲线程使用,用户一般不使用。在系统中,当有比当前线程优先级更高的线程就绪时,当前线程将立刻被换出,高优先级线程抢占处理器运行。

4.线程时间片

  RT-Thread只对优先级相同的就绪线程采用时间片轮转的方式调度,时间片起到约束线程单词运行时长的作用。为了实现线程轮转调度,系统把所有就绪的线程先入先出的原则进行排列,每当执行线程调度时,让它在CPU上运行一个时间片的时间,实现线程的轮转运行。

三、线程状态切换

  RT-Thread提供一系列的操作系统调用接口,使得线程的状态在这五个状态之间来回切换。

线程通过调用函数 rt_thread_create/init() 进入到初始状态(RT_THREAD_INIT);

初始状态的线程通过调用函数 rt_thread_startup() 进入到就绪状态(RT_THREAD_READY);就绪状态的线程被调度器调度后进入运行状态(RT_THREAD_RUNNING);当处于运行状态的线程调用①中函数或者获取不到资源时,将进入到挂起状态(RT_THREAD_SUSPEND);

处于挂起状态的线程,如果等待超时依然未能获得资源或由于其他线程释放了资源,那么它将返回到就绪状态。挂起状态的线程,如果调用 rt_thread_delete/detach() 函数,将更改为关闭状态(RT_THREAD_CLOSE);

而运行状态的线程,如果运行结束,就会在线程的最后部分执行 rt_thread_exit() 函数,将状态更改为关闭状态。

四、线程管理

  线程的相关操作包含:创建/初始化线程、启动线程、运行线程、删除/脱离线程。可以使用rt_thread_create()创建一个动态线程,使用rt_thread_init()初始化一个静态线程,动态线程与静态线程的区别是:动态线程是系统自动从动态内存堆上分配栈空间与线程句柄,静态线程是由用户分配栈空间与线程句柄。我们重点来讲动态线程和静态线程的创建和线程的启动。RT-Thread中的线程一般由三部分组成:线程代码、线程控制块、线程堆栈。

  线程控制块由结构体struct rt_thread表示,线程控制块是操作系统用于管理线程的一个数据结构,用于存放优先级、线程名称、线程状态等,也包含线程与线程之间连接用的链表结构。例如为led线程定义一个线程控制块:static struct rt_thread led_thread;

  静态线程和动态线程最大的区别是线程堆栈的使用,线程堆栈是一段连续的内存块,当线程切换后,为了满足线程切换和响应中断时保存CPU寄存器中的内容及调用其他函数时的准备,每个线程都要有自己的堆栈。静态线程会占用RW空间,但是不需要动态分配内存,运行时效率高;动态线程不会占用额外的RW空间,占用空间小,但是运行时需要动态分配内存,效率没有静态方式高。

  启动线程:当线程初始化好后,此时还不能参与操作系统的调度,通过rt_thread_startup()函数将初始化状态转为就绪状态,只有当线程进入就绪状态(RT_THREAD_READY)后才能参与操作系统的调度。

1、动态线程创建

  使用动态线程时,RT-Thread会动态申请线程控制块和堆栈空间,编译时,编译器不会感知到这段空间,只有在程序运行时,RT-Thread才会从系统堆中申请分配内存空间,当不需要使用该线程时,调用rt_thread_delete会将该申请的内存空间释放。

rt_thread_t rt_thread_create(const char *name,                //线程名称
                             void (*entry)(void *parameter),  //线程入口函数
                             void       *parameter,           //线程入口参数
                             rt_uint32_t stack_size,          //线程栈大小,单位为字节
                             rt_uint8_t  priority,            //线程的优先级
                             rt_uint32_t tick)                //线程的时间片大小

2、静态线程创建

  使用静态线程时,必须先定义静态的线程控制块,并且定义好堆栈空间,然后调用rt_thread_init来完成线程的初始化,线程控制块和堆栈所用的内存放在RW中,此内存在编译时已被确定,因为不是动态分配的,所以不会被释放,必须使用rt_thread_detach函数将该线程控制块从对象管理器中脱离。

static 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)        //线程的时间片大小

五、线程应用实例

具体代码见工程中thread_sample.c。

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

/*静态线程相关定义*/
#define THREAD_PRIORITY    25                     /*优先级 */
#define STACK_SIZE         512                    /* 栈大小*/
#define TIMESLICE          5                      /* 时间片*/

/* 静态线程三要素 */
static rt_uint8_t static_thread_stack[STACK_SIZE];        /* 线程栈       */
static struct rt_thread static_thread;                    /* 线程控制块   */
static void static_thread_entry(void* parameter);         /* 线程入口函数 */

/* 动态线程 */
static void dynamic_thread_entry(void* parameter);        /* 线程入口函数 */

/* 静态线程入口函数 */ 
static void static_thread_entry(void* parameter)
{
    rt_uint32_t i = 0;
      rt_kprintf("This is static thread!
");
    /* 无限循环*/
    while (1)
    {
        rt_kprintf("static thread count:%d 
", ++i);
        /* 等待0.5s,让出cpu权限,切换到其他线程 */
        rt_thread_delay(500);
    }
}
/* 动态线程入口函数 */
static void dynamic_thread_entry(void* parameter)
{
    rt_uint32_t i;
      rt_kprintf("This is dynamic thread!
");
        for (i = 0; i < 5; i++)
        {
          rt_kprintf("dynamic thread count:%d 
", i);
        }
}

static rt_thread_t tid1 = RT_NULL;
static struct rt_thread static_thread;

int thread_sample(void)
{
        /*创建静态线程:优先级25,时间片5个系统滴答,线程栈512字节*/
         rt_thread_init(&static_thread,
                        "static_thread",
                        static_thread_entry, 
                        RT_NULL,
                        (rt_uint8_t*)&static_thread_stack[0], 
                        STACK_SIZE, 
                        THREAD_PRIORITY, 
                        TIMESLICE);

        /* 创建成功则启动静态线程 */
        rt_thread_startup(&static_thread);
               
        /* 创建动态线程 : 优先级24,时间片5个系统滴答,线程栈512字节 */
        tid1 = rt_thread_create("dynamic_thread",
                                 dynamic_thread_entry,
                                 RT_NULL,
                                 STACK_SIZE,
                                 THREAD_PRIORITY-1,
                                 TIMESLICE);    

        rt_thread_startup(tid1);

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

六、实验结果如下:

打开putty ,选择正确的COM口,设置波特率为115200,下载程序完成后iCore3 ARM_LED三色灯循环闪烁,输入thread_sample命令即可运行。

原文地址:https://www.cnblogs.com/xiaomagee/p/13029514.html