搜索
bottom↓
回复: 24

嵌入式OS学习心得

[复制链接]

出0入0汤圆

发表于 2013-8-15 17:16:06 | 显示全部楼层 |阅读模式
一般不跑OS的系统都是单任务的,即只有一个大循环(进程),里面可以存在多线程,所谓的多任务,就是存在多个无限循环,按照程序执行的原理,这样的多个死循环,如果没有条件让执行的那个循环跳出的话,其它的循环体应该都不会被执行到的,既然是死循环,作为没有任务调度机制的程序,肯定无法跳出,即使发生了中断,之后也会重新回到循环中去,因此,任务调度实现了,程序从一个死循环切换到另一个死循环,由于切换速度相当之块,让我们看起来,好像多个死循环在同时执行。我们称每一个死循环为一个任务。任务通常呈现四种状态:1.运行(此任务被调度器分配到时间,正在执行)2.就绪(等待调度器分配时间的任务,即没有暂停和阻塞)3.暂停(也称挂起,通过vTaskSuspend()而进入的状态,通过vTaskResume()退出,处于暂停状态的任务,调度器不会给它分配时间,直致它退出此状态)4.阻塞(使用了vTaskDelay()的任务和等待同步事件的任务,直到delay时间到或者同步事件发生才退出,与暂停状态一样,不参与调度)

这些概念中有很多地方都提到了调度,分配时间等概念,所谓的调度就是控制从一个任务切换到另一个任务,每到一个任务,就分配固定tick的时间,当tick时间到时,就会跳入中断,在所有处于就绪状态的任务中选择优先级最高的与正在执行的任务进行比较,如果此任务比正在执行的任务优先级低则继续执行当前任务,如果优先级相同(轮转机制)或者要高于正在执行的任务就要进行上下文切换。通常情况,任务在单一时间片内的运行过程中不会被任何任务抢占,只有在当前任务主动调用了vTaskYeild();函数,产生一个软中断,/*在任务中调用时,会立刻切换到优先级高的任务中去,然后再返回执行接下来的语句,如果此语句在中断中调用时,会将中断程序先处理完毕后才会跳出切换到其它高优先级的任务上,也就是说,这个函数在任务中调用和在中断中调用是不一样的*/,或者使用延迟vTaskDelay()和等待同步事件而启动了任务阻塞时,当delay时间到或者同步事件到达,都会在中断中进入上下文切换,被其它任务抢占执行。可以说,所有的上下文切换都是在中断中进行的。

因为,每次tick时间到,都会在就绪状态的任务中取优先级最高的执行,而任务的优先级在创建时就已经确定了不会改变(除非后续有强制设置任务优先级),因此如果高优先级的任务一直处于就绪状态的话,那调度器每次都会把时间分配给它,而其它的任务就没有执行的机会(被饿死),显然这种情况不是我们希望的,我们可以通过三种方式更改:1.把处于就绪状态的任务的优先级设置成一样的,这样就会按时间轮循来执行任务;2.如前面已经提到的,就是在任务执行过程中,对低优先级的任务的优先级进行修改成为当前最高的优先级,从而使得在下一次tick到来时,能够顺利分配到时间;3.不要让优先级高的任务一直处于就绪态,根据处于暂停或者阻塞状态的任务不参与调试,因此如果任务被阻塞或者暂停就可以让其它的任务分配到处理时间;这三种方式要在实际过程中合理地结合着来使用。

具体三种方式的实现过程如下:

1.使处于就绪状态的任务优先级一致,先要分析,在整个过程中,哪些任务可能同时出现在就绪状态,然后将这些任务的优先级设置成一样的即在任务创建时,将任务优先级参数设置统一。默认的优先级是从0~最大优先级号 - 1。这个最大优先级号在FreeRTosConfig.h文件里有定义,可自行更改。

2.在任务执行过程中,对优等级进行修改。如果在一个任务中要对另一个任务的优先级进行更改的话,那另一个任务就要设置一个全局句柄,即任务创建函数的最后一个参数(非NULL),这样才能使得其它的任务通过这个参数,对此任务进行相关性能的设置或者控制。

