资料库

基于嵌入式实时操作系统的程序设计技术+周航慈.pdf

第1页 / 共18页
第2页 / 共18页
第3页 / 共18页
第4页 / 共18页
第5页 / 共18页
第6页 / 共18页
第7页 / 共18页
第8页 / 共18页
资料共18页,剩余部分请下载后查看
如果该任务使用了共享资源,必须在自我删除之前释放(如释放内存块,发送互斥 信号量) 。
第四章 任务设计 在基于实时操作系统的应用程序设计中,任务设计是整个应用程序的基础,其他软件设 计工作都是围绕任务设计来展开,任务设计就是设计“任务函数”和相关的数据结构。 4.1 任务函数的结构 在用户任务函数中,必须包含至少一次对操作系统服务函数的调用,否则比其优先级低 的任务将无法得到运行机会,这是用户任务函数与普通函数的明显区别。任务函数的结构按 任务的执行方式可以分为三类:单次执行类、周期执行类和事件触发类,下面分别介绍其结 构特点。 4.1.1 单次执行的任务 此类任务在创建后只执行一次,执行结束后即自行删除,其任务函数的结构如下: 程序清单 L4-1 单次执行任务函数的结构 void MyTask (void *pdata) //单次执行的任务函数 { 进行准备工作的代码; 任务实体代码; 调用任务删除函数; } //调用 OSTaskDel(OS_PRIO_SELF) 单次执行的任务函数由三部分组成:第一部分是“进行准备工作的代码”,完成各项准 备工作,如定义和初始化变量、初始化某些设备等等,这部分代码的多少根据实际需要来决 定,也可能完全空缺。第二部分是“任务实体代码”,这部分代码完成该任务的具体功能, 其中通常包含对若干系统函数的调用,除若干临界段代码(中断被关闭)外,任务的其它代 码均可以被中断,以保证高优先级的就绪任务能够及时运行。第三部分是“调用任务删除函 数”,该任务将自己删除,操作系统将不再管理它。 单次执行的任务采用“创建任务函数”来启动,当该任务被另外一个任务(或主函数) 创建时,就进入就绪状态,等到比它优先级高的任务都被挂起来时便获得运行权,进入运行 状态,任务完成后再自行删除,“启动任务”就是一个例子。 采用“启动任务”后,主函数就可以简化为三行,只负责与操作系统有关的事情,即初 始化操作系统、创建“启动任务”、启动操作系统,使主函数的内容固定下来,与具体的应 用系统无关。真正启动系统所需要的准备工作由“启动任务”来完成,它的内容与具体的系 - 1 -
//初始化操作系统 OSInit (); OSTaskCreate(TaskStart,(void *)0,&TaskStartStk[TASK_STK_SIZE-1],1);//创建启动任务 OSStart (); //启动操作系统,开始对任务进行调度管理 统密切相关。主函数和“启动任务”的示意代码如下: 程序清单 L4-2 使用启动任务 void main (void) //主函数 { } void TaskStart(void *pdata) { 系统硬件初始化; //时钟系统、中断系统、外设等等 创建各个任务; 创建各种通信工具; } OSTaskDel (OS_PRIO_SELF); pdata = pdata; //启动任务 //如键盘任务、显示任务、采样任务、数据处理任务、打印任务等等 //如信号量、消息邮箱、消息队列等等 //删除自己 在“启动任务”中完成与系统硬件有关的各种初始化工作,然后创建各个实质任务和所 需要的各种通信工具,至此系统才真正完成准备工作,“启动任务”的使命也就结束了,最 后将自己删除。为了保证“启动任务”能够连续运行,必须将“启动任务”的优先级选择为 最高。否则,当“启动任务”创建一个优先级高于自己的任务时,刚刚创建的任务就会立即 进入运行状态,而与这个任务关联的其它任务可能还没有创建,它使用的通信工具也还没有 创建,系统必然出错。“启动任务”调用的“自我删除”函数会进行任务调度操作,从而使 系统开始运行各个实质任务。 由于“启动任务”不是用户系统的实质任务,又占用高优先级资源和任务资源,故不常 用。更常用的方法是将“启动任务”所完成的操作交给一个用户系统的实质任务来完成。这 时,主函数和有启动功能的任务函数的示意代码如下: 程序清单 L4-3 以用户任务代替启动任务 void main (void) //主函数 { } //初始化操作系统 OSInit (); OSTaskCreate(TaskUser1,(void *)0,&TaskUser1Stk[TASK_STK_SIZE-1],1);//创建任务 1 OSStart (); //启动操作系统,开始对任务进行调度管理 - 2 -
//用户任务 1 pdata = pdata; void TaskUser1(void *pdata) { 系统硬件初始化; //时钟系统、中断系统、外设等等 创建各个任务; 创建各种通信工具; 用户任务 1 本身的代码; } //如键盘任务、显示任务、采样任务、数据处理任务、打印任务等等 //如信号量、消息邮箱、消息队列等等 使用“单次执行”的任务函数结构的场合反而是可以多次执行的任务,每当需要执行该 任务时就将该任务创建一次。由键盘操作来启动的任务常采用这种结构,如用一个“发送” 按钮启动串行口通信任务,其程序如下: 程序清单 L4-4 用创建任务的方式启动任务 //无限循环,也可用 while (1) //“发送”按钮,创建串行口发送任务 OSTaskCreate(TaskUart,(void *)0,&TaskUartStk[TASK_STK_SIZE-1],3); break; //获取按键操作信息 INT8U key; for (;;) { key=keyin(); switch (key) OSTimeDly(2); } void TaskKey (void *pdata) //键盘任务函数(示意) { } void TaskUart(void *pdata) { 串行口初始化; { case KEY_SUART: case KEY_$$$: } //串行口发送任务(示意) //其它按钮的处理代码 pdata = pdata; //延时 . . . - 3 -
组织发送帧; 数据指针初始化; 发送数据; } OSTaskDel (OS_PRIO_SELF); //删除自己 采用“任务创建”的方式来启动任务,可以省略用通信手段触发任务的麻烦,还可以通 过*pdata 来传递原始参数,使得每次启动任务时可以有不同的工作状态。如下面的程序在创 建串行口发送任务时同时指定波特率: 程序清单 L4-5 在创建任务时传送参数 //波特率,由用户通过键盘选定 //无限循环,也可用 while (1) //“发送”按钮,创建串行口发送任务,带参数 OSTaskCreate(TaskUart,&baud,&TaskUartStk[TASK_STK_SIZE-1],3); break; //获取按键操作信息 { case KEY_SUART: case KEY_$$$: } . . . INT8U key; INT16U baud; for (;;) { key=keyin(); switch (key) OSTimeDly(2); } void TaskKey (void *pdata) //键盘任务函数(示意) { } void TaskUart(void *pdata) { 串行口初始化; 组织发送帧; 数据指针初始化; 发送数据; //串行口发送任务(示意) //其它按钮的处理代码 baud = *pdata; //延时 - 4 - //获取波特率 //用获取波特率的波特率初始化串行口
OSTaskDel (OS_PRIO_SELF); //删除自己 } 虽然用“创建任务”的方式来启动一个任务有以上方便之处,但每次启动任务都要调用 “任务创建函数”,需要对“任务控制块”进行全面初始化,并对“任务控制块链表”和“任 务就绪表”进行操作,比较耗时,故只适用于实时性要求不高的任务(如键盘操作启动的任 务)。采用“创建任务”的方式来启动一个任务除了实时性差外,还可能在任务自我删除后 出现后遗症: 占用的共享资源尚未释放,使其它需要使用该资源的任务不能运行。 通信关系的“上家”任务(或 ISR)发出的信号量或消息将被积压而得不到响应。 通信关系的“下家”任务因为得不到信号量或消息而被遗弃(被永远挂起)。 可能留下未删除干净的废弃变量。 因此: 如果该任务使用了共享资源,必须在自我删除之前释放(如释放内存块,发送互斥 信号量)。 如果该任务有关联任务(或 ISR),必须在自我删除之前将这种关联关系解除,而解 除关联关系需要删除关联任务和通信工具,这是得不偿失而又非常麻烦的事情。 适合采用“创建任务”的方式来启动的任务,通常是“孤立任务”,它们不和其它任务 进行通信(ISR 除外),只使用共享资源来获取信息和输出信息。如果不满足这个条件,应该 采用下面两种任务函数机构,并在系统启动时创建好。 4.1.2 周期性执行的任务 此类任务在创建后按一个固定的周期来执行,其任务函数的结构如下: 程序清单 L4-6 周期性任务函数的结构 //无限循环,也可用 while (1) void MyTask (void *pdata) //周期性执行的任务函数 { 进行准备工作的代码; } for (;;) { 任务实体代码; 调用系统延时函数; } //调用 OSTimeDly( )或 OSTimeDlyHMSM( ) 周期性执行的任务函数也由三部分组成:第一部分“进行准备工作的代码”和第二部分 “任务实体代码”的含义与单次执行任务的含义相同,第三部分是“调用系统延时函数”, 把 CPU 的控制权主动交给操作系统,使自己挂起,再由操作系统来启动其它已经就绪的任务。 - 5 -
当延时时间到后,重新进入就绪状态,通常能够很快获得运行权。 通过合理设置调用 OSTimeDly( ) 或 OSTimeDlyHMSM( )时的参数值可以调整任务的执 行周期。当任务执行周期远大于系统时钟节拍时,任务执行周期的相对误差比较小;当任务 执行周期只有几个时钟节拍时,相邻两次执行的间隔时间抖动不能忽视,任务的执行周期的 相对误差比较大,只适用于对周期稳定性要求不高的任务(如键盘任务);当任务执行周期 只有一个时钟节拍时,可将该任务的功能放到 OSTimeTickHook( )(时钟节拍函数中的钩子 函数)中去执行;当任务执行周期小于一个时钟节拍或者不是时钟节拍的整数倍时,将无法 使用延时函数对其进行周期控制,只能采用独立于操作系统的定时中断来触发。采用独立定 时器触发的任务具有很高的周期稳定性。 周期性执行的任务函数编程比较单纯,只要创建一次,就能周期运行。在实际应用中, 很多任务都具有周期性,它们的任务函数都使用这种结构,如键盘扫描任务、显示刷新任务、 模拟信号采样任务等等,键盘任务的示意代码参阅上一小节。 4.1.3 事件触发执行的任务 此类任务在创建后,虽然很快可以获得运行权,但任务实体代码的执行需要等待某种事 件的发生,在相关事件发生之前,则被操作系统挂起。相关事件发生一次,该任务实体代码 就执行一次,故该类型任务称为事件触发执行的任务,其任务函数的结构如下: 程序清单 L4-7 事件触发的任务函数的结构 void MyTask (void *pdata) //事件触发执行的任务函数 { 进行准备工作的代码; } for (;;) { 调用获取事件的函数; 任务实体代码; //无限循环,也可用 while (1) } //如:等待信号量、等待邮箱中的消息等等。 事件触发执行的任务函数也由三部分组成:第一部分“进行准备工作的代码”和第三部 分“任务实体代码”的含义与前面两种任务的含义相同,第二部分是“调用获取事件的函数”, 使用了操作系统提供的某种通信机制,等待另外一个任务(或 ISR)发出的信息(如信号量 或邮箱中的消息),在取得这个信息之前处于等待状态(挂起状态),当另外一个任务(或 ISR) 发出相关信息时(调用了操作系统提供的通信函数),操作系统就使该任务进入就绪状态, 通过任务调度,任务的实体代码获得运行权,完成该任务的实际功能。 如用一个“发送”按钮启动串行口通信任务,将数据发送到上位机。在键盘任务中,按 下“发送”按钮后就发出信号量。在串行口任务中,只要得到信号量就将数据发给上位机, - 6 -
示意代码如下: 程序清单 L4-8 用信号量触发任务 //信号量指针 //“发送”按钮 //读入按键操作信息 //其它按钮的处理代码 . . . break; case KEY_$$$: } OSTimeDly(2); } //无限循环,也可用 while (1) //向串行口发送任务发出信号量 { case KEY_SUART: INT8U key; for (;;) { key=keyin(); switch (key) OS_EVENT *Sem; void TaskKey (void *pdata) //键盘任务函数(示意) { OSSemPost(Sem); } void TaskUart(void *pdata) { } 如果在触发任务时还需要传送参数,可以采用发送信息的方法,程序如下: pdata = pdata; INT8U err; for (;;) { OSSemPend(Sem, 0, &err); //等待键盘任务发出的信号量 串行口初始化; 组织发送帧; 数据指针初始化; 发送数据; //串行口发送任务(示意) //延时 //无限循环 } - 7 -
程序清单 L4-9 用消息触发任务 //发送消息(波特率) INT8U key; INT16U baud; for (;;) { key=keyin(); switch (key) OSTimeDly(2); } { case KEY_SUART: case KEY_$$$: } break; . . . //消息邮箱 //读入按键操作信息 //其它按钮的处理代码 //“发送”按钮 OSMboxPost(Mybox,&baud) //波特率,由用户通过键盘选定 //无限循环,也可用 while (1) OS_EVENT *Mybox; void TaskKey (void *pdata) //键盘任务函数(示意) { } void TaskUart(void *pdata) { } INT16U baud; INT8U err; for (;;) { pdata=OSMboxPend(Mybox, 0, &err); //等待键盘任务发出的消息 baud=(INT16U)*pdata; 串行口初始化; 组织发送帧; 数据指针初始化; 发送数据; //获取波特率 //用获取的波特率初始化串行口 //串行口发送任务(示意) //无限循环 //波特率 //延时 } - 8 -
分享到:
收藏