最近有时间学习操作系统方面的知识,所以做些笔记加深印象。在这方面确实是小白了。学的是野火的资料。这个资料确实不错,但是有些地方还是要自己进行总结归纳进行学习。
我总结这个有点多,慢慢看可以看懂的。
之前接触的比较多的是前后台系统,就是
外部事件的响应在中断里面完成,事件的处理还是回到轮询系统中完成,中断在这里我们称为后台,main 函数里面的无限循环我们称为后台。
在顺序执行后台程序的时候,如果有中断来临,那么中断会打断后台程序的正常执行流,转而去执行中断服务程序,在中断服务程序里面标记事件,如果事件要处理的事情很简短,则可在中断服务程序里面处理,如果事件要处理的事情比较多,则返回到后台程序里面处理。
相比前后台系统,多任务系统的事件响应也是在中断中完成的,但是事件的处理是在任务中完成的。
在多任务系统中,任务跟中断一样,也具有优先级,优先级高的任务会被优先执行。当一个紧急的事件在中断被标记之后,如果事件对应的任务的优先级足够高,就会立马得到响应。相比前后台系统,多任务系统的实时性又被提高了。
相比前后台系统中后台顺序执行的程序主体,在多任务系统中,根据程序的功能,我们把这个程序主体分割成一个个独立的,无限循环且不能返回的小程序,这个小程序我们称之为任务。每个任务都是独立的,互不干扰的,且具备自身的优先级,它由操作系统调度管理。加入操作系统后,我们在编程的时候不需要精心地去设计程序的执行流,不用担心每个功能模块之间是否存在干扰。
在编程时,ucosIII有很多固定文件,这里就先不全部写了。写一些重要的。
main函数写在app.c文件里
#include "os.h"
#include "ARMCU3.h"
uint32_t flag1;
uint32_t flag2;
//TCB,STACK声明
#define TASK1_STK_SIZE 20
#define TASK2_STK_SIZE 20
static CPU_STK TASK1Stk[TASK1_STK_SIZE];
static SPU_STK TASK2Stk[TASK2_STK_SIZE];
static OS_TCB TASK1TCB;
static OS_TCB TASK2TCB;
void Task1(void *p_arg);
void Task2(void *p_arg);
int main(void)
{
OS_ERR err;
//初始化全局变量
OSInit(&err);
OSTaskCreate((OS_TCB *) &Task1TCB,
(OS_TASK_PTR) Task1,
(void * ) 0,
(CPU_STK *) &Task1Stk[0],
(CPU_STK_SIZE) Task1_STK_SIZE,
(OS_ERR *) &err);
OSTaskCreate((OS_TCB *) &Task2TCB,
(OS_TASK_PTR) Task2,
(void *) 0,
(CPU_STK *) &Task2Stk[0],
(CPU_STK_SIZE) Task2_STK_SIZE,
(OS_ERR *) &err);
//将任务假如到就绪列表
OSRdyList[0].HeadPtr = &Task1TCB;
OSRdyList[1].HeadPtr = &Task2TCB;
//启动OS,不再返回
OSStart(&err);
void Task1(void *p_arg)
{
while(1)
{
flag1 =1;
delay(100);
flag1 = 0;
delay(100);
//手动切换任务
OSOched();
}
}
void Task2(void *p_arg)
{
while(1)
{
flag2 =1;
delay(100);
flag2 = 0;
delay(100);
//手动切换任务
OSOched();
}
}
}
这就是大致流程,具体后序有说明。在这只要知道这个和前后台区别和大致代码流程。
**在多任务系统中,我们根据功能的不同,把整个系统分割
成一个个独立的且无法返回的函数,这个函数我们称为任务,也有人称之为线程。**就像上面的Task1这样的函数。
在多任务系统中,每个任务都是独立的,互不干扰的,所以要为每个任务都分配独立的栈空间,这个栈空间通常是一个预先定义好的全局数组。就是上面的 TsakStk[TASK1_STK_SIZE]。
在多任务系统中,有多少个任务就需要定义多少个任务堆栈。
typedef unsigned short CPU_INT16U;
typedef unsigned short CPU_INT32U;
typedef unsigned short CPU_INT08U;
typedef CPU_INT32U CPU_ADDR;
typedef CPU_INT32U CPU_STK;
typedef CPU_ADDR CPU_STK_SIZE;
系统为了顺利的调度任务,为每个任务都额外定义了一个任务控制块TCB(Task Control Block),这个任务控制块就相当于任务的身份证,里面存有任务的所有信息,比如任务的堆栈,任务名称,任务的形参等。有了这个任务控制块之后,以后系统对任务的全部操作都可以通过这个 TCB 来实现。
TCB也是一种数据类型,定义如下:
typedef struct os_tcb OS_TCB;
struct os_tcb{
CPU_STK *StkPtr; //堆栈指针
CPU_STK_SIZE StkSize; //堆栈大小
};
现在OS_TCB成员还少,后面会增加。
任务的堆栈,任务的函数实体,任务的 TCB 最终需要联系起来才能由系统进行统一调度。那么这个联系的工作就由任务创建函数 OSTaskCreate 来实现。这个函数在os_task.c中。
typedef void (*OS_TASK_PTR)(void *p_arg);
void OSTaskCreate(OS_TCB *p_tcb,
OS_TASK_PTR p_task, //(1)
void *p_arg, //(2)
CPU_STK *p_stk_base, //(3)
CPU_STK_SIZE stk_size, //(4)
OS_ERR *p_err) //(5)
{
CPU_STK *p_sp;
p_sp = OSTaskStkInit(p_task,
p_arg,
p_stk_base,
stk_size);
p_tcb->StkPtr = p_sp;
p_tcb->StkSize = stk_size;
*p_err = OS_ERR_NONE;
}
(1) 这个我的理解是类似于回调函数那样
(2)是任务形参,用来传递任务参数
(3) 用来指向任务堆栈的起始地址
(4) 堆栈的大小
(5) 错误码
OSTaskStkInit实现不写了,太长了。
(1) p_task 是任务名,指示着任务的入口地址,在任务切换的时候,需要加载到 R15,即 PC 寄存器,这样 CPU 就可以找到要运行的任务。
(2)p_arg 是任务的形参,用于传递参数,在任务切换的时候,需要加载到寄存器 R0。R0 寄存器通常用来传递参数。
(6)任务第一次运行的时候,加载到 CPU 寄存器的环境参数我们要 预先初始化好。初始化的顺序固定,首先是异常发生时自动保存的 8 个寄存器,即 xPSR、 R15、R14、R12、R3、R2、R1 和 R0。其中 xPSR 寄存器的位 24 必须是 1,R15 PC 指针必须存的是任务的入口地址,R0 必须是任务形参。
这个意思是将剩余栈的栈顶指针 p_sp 保存到任务控制块 TCB 的第一个成员 StkPtr 中。
**任务创建好之后,我们需要把任务添加到一个叫就绪列表的数组里面,表示任务已经
就绪,系统随时可以调度。**就是这个
这里数组是有类型的:
typedef struct os_rdy_list OS_RDY_LIST;
struct os_rdy_list{
OS_TCB *HeadPtr;
OS_TCB *TailPtr;
};
HeadPtr用来指向任务TCB,TailPtr是同一个优先级支持多个任务的时候才需要使用头尾指针来将 TCB 串成一个双向链表。
先写到这,手打有点累了。