0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

stm32裸机RT—thread开始创建线程详解

嵌入式应用开发 来源:嵌入式应用开发 作者:嵌入式应用开发 2022-05-19 15:02 次阅读

在裸机系统 中,他们统统放在一个叫栈的地方,栈是单片机RAM里面一段连续的内存空间,栈的大小一般在启动文件或者链接脚 本里面指定,最后由C库函数_main进行初始化。

在多线程系统中,每个线程都是独立的,互不干扰的,所以要为每个线程都分配独立的栈空间,这个栈空间通常是一个预先定义好的全局数组,也可以是动态分配的一段内存空间,但它们都存在于RAM中。

创建线程栈,需要几个线程创建几个线程栈,这里线程栈实际上就是全局变量,大小为512,这里创建两个工作线程,如下:

ALIGN(RT_ALIGN_SIZE)//
/* 定义线程栈*/
rt_uint8_t rt_flag1_thread_stack[512];
rt_uint8_t rt_flag2_thread_stack[512];

ALIGN是一个 带参宏,在rtdef.h中定义,设置变量需要多少个字节对齐,对在它下面的变量起作用。RT_ALIGN_SIZE是一个在rtconfig.h(rtconfig.h第一次使用需要在User文件夹下面新建然后添加到工程user这个组文件)中定义 的宏,默认为4,表示4个字节对齐。

#ifndef __RTTHREAD_CFG_H__
#define __RTTHREAD_CFG_H__

#define RT_THREAD_PRIORITY_MAX  32     /* 最大优先级 */
#define RT_ALIGN_SIZE           4      /* 多少个字节对齐 */

#endif /* __RTTHREAD_CFG_H__ */

下一步,定义线程函数:

这里我们建立线程函数,并给函数命名flag1_thread_entry,flag2_thread_entry两个函数 ,分别代表着两个无限循环不返回的线程

/* 软件延时 */
void delay (uint32_t count)
{
    for(; count!====0; count--);
}
/* 线程1 */
void flag1_thread_entry( void *p_arg )//   (1)
{
    for( ;; )
    {
        flag1 ==== 1;
        delay( 100 );
        flag1 ==== 0;
        delay( 100 );

        /* 线程切换,这里是手动切换 */
        rt_schedule();
    }
}
/* 线程2 */
void flag2_thread_entry( void *p_arg )//   (2)
{
    for( ;; )
    {
        flag2 ==== 1;
        delay( 100 );
        flag2 ==== 0;
        delay( 100 );

        /* 线程切换,这里是手动切换 */
        rt_schedule();
    }
}

下一步定义线程控制块

在裸机系统中,程序的主体是CPU按照顺序执行的。而在多线程系统中,线程的执行是由系统调度的。系统为了顺利的调度线程,为每个线程都额外定义了一个线程控制块,这个线程控制块就相当于线程的身份证,里面存有线程的所有信息,比如线程的栈指针,线程名称,线程的形参等。有了这个线程控制块之后,以后系统对线程的全部操作都可以通过这个线程控制块来实现。定义一个线程控制块需要一个新的数据类型,该数据类型在rtdef.h这个头 文件中声明,使用它可以为每个线程都定义一个线程控制块实体。下面来看一下,此结构体定义函数及内容:

struct rt_thread
{
    void        *sp;                    /* 线程栈指针 */
    void        *entry;              /* 线程入口地址 */
    void        *parameter;       /* 线程形参 */
    void        *stack_addr;      /* 线程起始地址 */
    rt_uint32_t stack_size;       /* 线程栈大小,单位为字节 */

    rt_list_t   tlist;            /* 线程链表节点 */
};
typedef struct rt_thread *rt_thread_t;// 

我们在main.c文件中为两个线程定义的线程控制块。

/* 定义线程控制块 */
struct rt_thread rt_flag1_thread;
struct rt_thread rt_flag2_thread;

下一步,创建线程实现函数

线程的栈,线程的函数实体,线程的控制块最终需要联系起来才能由系统进行统一调度。那么这个联系的工作就由线 程初始化函数rt_thread_init()来实现,该函数在thread.c(thread.c第一次使用需要自行在文件夹rtthread/3.0.3/src中新建并添加到工程的rtt/source组)中定义,在rtthread.h中声明,所有跟线程相关的函数都在这个文件定义。rt_thread_init()函数的实现如下 :

