title: rtthread线程定义与切换实现
date: 2020-10-22 12:39:15
tags: rtthread
创建线程
包括:线程栈大小、线程栈、线程函数、线程控制块
定义线程栈
在多线程系统中,每个线程都是独立的,互不干扰的,所以要为每个线程都分配独立的栈空间,这个栈空间通常是一个预先定义好的全局数组,也可以是动态分配的一段内存空间,但它们都存在于 RAM 中。
定义线程栈
线程栈即rt_uint8_t
类型数组
ALIGN(RT_ALIGN_SIZE)
/* 定义线程栈 */
rt_uint8_t rt_flag1_thread_stack[512];
rt_uint8_t rt_flag2_thread_stack[512];
重定义数据类型
rtdef.h
/* RT-Thread basic data type definitions */
typedef signed char rt_int8_t; /**< 8bit integer type */
typedef signed short rt_int16_t; /**< 16bit integer type */
typedef signed long rt_int32_t; /**< 32bit integer type */
typedef unsigned char rt_uint8_t; /**< 8bit unsigned integer type */
typedef unsigned short rt_uint16_t; /**< 16bit unsigned integer type */
typedef unsigned long rt_uint32_t; /**< 32bit unsigned integer type */
typedef int rt_bool_t; /**< boolean type */
/* 32bit CPU */
typedef long rt_base_t; /**< Nbit CPU related date type */
typedef unsigned long rt_ubase_t; /**< Nbit unsigned CPU related data type */
typedef rt_base_t rt_err_t; /**< Type for error number */
typedef rt_uint32_t rt_time_t; /**< Type for time stamp */
typedef rt_uint32_t rt_tick_t; /**< Type for tick count */
typedef rt_base_t rt_flag_t; /**< Type for flags */
typedef rt_ubase_t rt_size_t; /**< Type for size number */
typedef rt_ubase_t rt_dev_t; /**< Type for device */
typedef rt_base_t rt_off_t; /**< Type for offset */
/* boolean type definitions */
#define RT_TRUE 1 /**< boolean true */
#define RT_FALSE 0 /**< boolean fails */
#ifdef __CC_ARM
#define rt_inline static __inline
#define ALIGN(n) __attribute__((aligned(n)))
#elif defined (__IAR_SYSTEMS_ICC__)
#define rt_inline static inline
#define ALIGN(n) PRAGMA(data_alignment=n)
#elif defined (__GNUC__)
#define rt_inline static __inline
#define ALIGN(n) __attribute__((aligned(n)))
#else
#error not supported tool chain
#endif
#define RT_ALIGN(size, align) (((size) + (align) - 1) & ~((align) - 1))
#define RT_ALIGN_DOWN(size, align) ((size) & ~((align) - 1))
#define RT_NULL (0)
系统配置文件
rtconfig.h
#define RT_THREAD_PRIORITY_MAX 32 /* 最大优先级 */
#define RT_ALIGN_SIZE 4 /* 多少个字节对齐 */
定义线程函数
线程是一个独立的函数,函数主体无限循环且不能返回。
/* 线程1 */
void flag1_thread_entry( void *p_arg )
{
for( ;; )
{
flag1 = 1;
delay( 100 );
flag1 = 0;
delay( 100 );
/* 线程切换,这里是手动切换 */
rt_schedule();
}
}
/* 线程2 */
void flag2_thread_entry( void *p_arg )
{
for( ;; )
{
flag2 = 1;
delay( 100 );
flag2 = 0;
delay( 100 );
/* 线程切换,这里是手动切换 */
rt_schedule();
}
}
定义线程控制块
即结构体指针
/* 定义线程控制块 */
struct rt_thread rt_flag1_thread;
struct rt_thread rt_flag2_thread;
线程控制块类型
目前线程控制块结构体成员比较少
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;
线程链表节点类型
双向链表
rtdef.h
struct rt_list_node
{
struct rt_list_node *next; /* 指向后一个节点 */
struct rt_list_node *prev; /* 指向前一个节点 */
};
typedef struct rt_list_node rt_list_t;
链表节点的初始化、插入、删除
rtservice.h
/* 初始化链表节点 */
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;
n->next = l->next;
l->next = n;
n->prev = l;
}
rt_inline void rt_list_insert_before(rt_list_t *l, rt_list_t *n)
{
l->prev->next = n;
n->prev = l->prev;
l->prev = n;
n->next = l;
}
/* 从双向链表删除一个节点 */
rt_inline void rt_list_remove(rt_list_t *n)
{
n->next->prev = n->prev;
n->prev->next = n->next;
n->next = n->prev = n;
}
线程创建实现
初始化线程链表节点,参数赋值给线程控制块,初始化线程栈指针,线程栈指针返回给线程控制块
thread.c
rt_err_t rt_thread_init(struct rt_thread *thread,
void (*entry)(void *parameter),
void *parameter,
void *stack_start,
rt_uint32_t stack_size)
{
rt_list_init(&(thread->tlist));
thread->entry = (void *)entry;
thread->parameter = parameter;
thread->stack_addr = stack_start;
thread->stack_size = stack_size;
/* 初始化线程栈,并返回线程栈指针 */
thread->sp = (void *)rt_hw_stack_init( thread->entry,
thread->parameter,
(void *)((char *)thread->stack_addr + thread->stack_size - 4) );
return RT_EOK;
}
rt_hw_stack_init()函数
cpuport.c
/* 线程栈初始化 */
rt_uint8_t *rt_hw_stack_init(void *tentry, //线程入口地址
void *parameter, //线程形参
rt_uint8_t *stack_addr) //线程栈顶地址 - 4
{
struct stack_frame *stack_frame;
rt_uint8_t *stk;
unsigned long i;
/* 获取栈顶指针
rt_hw_stack_init 在调用的时候,传给stk的是(栈顶指针)*/
stk = stack_addr + sizeof(rt_uint32_t);
/* 让stk指针向下8字节对齐 */
stk = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);
/* stk指针继续向下移动sizeof(struct stack_frame)个偏移 */
stk -= sizeof(struct stack_frame);
/* 将stk指针强制转化为stack_frame类型后存到stack_frame */
stack_frame = (struct stack_frame *)stk;
/* 以stack_frame为起始地址,将栈空间里面的sizeof(struct stack_frame)
个内存初始化为0xdeadbeef */
for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++)
{
((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef;
}
/* 初始化异常发生时自动保存的寄存器 */
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;
}
如果栈顶指针是 8 字节对齐的,在进行向下 8 字节对齐的时候,指针不会移动,如果不是 8 字节对齐的,在做向下 8 字节对齐的时候,就会空出几个字节,不会使用,比如当 stk 是 33,明显不能整除 8,进行向下 8 字节对齐就是 32,那么就会空出一个字节不使用。
stk 指针继续向下移动 sizeof(struct stack_frame)
个偏移,即 16 个字的大小。如果栈顶指针一开始都是 8 字节对齐的
stk强制转换类型并赋值给指针stack_frame
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qMx3vPUG-1608216139673)(https://i.loli.net/2020/10/22/qGZNb4awLvkrOe3.png)]
for循环所作的内容如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vOMeBIGl-1608216139676)(https://i.loli.net/2020/10/22/6oaEMxS1uTQq3hf.png)]
最后初始化异常发生时自动保存的寄存器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pbFuKdhl-1608216139678)(https://i.loli.net/2020/10/22/hYTjU4XK9ogGPap.png)]
stack_frame结构体
struct stack_frame
{
/* r4 ~ r11 register
异常发生时需手动保存的寄存器 */
rt_uint32_t r4;
rt_uint32_t r5;
rt_uint32_t r6;
rt_uint32_t r7;
rt_uint32_t r8;
rt_uint32_t r9;
rt_uint32_t r10;
rt_uint32_t r11;
struct exception_stack_frame exception_stack_frame;
};
struct exception_stack_frame
{
/* 异常发生时自动保存的寄存器 */
rt_uint32_t r0;
rt_uint32_t r1;
rt_uint32_t r2;
rt_uint32_t r3;
rt_uint32_t r12;
rt_uint32_t lr;
rt_uint32_t pc;
rt_uint32_t psr;
};
错误码宏定义
rtdef.h
/*
*************************************************************************
* 错误码定义
*************************************************************************
*/
/* RT-Thread 错误码重定义 */
#define RT_EOK 0 /**< There is no error */
#define RT_ERROR 1 /**< A generic error happens */
#define RT_ETIMEOUT 2 /**< Timed out */
#define RT_EFULL 3 /**< The resource is full */
#define RT_EEMPTY 4 /**< The resource is empty */
#define RT_ENOMEM 5 /**< No memory */
#define RT_ENOSYS 6 /**< No system */
#define RT_EBUSY 7 /**< Busy */
#define RT_EIO 8 /**< IO error */
#define RT_EINTR 9 /**< Interrupted system call */
#define RT_EINVAL 10 /**< Invalid argument */
实现就绪列表
线程创建好之后,我们需要把线程添加到就绪列表里面,表示线程已经就绪,系统随时可以调度 。
就绪列表实际上就是一个rt_list_t
类型的数组。
数组的下标对应了线程的优先级,同一优先级的线程统一插入到就绪列表的同一条链表中。
schedeler.c
/* 线程就绪列表 */
rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]; // RT_THREAD_PRIORITY_MAX 在rtconfig.h定义
线程初始化
/* 初始化线程 */
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) ); /* 线程栈大小,单位为字节 */
线程插入就绪列表
/* 将线程插入到就绪列表 */
rt_list_insert_before( &(rt_thread_priority_table[0]),&(rt_flag1_thread.tlist) );
rt_list_insert_before( &(rt_thread_priority_table[1]),&(rt_flag2_thread.tlist) );
就绪列表的下标对应的是线程的优先级,但是目前我们的线程还不支持优先级,有关支持多优先级的知识点我们后面会讲到,所以 flag1 和 flag2 线程在插入到就绪列表的时候,可以随便选择插入的位置。
线程插入就绪列表示意图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UkeUVkUY-1608216139680)(https://i.loli.net/2020/10/22/g5FNoRJ3DiYCBtu.png)]
*实现调度器
调度器是操作系统的核心,其主要功能就是实现线程的切换,即从就绪列表里面找到优先级最高的线程,然后去执行该线程。
调度器初始化
schedeler.c
/* 线程控制块指针,用于指向当前线程 */
struct rt_thread *rt_current_thread; // 全局指针,用于指向当前正在运行的线程的线程控制块。
/* 线程休眠列表 */
rt_list_t rt_thread_defunct;
/* 初始化系统调度器 */
void rt_system_scheduler_init(void)
{
/* 用 C 语言关键词 register 修饰,防止被编译器优化。*/
register rt_base_t offset;
/* 线程就绪列表初始化 */
for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++)
{
rt_list_init(&rt_thread_priority_table[offset]);
}
/* 初始化当前线程控制块指针 */
rt_current_thread = RT_NULL;
/* 初始化线程休眠列表,当线程创建好没有启动之前会被放入到这个列表 */
rt_list_init(&rt_thread_defunct);
}
线程就绪列表初始化后,整个为空,如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WVYANNjR-1608216139682)(https://i.loli.net/2020/10/22/AjaRLltUBDFmqpx.png)]
启动调度器
调度器在启动的时候会从就绪列表中取出优先级最高的线程的线程控制块,然后切换到该线程。但是目前我们的线程还不支持优先级,那么就手动指定第一个运行的线程为就绪列表下标为 0 这条链表里面挂着的线程。
/* 启动系统调度器 */
void rt_system_scheduler_start(void)
{
register struct rt_thread *to_thread;
/* 手动指定第一个运行的线程 */
to_thread = rt_list_entry(rt_thread_priority_table[0].next,
struct rt_thread,
tlist);
rt_current_thread = to_thread;
/* 切换到第一个线程,该函数在context_rvds.S中实现,在rthw.h声明,
用于实现第一次任务切换。当一个汇编函数在C文件中调用的时候,
如果有形参,则执行的时候会将形参传人到CPU寄存器r0。*/
rt_hw_context_switch_to((rt_uint32_t)&to_thread->sp);
}
rt_list_entry()
是一个已知一个结构体里面的成员的地址,反推出该结构体的首地址的宏。
rtservice.h
/* 已知一个结构体里面的成员的地址,反推出该结构体的首地址 */
#define rt_container_of(ptr, type, member) \
((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))
#define rt_list_entry(node, type, member) \
rt_container_of(node, type, member)
node
表示一个节点的地址(rt_thread_priority_table[0].next
),type
表示该节点所在的结构体的类型(struct rt_thread
),member
表示该节点在该结构体中的成员名称(tlist
)。
rt_container_of()
的实现算法具体见图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z5ihTUF6-1608216139685)(https://i.loli.net/2020/10/22/hpuqFfj9yrW7Iez.png)]
我们知道了一个节点tlist
的地址ptr
,现在要推算出该节点所在的type
类型的结构体的起始地址f_struct_ptr
。我们可以将ptr
的值减去图中灰色部分的偏移的大小就可以得到f_struct_ptr
的地址,现在的关键是如何计算出灰色部分的偏移大小。这里采取的做法是将 0 地址强制类型类型转换为type
,即(type *)0
,然后通过指针访问结构体成员的方式获取到偏移的大小,即(&((type *)0) -> member)
。
最后即可算出f_struct_ptr = ptr - (&((type *)0) -> member)
。
第一次线程切换
rt_hw_context_switch_to()函数
- 将形参
to
赋值给rt_interrupt_to_thread
. - 将
rt_interrupt_from_thread
赋值为0
. - 设置中断标志位
rt_thread_switch_interrupt_flag
的值为1
. - 设置 PendSV 异常的优先级.
- 触发 PendSV 异常 (产生上下文切换).
rt_hw_context_switch_to()函数在rthw.h
声明,context_rvds.s
实现。
rthw.h
void rt_hw_context_switch(rt_uint32_t from, rt_uint32_t to);
void rt_hw_context_switch_to(rt_uint32_t to);
cpuport.c
/*
*************************************************************************
* 全局变量
*************************************************************************
*/
rt_uint32_t rt_interrupt_from_thread; /* 用于存储上一个线程的栈的sp的指针 */
rt_uint32_t rt_interrupt_to_thread; /* 用于存储下一个将要运行的线程的栈的sp的指针 */
rt_uint32_t rt_thread_switch_interrupt_flag; /* PendSV中断服务函数执行标志 */
当一个汇编函数在C文件中调用的时候,如果有一个形参,则执行的时候会将这个形参传入到CPU寄存器r0
,如果有两个形参,第二个则传入到r1
。
context_rvds.s
;*************************************************************************
; 全局变量
;*************************************************************************
IMPORT rt_thread_switch_interrupt_flag
IMPORT rt_interrupt_from_thread
IMPORT rt_interrupt_to_thread
;*************************************************************************
; 常量
;*************************************************************************
;-------------------------------------------------------------------------
;有关内核外设寄存器定义可参考官方文档:STM32F10xxx Cortex-M3 programming manual
;系统控制块外设SCB地址范围:0xE000ED00-0xE000ED3F
;-------------------------------------------------------------------------
SCB_VTOR EQU 0xE000ED08 ; 向量表偏移寄存器
NVIC_INT_CTRL EQU 0xE000ED04 ; 中断控制状态寄存器
NVIC_SYSPRI2 EQU 0xE000ED20 ; 系统优先级寄存器(2)
NVIC_PENDSV_PRI EQU 0x00FF0000 ; PendSV 优先级值 (lowest)
NVIC_PENDSVSET EQU 0x10000000 ; 触发PendSV exception的值
;*************************************************************************
; 代码产生指令
;*************************************************************************
AREA |.text|, CODE, READONLY, ALIGN=2
THUMB
REQUIRE8
PRESERVE8
;/*
; *-----------------------------------------------------------------------
; * 函数原型:void rt_hw_context_switch_to(rt_uint32 to);
; * r0 --> to
; * 该函数用于开启第一次线程切换
; *-----------------------------------------------------------------------
; */
rt_hw_context_switch_to PROC
; 导出rt_hw_context_switch_to,让其具有全局属性,可以在C文件调用
EXPORT rt_hw_context_switch_to
; 设置rt_interrupt_to_thread的值
LDR r1, =rt_interrupt_to_thread ;将rt_interrupt_to_thread的地址加载到r1
STR r0, [r1] ;将r0的值存储到rt_interrupt_to_thread
; 设置rt_interrupt_from_thread的值为0,表示启动第一次线程切换
LDR r1, =rt_interrupt_from_thread ;将rt_interrupt_from_thread的地址加载到r1
MOV r0, #0x0 ;配置r0等于0
STR r0, [r1] ;将r0的值存储到rt_interrupt_from_thread
; 设置中断标志位rt_thread_switch_interrupt_flag的值为1
LDR r1, =rt_thread_switch_interrupt_flag ;将rt_thread_switch_interrupt_flag的地址加载到r1
MOV r0, #1 ;配置r0等于1
STR r0, [r1] ;将r0的值存储到rt_thread_switch_interrupt_flag
; 设置 PendSV 异常的优先级
LDR r0, =NVIC_SYSPRI2
LDR r1, =NVIC_PENDSV_PRI
LDR.W r2, [r0,#0x00] ; 读
ORR r1,r1,r2 ; 改
STR r1, [r0] ; 写
; 触发 PendSV 异常 (产生上下文切换)
LDR r0, =NVIC_INT_CTRL
LDR r1, =NVIC_PENDSVSET
STR r1, [r0]
; 开中断
CPSIE F
CPSIE I
; 永远不会到达这里
ENDP
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OuCvTu9G-1608216139686)(https://i.loli.net/2020/10/22/fVQTH5UIYSn2FOK.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-swjLYZC4-1608216139687)(https://i.loli.net/2020/10/22/YbchUyLMkvxNudl.png)]
**汇编代码产生指令:**当我们新建一个汇编文件写代码时,必须包含类似的指令。AERA 表示汇编一个新的数据段或者代码段,.text 表示段名字,如果段名不是以字母开头,而是以其它符号开头则需要在段名两边加上‘|’,CODE 表示为代码,READONLY 表示只读,ALIGN=2,表示当前文件指令要 22 字节对齐。THUMB 表示THUMB 指令代码,REQUIRE8 和 PRESERVE8 均表示当前文件的栈按照 8 字节对齐。
PendSV异常: PendSV 异常的优先级为最低。触发 PendSV 异常 (产生上下文切换)。如果前面关了,还要等中断打开才能去执行 PendSV 中断服务函数。
PendSV_Handler()函数
- 失能中断,为了保护上下文切换不被中断
- 获取中断标志位,看看是否为0,为0就退出中断.
- r1不为0则清0.
- 判断rt_interrupt_from_thread的值是否为0,为0表示第一次线程切换
- 上文保存(看注释说明),手动保存r4~r11
- 下文切换(看注释说明),
context_rvds.s
;/*
; *-----------------------------------------------------------------------
; * void PendSV_Handler(void);
; * r0 --> switch from thread stack
; * r1 --> switch to thread stack
; * psr, pc, lr, r12, r3, r2, r1, r0 are pushed into [from] stack
; *-----------------------------------------------------------------------
; */
PendSV_Handler PROC
EXPORT PendSV_Handler
; 失能中断,为了保护上下文切换不被中断
MRS r2, PRIMASK
CPSID I
; 获取中断标志位,看看是否为0
LDR r0, =rt_thread_switch_interrupt_flag ; 加载rt_thread_switch_interrupt_flag的地址到r0
LDR r1, [r0] ; 加载rt_thread_switch_interrupt_flag的值到r1
CBZ r1, pendsv_exit ; 判断r1是否为0,为0则跳转到pendsv_exit
; r1不为0则清0
MOV r1, #0x00
STR r1, [r0] ; 将r1的值存储到rt_thread_switch_interrupt_flag,即清0
; 判断rt_interrupt_from_thread的值是否为0
LDR r0, =rt_interrupt_from_thread ; 加载rt_interrupt_from_thread的地址到r0
LDR r1, [r0] ; 加载rt_interrupt_from_thread的值到r1
CBZ r1, switch_to_thread ; 判断r1是否为0,为0则跳转到switch_to_thread
; 第一次线程切换时rt_interrupt_from_thread肯定为0,则跳转到switch_to_thread
; ========================== 上文保存 ==============================
; 当进入PendSVC Handler时,上一个线程运行的环境即:
; xPSR,PC(线程入口地址),R14,R12,R3,R2,R1,R0(线程的形参)
; 这些CPU寄存器的值会自动保存到线程的栈中,剩下的r4~r11需要手动保存
MRS r1, psp ; 获取线程栈指针到r1
STMFD r1!, {r4 - r11} ;将CPU寄存器r4~r11的值存储到r1指向的地址(每操作一次地址将递减一次)
LDR r0, [r0] ; 加载r0指向值到r0,即r0=rt_interrupt_from_thread
STR r1, [r0] ; 将r1的值存储到r0,即更新线程栈sp
; ========================== 下文切换 ==============================
switch_to_thread
LDR r1, =rt_interrupt_to_thread ; 加载rt_interrupt_to_thread的地址到r1
; rt_interrupt_to_thread是一个全局变量,里面存的是线程栈指针SP的指针
LDR r1, [r1] ; 加载rt_interrupt_to_thread的值到r1,即sp指针的指针
LDR r1, [r1] ; 加载rt_interrupt_to_thread的值到r1,即sp
LDMFD r1!, {r4 - r11} ;将线程栈指针r1(操作之前先递减)指向的内容加载到CPU寄存器r4~r11
MSR psp, r1 ;将线程栈指针更新到PSP
pendsv_exit
; 恢复中断
MSR PRIMASK, r2
ORR lr, lr, #0x04 ; 确保异常返回使用的堆栈指针是PSP,即LR寄存器的位2要为1
BX lr ; 异常返回,这个时候任务堆栈中的剩下内容将会自动加载到xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)
; 同时PSP的值也将更新,即指向任务堆栈的栈顶。在ARMC3中,堆是由高地址向低地址生长的。
; PendSV_Handler 子程序结束
ENDP
ALIGN 4
END
当前文件指令代码要求 4 字节对齐,不然会有警告。
汇编文件结束,每个汇编文件都需要一个 END。
系统调度
系统调度就是在就绪列表中寻找优先级最高的就绪线程,然后去执行该线程。但是目前我们还不支持优先级,仅实现两个线程轮流切换。
rt_schedule()函数
/* 系统调度 */
void rt_schedule(void)
{
struct rt_thread *to_thread;
struct rt_thread *from_thread;
/* 两个线程轮流切换 */
if( rt_current_thread == rt_list_entry( rt_thread_priority_table[0].next,
struct rt_thread,
tlist) )
{
from_thread = rt_current_thread;
to_thread = rt_list_entry( rt_thread_priority_table[1].next,
struct rt_thread,
tlist);
rt_current_thread = to_thread;
}
else
{
from_thread = rt_current_thread;
to_thread = rt_list_entry( rt_thread_priority_table[0].next,
struct rt_thread,
tlist);
rt_current_thread = to_thread;
}
/* 产生上下文切换 */
rt_hw_context_switch((rt_uint32_t)&from_thread->sp,(rt_uint32_t)&to_thread->sp);
}
如果当前线程为线程 1,则把下一个要运行的线程改为线程 2。
如果当前线程为线程 2,则把下一个要运行的线程改为线程 1。
rt_hw_contex_switch()函数
rt_hw_contex_switch()
函数的形参为&thread->sp
,即线程栈指针的指针。
rt_hw_contex_switch()
函数用于产生上下文切换,在 context_rvds.S
中实现,在 rthw.h
声明。
;/*
; *-----------------------------------------------------------------------
; * void rt_hw_context_switch(rt_uint32 from, rt_uint32 to);
; * r0 --> from
; * r1 --> to
; *-----------------------------------------------------------------------
; */
rt_hw_context_switch PROC
EXPORT rt_hw_context_switch
; 设置中断标志位rt_thread_switch_interrupt_flag为1
LDR r2, =rt_thread_switch_interrupt_flag ; 加载rt_thread_switch_interrupt_flag的地址到r2
LDR r3, [r2] ; 加载rt_thread_switch_interrupt_flag的值到r3
CMP r3, #1 ; r3与1比较,相等则执行BEQ指令,否则不执行
BEQ _reswitch
MOV r3, #1 ; 设置r3的值为1
STR r3, [r2] ; 将r3的值存储到rt_thread_switch_interrupt_flag,即置1
; 设置rt_interrupt_from_thread的值
LDR r2, =rt_interrupt_from_thread ; 加载rt_interrupt_from_thread的地址到r2
STR r0, [r2] ; 存储r0的值到rt_interrupt_from_thread,即上一个线程栈指针sp的指针
_reswitch
; 设置rt_interrupt_to_thread的值
LDR r2, =rt_interrupt_to_thread ; 加载rt_interrupt_from_thread的地址到r2
STR r1, [r2] ; 存储r1的值到rt_interrupt_from_thread,即下一个线程栈指针sp的指针
; 触发PendSV异常,实现上下文切换
LDR r0, =NVIC_INT_CTRL
LDR r1, =NVIC_PENDSVSET
STR r1, [r0]
; 子程序返回
BX LR
; 子程序结束
ENDP
main函数
/*
*************************************************************************
* 全局变量
*************************************************************************
*/
rt_uint8_t flag1;
rt_uint8_t flag2;
extern rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];
/*
*************************************************************************
* 线程控制块 & STACK & 线程声明
*************************************************************************
*/
/* 定义线程控制块 */
struct rt_thread rt_flag1_thread;
struct rt_thread rt_flag2_thread;
ALIGN(RT_ALIGN_SIZE)
/* 定义线程栈 */
rt_uint8_t rt_flag1_thread_stack[512];
rt_uint8_t rt_flag2_thread_stack[512];
/* 线程声明 */
void flag1_thread_entry(void *p_arg);
void flag2_thread_entry(void *p_arg);
int main(void)
{
/* 硬件初始化 */
/* 将硬件相关的初始化放在这里,如果是软件仿真则没有相关初始化代码 */
/* 调度器初始化 */
rt_system_scheduler_init();
/* 初始化线程 */
rt_thread_init( &rt_flag1_thread, /* 线程控制块 */
flag1_thread_entry, /* 线程入口地址 */
RT_NULL, /* 线程形参 */
&rt_flag1_thread_stack[0], /* 线程栈起始地址 */
sizeof(rt_flag1_thread_stack) ); /* 线程栈大小,单位为字节 */
/* 将线程插入到就绪列表 */
rt_list_insert_before( &(rt_thread_priority_table[0]),&(rt_flag1_thread.tlist) );
/* 初始化线程 */
rt_thread_init( &rt_flag2_thread, /* 线程控制块 */
flag2_thread_entry, /* 线程入口地址 */
RT_NULL, /* 线程形参 */
&rt_flag2_thread_stack[0], /* 线程栈起始地址 */
sizeof(rt_flag2_thread_stack) ); /* 线程栈大小,单位为字节 */
/* 将线程插入到就绪列表 */
rt_list_insert_before( &(rt_thread_priority_table[1]),&(rt_flag2_thread.tlist) );
/* 启动系统调度器 */
rt_system_scheduler_start();
}
/*
*************************************************************************
* 函数实现
*************************************************************************
*/
/* 软件延时 */
void delay (uint32_t count)
{
for(; count!=0; count--);
}
/* 线程1 */
void flag1_thread_entry( void *p_arg )
{
for( ;; )
{
flag1 = 1;
delay( 100 );
flag1 = 0;
delay( 100 );
/* 线程切换,这里是手动切换 */
rt_schedule();
}
}
/* 线程2 */
void flag2_thread_entry( void *p_arg )
{
for( ;; )
{
flag2 = 1;
delay( 100 );
flag2 = 0;
delay( 100 );
/* 线程切换,这里是手动切换 */
rt_schedule();
}
}
以上内容配合野火教程学习。