网站affiliate怎么做,wordpress前端,山东建设兵团网站,响应式企业展示型网站模板从0写RT-Thread内核之线程定义及切换的实现具体可以分为以下六步来实现
一#xff1a;分别定义线程栈、线程函数、线程控制块#xff1b;
ALIGN(RT_ALIGN_SIZE)//设置4字节对齐
/* 定义线程栈 */
rt_uint8_t rt_flag1_thread_stack[512];
rt_uint8_t rt_flag2_thread_stack…从0写RT-Thread内核之线程定义及切换的实现具体可以分为以下六步来实现
一分别定义线程栈、线程函数、线程控制块
ALIGN(RT_ALIGN_SIZE)//设置4字节对齐
/* 定义线程栈 */
rt_uint8_t rt_flag1_thread_stack[512];
rt_uint8_t rt_flag2_thread_stack[512];
/* 线程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();//此函数在下面介绍}
}
/* 此结构体在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;/*
*************************************************************************
* 双向链表结构体
*************************************************************************
*/
struct rt_list_node
{struct rt_list_node *next; /* 指向后一个节点 */struct rt_list_node *prev; /* 指向前一个节点 */
};
typedef struct rt_list_node rt_list_t; /* 在main.c定义线程控制块 */
struct rt_thread rt_flag1_thread;
struct rt_thread rt_flag2_thread;
二用线程初始化函数rt_thread_init函数体如下图调用rt_list_init函数初始化rt_list_t类型的节点其实就是将节点里面的next和prev这两个节点指针指向节点本身然后把上面第一步定义的线程栈、线程函数、线程控制块这三部分联系起来实际上就是初始化线程控制块由该函数的函数体我们知道它还调用了rt_hw_stack_init初始化了线程的栈。
/* 初始化链表节点在rtserver.h中定义 */
rt_inline void rt_list_init(rt_list_t *l)
{l-next l-prev l;
} 三定义就绪列表并把线程插入到就绪列表中线程控制块里有一个tlist成员数据类型为rt_list_t我们将线程插入就绪列表里面就是通过将线程控制块的tlist这个节点插入到就绪列表中来实现的。如果把就绪列表比作晾衣杆线程是衣服那tlist就是晾衣架每个线程都自带晾衣架就是为了把自己挂在各种不同的链表中。
/* 定义就绪列表 */
extern rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];就绪列表的下标对应的是线程的优先级但是目前我们的线程还不支持优先级有关支持多优先级的知识点我们后面会讲到所以flag1和flag2线程在插入到就绪列表的时候可以随便选择插入的位置。我们这里将flag1线程插入到就绪列表下表为0的链表中flag2线程插入到就绪列表下标为1的链表中。/* 将线程1插入到就绪列表 */
rt_list_insert_before( (rt_thread_priority_table[0]),(rt_flag1_thread.tlist) );/* 将线程2插入到就绪列表 */
rt_list_insert_before( (rt_thread_priority_table[1]),(rt_flag2_thread.tlist) );
四调度器的初始化(初始化就绪列表里面各个不同优先级的链表其实就是将节点里面的next和prev这两个节点指针指向节点本身)
/* 初始化系统调度器 */
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);
}
五使用rt_system_scheduler_start()启动调度器手动指定第一个运行的线程并切换到该线程中去利用rt_hw_context_switch_to函数切换线程该函数用于第一次线程的切换该函数只是开启中断并设置中断标志位真正实现线程切换是在中断服务函数里。
/* 启动系统调度器 */
void rt_system_scheduler_start(void)
{register struct rt_thread *to_thread;/* 手动指定第一个运行的线程 *//* rt_list_entry是一个已知一个结构体里面成员的地址反推出该结构体的首地址的宏 *//* 此处是通过struct rt_thread里面tlist成员获取该结构体首地址即获取该线程控制块的首地址 */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);
}
六定义一个void rt_schedule(void)函数并在其中调用rt_hw_context_switch()函数该函数实现新老线程的切换和第五点中的rt_hw_context_switch_to()一样都只是开启中断并设置中断标志位真正的线程切换在中断服务函数中实现
/* 系统调度 */
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); }
中断服务函数PendSV_Handler的实现如下该函数真正的实现了线程的切换
;/*
; *-----------------------------------------------------------------------
; * 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 PROCEXPORT PendSV_Handler; 失能中断为了保护上下文切换不被中断MRS r2, PRIMASKCPSID I; 获取中断标志位看看是否为0LDR r0, rt_thread_switch_interrupt_flag ; 加载rt_thread_switch_interrupt_flag的地址到r0LDR r1, [r0] ; 加载rt_thread_switch_interrupt_flag的值到r1CBZ r1, pendsv_exit ; 判断r1是否为0为0则跳转到pendsv_exit; r1不为0则清0MOV r1, #0x00STR r1, [r0] ; 将r1的值存储到rt_thread_switch_interrupt_flag即清0; 判断rt_interrupt_from_thread的值是否为0LDR r0, rt_interrupt_from_thread ; 加载rt_interrupt_from_thread的地址到r0LDR r1, [r0] ; 加载rt_interrupt_from_thread的值到r1CBZ r1, switch_to_thread ; 判断r1是否为0为0则跳转到switch_to_thread; 第一次线程切换时rt_interrupt_from_thread肯定为0则跳转到switch_to_thread; 上文保存 ; 当进入PendSVC Handler时上一个线程运行的环境即; xPSRPC线程入口地址R14R12R3R2R1R0线程的形参; 这些CPU寄存器的值会自动保存到线程的栈中剩下的r4~r11需要手动保存MRS r1, psp ; 获取线程栈指针到r1STMFD r1!, {r4 - r11} ;将CPU寄存器r4~r11的值存储到r1指向的地址(每操作一次地址将递减一次)LDR r0, [r0] ; 加载r0指向值到r0即r0rt_interrupt_from_threadSTR r1, [r0] ; 将r1的值存储到r0即更新线程栈sp; 下文切换
switch_to_threadLDR 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即spLDMFD r1!, {r4 - r11} ;将线程栈指针r1(操作之前先递减)指向的内容加载到CPU寄存器r4~r11MSR psp, r1 ;将线程栈指针更新到PSPpendsv_exit; 恢复中断MSR PRIMASK, r2ORR lr, lr, #0x04 ; 确保异常返回使用的堆栈指针是PSP即LR寄存器的位2要为1BX lr ; 异常返回这个时候任务堆栈中的剩下内容将会自动加载到xPSRPC任务入口地址R14R12R3R2R1R0任务的形参; 同时PSP的值也将更新即指向任务堆栈的栈顶。在ARMC3中堆是由高地址向低地址生长的。; PendSV_Handler 子程序结束ENDP ALIGN 4END 通过上面六个步骤我们已经完成了线程切换的各个函数接下来的是我们main()函数先初始化调度器然后分别初始化线程1和2并把两者分别插入到就绪列表中的0号和1号链表然后启动系统调度器启动系统调度器之后就会进行线程的第一次切换切换到线程flag1的函数中去在该函数的最后又会切换到线程flag2中去然后线程flag2又会切换会线程flag1一直这样来回切换。
/************************************************************************* brief main函数* param 无* retval 无** attention*********************************************************************** */
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();
} 最后我们通过软件仿真的效果如下图可以看到flag1和flag2两个变量波形的占空比为1/4刚好符合我们两个线程函数来回切换的效果。 最后声明一下我这里只是对学习的知识点进行总结本文章的大多数知识来自于野火公司出版的《RT-Thread 内核实现与应用开发实战—基于STM32》这本书非常不错有志学习RT-Thread物联网操作系统的人可以考虑一下。