rt_err_t rt_thread_init(struct rt_thread *thread,//          (1)
                        void (*entry)(void *parameter),//    (2)
                        void             *parameter,//       (3)
                        void             *stack_start,//     (4)
                        rt_uint32_t       stack_size)//      (5)
{
    rt_list_init(&(thread->tlist));//                         (6)

    thread->entry ==== (void *)entry;//                       (7)
    thread->parameter ==== parameter;//                       (8)

    thread->stack_addr ==== stack_start;//                    (9)
    thread->stack_size ==== stack_size;//                     (10)

    /* 初始化线程栈,并返回线程栈指针 */ //                      (11)
    thread->sp ==== (void *)rt_hw_stack_init( thread->entry,
                                        thread->parameter,
                                    (void *)((char *)thread->stack_addr + thread->stack_size - 4) );

    return RT_EOK;//                                          (12)
}

(1) :thread是线程控制块指针。

(2) :entry 是线程函数名, 表示线程的入口。

(3) :parameter是线程形参,用于传递线程参数。

(4) :stack_start 用于指向线程栈的起始地址。

(5) :stack_size表示线程栈的大小,单位为字节。

(7) :将线程入口保存到线程控制块的entry成员中。

(8) :将线程入口形参保存到线程控制块的parameter成员中。

(9) :将线程栈起始地址保存到线程控制块的stack_start成员中。

(10) :将线程栈起大小保存到线程控制块的stack_size成员中。

(11) :初始化线程栈,并返回线程栈顶指针。

rt_hw_stack_init()用来初始化线程栈, 当线程第一次运行的时候,加载到CPU寄存器参数就放在线程栈里面,该函数在cpuport.c中实现。cpuport.c第一次使用需要自行 在rtthread/3.0.3/ libcpu/arm/cortex-m3(cortex-m4或cortex-m7)文件夹下新建,然后添加到工程 的rtt/ports组中。

下一步,定义链表节点数据类型:

struct rt_list_node
{
   struct rt_list_node *next;              /* 指向后一个节点 */
   struct rt_list_node *prev;              /* 指向前一个节点 */
};
typedef struct rt_list_node rt_list_t;

rt_list_t 类型的节点里面有两个rt_list_t类型的节点指针next和prev,分别用来指向链表中的下一个节点和上一个节点。

pYYBAGKF8peAQwaOAAB3seF5MG4606.png双向链表轮询示意图

双向链表的相关操作,这些函数均在rtservice.h中实现,rtservice.h第一次使用需要自行在rtthread/3.0.3/include文件夹下新建,然后添加到工程的rtt/source组中。下面从链表操作逐步实现。

链表节点初始化:

rt_inline void rt_list_init(rt_list_t *l)
{
    l->next ==== l->prev ==== l;
}

实际上进行了如下操作:

poYBAGKF9FaAZa-8AAA6i6E1JnY592.png链表初始化示意图

下面在链表头部和尾部分别插入一个链表节点:

表头后面插入一个节点

/* 在双向链表头部插入一个节点*/
rt_inline void rt_list_insert_after(rt_list_t *l, rt_list_t *n)
{
    l->next->prev ==== n; /* 第 1 步*/
    n->next ==== l->next; /* 第 2 步*/
    l->next ==== n; /* 第 3 步*/
    n->prev ==== l; /* 第 4 步*/
}

表头前边插入一个节点

rt_inline void rt_list_insert_before(rt_list_t *l, rt_list_t *n)
{
    l->prev->next ==== n; /* 第 1 步*/
    n->prev ==== l->prev; /* 第 2 步*/
    l->prev ==== n; /* 第 3 步*/
    n->next ==== l; /* 第 4 步*/
}

从双向链表删除一个节点

rt_inline void rt_list_remove(rt_list_t *n)
{
    n->next->prev ==== n->prev; /* 第 1 步*/
    n->prev->next ==== n->next; /* 第 2 步*/
    n->next ==== n->prev ==== n; /* 第 3 步*/
}

创建线程初始化函数

