5.07 (野火开源图书读书笔记)

首先要思考的一个问题:

线程的基本结构  :

1 void thread_entry (void *parg)
2 {
3  /* 线程主体,无限循环且不能返回*/
4  for (;;)
5  {
6  /* 线程主体代码*/
7  }
8  }

线程是一个无限循环且不返回的函数----------------------------------划重点。

线程是实现目的的任务主体,而操作系统又是为了实现管理任务而生,因而对于线程的理解很重要。

所以在学习rt_thread时需要了解其线程控制块的构成。

基本的线程控制块成员:

 

struct rt_thread
{
   void  *sp;     //线程栈指针
    
   void   *entry;      //线程入口地址
   void   *parameter;    //线程形参
   void    *stack_addr;   //线程起始地址
   rt_uint32_t     stack_size;       //线程栈大小,单位为字节
   rt_list_t            tlist;            //线程链表节点

};
typedef rt_thread *rt_thread_t;

 只有线程的栈+线程控制块+线程实体联系在一起,才能完成一次实际的任务调度。所需函数为:rt_thread_init

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;//


}

在系统中不可能只存在一个线程,所有线程都通过双向数据链表结构串联在一起。

双向链表结构如下:

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;
} 

相比于裸机系统而言,操作系统细化了程序内部的等级秩序和切换规则,为便于管理运行过程中的变量以及环境参数等信息,每一个线程都需要有自己的栈空间。

线程栈的定义:

 1 rt_uint8_t *rt_hw_stack_init(void *tentry,
 2 void *parameter,
 3 rt_uint8_t *stack_addr)
 4 {
 5      struct stack_frame *stack_frame;  //truct stack_frame 类结构体 7      rt_uint8_t *stk;
 8      unsigned long i;
 9      /*获取栈顶指针*/
10     stk = stack_addr + sizeof(rt_uint32_t);
11     /* 让stk 指针向下8 字节对齐*/
12   stk = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);//满足浮点运算要求
13    /* stk 指针继续向下移动sizeof(struct stack_frame) 个偏移*/
14      stk -= sizeof(struct stack_frame);
15     /* 将stk 指针强制转化为stack_frame 类型后存到stack_frame */
16     stack_frame = (struct stack_frame *)stk;
17    /* 以stack_frame 为起始地址,将栈空间里面的sizeof(struct stack_frame)25 个内存初始化为0xdeadbeef */
18    for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++)
19 {
20    ((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef;
21 }
22 /* 初始化异常发生时自动保存的寄存器*/
23    stack_frame->exception_stack_frame.r0 = (unsigned long)parameter;
24    stack_frame->exception_stack_frame.r1 = 0;
25    stack_frame->exception_stack_frame.r2 = 0;
26    stack_frame->exception_stack_frame.r3 = 0;
27    stack_frame->exception_stack_frame.r12 = 0;
28    stack_frame->exception_stack_frame.lr = 0;
29    stack_frame->exception_stack_frame.pc = (unsigned long)tentry;
30    stack_frame->exception_stack_frame.psr = 0x01000000L;
31    return stk;
32 }

线程的栈有了,线程控制块有了,线程实体也具备了,一个可以运行的任务模块完成了,用来实现挂载任务模块的数据链表该出现了。

每一个线程按照自己不同的优先级,挂载到不同的数据链表上,如下:

 

通过上述的就绪链表,将需要实现的线程(任务)相互联系起来,并按照优先级进行了分组,接下来就是如何调度的问题。

调度器的实现:

初始化函数

/* 初始化系统调度器*/
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;//
}

启动调度器:

 1 /* 启动系统调度器*/
 2 void rt_system_scheduler_start(void)
 3 {
 4    register struct rt_thread *to_thread;
 5    /* 手动指定第一个运行的线程*//
 6    to_thread = rt_list_entry(rt_thread_priority_table[0].next,
 7                                            struct rt_thread, 
 8                                             tlist);
 9     rt_current_thread = to_thread;
10     /* 切换到第一个线程,该函数在context_rvds.S 中实现,在rthw.h 声明,用于实现第一次任务切换。当一个汇编函数在C 文件中调用的时候,
11 如果有形参,则执行的时候会将形参传人到CPU 寄存器r0。*/                                     
12     rt_hw_context_switch_to((rt_uint32_t)&to_thread->sp);
13 
14 }

 

 

调度器在启动的时候会从就绪列表中取出优先级最高的线
程的线程控制块,然后切换到该线程。但是目前我们的线程还不支持优先级,那么就手动
指定第一个运行的线程为就绪列表下标为0 这条链表里面挂着的线程。

