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

描述

      在裸机系统 中,他们统统放在一个叫栈的地方,栈是单片机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,分别用来指向链表中的下一个节点和上一个节点。

嵌入式双向链表轮询示意图

双向链表的相关操作,这些函数均在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;
}

实际上进行了如下操作:

嵌入式链表初始化示意图

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

表头后面插入一个节点

/* 在双向链表头部插入一个节点*/
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) );  /* 线程栈大小,单位为字节 */


审核编辑:符乾江

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

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分