/* 线程栈初始化 */
rt_uint8_t *rt_hw_stack_init(void       *tentry,//                  (1)
                            void       *parameter,//                 (2)
                            rt_uint8_t *stack_addr)// 线程栈顶地址-4,在该函数调用的时候传进来的是线程栈的栈顶地址-4
{

    struct stack_frame *stack_frame;//                               (4)
    rt_uint8_t         *stk;
    unsigned long       i;

    /* 获取栈顶指针
    rt_hw_stack_init 在调用的时候,传给stack_addr的是(栈顶指针)*/
    stk  ==== stack_addr + sizeof(rt_uint32_t);//                       (5)

    /* 让stk指针向下8字节对齐 */
    stk  ==== (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);//       (6)

    /* stk指针继续向下移动sizeof(struct stack_frame)个偏移 */
    stk -==== sizeof(struct stack_frame);//                             (7)

    /* 将stk指针强制转化为stack_frame类型后存到stack_frame */
    stack_frame ==== (struct stack_frame *)stk;//                       (8)

    /* 以stack_frame为起始地址,将栈空间里面的sizeof(struct stack_frame)
    个内存初始化为0xdeadbeef */
    for (i ==== 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++)//   (9)
    {
            ((rt_uint32_t *)stack_frame)[i] ==== 0xdeadbeef;
    }

    /* 初始化异常发生时自动保存的寄存器 *///                            (10)
    stack_frame->exception_stack_frame.r0  ==== (unsigned long)parameter; /* r0 : argument */
    stack_frame->exception_stack_frame.r1  ==== 0;                        /* r1 */
    stack_frame->exception_stack_frame.r2  ==== 0;                        /* r2 */
    stack_frame->exception_stack_frame.r3  ==== 0;                        /* r3 */
    stack_frame->exception_stack_frame.r12 ==== 0;                        /* r12 */
    stack_frame->exception_stack_frame.lr  ==== 0;                        /* lr */
    stack_frame->exception_stack_frame.pc  ==== (unsigned long)tentry;    /* entry point, pc */
    stack_frame->exception_stack_frame.psr ==== 0x01000000L;              /* PSR */

    /* 返回线程栈指针 */
    return stk;//                                                    (11)
}

(5) :获取栈顶指针,将栈顶指针传给指针stk。rt_hw_stack_init()函数 在rt_thread_init ()函数中调用的时候传给形参stack_addr的值是栈顶指针减去4,所以现在 加上sizeof(rt_uint32_t)刚好与减掉的4相互抵消,即传递给stk的是栈顶指针。

(6) :让stk这个指针向下8个字节对齐,确保stk是8字节对齐的地址。 在Cortex-M3(Cortex-M4或Cortex-M7)内核的单片机中,因为总线宽度是32位的,通常只要栈保持4字节对齐就 行,可这样为啥要8字节?难道有哪些操作是64位的?确实有,那就是浮 点运算,所以要8字节对齐(但是目前我们都还没有涉及到浮点运算,只是为了后续兼容浮点运行的考虑)。 如果栈顶指针是8字节对齐的,在进行向下8字节对齐的时候,指针不会移动,如果不是8字节对齐的, 在做向下8字节对齐的时候,就会空出几个字节,不会使用,比如当stk是33,明显不能整除8, 进行向下8字节对齐就是32,那么就会空出一个字节不使用。

(7) :stk指针继续向下移动sizeof(struct stack_frame) 个偏移,即16个字的大小。

ok!!!!!!!

这些了解了之后,我们在主函数内加入线程初始化即可

/* 初始化线程 */
rt_thread_init(&rt_flag1_thread,                 /* 线程控制块 */
                flag1_thread_entry,               /* 线程入口地址 */
                RT_NULL,                          /* 线程形参 */
                &rt_flag1_thread_stack[0],        /* 线程栈起始地址 */
                sizeof(rt_flag1_thread_stack) );  /* 线程栈大小,单位为字节 */
/* 将线程插入到就绪列表 */

/* 初始化线程 */
rt_thread_init(&rt_flag2_thread,                 /* 线程控制块 */
                flag2_thread_entry,               /* 线程入口地址 */
                RT_NULL,                          /* 线程形参 */
                &rt_flag2_thread_stack[0],        /* 线程栈起始地址 */
                sizeof(rt_flag2_thread_stack) );  /* 线程栈大小,单位为字节 */


审核编辑:符乾江

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式
    +关注

    关注

    4981

    文章

    18274

    浏览量

    288351
  • 线程机制
    +关注

    关注

    0

    文章

    2

    浏览量

    5573
  • RT Thread操作系统

    关注

    0

    文章

    4

    浏览量

    680
