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) );  /* 线程栈大小,单位为字节 */


审核编辑:符乾江

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

    关注

    5062

    文章

    18984

    浏览量

    302362
  • 线程机制
    +关注

    关注

    0

    文章

    2

    浏览量

    5586
  • RT Thread操作系统

    关注

    0

    文章

    4

    浏览量

    687
收藏 人收藏

    评论

    相关推荐

    线程创建成功了,为啥ai_thread_entry()函数不运行呢?

    我这个线程创建成功了,为啥ai_thread_entry()函数不运行呢? void airun_thread() { /* 创建 se
    发表于 09-27 09:35

    如何在RT-thread studio的裸机例程上移植freertos?

    如何在RT-thread studio的裸机例程上移植freertos
    发表于 09-13 06:32

    玩转RT-Thread之消息队列的应用

    在嵌入式系统开发中,实时处理串口和ADC数据是一项重要的任务。本文将介绍如何在RT-Thread实时操作系统中,利用消息队列来同时处理来自串口和ADC的数据。通过这种方法,我们能够高效地管理和处理
    的头像 发表于 07-23 08:11 524次阅读
    玩转<b class='flag-5'>RT-Thread</b>之消息队列的应用

    使用rt_thread_mdelay函数后出现hardfault的原因?

    问题:在一个线程中共有前后2部分使用rt_thread_mdelay()进行延时等待,前面部分延时是正常的,后面部分进入rt_thread_mdelay()函数后立马打印hardfault错误,定位
    发表于 07-16 07:07

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

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

    RT-thread中运行了好多个线程,有的线程很长时间不执行是什么原因?

    RT-thread中运行了好多个线程,有的线程很长时间不执行,通过什么方式知道线程因为什么原因阻塞
    发表于 03-22 06:48

    【从0开始创建AWTK应用程序】编译应用到RTOS平台

    AWTK是基于C语言开发的跨平台GUI框架。本系列文章介绍如何从0开始创建AWTK应用程序,包括搭建开发调试环境、使用AWTK创建Hello工程并在模拟器上运行、将AWTK应用程序移植到其它平台。在
    的头像 发表于 03-21 08:23 553次阅读
    【从0<b class='flag-5'>开始创建</b>AWTK应用程序】编译应用到RTOS平台

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

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

    线程中调用rt_thread_mdelay()函数程序卡死了怎么解决?

    线程中调用rt_thread_mdelay()函数程序卡死。搞了两天也不知道问题出在哪,怎么解决。 int main(void) { interrupt_config
    发表于 02-26 08:39

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

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

    使用rt_thread nano有办法查看线程的资源占用情况吗?

    使用rt_thread nano ,有办法查看线程的资源占用情况麽?
    发表于 02-26 06:05

    RT-Thread Nano在调用rt_thread_control时,修改线程优先级一直不执行的原因?如何解决?

    RT-Thread Nano在调用rt_thread_control时,修改线程优先级一直不执行
    发表于 02-23 07:32

    线程池的创建方式有几种

    线程池是一种用于管理和调度线程的技术,能够有效地提高系统的性能和资源利用率。它通过预先创建一组线程并维护一个工作队列,将任务提交给线程池来处
    的头像 发表于 12-04 16:52 809次阅读

    【从0开始创建AWTK应用程序】创建应用程序并在模拟器运行

    AWTK是基于C语言开发的跨平台GUI框架。本系列文章介绍如何从0开始创建AWTK应用程序,包括搭建开发调试环境、使用AWTK创建Hello工程并在模拟器上运行、将AWTK应用程序移植到其它平台
    的头像 发表于 12-01 08:24 457次阅读
    【从0<b class='flag-5'>开始创建</b>AWTK应用程序】<b class='flag-5'>创建</b>应用程序并在模拟器运行

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

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