在裸机系统 中,他们统统放在一个叫栈的地方,栈是单片机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) ); /* 线程栈大小,单位为字节 */
审核编辑:符乾江
全部0条评论
快来发表一下你的评论吧 !