rt_list_entry() 是一个已知一个结构体里面的成员的地址,反推出该结构体的首地址的宏。

/* 已知一个结构体里面的成员的地址,反推出该结构体的首地址*/
#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 表示该节点在该结构体中的成员名称。

将0 地址强制类型类型转换为type,即(type*)0,然后通过指针访问结构体成员的方式获取到偏移的大小,用已知的节点地址减去偏移地址,就是该结构体首地址。

实现线程切换的函数是采用汇编语言编写:

 1 全局变量
 2 IMPORT rt_thread_switch_interrupt_flag
 3 IMPORT rt_interrupt_from_thread
 4 IMPORT rt_interrupt_to_thread
 5 
 6 常量
 7 有关内核外设寄存器定义可参考官方文档:STM32F10xxx Cortex-M3 programming manual
 8 系统控制块外设SCB 地址范围:0xE000ED00-0xE000ED3F
 9 
10 
11 SCB_VTOR EQU 0xE000ED08 ; 向量表偏移寄存器
12 NVIC_INT_CTRL EQU 0xE000ED04 ; 中断控制状态寄存器
13 NVIC_SYSPRI2 EQU 0xE000ED20 ; 系统优先级寄存器(2)
14 NVIC_PENDSV_PRI EQU 0x00FF0000 ; PendSV 优先级值(lowest)
15 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 的值
;将rt_interrupt_to_thread 的地址加载到r1
LDR r1, =rt_interrupt_to_thread
; 将r0 的值存储到rt_interrupt_to_thread(ro的值来自函数值传参)
STR r0, [r1]
; 设置rt_interrupt_from_thread 的值为0,表示启动第一次线程切换
; 将rt_interrupt_from_thread 的地址加载到r1
LDR r1, =rt_interrupt_from_thread
; 配置r0 等于0
MOV r0, #0x0
; 将r0 的值存储到rt_interrupt_from_thread
STR r0, [r1]
; 设置中断标志位rt_thread_switch_interrupt_flag 的值为1
; 将rt_thread_switch_interrupt_flag 的地址加载到r1
LDR r1, =rt_thread_switch_interrupt_flag

 