3.使任务阻塞或者暂停。如在任务中使用vTaskDelay(BlockTickTime)[每次调用延迟语句进入阻塞,(进入阻塞时间点(不固定)+BlockTickTime)为退出阻塞时间点]/*也可通过vTaskDelayUtil(&LastTickTime,BlockTickTime),以第一次获取的tick时间(固定)为起点,每次到达调用语句时开始进入阻塞,(起始时间+BlockTickTime)即为退出阻塞时间点*/或者等待一个同步事件并设置其阻塞时间来阻塞任务,如在任务中使用vTaskSuspend从而暂停任务。

当所有的任务都不被执行时,就会执行一个空闲任务,这个任务是在任务调度函数中被创建的,该任务的优先级为最低优先级0,只有当就绪状态的任务只有它时才会被执行到,空闲任务中可以加入一些处理函数即空闲任务钩子(hook),处理一些在后台运行的应用功能,需要不断进行处理,因为在空闲任务中所以优先级为0,而且需要在其它任务都处于阻塞状态时被执行到,因此它不能有阻塞或者挂起,而且因为要在tick内留有足够的时间供空闲任务进行当其它任务vTaskDelet()时的栈清理和资源回收,所以,空闲任务钩子不能占用太多的处理时间。同样的还有一个时间片钩子,每一个时间片中断产生时都会被调用。这两个函数当启用时,要在FreeRtosConfig.h中对相应的宏设置为1。

任务的创建有两种方式:1.在main函数中创建任务;2.在任务中创建任务;

任务的定义:任务的执行体,可以为其它任务使用,只要用任务名字和任务的传的参数不同来区分。

任务之间的通信:

1.二值信号量,深度为1的队列,需要像队列一样,先创建一个二值信号量,然后,当相应的中断触发时,调用xSemaphoreGiveFromISIR( xBinarySemaphore,&xHigherPriorityTaskWoken ); 给出信号量,即相当于队列中加入一个信号到队列中,使得等待获取此信号量的任务立刻解除阻塞进入就绪状态,如果此时解锁的任务比当前任务和其它任务的优先级均高则直接在中断中进行上下文切换了,去执行解除阻塞的任务,刚解除阻塞的任务通过xSemaphoreTake( xBinarySemaphore, portMAX_DELAY );从队列中获取出信号量,将队列置空,当再处理完任务后,再去获取信号量时,发现已为空,则再度进入portMAX_DELAY长的阻塞状态。(从任务解除阻塞后如果优先级较高就可以立即进行上下文切换这一特性看来,此操作系统是支持抢占式任务)

2.计数信号量。相当于一个深度大于二的队列,不像二值信号量一次只能锁存一个事件,而它可以存放多个,为了避免使用二值信号量时,在短时间内发生多次同步事件,使得任务来不及响应将事件取出,后续事件再到来,而导致事件丢失。

