线程的定义与线程切换的实现

代码部分均来自《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)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值