OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 5346|回复: 3

分享一点UCOSIII学习心得——浅谈任务切换过程的理解

[复制链接]

21

主题

86

帖子

0

精华

高级会员

Rank: 4

积分
639
金钱
639
注册时间
2017-3-6
在线时间
64 小时
发表于 2021-3-12 14:07:11 | 显示全部楼层 |阅读模式
本帖最后由 习惯了寂寞 于 2021-3-12 17:10 编辑

算是记录学习历程,不对之处还请各位坛友多多批评指正。如果不小心被我误导了,在这里先说抱歉啦,另外,其实任务切换的底层就是一些汇编代码。内容主要是参考正点原子相关的开发资料加一些自己的拙见。
UCOS系统属于RTOS系统中的一种,RTOS英文全称为:Real Time Operating System,即实时操作系统,强调实时性。在介绍RTOS之前,先简单扯一些其他的,在早起嵌入式开发时并没有明确的嵌入式操作系统的概念,直接操作裸机写代码(比如操作51单片机)。通常我们会发现,在51的代码里只有一个while死循环,循环中包括的各种应用函数如点亮LED灯等,程序中最大的while死循环即后台系统;中断服务程序用于处理系统的异步事件,即前台系统,可理解为:前台是中断级,后台是任务级,这即和RTOS对应的前后台系统,示意图如下:
1.jpg
   接着说回RTOS系统,除UCOS外,还有FreeRTOS、RTX等,其核心内容在于:实时内核。RTOS实时又分为硬实时和软实时。硬实时即某一操作必须在规定的时间内执行完,否则直接跳转下一步或者造成系统卡死;软实时则对延时没有那么的严格,相当于有商量的余地,就好比生活中跟别人约好了时间见面,但是到点了自己手中的事还没做完,这时就会询问那个人可不可以等一下,然后他说可以,就可以接着做之前没做完的事儿,在程序里面就好比有一个任务需要比较长的时间执行并且该任务拥有最高优先级且不允许被打断,而此时又到了该切换为其他任务的时候,这时候就需要软实时了。
不同于前后台系统,在RTOS中我们把要实现的功能划分为多个简单的小任务(即一个简单的程序,我个人理解为比如51里面的点亮LED灯程序),这个小任务程序通常而言是一个死循环。这时候很多人可能跟我刚开始接触UCOS一样,就会产生疑问,这么多死循环,程序怎么能够跳转的出去,也许两个三个死循环还能通过自己定义标志变量以及控制while循环里的条件跳出死循环进入其他循环。其实个人感觉UCOS里跳出死循环也是一样,只不过它因为层层调用,不容易找到或者理清程序是如何跳出一个死循环任务而执行另一个循环的。
在介绍UCOS里面具体怎么跳来跳去的之前,先简单看一下RTOS的核心:实时内核,它决定了某个阶段该执行哪个任务,又何时停止当前任务切换到其他任务,即多任务管理能力。给人的感觉就好像是多个CPU在同时执行任务,就好比人在生活中做事一样,比如我在中午12点用电饭煲煮了饭(假定煮饭需要半小时),那么在我接通电饭煲电源之后的30分钟内,我是不需要一直守在现场的;我可以去做点其他事,比如我又用洗衣机去洗了衣服(假定洗衣服只要20分钟);那我开了洗衣机之后,是不是又可以看会儿电视(这个是需要我一直在的),在看电视的同时,脑海里会隔一段时间想洗衣机和电饭煲是否运行完毕,并确认时间,如果时间到了,洗衣机运行完了,那么我就会去关洗衣机晾衣服,这件事做完之后,回来继续看电视,再过了一会儿,继续看时间发现饭应该煮好了,那么我会停止看电视,去准备做菜。这半个小时之内,我煮好了饭洗好了衣服也看了电视,就像是同时做了多件事一样。在UCOSIII系统中,也会有类似看时间的函数。其实到这里大概就有点能明白程序是在哪里切换不同任务的了。要是将这些事放在以前的51程序里,在接通电饭煲的一瞬间,程序就进入了漫长的等待过程,如果用RTOS,内核则可以像人一样处理刚刚上述的过程。当然这个比喻可能有些描述的不太恰当,能理解就行,如果有错可以直接纠正了或者滑走勿喷,谢谢啦。多任务管理实现了CPU资源的最大化利用,多任务管理有助于实现程序的模块化开发,能够实现复杂的实时应用。RTOS内核是可剥夺型内核,顾名思义就是可以剥夺其他任务的CPU使用权,它总是运行就绪任务中的优先级最高的那个任务,这也是任务来回切换的根本原因。
2.jpg

