从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时,上一个线程运行的环境即:; xPSR,PC(线程入口地址),R14,R12,R3,R2,R1,R0(线程的形参); 这些CPU寄存器的值会自动保存到线程的栈中,剩下的r4~r11需要手动保存MRS     r1, psp                                  ; 获取线程栈指针到r1STMFD   r1!, {r4 - r11}                          ;将CPU寄存器r4~r11的值存储到r1指向的地址(每操作一次地址将递减一次)LDR     r0, [r0]                                 ; 加载r0指向值到r0,即r0=rt_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                                        ; 异常返回,这个时候任务堆栈中的剩下内容将会自动加载到xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参); 同时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物联网操作系统的人可以考虑一下。

从0到1写RT-Thread内核——线程定义及切换的实现相关推荐

  1. rust 手动关闭子线程_从零开始写 OS (9) —— 内核线程

    上一篇 小源:从零开始写 OS (8) -- 创建页表​zhuanlan.zhihu.com 本章代码对应 commit :de86ae6e1e8bdfe3388530f82b2081fe29e40b ...

  2. freertos内核 任务定义与切换 原理分析

    freertos内核 任务定义与切换 原理分析 主程序 任务控制块 任务创建函数 任务栈初始化 就绪列表 调度器 总结任务切换 主程序 这个程序目的就是,使用freertos让两个任务不断切换.看两个 ...

  3. 从0到1写RT-Thread内核——空闲线程与阻塞延时的实现

    在之前写的另外一篇文章--<从0到1写RT-Thread内核--线程定义及切换的实现>中线程体内的延时使用的是软件延时,即还是让CPU空等来达到延时的效果.RTOS中的延时叫阻塞延时,即线 ...

  4. 从0到1写RT-Thread内核——支持多优先级

    在本章之前,RT-Thread还没有支持多优先级,我们手动指定了第一个运行的线程,并在此之后三个线程(包括空闲线程)互相切换,在本章中我们加入优先级的功能,第一个运行的程序是就绪列表里优先级最高的程线 ...

  5. Snail OS 0.03的thread目录是内核线程

    内核线程参考的是郑钢先生<操作系统真相还原>,主要是觉得这种实现线程和进程的方式非常的巧妙,另外在这方面想要创新的话,空间也是很小的. 为了编程方便,thread当中包含了进程的阻塞和唤醒 ...

  6. 关于RT thread系统节拍时钟的配置

    关于RT thread系统节拍时钟的配置                  -----本文基于rt-thread-3.1.3版本编写 首先,使用RTthread OS时,要配置(或者明白)它的系统节拍 ...

  7. rt thread studio使用QBOOT和片外flash实现OTA升级

    我们这里要使用单片机外部flash作为OTA的下载分区,外部flash硬件连接关系 PB3-->SPI3_CLK PB4-->SPI3_MISO PB5-->SPI3_MOSI PE ...

  8. rt thread 使用FAL遇到fal_init() undefined reference

    rt thread FAL 0.5版,之前有没有不知道,遇到一个坑. 在main.cpp里面已经 #include <fal.h> fal_init() 编译报错,说 fal_init() ...

  9. RT Thread Free Modbus移植问题整理

    RT Thread Free Modbus移植问题整理 问题描述: 在读写寄存器中,写数据正常,只能读1个寄存器的值,多个值会异常. 在移植过程中发现串口(或RS485)数据接收长度异常. 一.环境描 ...

最新文章

  1. 学java是不是必须要参加java培训班?
  2. 2007年你必须学习的10项.NET技术
  3. 【BZOJ 3620】 3620: 似乎在梦中见过的样子 (KMP)
  4. remo video repair中文版
  5. 下载 | 新版Java开发手册有哪些亮点?
  6. RMAN CONFIGURE解释
  7. EXP的flashback_scn和flashback_time
  8. 物联网通讯协议:MQTT,NB-IOT,Zigbee,CoAP,RFID,BLUETOOTH,NFC,4G,HTTP
  9. php 输出text格式化,php printf() 输出格式化的字符串,phpprintf
  10. Python入门 程序的套路
  11. javax.servlet.http.HttpServlet was not found
  12. 三极管开关电路_利用三极管设计开关电路
  13. 北大青鸟linux运维,北大青鸟幸福IT学院:Linux运维学习路线,实用Linux教程,推荐学习收藏...
  14. 计算机等级考试ppt怎么做,计算机等级考试PPT教学.ppt
  15. 封装class,让className可以在IE8及其以下版本的浏览器中使用
  16. 6.3.3 非信贷交易信息 6.3.4 公共信息 6.3.5 查询记录概要
  17. unity3dwebgl building之后没有反应_晚会是在考验明星临场反应吗?王源开场无伴奏阿云嘎差点原地跳舞...
  18. 程序员分分钟就能把代码和文艺完美结合
  19. python3 beautiful爬取安居客的二手房信息
  20. CKplayer 超酷Flv播放器演示

热门文章

  1. HDU - 1024 Max Sum Plus Plus 最大m段子段和+滚动数组优化
  2. POJ1789-Truck History .
  3. 【转】怎么样从一个疯狂下载者成为一个学习者!!!值得反省下的问题·~~
  4. 数学图形之Boy surface
  5. iOS判断为空或者只为空格
  6. android根据拍摄url获取格式,Android如何通过URI获取文件路径示例代码
  7. linux shell 宏定义_linux内核修炼之系统调用
  8. android paint 圆角 绘制_[BOT] 一种android中实现“圆角矩形”的方法
  9. mysql alter怎么用,mysql alter话语用法
  10. python运行结果图_[宜配屋]听图阁