; 配置r0 等于1
MOV r0, #1
; 将r0 的值存储到rt_thread_switch_interrupt_flag
STR r0, [r1]
   ; 设置 PendSV 异常的优先级
    LDR     r0, =NVIC_SYSPRI2     ;r0中加载中断优先级寄存器2的地址
    LDR     r1, =NVIC_PENDSV_PRI ;r1中加载设置PendSV中断为低优先级的数据地址
    LDR.W   r2, [r0,#0x00]       ; 读     ;R2中存入中断优先级地址
    ORR     r1,r1,r2             ; 改          ;r2与r1相与的结果存入r1中
    STR     r1, [r0]             ; 写           ;设置PendSV中断优先级为低优先级
; 触发 PendSV 异常 (产生上下文切换)
    LDR     r0, =NVIC_INT_CTRL
    LDR     r1, =NVIC_PENDSVSET
    STR     r1, [r0]                               ;触发中断

    ; 开中断     
    CPSIE   F                                    ;开异常
    CPSIE   I                                       ;开中断

    ; 永远不会到达这里
    ENDP
ALIGN 4
END

以上操作只是触发了PendSV的异常中断:

真正的PendSV的异常中断:在下面

//PendSV_Handler() 函数
//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 PROC
EXPORT PendSV_Handler
; 失能中断,为了保护上下文切换不被中断
MRS r2, PRIMASK
CPSID I
; 获取中断标志位,看看是否为0
; 加载rt_thread_switch_interrupt_flag 的地址到r0
LDR r0, =rt_thread_switch_interrupt_flag
; 加载rt_thread_switch_interrupt_flag 的值到r1
LDR r1, [r0]
; 判断r1 是否为0,为0 则跳转到pendsv_exit
CBZ r1, pendsv_exit
; r1 不为0 则清0
MOV r1, #0x00
; 将r1 的值存储到rt_thread_switch_interrupt_flag,即清0
STR r1, [r0]
; 判断rt_interrupt_from_thread 的值是否为0
; 加载rt_interrupt_from_thread 的地址到r0
LDR r0, =rt_interrupt_from_thread
; 加载rt_interrupt_from_thread 的值到r1
LDR r1, [r0]
; 判断r1 是否为0,为0 则跳转到switch_to_thread
; 第一次线程切换时rt_interrupt_from_thread 肯定为0,则跳转到switch_to_thread
CBZ r1, switch_to_thread

;;;上文保存!!!!!!!

; 当进入PendSVC Handler 时,上一个线程运行的环境即:
; xPSR,PC(线程入口地址),R14,R12,R3,R2,R1,R0(线程的形参)
; 这些CPU 寄存器的值会自动保存到线程的栈中,剩下的r4~r11 需要手动保存
; 获取线程栈指针到r1
MRS r1, psp
; 将CPU 寄存器r4~r11 的值存储到r1 指向的地址(每操作一次地址将递减一次)
STMFD r1!, {r4 - r11}
; 加载r0 指向值到r0,即r0=rt_interrupt_from_thread
LDR r0, [r0]
; 将r1 的值存储到r0,即更新线程栈sp
STR r1, [r0]

;;下文切换␣   !!!!!
switch_to_thread
; 加载rt_interrupt_to_thread 的地址到r1
; rt_interrupt_to_thread 是一个全局变量,里面存的是线程栈指针SP 的指针
LDR r1, =rt_interrupt_to_thread
; 加载rt_interrupt_to_thread 的值到r1,即sp 指针的指针
LDR r1, [r1]
; 加载rt_interrupt_to_thread 的值到r1,即sp
LDR r1, [r1]
; 将线程栈指针r1(操作之前先递减) 指向的内容加载到CPU 寄存器r4~r11
LDMFD r1!, {r4 - r11}
; 将线程栈指针更新到PSP
MSR psp, r1


pendsv_exit
; 恢复中断
MSR PRIMASK, r2
; 确保异常返回使用的栈指针是PSP,即LR 寄存器的位2 要为1
ORR lr, lr, #0x04
; 异常返回,这个时候栈中的剩下内容将会自动加载到CPU 寄存器:
; xPSR,PC(线程入口地址),R14,R12,R3,R2,R1,R0(线程的形参)
; 同时PSP 的值也将更新,即指向线程栈的栈顶
BX lr
; PendSV_Handler 子程序结束
ENDP

 调度的本质是:在就绪列表中寻找优先级别最高的线程,然后执行该线程,

简单起见先实现两个任务的轮转。

rt_schedule() 函数:

 1 void rt_schedule(void)
 2 {
 3 struct rt_thread *to_thread;
 4 struct rt_thread *from_thread;
 5 /* 两个线程轮流切换*///
 6 if( rt_current_thread ==rt_list_entry(rt_thread_priority_table[0].next,struct rt_thread,tlist))
 7 {
 8     from_thread = rt_current_thread;
 9    to_thread = rt_list_entry( rt_thread_priority_table[1].next,
10                                           struct rt_thread,
11                                            tlist);
12    rt_current_thread = to_thread;
13 
14 }
15 else//
16 {
17 from_thread = rt_current_thread;
18 to_thread = rt_list_entry( rt_thread_priority_table[0].next,
19                                           struct rt_thread,
20                                            tlist);
21 rt_current_thread = to_thread;
22 }
23 /* 产生上下文切换*/
24 rt_hw_context_switch((rt_uint32_t)&from_thread->sp,(rt_uint32_t)&to_
25 ,→thread->sp);
26 }

rt_hw_contex_switch() 函数

/*
 ; *----------------------------------------------------------------------3; * 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
; 加载rt_thread_switch_interrupt_flag 的地址到r2
LDR r2, =rt_thread_switch_interrupt_flag
; 加载rt_thread_switch_interrupt_flag 的值到r3
LDR r3, [r2]
;r3 与1 比较,相等则执行BEQ 指令,否则不执行
CMP r3, #1
BEQ _reswitch
; 设置r3 的值为1
; 将r3 的值存储到rt_thread_switch_interrupt_flag,即置1
STR r3, [r2]
; 设置rt_interrupt_from_thread 的值
; 加载rt_interrupt_from_thread 的地址到r2
LDR r2, =rt_interrupt_from_thread
; 存储r0 的值到rt_interrupt_from_thread,即上一个线程栈指针sp 的指针
STR r0, [r2]
_reswitch
; 设置rt_interrupt_to_thread 的
32 ; 加载rt_interrupt_from_thread 的地址到r2
LDR r2, =rt_interrupt_to_thread
; 存储r1 的值到rt_interrupt_from_thread,即下一个线程栈指针sp 的指针
STR r1, [r2]
; 触发PendSV 异常,实现上下文切
LDR r0, =NVIC_INT_CTRL
LDR r1, =NVIC_PENDSVSET
STR r1, [r0]
; 子程序返回
BX LR
; 子程序结束
ENDP

 

posted @ 2021-05-07 16:33  大木繁生  阅读(136)  评论(0)    收藏  举报