写完了coos的说明书,还真感觉到写东西不简单啊。虽然coos是我写的,但是为它写个说明书却比想象中麻烦很多,而且就算写完了说明书,我自己看一遍,其实还是有点晕晕的。
![]() ![]()
最新下载地址:
旧版下载地址(不推荐):http://pan.baidu.com/share/link?shareid=3061&uk=218708851
分时任务管理coos
by
2012-08-12
目录
第一章 前言 第二章 coos简介 第三章 任务管理详解 第四章 任务管理的幕后操作 第五章 任务管理的幕后操作 第六章 coos条件编译 第七章 coos的配置 裁剪 类型定义
(以下所说的51特指STC89C52RC,晶振12M)
有点夸张。从产生为51写任务管理的念头,到开始着手写代码,应该有3,4天的时间。然后接下来就一直写个不停了。本来是从开始写到第二天,功能就实现了,剩下的就是慢慢完善。但是,事情总是没那么顺利,发现了一个又一个的bug,修改完一个又有另一个,再修改一个。这样持续了好几天。有时候一整天没解决一个bug
修改bug就是每天晚上睡不好,老是想着coos的bug,失眠是必须的。到最后发现了一个致命的bug,发现不得不用汇编来解决,然后就尝试着C语言嵌入汇编,但却偏偏一个C语言函数汇编出来的代码执行有错…让我只能怀疑编译器的问题的,因为本来没嵌入汇编那个函数是正常的,嵌入汇编后,函数的返回值传递出错了(编译器把返回值放到R3还是哪个忘记了,然后却从R4取返回值?问题我至今仍未能理解),我只能崩溃了。最后终于还是上火了,感冒发烧头痛…到校医院看病额。
在电脑上安装win8来玩玩,挺新鲜的,界面也挺好看。期间曾一度因为在win8上运行不了RVMDK而让我再度崩溃多次。最终放弃win8,用回win7。
coos是利用51的定时器0中断作为分时依据,进入中断后判断需要运行哪个任务,中断退出便切换到相应的任务去执行,实现了任务调度。这种管理机制类似于我们熟悉Windows,例如我们平时在Windows上运行多个任务时,有时会觉得卡。coos也一样,如果任务多了,每个任务占用的CPU必然就减少,给我们的感觉也是有点卡。
coos.c coos.h coos_hook.c coos_hook.h coos_conf.h coos_typedef.h
coos的实现由coos.c完成,是coos最核心的文件。 coos_typedef.h是为了coos的移植性需要加入的,定义coos使用的变量类型,如uint8_t coos_conf.h是coos的配置文件,实现的可裁剪功能都在这个文件设置,也就是条件编译的开关。 coos_hook.c钩子函数源文件是由用户实现的,类似于ucos的钩子函数,例如选择启用任务调度的钩子函数,用户将自己的钩子函数写好后每次任务调度都会调用任务调度的钩子函数。
第三章
功能看截图就大概可以理解了。我再说明一下大概思路: 606行之前:51产生定时中断,C语言看不到的入栈操作:包含R0~R7,ACC,B,DP0L,DP0H,PSW,还有最重要的PC指针,也就是现场保护。 606行:一个赋值语句,保存的是当前的SP(栈指针),保存在当前任务的任务控制块里面 607行:也是赋值语句,修改当前的SP,将当前栈指针修改为定时器函数专用的栈 609~627行:先别管 629行:赋值语句,修改SP指针为下一个任务的SP指针,中断退出将按下一个任务的任务栈出栈,出栈时修改了PC指针,实现了现场还原和任务调度
至于为什么要这么做我也说明一下。任务调度最重要的就是现场保护和现场还原了。现场保护利用的是51产生中断时的入栈操作,这是C语言不用管也管不了的。现场还原是利用51退出中断的出栈操作,也是C语言所管不了的。虽然说不用管,但是大部分时候现场保护并不完全。我们需要将每个任务用到的寄存器都入栈。经调试发现,51入栈机制是,在中断服务程序有用到的寄存器就一定会入栈,PSW和PC是一定会入栈的。
为了现场保护完全,我在中断服务函数里面,用C语言把每个通用寄存器都访问了一遍(因为有一些通用寄存器没入栈保护),所以每个通用寄存器都入栈。其他像ACC,B寄存器等也入栈了。这些操作在606行之前完成的。要看汇编出来的代码才知道,所以说任务管理还是从汇编的角度去理解好一点。 然后现场还原就好办了。只要在中断服务函数退出前修改SP指针(629行),SP指针在中断服务函数就保存在相应任务的任务控制块了(606行),629行一个赋值语句的作用就是,中断会按照相应的任务栈出栈(在606行保存的SP的值),之后会还原寄存器和PC指针,实现了任务调度。只要修改了PC指针,程序就跳转到相应的位置执行,而PC指针在中断服务函数里面已将入栈保存起来了,出栈后就切换到上次该任务执行的断点位置,实现任务的继续。
第四章
609~612行,624~627行。这两个实现相同的功能,为定时器赋初值,条件编译的条件为是否使能精确延时,条件编译结果是选择其中的一个。 在coos_conf.h文件里有:
#define 也就是默认没有使能精确延时,如果需要使能精确延时的话只要将0改为1就行了。 说明一下65535
OS_TICK_CNT
OS_ETR_TICK_TIM
使能精确延时的结果是,coos提供的时钟对外是比较精确的,例如做一个定时器时钟,可以比较精确的运行一段时间。但是每个任务执行的时间却减少了,因为中断响应函数的执行也是需要时间的,这对于分时任务管理来说是不公平的,因为任务调度的时间是不一定相同的,导致任务执行的时间也不一定相同。 没有使能精确延时,那么coos提供的延时默认是5ms
函数OS_Hook_Tick()在coos_hook.c文件,默认函数体为空,该函数由用户实现。
先看最简单的617行,OS_ResSav();该函数访问通用寄存器,引起定时器中断函数执行前,通用寄存器入栈(中断服务函数访问到哪些通用寄存器,那些通用寄存器就会入栈),具体代码是: 540行:让res_ptr指向0x00,也就是51的工作寄存器组0的R0,这个还是从汇编的角度去看会好理解一点。51的256Byte的RAM,前8Byte 542~549行:访问通用寄存器,用下标的形式访问,自己给自己赋值,分别访问R0,R1…R7,实际寄存器值没改变,目的是要在定时器函数执行前将通用寄存器入栈。完善现场保护工作。
接下来看614行,OS_TickTimDeal(); 代码也是相当简单的,看截图 该函数处理coos的时间,由于是分时任务管理,当然要知道当前运行到哪个时间片,并且每个任务一旦运行了一个时间片,也要将时间片减一。 499行:当前任务时间片减一,也就是当前任务运行了一个时间片 501行:系统时间片加一,分时管理任务,当然要有一个变量(OS_Tick)记录当前时间片
502行:OS_TICK_PERIOD系统时间片周期,默认为20,判断是否满一个周期 504行:满一个时间片周期,时间片清零 505行:满一个时间片周期,置位系统周期标志位
508行:判断是否满一个系统周期,标志位是在505行置位的 510行:满一个系统周期,标志位清零 512行~518行:条件编译,如果使能系统提供参考时间,也就是延时时间才编译这几行 520行:调用任务时间片初始化函数,重新刷新任务时间片,具体代码如下截图 477行:一个for循环,循环次数为任务个数,其中OS_TASK_NUM可配置,最大为8 479行:for循环的循环体,OS_TaskTimGrp数组记录任务时间片,这里是按照任务创建时的时间片去刷新 481~483行:条件编译,如果使能系统空闲任务。系统空闲任务是指在一个系统周期内(例如20个时间片内),如果没有可运行任务(只需要执行不到20个时间片的任务),也就是创建的各个任务时间片已经执行完,但是一个周期还没到,就执行空闲任务。coos默认提供的空闲任务是让51进入低功耗模式,省电。当然可配置,用户可以自行创建空闲任务。 482行:刷新空闲任务时间片
任务调度还剩下最后一个函数,继续任务调度那张截图: 看615行最后一个函数:目的是获取下一个需要执行的任务,为切换任务做准备 具体函数代码如下截图:
该函数的思路是:在任务状态表上查找下一个任务。任务状态表是579行的OS_TaskStatGrp,是一个8bit的变量,每一位为0或者为1代表是否创建了任务,1表示创建了任务,当然最多支持8个任务。 查找到有创建的任务,然后判断该任务时间片是否为0,如果不为0,那么下一个任务就是被查找到的任务,如果为0,那么继续查找下一个任务。如果所有被创建的任务时间片都为0,那么返回OS_TASK_NUM,代表着下一个任务执行的是空闲任务。 567行:为id赋初值,为当前执行任务的id 568行:i初值为0,循环控制用 570行:进入循环,循环次数为coos管理任务的个数,由OS_TASK_NUM决定,可配置 579行:重点!!判断该id的任务是否需要执行,判断的条件有两个,分别是: 1、在该id上创建了任务 2、该任务时间片不为0
前半部分:(OS_TaskStatGrp OS_TaskStatGrp是coos任务状态表,8bit,每一位代表一个任务,为1表示创建了任务 SET_BIT()是一个宏操作,代码是
#define
SET_BIT(i)
(1 表示置位某个位,这里SET_BIT(id)是置位id那一位 操作(OS_TaskStatGrp
后半部分:OS_TaskTimGrp[id] 判断任务时间片数组上该任务时间片是否为0 如果满足前半部分和后半部分都为真,那么就需要切换到的任务就是临时变量id了。 所以就有: return
当然如果不能同时满足两个条件,就 return 表示下一个任务是空闲任务。
至此,任务调度讲解是完了,不过还是再总结一下:
1、
2、
3、
4、
5、
6、
7、
8、
9、 10、退出任务调度函数,按照下一个任务的SP出栈,还原寄存器,PC指针等,实现了任务调度
到这里其实还遗留一个问题:切换到的任务如果还没被执行过的话(定时器中断入栈是入栈被中断的任务,说明该任务已经执行了),那么相应的PC指针,寄存器值是什么?下一章继续介绍。
第五章
来到这里,可以看到,要实现任务管理功能,还需要很多幕后操作的。这次我们实际来看任务管理从头到尾怎么做的,要开始来弄清楚来龙去脉了。这一章,需要分几个小节了。
5、1
看看coos的C语言工程吧。截图:
先看左边的文件,两个文件夹coos和user。 coos里面两个源文件coos.c和coos_hook.c。这两个文件是任务管理文件,只要包含了这两个文件和相应的头文件,再调用几个函数就可以实现分时任务管理功能了。 user里面两个源文件是用户自己编写的。main.c和task.c分别是主函数文件和被管理任务的文件。任务到底是什么,等一下再看。这里先介绍主函数应该怎么样写,也就是前面啰嗦了那么多,那到底coos怎么用?
要先添加了coos的几个文件到自己的工程,然后大概浏览一下那10来行代码。可以看到主函数的操作分成三部分: 1、初始化coos 2、创建任务 3、开始运行coos
具体再看代码:
12~14行:定义三个数组,用来做任务栈。可以看到该主函数创建了三个任务,因为每个任务要有一个任务栈,所以定义了三个数组来当作任务栈。 16行:主函数开始入口。 18行:coos初始化。调用coos的函数,一些coos运行必要的操作,等一下再说。 20~22行:创建任务。也是调用coos的函数,创建任务后准备交给coos管理。 24行:coos开始运行。同样是coos的函数,coos开始分时管理被创建的三个任务。
可以看到,要使用来管理任务是非常简单的,主要在主函数里面调用几个coos提供的函数就行了。没错,但是这次我们要看coos是怎样运行的,了解到具体的每一步。只要理解coos几个重要的函数,就能很深入地理解coos了。
5、2
1、void coos定时器初始化函数,跟普通的51定时器初始化函数没什么不同,但也看一下具体代码。 功能是配置定时器0,让51能够产生中断,相当好理解。 其中457行的TR0
2、void coos创建任务的函数,非常重要!!
可以说coos得以运行都是这个函数和定时器中断函数在幕后的操作了,这个函数有这跟定时器中断函数同样级别的重要性!!并且coos的很多概念都在这个函数里面使用到。
先介绍一下一些基本概念,再来说明任务创建函数。
①在C语言中,函数名代表函数的地址。什么意思?举个例子说明一下: 比如有一个函数:void Func是函数名, Func()是这个函数。 要知道它们是有区别的。 首先我们可以把Func当成一个常量,这个常量是一个地址,也就是Func()在ROM存放的地址,ROM在51上我们称为code。这些属于C语言的知识,大家有兴趣在继续深入了解。我们这里要用到的只是要知道Func就是一个常量,这个常量是一个地址,这个地址是函数Func()在ROM上的地址。就行了。
但是它有什么用?如果跟PC指针联系起来的话就知道了解这个是有作用了。 PC指针存放的是下一条指令的地址,如果把Func赋值给PC,那么CPU下一个执行的指令就是Func()函数的指令了,类似于C语言的函数调用,调用的函数是Func()。其实调用函数的时候就是将PC指针修改成对应函数的地址,CPU就跳转去执行相应的函数了。但是调用函数的话还涉及到函数参数的传递和函数的返回值,与只修改PC指针也是有区别的。
②任务控制块 先看代码截图: 要管理任务,当然要有任务的信息。任务控制块就是用来保存任务信息的。因为51的RAM只有256Byte,所以任务控制块我也尽量减小,只有任务栈和任务时间片是必要的。
任务栈地址:每个任务要有自己的栈,任务栈的地址用来记录任务进入中断的SP的值(栈指针),方便中断退出按该任务栈出栈实现现场还原。在第二章讲任务管理原理时,进入中断的第一件事就是保存任务的SP到任务控制块的栈地址,也就是606行的赋值语句。 任务时间片:每个任务还要有时间片。也就是任务在一个周期内最多执行多少个时间片。因为是分时任务管理,所以不能让某个任务占用所有CPU,要按照时间片去执行。
接下来看任务创建函数,截图:
函数带四个参数,每个参数都相当重要。 第一个参数: void 第二个参数: uint8_t 第三个参数: uint8_t 第四个参数: uint8_t
开始看具体的函数代码,现在应该很好理解了。 099行:目的是判断该id上是否已经创建了任务。判断条件是任务状态表该为是否为1。其中操作SET_BIT()是一个宏操作。在coos.h里面有:
#define 目的是置位某个位,操作(SET_BIT(TaskID))是置位id那一位,然后跟OS_TaskStatGrp(任务状态表)与一下是否为真。如果为真,该id上已经创建了任务;如果不为真,该id上未创建任务。
如果任务id已被占用存在,那么运行101~104行。 101~103行:条件编译,条件是是否使能系统错误统计,如果使能系统错误统计则运行OS_ErrCnt(); 104行:return,函数返回。因为不能创建任务(任务id被占用了)
如果任务id未被占用,则运行108~118行: 108行:因为要在该id上创建任务,所以置位任务状态表在该id上的那一位
109行:记录任务的任务栈,也就是任务的SP。记录在对应任务的任务控制块里面。任务控制块是一个结构,任务控制块在上面已经说明了。我这里是定义一个结构数组,结构就是任务控制块结构,每个任务的任务控制块就是在该结构数组上的一个元素(结构数组的元素是一个结构)。
右边(uint8_t)TaskStk 第一部分(uint8_t)TaskStk 第二部分 了解了这些之后就可以推断出
左边OS_TaskTcbGrp[TaskID].TaskStk
110行:右边是任务创建函数的第三个参数,任务时间片,在任务控制块上记录任务时间片。 112~113行也是重点和难点!! 赋值语句,模拟入栈。要先知道PC指针是16bit的,入栈占用两个字节。但是究竟是高字节先入栈还是低字节先入栈?有办法!进入调试模式,然后观察内存。我是用这个办法知道51入栈PC指针是低地址先入栈,然后高地址的。大家有兴趣可以自行尝试。 知道了先入栈低地址,下一步是模拟出入栈的是任务刚要运行时的PC指针。上面已经讲到函数名就是函数的地址,与PC指针有联系。 112行: 右边是一个宏操作,在coos.h里面有:
#define 目的是获取函数的低地址,宏操作的参数是func,具体应用时候是一个函数名,也就是一个常量(函数的地址),操作先将函数的地址func强制转换成一个32bit的变量,右移0是为了配合获取函数高地址操作写的,实际没用,然后再强制转换成8bit的变量。 左边是任务栈,用下标形式访问栈底,将函数的低地址赋值给栈底,模拟了PC指针入栈时的低八位地址入栈。 113行:TaskStk[1] 理解了112行,这一行就好办了。模拟入栈PC指针的高八位。 右边一样是一个宏操作,在coos.h里面有:
#define 目的是获取函数func的高八位地址。 左边任务栈,因为刚才入栈函数的低八位地址,所以栈要向上增长,用下标访问的形式就是 TaskStk[1]了,这一行模拟出PC指针入栈是的高八位地址入栈。
115~118行:模拟其它寄存器入栈,默认都是0x00,其他寄存器包含有R0~R7,ACC,DP0H,DP0L,PSW具体要看汇编代码才知道是这些寄存器的。
至此任务创建函数就完了。这里在总结一下,在coos里要创建一个任务就是要将任务挂在任务状态表上,供coos管理,而且要填好任务的信息,包括任务栈顶,任务时间片。最后模拟任务被中断服务函数中断了的入栈操作。具体再分几步说明: 1、判断任务id是否被占用 2、如果任务id被占用就不创建任务,根据条件编译是否运行系统错误统计函数
3、任务id没有被占用,置位coos任务状态表对应id那一位,说明要在该id创建任务
3、void 因为coos管理任务是按时间片管理的,任务一旦运行一个时间片,时间片就被减一。所以每个周期要刷新一次任务时间片。这个函数就是每个周期调用一次的任务时间片初始化,重新刷新任务时间片共coos管理。 477行:for循环,循环次数是coos管理的任务个数 479行:for的循环体,功能是根据任务时间片初始化任务时间片数组,就是记录各个任务创建时的时间片,创建一个副本。任务运行后的时间片减一操作在这个副本进行的。 481~483行:条件编译,条件是如果使能系统提供空闲任务。如果使能了系统提供空闲任务,那么就会刷新空闲任务的时间片。其中常量OS_IDLE_TASK_TIM的值与coos一个周期的时间片个数相等。在coos_conf.h里面有:
#define 其中后半部分OS_TICK_PERIOD是coos一个周期时间片的个数。
理解了这三个函数后就可以来看看coos具体的运行过程了。现在开始要具体从main函数开始看coos的任务管理了。
5、3
再来main函数的截图吧: 从现在开始一步一步分析main函数。
1、OS_Init();
进入这个函数继续分析。看该函数截图: 062行:OS_Tick_Init();代码上节已经分析过,重温一下,看截图: 064行:变量OS_CurTaskID记录当前是执行那个任务,初始化将它初始化为空闲任务。 065行:变量OS_TaskStatGrp是coos任务状态表,初始化为0表示没有创建任务。 067行:for循环,循环次数是coos管理任务的个数,也就是常量OS_TASK_NUM,可配置 069~070行:for循环的循环体,将coos管理的任务的任务控制块任务栈初始化为0x00,时间片初始化为0 073~075行:条件编译,两个条件。分别是 1、如果使能coos空闲任务,可以不使能 2、如果使能coos提供的空闲任务,当然可以自行创建其他任务作为coos空闲任务 coos默认提供的空闲任务是让51进入低功耗模式,省电。此时51等待中断唤醒。具体可以查找51数据手册继续深入理解,代码看截图: 到了这里,主函数的第一个函数就完了,接下来继续看主函数的其他操作。
2、OS_TaskCreate(Task_One,
重温一下任务创建函数,截图:
这个函数在上一节也已经说明过,再罗嗦一下。 该函数的操作是创建一个名字为“Task_One”的任务,其中Task_One是一个函数名,也就是有一个Task_One()的函数,具体任务做什么等一下再说。 任务 任务Task_One的时间片为5,也就是Task_One一个周期最多执行5个时间片 任务Task_One的id为0,也就是Task_One在任务状态表OS_TaskStatGrp占用了第0位
Task_One的具体代码再看一下,相当简单的一个任务。截图: 说明一下任务必须在一个死循环里面,即时任务只需执行一次,也必须让任务在死循环里面执行,不能让任务函数返回。因为返回的话,有发生不可预知的结果,一般就是程序跑飞了。因为返回时谁也不知PC指针会指向哪里。 如果有只需执行一次的任务的话,coos当然考虑了这个情况。那就是在任务运行完后调用coos的任务删除函数void OS_TaskDel(0); 就将任务删除了。 3、OS_TaskCreate(Task_Two,
OS_TaskCreate(Task_Thr, 再创建两个任务,类似与创建任务1。 任务代码也很简单,截图: 功能类似与任务一。任务二中被注释的内容是我在写coos时测试函数功能是否正常用的。 创建了任务之后,我们要让coos开始运行了。继续看mian函数中的最后一个函数。
3、OS_Start(); 代码在截图:
135行:coos任务时间片初始化,该函数上面已经上一节也已经说过,重温一下代码: 因为创建任务的时候在任务控制块里面填写了任务的时间片。然后我们想让coos管理这些任务,我就创建了一个数组,作为任务时间片的副本。因为任务运行后我们要记录已经运行了,直接在任务控制块里面操作会造成任务信息丢失,所以创建一个副本。在coos.c里面有:
static 用这个数组来记录任务时间片,也就是运行了任务就来一次任务时间片减一操作,满一个周期后再重新刷新。任务时间片为0任务就不会被执行。
138行:因为coos运行前默认coos是在运行空闲任务,第一次进入中断服务函数(任务调度函数),会将相应任务的时间片减一(第一次进入中断空闲任务时间片会被减一),所以先加一,解决第一次进行任务调度出现的bug 138行:因为第一次进入中断,OS_Tick会加一(coos时间片记录函数),表示中断了一个任务,coos运行了一个时间片,但实际第一次进入中断并没有运行一个时间片的任务,所以OS_Tick减一,解决第一次进行任务调度的bug
140行:表示定时器开始计时。 141行:表示定时器产生溢出,实际没溢出,只是为了立刻进入中断。 此时CPU的不会继续执行141行下面的代码了,它会进入定时器0中断服务函数,但是下面的代码还是有可能被执行的。 143~147行:条件编译,如果使能空闲任务和coos提供的空闲任务 空闲任务放在这里,是因为当没有可运行任务的时候,coos任务调度返回,141行下面的代码。为什么,可以自行思考一下。原因是141行是第一次被中断的断点,然后进行任务调度。任务调度函数会记录断点,入栈保护,并且coos初始化是默认的任务是空闲任务,所以入栈保护入的栈是空闲任务的栈。如果将空闲任务放在141行下面,那么就模仿的非常的像是第一次中断断点就是空闲任务了。
至此,coos任务调度讲解完毕。当然要看代码,要调试,要看一下汇编代码,要看51的说明书,要懂一点微机原理,要有C语言基础,才能更深刻的了解coos。 简单总结: 1、初始化定时器,coos使用的变量。为coos分时管理任务做准备 2、创建任务,准备让coos管理(创建任务是记录任务信息,并模拟任务被中断函数打断) 3、开始运行coos,启用定时器中断根据任务信息调度任务
当然为了让coos更强大,并且作为嵌入式的东西,可裁剪是必不可少的。所以很多条件编译默认都是不编译的,下一章主要说一下条件编译函数。
以下已经不是重点了,可有可无。coos的主要目的是任务管理。并且发现分时任务管理比较简单实现,所以就是分时任务管理的coos了。
第六章
数了一下是11个条件编译函数,都是相当简单或者说理所当然会有这些函数,没有多少技术含量了这些。下面一一说明:
1、void 任务删除函数,看截图: 169行:传递进来的参数是任务的id 171行:判断任务是否创建了,因为创建任务函数默认已经置位了任务状态表(OS_TaskStatGrp)对应的id那一位,所以如果对应的为1则说明确实创建了任务,准备删除任务。 173行:目的是将任务状态表(OS_TaskStatGrp)对应任务id那一位清零。其中RESET_BIT()是一个宏操作,在coos.h文件里面有:
#define 目的是清零对应i的那一位,最重要是这一步就把任务删除了,任务就再也不会运行了,下面的步骤只能说是善后吧。 174~175行:任务栈置为0x00,时间片置零 176行:任务时间片数组(OS_TaskTimGrp)对应任务id那个元素的时间片清零
如果任务状态表(OS_TaskStatGrp)对应任务id那一位为零,也就是对应id上没有创建任务,根据条件编译是否运行系统错误统计函数,这里不是重点。
2、void coos提供的空闲任务,老样子,截图: 跟普通任务一样是进入一个while死循环,因为一旦函数返回,会有意想不到的结果反生,难以预测发生什么事,程序跑飞了那是不可避免的。 204行:根据51数据手册,置位PCON的第0位让51进入低功耗,具体可参考数据手册
3、uint8_t 获取当前运行任务id的函数,截图: 直接将OS_CurTaskID返回,该变量记录当前被运行的任务 来到这里相信同学们也觉得条件编译函数是相当的…继续
4、uint8_t 获取coos提供的参考时间,截图: 类似与上一个函数,不说了。
5、void 设置coos当前参考时间,截图: 对OS_Tim赋值,就是设置coos的参考时间了
6、void 例如当前正在运行某个任务,我们已经不想再让任务运行下去了,就可以调用这个函数,进入任务调度函数,让任务调度函数判断下一个任务是什么而去执行下一个任务,当前任务就没有被执行了。 这个时候任务这个函数的价值就体现出来了。因为我们的任务都必须设置成在一个死循环里面执行,如果没有这个函数,那么coos必须也让该任务运行一个时间片,如果在这个时间片内引脚的状态都为0,那么可想而知,该任务是在浪费CPU。如果我们改进一下: 如果状态为0,则一样运行一段代码,如果引脚状态为1,调用该函数,让任务放弃CPU。 那么CPU的利用率就大大提高了,下面看代码。截图: 282行:判断任务是否存在 284行:任务确实存在,置位TF0,定时器0溢出标志,则进入定时器中断函数进行任务调度。 289行:任务并不存在,根据条件编译是否运行coos错误统计函数(这里不是重点)
7、void 任务时间片修改函数:截图 因为创建任务的时候我们指定了任务的时间片,coos管理任务后任务的CPU使用率是一定的,如果要增加或减少任务的CPU使用率可以试着用这个函数。 传递进来的参数: 1、uint8_t 2、uint8_t 当然311行先判断任务是否存在 任务存在则修改任务控制块数组中该任务的任务控制块的时间片为新设置的时间片 任务不存在根据条件是否编译运行coos错误统计函数
8、void coos错误次数清零函数。截图: 条件编译有两个条件: 1、使能coos的错误统计 2、使能错误统计清零 340行:简单的将OS_Err_Cnt赋值为0就清零了
9、uint8_t 获取coos当前时间片,截图: 360行:直接将OS_Tick返回。
10、void coos提供延时函数,截图: 稍微有一点点复杂,不过说一下基本思路应该就很清晰了。 延时的话是根据coos提供的参考时间作为延时依据的,所以要根据延时的时间和coos提供的参考时间,计算出延时的终点(延时的结束条件),在延时的条件内就等待。 384行:临时保存coos参考时间变量OS_Tim 385行:Tim是传递进来的参数,加上OS_Tim是延时的终点,但要判断是否溢出 387行:判断延时终点是否溢出,溢出条件是Tim
OS_Tim
因为延时终点溢出也可知延时终点coos的参考时间OS_Tim大于计算出的延时终点Tim(Tim是计算出的延时终点,第二个条件是只要coos参考时间大于该值则等待结束)
OS_Tim
如果对延时函数不理解的话拿起笔和纸写一下就懂了,我是用笔和纸计算出来的…(——#!)
11、void 最后一个条件编译函数,coos错误统计。虽然是个很简单的函数,不过因为没有更复杂的函数了,所以也只能拿它来压压轴,看截图: 415行:判断错误次数是否溢出,未溢出进入417~420行
422~428行:条件编译,条件是是否使能错误次数溢出钩子函数,进行错误次数溢出处理。
条件编译函数就完了,最后还剩下的就是coos的配置,裁剪了,下一章介绍。
第七章
当然作为嵌入式的东西很将就可裁剪性的,配置和裁剪在coos_conf.h文件里面。 先看看配置部分,截图: 一个一个说: 19行:coos定时器计数值,也就是coos一个时间片的长度。该值越大,coos任务调度的频率就越小,CPU的使用率就越高。但是太大的话任务被打算时间太长,所以要折衷配置。默认5000,晶振12M的时候一个任务时间片是5ms
20行:coos时间片周期,也就是多少个时间片为一个coos周期。一个周期后coos会重新刷新任务时间片。同样该值越大,CPU利用率越高,但这个值的影响是不明显的。
21行:coos参考时间的周期,也是coos提供延时的最大值。对CPU利用率几乎没影响。
22行:coos管理任务的个数。该值越小,CPU利用率越高,RAM占用越少。因为任务调度的时候遍历了每个任务,需要时间。并且每个任务都要有任务控制块(2Byte)和任务时间片副本(1Byte),占用RAM。综合说名一下就是,我们需要创建多少个任务,就定义该值为多少,不用预留余量之类的。
23行:定时器专用栈的栈大小,默认为8其实已经够用了,如果使能的定时器钩子函数,并且钩子函数过于复杂(运行起来占用栈较大),那么该值需要适当加大,因为栈溢出了程序运行是肯定会有问题的。
24行:coos空闲任务默认的时间片,该值与coos时间片周期大小一样。目的是为了在一个时间片周期内,如果没有一个可以运行的任务,那么所有时间片周期运行空闲任务(不会因为空闲任务时间片不够而发生意想不到的结果)
接下来是裁剪,老样子,截图: 裁剪也就是上一章的条件编译函数的使能,后面记录裁剪掉多少RAM和code 需要使用到上一章的条件编译函数就使能,1是使能 可以看到coos默认使用很少条件编译函数,因为都是比较简单的(就是上一章的内容),所以不说了。
钩子函数的配置,截图: 相信大家一看就懂了的,1使能。 coos钩子函数要用户自己实现(自定义的功能了),在coos_hook.c文件里面,将自己的代码写进去就行了,当然要使能了才有用。看一下coos_hook.c这几个函数吧。 截图:
还有类型定义,看截图: uint8_t
附录 coos的说明书也应该结束了吧。顺便提一下就是,实现coos任务管理功能也就那不到200行代码(除去条件编译这些可有可无的东西),但是也写了我一个月左右的时间了。因为确实也走了一下弯路。例如我想让任务调度独立成为一个函数,定时器中断只是负责篡改返回地址,但是却有寄存器没入栈保存完善的bug。这个bug曾让我一度想放弃,因为发现到不得不用汇编,我是大大的不想用汇编的。机器语言已经跟机器密切相关了,移植性大大减少了,同时新鲜性也没了。因为用汇编的话实现任务管理难度是不大的。 期间也上火,头痛,牙痛,在校医院看了两次,差点要放弃。 装上win8,安装了RVMDK,运行不了,用什么win7兼容啊,XP (RVMDK是什么?接近keil增强版吧,我就是用RVMDK写coos的,keil是RVMDK的前身,RVMDK是stm32,arm之类用的,当然51也行,支持很多微处理器)
参考文献: 挺多的,可以说看了不少书吧。能想到的就写出来吧 1、C和指针(C语言的书) 2、51单片机汇编实例(网上随便下载看看的) 3、c51中文书名keil 4、微型计算机原理与接口技术(上课的课本…) 5、《嵌入式实时操作系统uCOS-II》(第二版) 6、其它的对coos贡献不大,有C语言的,数据结构的,算法的,stm32的
|
|