代码部分均来自《RT-Thread内核实现与应用开发实战指南》—刘火良、杨森
以下仅为学习笔记
工程链接如下:
链接:https://pan.baidu.com/s/1MgEL2W8RgkQXKF89_lbxSA?pwd=RTOS
提取码:RTOS
主函数部分
#include <rtthread.h>
#include "ARMCM4.h"
/*
*************************************************************************
* 全局变量
*************************************************************************
*/
rt_uint8_t flag1;
rt_uint8_t flag2;
extern rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];
/*
*************************************************************************
* 线程控制块 & STACK & 线程声明
*************************************************************************
*/
// (3)
/* 定义线程控制块 */
struct rt_thread rt_flag1_thread;
struct rt_thread rt_flag2_thread;
// (2)
ALIGN(RT_ALIGN_SIZE)
/* 定义线程栈 */
// (1)
rt_uint8_t rt_flag1_thread_stack[512];
rt_uint8_t rt_flag2_thread_stack[512];
// (4)
/* 线程声明 */
void flag1_thread_entry(void *p_arg);
void flag2_thread_entry(void *p_arg);
/*
*************************************************************************
* 函数声明
*************************************************************************
*/
void delay(uint32_t count);
/************************************************************************
* @brief main函数
* @param 无
* @retval 无
*
* @attention
***********************************************************************
*/
int main(void)
{
/* 硬件初始化 */
/* 将硬件相关的初始化放在这里,如果是软件仿真则没有相关初始化代码 */
/* (5)调度器初始化 */
rt_system_scheduler_init();
/* (6)初始化线程 */
rt_thread_init( &rt_flag1_thread, /* 线程控制块 */
flag1_thread_entry, /* 线程入口地址 */
RT_NULL, /* 线程形参 */
&rt_flag1_thread_stack[0], /* 线程栈起始地址 */
sizeof(rt_flag1_thread_stack) ); /* 线程栈大小,单位为字节 */
/* (6)将线程插入到就绪列表 */
rt_list_insert_before( &(rt_thread_priority_table[0]),&(rt_flag1_thread.tlist) );
/* (6)初始化线程 */
rt_thread_init( &rt_flag2_thread, /* 线程控制块 */
flag2_thread_entry, /* 线程入口地址 */
RT_NULL, /* 线程形参 */
&rt_flag2_thread_stack[0], /* 线程栈起始地址 */
sizeof(rt_flag2_thread_stack) ); /* 线程栈大小,单位为字节 */
/* (6)将线程插入到就绪列表 */
rt_list_insert_before( &(rt_thread_priority_table[1]),&(rt_flag2_thread.tlist) );
/* (7)启动系统调度器 */
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();
}
}
代码解释部分
(1)线程栈定义
rt_uint8_t rt_flag1_thread_stack[512];
rt_uint8_t rt_flag2_thread_stack[512];
相关宏定义如下:
typedefunsignedchar rt_uint8_t;
在多线程系统中,每个线程都是独立的,互不干扰的,所以要为每个线程都分配独立的栈空间,这个栈空间通常是一个预先定义好的全局数组,也可以是动态分配的一段内存空间,但它们都存在于RAM中。
(2)设置变量字节对齐位数
ALIGN(RT_ALIGN_SIZE)
相关宏定义如下:
#ifdef __CC_ARM // 如果用#define 定义了 __CC_ARM 则执行下面指令
#define rt_inline static __inline
#define ALIGN(n) __attribute__((aligned(n)))
#elif defined (__IAR_SYSTEMS_ICC__) //如果用#define 定义了 __IAR_SYSTEMS_ICC__ 则执行下面指令
#define rt_inline static inline
#define ALIGN(n) PRAGMA(data_alignment=n)
#elif defined (__GNUC__) //如果用#define 定义了 __GNUC__ 则执行下面指令
#define rt_inline static __inline
#define ALIGN(n) __attribute__((aligned(n)))
#else // 上述均不成立,则提示错误信息
#error not supported tool chain // 不支持工具链
#endif
#ifdef…#elif…#else…#endif 为条件编译
#error 是一种预编译器指示字,用于生成一个编译错误消息 。
上述条件编译是用来识别编译器的宏,如:__CC_ARM 为 Keil 里带的编译器所定义的一个宏__IAR_SYSTEMS_ICC__为 IAR里带的编译器所定义的一个宏;__GNUC__则为GCC。
attribute ((aligned (n))) 告诉编译器一个结构体或者类或者联合或者一个类型的变量 (对象)>分配地址空间时的地址对齐方式。
(3) 定义线程控制块
struct rt_thread rt_flag1_thread;
struct rt_thread rt_flag2_thread;
系统为了顺利的调度线程,为每个线程都额外定义了一个线程控制块,线程控制块里面存有线程的所有信息,比如线程的栈指针,线程名称,线程的形参等。
相关宏定义如下:
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;
链表节点定义及初始化
struct rt_list_node
{
struct rt_list_node *next; /* 指向后一个节点 */
struct rt_list_node *prev; /* 指向前一个节点 */
};
typedef struct rt_list_node rt_list_t;
rt_inline void rt_list_init(rt_list_t *l)
{
l->next = l->prev = l;
}
** (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();
}
}
**(5)调度器初始化 **
调度器是操作系统的核心,其主要功能就是实现线程的切换,即从就绪列表里面找到优先级最高
的线程,然后去执行该线程。由几个全局变量和一些可以实现线程切换的函数组成。
就绪列表的下标为线程的优先级
rt_system_scheduler_init();
#define RT_NULL (0)
#define RT_THREAD_PRIORITY_MAX 32 /* 最大优先级 */
/* 线程控制块指针,用于指向当前线程 */
struct rt_thread *rt_current_thread;
/* 线程就绪列表 */
rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];
/* 线程休眠列表 */
rt_list_t rt_thread_defunct;
void rt_system_scheduler_init(void)
{
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);
}
** (6)初始化线程 **
/* 初始化线程 */
rt_thread_init( &rt_flag1_thread, /* 线程控制块:flag1 */
flag1_thread_entry, /* 线程入口地址:函数名 */
RT_NULL, /* 线程形参:0 */
&rt_flag1_thread_stack[0], /* 线程栈起始地址:0号地址 */
sizeof(rt_flag1_thread_stack) ); /* 线程栈大小,单位为字节 */
/* 将线程插入到就绪列表,在table[0]上插入flag1 */
rt_list_insert_before( &(rt_thread_priority_table[0]),&(rt_flag1_thread.tlist) );
// rt_thread 结构体定义如下:
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;
// void (*entry)(void *parameter) 为一个函数指针形参
// void *相当于一个泛型形参,可以在函数中进行强制转换成需要的类型
// 初始化线程:
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_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;
}
**初始化线程栈,并返回线程栈指针 **
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;
};
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;
};
/* 线程栈初始化 */
rt_uint8_t *rt_hw_stack_init(void *tentry,
void *parameter,
rt_uint8_t *stack_addr)
{
struct stack_frame *stack_frame;
rt_uint8_t *stk;
unsigned long i;
/* 获取栈顶指针
rt_hw_stack_init 在调用的时候,传给stack_addr的是(栈顶指针)*/
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;
}
**(7)启动系统调度器 **
rt_system_scheduler_start();
/* 已知一个结构体里面的成员的地址,反推出该结构体的首地址 */
// 具体推算过程在下文说明
#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)
/* 启动系统调度器 */
// 通过地址的转换实现线程的切换
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);
}
#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 表示一个节点的地址,type 表示该节点所在的结构体的类型,member 表示该节点在该结构体中的成员名称。
知道了一个节点 tlist 的地址 ptr,现在要推算出该节点所在的 type 类型的结构体的
起始地址 f_struct_ptr。我们可以将 ptr 的值减去图中灰色部分的偏移的大小就可以得到 f_struct_ptr
的地址,现在的关键是如何计算出灰色部分的偏移大小。这里采取的做法是将 0 地址强制类
型类型转换为 type,即 (type*)0,然后通过指针访问结构体成员的方式获取到偏移的大小,即(&((type*)0)->member),最后即可算出 f_struct_ptr = ptr - (&((type*)0)->member)。