3.互斥量(mutex)。信号量一种,不同于二值信号量,由中断给出(give)信号量,而任务获取(take),主要起中断和任务的同步作用。它的信号量的give和take是成对出现在同一个任务中,作用是:获取系统资源并在take和give 这段时间内独自占用资源,不允许任何其它任务使用。这期间允许有中断产生,也允许有任务上下文的切换,唯独不允许任务占用自己所take的资源。当take动作时,发现无令牌,则进入阻塞状态,直到信号量中的令牌被其它的任务give出来后,才解除阻塞进入就绪状态,等待任务调试。之所以有这样的用法,是因为在多任务系统中,同一个硬件资源可能被多个任务同时应用到,如果其中的一个任务正在使用资源(向LCD输出一个"Hello world"),还没有使用完(刚输出到“Hello w”)就被其它任务抢占,抢占到的任务也需要向LCD发送一个“Merry chrismas ”,这时屏幕上就出现了“Hello wMerry ch..”,而我们的本意是想先执行完第一个任务后即输出完hello world后,再去执行第二个任务.于是,就产生了互斥机制,主要是确保同一个硬件资源,在某一段时间内,只由一个任务独自占用。但是,使用互斥量,会有优先级倒置的缺陷,这种问题主要是由于任务只能被并不想获取该任务占用资源的高优先级的任务打断,但不能被想获取此任务占用资源的高优先级的任务打断,而刚好当,前一种高优先级任务的优先级别低于后一种高优先级任务的优先级时,导致最高优先级的任务反而要等待最长时间才能处理自己的事件,相当于最高优先级的任务,实际上优先级别反倒最低了,而实际上我们想要的效果是,当前占用资源的任务,最先让最高优先级的任务得以执行,也就是说,在我的资源尚未使用完成期间,我不能让中优先级别的任务切换上下文,那我只能,提高自身的优先级,使得优先级至少要高于所有并不想获取我资源的任务优先级,以防止此期间进行上下文切换,由于任务的状态随时在变化,也许这会儿其它任务不想获取当前任务的资源,过一会儿就需要获取当前任务的资源,因此为了简明,干脆将正在占用资源的任务的优先级设置为最高,然后,当我享用完我的资源后,再把优先级改回来。这就是,优先级继承,但是,如果不想获取我资源的任务的优先级为最高时,我又需要它在我进入互斥状态期间,能够打断当前的任务,因此,可以说,不同的情况,使用不同的方法,而系统的状态又很不固定,因此我们也很难弄清楚,某一时刻,系统的各任务之间是一种什么样的状况,因此,我们在最初设计时,就应该先避免这种会出现优先级倒置的情况。互斥量的使用还会出现一种情况,即当我一个任务同时要使用两种资源,当前我已经获取一种资源,这个资源的使用过程中需要使用另一种资源,而存在另一个任务正在等待我释放出我占用的资源,但是同时,这个任务刚好占用了前一个任务,正在等待的资源,这样,导致两个任务僵持着等待各自需要的资源,而无法得以继续往下执行。这就是所谓的死锁。这种缺陷只能在设计阶段予以避免。除了互斥量可以实现互斥外,还有临界状态和任务全部挂起,守护任务三种方式。

(1)临界状态:即通过taskENTER_CRITICAL()与 taskEXIT_CRITICAL()实现在这两句区间,将低于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断(即可以调用FreeRTos API的中断,包括心跳中断)全部屏蔽起来,这期间由于心跳中断也屏蔽了,一些同步事件中断也屏蔽了,所以无法进行上下文切换。可以说实现完全独享资源,且此期间一直运行不被打断。但是这样做有一个缺点,就是通常对一些资源的写操作要持续很长的时间,而临界状态应该短暂,长时间的写操作使得临界状态维持很长,而不能对其它紧急事件进行及时的处理。

(2)挂起调度器。通过vTaskSuspendAll() 和xTaskResumeAll()实现将调度器挂起,即此期间,所有的任务都处于暂停状态,自然不能进行上下文切换(如果有上下文切换的请求,会等到,调度器恢复时被执行)从而达到当前的任务独自占用资源的目的,这期间的中断是有效的,以便处理一些紧急事件。挂起调试器期间,若有tick或者同步事件的中断产生,则先将它们放入pending队列中,等待恢复调度器时,立即从pending队列中取出,根据情况进行上下文切换,另外还要把在挂起期间错过的tick补回,再根据情况进行上下文切换。

(3)守护任务。最简单直接地解决互斥问题,如果有多个任务要占用资源时,大家按照先后或者优先级,排队,我的资源一个接一个的进行处理。因此需要独立一个任务直接操作资源,其它任务,想要使用资源,必须把需求通过队列传递给此任务,其它不允许有对资源的直接操作。也就是多方输入,单方输出。这个专门用于操作资源的任务被称为守护任务。由于守护任务本身有开销,因此会使得任务的处理略有延迟。

4.消息队列。不同的任务之间的通信可以通过消息队列来实现,如果需要实现数据在内部的收发功能,则可以将任务的发送和接收分别放在两个任务中,一个专用于发送,另一个专用于接收处理,这样可以通过消息队列的方式来实现,接收任务在无数据放入到队列中时,一直处于阻塞状态,当发送任务发送数据到队列时,当即解除阻塞,在队列处理的函数中,有调用TaskYeild()函数,从而启动了软中断,如果刚解除阻塞的接收任务的优先级在就绪列表中处于最高,立刻进行上下文切换。这样,就无需等待时间片耗尽再进行切换。

专业术语之句柄:如task,quue在使用时,都要先进行创建,创建就是对它们先分配一段内存,并进行初始化,那指向这一段内存的指针就被称为句柄,各个任务或者队列,当申明有句柄时,其它的任务就可以通过这个全局句柄对它进行控制和操作。

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入0汤圆