在UCOS中,其实可以不必关注具体如何跳出的(即不用去啃底层代码),只需要知道在死循环中调用特定的几个API函数,是可以跳出死循环的。当然我是不喜欢这样的,所以去啃了底层代码,这里我简单的介绍一下我是如何理解程序是如何从一个死循环跳到另一个死循环的。这儿的内容的基本来自正点原子UCOSIII的开发手册的第五章。理解UCOSIII的任务管理是理解任务如何切换的前提条件。在手册里任务的管理共分为:UCOSIII的启动和初始化,任务状态,任务控制块,任务堆栈,任务就绪表和任务调度和切换六个部分讲解,这儿以UCOS的例程4-1 UCOSIII移植为例。打开工程看到main()函数,除前面必要的一些外设初始化之后,就开始进行了UCOSIII的初始化(OSInit(&err)),进入初始化函数可以在函数后面一点发现如下图的代码:
3.jpg


没错,这就是在main.c文件中头文件下面注释中提到的几个不能用的优先级所对应的任务,同时在UCOSIII启动之前(OSStart())一定要至少创建一个任务,在调用OSInit()函数时,就已经创建了时钟节拍任务和一个空闲任务,其他三个任务可由用户自己控制是否开启。另外需要注意的是,在初始化函数之后,必须先进入临界保护区,才能调用OSTaskCreate()函数创建开始任务,创建完之后再用对应的退出函数退出临界区,这是因为在创建任务时不能被打断,而临界区内代码的执行不会被干扰。最后调用OSStart()开启系统运行,别捉急,急不得慢慢写。代码里的LED0和LED1任务及浮点测试任务都是在开始任务start task里创建的,里面在创建任务时也调用了临界区。在这里系统已经开始运行了,要说也是可以依葫芦画瓢慢慢可以用了。可是这该死的好奇心啊,非得要我继续看。之前看到了那个搞UNIX的神啊,197x年时他居然在内存只有24KB的电脑上跑起了操作系统,顺带还创造了NewB语言(C),自己写语言,写编译器,写系统,什么人哦,我看个这么成熟的操作系统还看不懂,气人哈哈哈。
接着说回任务管理,应该是到了任务的状态这一部分了。因为UCOSIII是单核CPU,这样在一个时刻永远都只有一个任务在运行,而其他再多的任务在该时刻都得等待。UCOSIII的任务有以下五个状态:
4.jpg 任务可以在这五个状态中进行转换,关系如下图所示:
5.jpg
看不懂没关系,里面的函数过一遍也就可以了,用的时候再看。在这儿还是瞎打个比方,帮助自己理解。这几个态呢,就好比有一群人商量好要去吃火锅,其中的A同志呢只是在讨论时说要去,但并没有去火锅店门口,我想把这个作为休眠态但似乎又不太恰当,这个直接看图中的解释也能看懂;然后剩下的人都来到火锅店门口并且登记了(获得火锅店CPU的使用权),但是他们运气不太好,里面座无虚席,那么只能先等待(等待态),过了一会儿,前台叫到他们的排号了(等到了一个信号标志,获得了CPU的使用权),他们一群人就进去吃吃喝喝了(运行态),吃着吃着一个人的手机突然响了,是老婆打来的(中断标志),正在吃hot pot的他,放下了筷子拿起了手机接听电话(中断服务态),打完之后接着吃火锅。
看完状态之后,再看任务控制块和任务堆栈。其中任务控制块OS_TCB是用来保存的信息,里面是一个结构体,在函数OSTaskCreate()创建任务时就会给任务分配一个控制块,打开结构体定义,可以发现成员有:指向当前任务堆栈的栈顶、任务名、任务优先级以及任务已经执行的时间等等,暂且都可不理。说到堆栈可能也有少部分和我一样的,因为没学过堆栈,根本不知道是个啥玩意儿,说白了堆栈就是块特殊的存储区,在UCOSIII中是用来在发生任务切换时保存现场的,所以每个任务都有自己的堆栈,大小根据情况来定。程序运行的最底层都是硬件电路,而直接与电路接触的则是寄存器,随着程序的运行各个寄存器中的数值也会发生变化。当代码执行到某一个时刻,突然有个更高优先级的任务需要被执行,那么在执行新任务之前,系统就会将当前任务相关的寄存器的值存储在自己的堆栈之中,等待再次获得CPU的使用权之时,将堆栈中存储的变量值又赋值给相同的寄存器,从而使得任务可以继续执行。
这儿简单提一下任务堆栈大小,可以在main.c文件中发现任务堆栈有大有小,这个具体应该分配多少合适呢?其实我也不太懂,哈哈哈,等待有缘人解答。我的理解是这样的:查看代码可以发现任务堆栈的变量类型是CPU_STK,在cpu.h中可以发现CPU_STK是unsigned int占用字节为4,所以实际堆栈大小是我们定义大小的4倍字节。因为内存大小始终是有限的,所以合理分配是必然的,具体大小怎么算。我猜是看自己执行的任务所涉及的寄存器吧,详细的我不懂。
扯了这么多终于快要到任务调度和切换了,顺带也可以再次巩固一下,切换任务也是按照一定规则来的,就好比火锅店老板叫号一样,是根据顾客们的登记情况,按照先登记先叫(优先级)和特殊情况处理来的。在UCOSIII中的登记表就是任务就绪表,具体怎么实现的还请看UCOSIII开发手册中的第78页左右,打不动了,这部分也没得必要理解那么深,知道在这儿干了啥就差不多了,在这里面就是将系统里的任务按照优先级在就绪表和就绪任务列表中排好,对于同一优先级下面的任务则有时间片轮转等方法处理。
终于到任务调度了,任务调度就是中止当前正在运行的任务转而去执行其他的任务。UCOSIII是可剥夺型内核,因此当一个高优先级的任务准备就绪,并且此时发生了任务调度,那么这个高优先级的任务就会获得CPU的使用权!UCOSIII中的任务调度是由任务调度器来完成的,任务调度器有2种:任务级调度器和中断级调度器。任务级调度器为函数OSSched()。中断级调度器为函数OSIntExit(),当退出外部中断服务函数的时候使用中断级任务调度。在什么时候会发生任务调度呢:
6.jpg 7.jpg
在用户调用这两个函数时,程序都会执行任务切换,具体的切换由这两个函数里调用的OS_TASK_SW()完成,在os_cpu_a.asm文件里,是一堆汇编代码,我人都傻了哈哈哈,看到汇编两个字,一步步看过来就是想看底层怎么实现的。还是把代码找出来一下了,并截屏了感兴趣的可以看看:
9.jpg