收藏 人收藏

    评论

    相关推荐

    rt-thread是如何在线程运行结束后识别到的呢?

    _mdelay(1000); } return RT_EOK; } 总所周知,main本身就是rt-thread创建线程,我在这个线程
    发表于 03-22 08:20

    RT-Thread创建SQLite数据库失败是什么原因呢?

    STM32F103ZET6基于RT-Thread V4.1.1,文件系统littlefs,SQLite是从github下载的;在线程中调用示例代码create_student_tbl()创建
    发表于 03-05 06:35

    RTT Nano线程创建成功,没有进入线程创建的函数运行怎么解决?

    RTT Nano线程创建成功,没有进入线程创建的函数运行int main(void) { interrupt_config(); // gd_eval_led_init(LED1
    发表于 02-26 06:27

    RT-Thread Nano入门:独立看门狗(IWDT)

    本文主要介绍怎么用RT-Thread Nano实现独立看门狗IWDT驱动,创建一个喂狗线程,实现定时喂狗功能。
    的头像 发表于 11-22 11:04 805次阅读
    <b class='flag-5'>RT-Thread</b> Nano入门:独立看门狗(IWDT)

    RT-thread源码移植到STM32F10x和STM32F4xx

    RT-thread源码移植到STM32F10x和STM32F4xx: 一、源码下载 点击入门->下载   在历史版本里边随便选取一个   会进入百度云盘的下载地址,里边有全部版本的源码。这里下载
    的头像 发表于 11-15 09:38 1003次阅读
    <b class='flag-5'>RT-thread</b>源码移植到<b class='flag-5'>STM32</b>F10x和<b class='flag-5'>STM32</b>F4xx

    RT-Thread使用Soft RTC(软件模拟RTC)

    开发环境:野火的stm32f407,rt-thread studio版本是版本: 2.2.6,rt-thread 使用版本为4.0.3,stm32f4的资源包为0.2.2。
    的头像 发表于 10-12 17:39 351次阅读
    <b class='flag-5'>RT-Thread</b>使用Soft RTC(软件模拟RTC)

    RT-Thread在Lan8720a和 lwip基础上移植ntp流程

    开发环境:野火的stm32f407,rt-thread studio版本是版本: 2.2.6,stm32f4的资源包为0.2.2。以RT-Thread中Lan8720和lwip协议栈的
    的头像 发表于 10-12 16:59 865次阅读
    <b class='flag-5'>RT-Thread</b>在Lan8720a和 lwip基础上移植ntp流程

    RT-Thread v5.0.2 发布

    RT-Thread 代码仓库地址: ●  https://github.com/RT-Thread/rt-thread RT-Thread 5.0.2 版本发布日志详情: ●  htt
    的头像 发表于 10-10 18:45 773次阅读
    <b class='flag-5'>RT-Thread</b> v5.0.2 发布

    新书上架|嵌入式系统原理及应用——基于STM32RT-Thread

    教程书籍编撰过程中的第一选择! 本次上新的书籍为胡永涛主编的《嵌入式系统原理及应用——基于STM32RT-Thread》。 本书以意法半导体(ST)的STM32L431系列微控制器为硬件核心,采用
    的头像 发表于 09-25 18:25 536次阅读
    新书上架|嵌入式系统原理及应用——基于<b class='flag-5'>STM32</b>和<b class='flag-5'>RT-Thread</b>

    rt_application创建失败的原因是什么呢?如何解决?

    :214 我用的是n32l4xcl,sram有24k, 用的是rtthread 标准版 这样创建线程 audio_thread = rt_thread_create(\"audio\
    发表于 09-07 16:27

    rt-thread线程栈初始化参数分析

    RT-Thread线程初始化的代码内有一段初始化线程堆栈的代码
    的头像 发表于 08-14 16:50 992次阅读
    <b class='flag-5'>rt-thread</b><b class='flag-5'>线程</b>栈初始化参数分析

    STM32L4 RT-Thread Studio解决lptimer不工作的问题

    使用RT-Thread Studio 生成的基于STM32L4 的工程,发现开启PM框架后,lptimer不能工作。
    的头像 发表于 06-07 14:29 668次阅读
    <b class='flag-5'>STM32</b>L4 <b class='flag-5'>RT-Thread</b> Studio解决lptimer不工作的问题

    rt-thread main函数的使用和其它线程创建问题求助?

    rt-thread main函数只是一个线程任务,如果想再创建其它线程任务,是不是在void rt_application_init(voi
    发表于 05-12 15:24

    怎么解决创建多个线程和消息队列失败的问题呢?

    我使用的是STM32L010RB,在Main函数中创建了4个线程线程栈大小分别为256\\\\1024\\\\1024\\\\1024,结果在后面的
    发表于 05-05 14:20

    RT-Thread线程切换最终导致线程创建失败请各位指点一下是什么原因

    我在测试rt-thread 在龙芯2k上线程切换所需的花销,我的思路是创建1000个线程,所创将的线程都存放在全局静态数组中,
    发表于 04-27 10:49