发表于 2013-8-15 17:31:17 | 显示全部楼层
楼主,我顶你

出0入0汤圆

发表于 2013-8-15 18:31:16 | 显示全部楼层
我菜鸟一个,顶你一个,向你学习

出0入0汤圆

发表于 2013-8-15 19:26:21 来自手机 | 显示全部楼层
看lz贴了这么一大段。。毅力啊,我是没毅力看了。概括操作系统可以是简化开发,方便移植,提高效率,如此是也

出0入4汤圆

发表于 2013-8-15 19:48:39 | 显示全部楼层
sunnyqd 发表于 2013-8-15 19:26
看lz贴了这么一大段。。毅力啊,我是没毅力看了。概括操作系统可以是简化开发,方便移植,提高效率,如此是 ...

对, 目的是为了简单,而不是复杂~~~

出0入0汤圆

发表于 2013-8-15 20:39:53 | 显示全部楼层
顶你,学习了

出0入0汤圆

发表于 2013-8-15 21:02:30 | 显示全部楼层
牛人帮顶啊,呵呵呵

出0入0汤圆

发表于 2013-8-16 14:59:34 | 显示全部楼层
前面的理解非常到位啊。顶起

出0入0汤圆

发表于 2013-8-16 15:02:48 | 显示全部楼层
给楼主提点意见,这么长的文章应该用适当的格式来排版。比如颜色、加粗、下划线之类的。简单几步就能给阅读带来很多方便。

出0入0汤圆

发表于 2013-8-16 15:24:39 | 显示全部楼层
希望LZ继续。。。。。

出0入0汤圆

 楼主| 发表于 2013-8-16 15:32:50 | 显示全部楼层
Mr_li 发表于 2013-8-16 15:02
给楼主提点意见,这么长的文章应该用适当的格式来排版。比如颜色、加粗、下划线之类的。简单几步就能给阅读 ...

排版确实很马虎,能看完的确要点耐心啊。

出0入0汤圆

发表于 2013-8-19 08:47:22 | 显示全部楼层
正在学习OS!

出0入0汤圆

发表于 2013-8-19 14:58:46 | 显示全部楼层
好吧 我承认我不是一个有耐心看完的人!!

出0入0汤圆

发表于 2013-9-11 15:00:20 | 显示全部楼层
终于看完了

出0入0汤圆

发表于 2013-9-18 19:02:14 来自手机 | 显示全部楼层
七七八八的看了下,最近在学ucos,遇到一个信号量的问题,有空请教下楼主!

出0入0汤圆

发表于 2014-4-28 11:42:18 | 显示全部楼层
路过路过 顺便看看  顶

出0入0汤圆

发表于 2014-4-29 22:14:11 | 显示全部楼层
浏览了一遍

出0入0汤圆

发表于 2014-4-30 15:41:15 | 显示全部楼层
大概就是这些东西

出0入0汤圆

发表于 2014-5-7 22:48:38 | 显示全部楼层
学习,学习~

出0入0汤圆

发表于 2014-5-8 19:28:46 | 显示全部楼层
跑系统其实就是为了 简单,虽然学习起来比较困难

出0入0汤圆

发表于 2014-5-16 13:07:47 | 显示全部楼层
强烈顶你

出0入0汤圆

发表于 2014-8-30 18:44:07 | 显示全部楼层
复制下来仔细看了,很给力,学到不少东西

出0入0汤圆

发表于 2014-8-30 22:47:34 | 显示全部楼层
lz提到互斥访问的三种做法,貌似还有种设定天花板的方法,即只有某个优先级以上的任务才可以使用此共享资源,这样的rtos一般提供任务优先级修改api,ucos2就是这么作的
不过我对你的守护任务更感兴趣,这种做法和rtx处理中断调用的方法异曲同工,实质也是对内核全局变量的互斥访问。请问在什么资料中提到了你所说的这种思路?

出0入0汤圆

发表于 2016-4-26 16:49:47 | 显示全部楼层
楼主,我顶你

出0入0汤圆

发表于 2016-4-26 17:09:25 | 显示全部楼层
总结中学习,这种方法是很快的。加油。
另外是否是楼主原创?
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-27 07:29

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

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