这儿举个跳转的例子,在开始从main()函数执行到OSStart()到开启UCOSIII系统,找到当前任务优先级最高的任务并开始执行,假定接下来执行的是LED0任务,可以看到这一行代码:OSTimeDlyHMSM(0,0,0,200,OS_OPT_TIME_HMSM_STRICT,&err);//延时200ms;进入这个函数继续寻找就可以找到函数OSSched();接着往下进入可以看到里面调用了函数OS_TASK_SW();这个函数又可以看到其实OSCtxSw()。OSCtxSw()和OSIntCtxSw()都一样其实都只是出发了一个PendSV中断,具体的切换过程还是在PendSV中断服务函数里面进行。
这个函数打开之后,就是实现任务切换的汇编代码了上上图。下图是OSStart()中调用的函数OSStartHighRdy()的汇编源码:也就是到底是怎么进入任务执行当前优先级最高的任务。
另外,这些汇编代码在UCOSIII的开发手册第40页有讲。至此,基本流程就结束了。
10.jpg







正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

1

主题

7

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
228
金钱
228
注册时间
2020-3-1
在线时间
32 小时
发表于 2021-3-17 13:30:22 | 显示全部楼层
真的是好有耐心啊,我辈楷模
回复 支持 反对

使用道具 举报

21

主题

86

帖子

0

精华

高级会员

Rank: 4

积分
639
金钱
639
注册时间
2017-3-6
在线时间
64 小时
 楼主| 发表于 2021-3-18 09:43:06 | 显示全部楼层
汤修雅 发表于 2021-3-17 13:30
真的是好有耐心啊,我辈楷模

谢谢啦,哈哈哈 楷模就不算了。
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
11
金钱
11
注册时间
2021-2-8
在线时间
3 小时
发表于 2021-4-22 14:59:30 | 显示全部楼层
TKS!!!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-2-28 00:03

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表