clingos 发表于 2010-6-1 16:26:12

多任务临界段(原语操作)讨论!

看了一些小型OS的代码,对临界段(原语操作)映像最为深刻,
但有些不解之处,相信大多数人也都会遇到这样的问题。

1.对一个全局变量的访问算不算一个临界段?
2.临界段和原语操作是否等价?

首先,在ucos-ii中很好的回答这第一个问题,在这个OS中,所有对全局变量的访问都是关中断的,
也许问题到这儿就该结束了,可是当我看到freertos时却很少找到这样的处理,在freertos中全局
变量的访问部分采用了关中断的处理,而有些是没有关中断的,如一些读操作和一些int类型的操作
当然对int型变量的访问可以认为是编译器或是cpu的基本类型cpu可以通过一条指令完成,因此可以
认为其本身就是原语操作,(再问难道所有cpu都能保证吗(8位的cpu)据我所知在keil c51中
int是16位的,因此是否可以说在移植时对于这类cpu和编译器就得考虑int类型访问的原语操作
或者说ucos-ii在这方面做的更加严谨,高手指教????)

如下freertos中一段代码:
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > ( unsigned portBASE_TYPE ) 1 )
{
        taskYIELD();
}
这段代码中对全局变量pxReadyTasksLists访问是没有关中断的(不信可以打开源码看看)当然在freertos中也给出了下
面的解释:
/* A critical region is not required here as we are just reading from
the list, and an occasional incorrect value will not matter.If
the ready list at the idle priority contains more than one task
then a task other than the idle task is ready to execute. */

对于这段解释我不是太了解,等待高人????
如果说分析到这儿,上面的解释也成立,问题也应该算是结尾了,可是让我更加惊讶的是在我读到NucleusPlus代码时!
竟看不到有一行对全局变量访问关中断的语句,这让我的疑问又重新回到了起点,先前的假设都不成立了,
但是我对ucos-ii我深信不疑,因此我又做出以下假设:

a.NucleusPlus中所有的全局变量均没有在中断中访问
b.NucleusPlus中所有的全局变量均为int型基本cpu变量对于cpu可以单指令完成

对于a点假设在5天4夜后得到了答案,NucleusPlus中所有全局结构变量(注结构变量就是结构体)在中断中没有访问,
包括用户的中断服务程序,可能有人会问怎么可能呢?是啊,刚开始我也不信,可事实就是如此,NucleusPlus中采用
中断分两部分的方法解决了这个问题,在所谓的LISR(低级的可管理的中断服务程序)中可使用的NucleusPlus服务是
很少的,而大部分工作需要HISR(高级的可管理的中断服务程序)来完成,而这个HISR其实就像是一个高优先级的任务
(和任务还是有些不同的),因些在任务间共享的系统链表结构就不需要用关中断来完成了,只需要禁止任务调度就
可以了。

然而事情总是不可能那样完美,NucleusPlus中也有相当一部分变量在中断程序中访问的,然而对于这些变量,又该做
何解释呢,那只能是上面的假设b要成立了,那事实是不是这样呢,事实是基中有一部分变量是int型的,还有一部分是
指针型变量。对于int型变量上面已经说过了假设成立。但对于指针型呢?那就得想想指针是什么了,可以理解指针就是
地址,在arm中编译器会把指针变量编成32位的,这是肯定的,那16机器呢,指针变量就一定是16位的吗?(没玩过16位不
知道但我知道8位的单片机指针就不一定是8位),还有就是对于其它架构的cpu呢?cpu是否也是单指令完成指针变量的操作?
难道说是NucleusPlus设计的不够合理,我不相信,我相信大家也都不信,那只能说是我今天的水平只能解读到这儿?

对于上面的疑问目前我不得而知!
敬等高手继续解读?

对于问题2个人认为临界段为该资源不可共享,而原语操作是不可中断的操作!但是话又说回来了,不可中断的操作也即实现
了临界段的功能,因此它们还是一回事!不知对否?

dr2001 发表于 2010-6-1 19:41:20

参与一下讨论。

临界段(Critical Segment)和原子操作(Atomic Operation)我理解到的,都意指独占某些资源的操作或操作序列。
原子操作更多倾向于由硬件支持的独占操作/操作序列;临界段更多表示通过软件手段达到的,独占资源的操作序列。如果硬件不支持某种要求具备原子性的操作,那么可以通过临界段代码达到等效的结果。
在实际操作上,二者是等价的。

对于全局变量来说,首先假设的两个原子操作是,Load/Store。如果从共享变量保存地址提取数据到临时处理地址(比如REG)这个操作本身不是原子的,那么需要临界段保护。写操作同样。典型情况如你所说的,8Bit Core Load一个32Bit数据,可能需要多条指令来完成。
如果OS考虑广泛的可移植性,那么可能需要更多的临界段保护。在某些内核上,这个保护就会是冗余的。

对于共享变量操作读,修改,写这样的流程,需要临界段保护。(假设读/写是原子的)
如果是读,应用数据,那么不一定需要保护。需要看应用读来的数据的过程中,如果共享变量变化了,会产生何种影响。如果没有影响,那么不用保护。FreeRTOS的那个问题就可以归类到这里。如果这是一个调度器的决断代码,调度状态的决断时间点就在读那个原子操作上,之后的数据修改,会在下次调度时反应。这就没有问题。

NucleusPlus的问题,简单想,中断做一个FIFO,所有中断对代码的影响都通过Message表达。这样确实中断中不需要操作任何OS需要的变量。而FIFO在定长,中断不重入的情况下,是可以用两个变量,通过追逐实现的。
我认为是其实现的技巧。中断处理函数中的工作,能在第一时间得到完成。但是考虑从中断发生到任务调度出来去响应中断的消息,不一定更快,因为临界段可能是通过加锁调度器实现的,这个操作可能需要两次启动调度器。

供参考。

clingos 发表于 2010-6-3 16:55:30

【1楼】 dr2001:
对于NucleusPlus本身全局变量的临界段如何看待?
还有RTT等不知如何处理!等RTT版主来说了!

ffxz 发表于 2010-6-3 17:13:30

全局变量访问 != 临界区
全局变量如果仅在一个线程上下文内访问,那么不存在临界区的问题。

另外,临界区访问 != 关中断

现代的RTOS尽量都不做关中断来处理临界区,但RT-Thread是这样做的,正在考虑把它更改为不关中断的方式。

临界段和原语操作基本等价
但原语操作 != 原子操作

原子操作 = 是一条指令完成的,且这条指令是不可被抢占的

Nu+的优点在它的Protect设计上。

dr2001 发表于 2010-6-3 17:15:35

不应该单纯的看全局变量的临界段如何如何,比如不访问等等。我前边提到,这个事情,整个系统的设计思路在前,然后才是机制实现。由于我没深入理解NucleusPlus的代码,因此无法评论。

但我认为,是其整体的设计思路,使得其系统的消息传递等机制的实现,无需共享全局变量,进而就不需要全局变量的临界保护。
前边我提到的就是实现全局变量无保护的一种可能的方法。
当然这些机制不是完全无代价的。都是美事儿没后果的情形很少。

clingos 发表于 2010-6-3 20:39:49

【4楼】 dr2001 :
现在的问题是OS内部的供OS使用的全局变量访问是否要加保护,而不是OS实现是否需要全局变量!!

【3楼】 ffxz :
全局变量如果仅在一个线程上下文内访问,那么不存在临界区的问题。
这点是肯定的,

现代的RTOS尽量都不做关中断来处理临界区,但RT-Thread是这样做的,正在考虑把它更改为不关中断的方式。
有些全局OS变量,如何做到不关中断访问,
1、如果这个OS全局变量仅在线程之间访问,可以通过禁止调度来获得访问
2、如果这个OS全局变量不仅在线程之间访问,还在中OS中断ISR中有访问,那只能通过关中断来实现了,不知ffxz 还有
    什么新的方法。
3、而现在的问题是NucleusPlus中在2的情况下没有做关中断处理??????

Nu+的优点在它的Protect设计上
Protect实现是建立在中断分两部分的基础上,这样中断程序就不会共享OS内的一些数据结构,所以才可以通过线程上锁来
进行保护,除非RTT也把中断分两部分,或是ISR中不进OS服务。

还是举个实际的例子吧
比如在ucos中OSTCBCur这个OS中使用的全局变量,ucos中每次对它的访问都是关中断再访问的原则,
而在nucleusPlus中比如TCD_Current_Thread这个OS中使用的全局变量每次访问时无任何保护,如下
两段注解,有时NucleusPlus好像考虑了这个问题,有时好像又没有考虑,不知是不是我理解有误!!

                if ((INT) (task -> tc_priority) < TCD_Highest_Priority)
                {

/////////////////////////////////////////////////////////////////
此处就应当关中断访问全局变量TCD_Highest_Priority而它却没
有关中断就访问了,当然这个变量仅在线程中修改,所以可以理解
//////////////////////////////////////////////////////////////////
         
                  /* Update the highest priority field.*/
                  TCD_Highest_Priority = (INT) task -> tc_priority;

                  /* See if there is a task to execute.*/
                  if (TCD_Execute_Task == NU_NULL)
/////////////////////////////////////////////////////////////////
此处修改全局变量TCD_Execute_Task时就使用了
一个平台相关函数而不是直接赋值TCD_Execute_Task = task;
所以NU似乎也有所考虑
//////////////////////////////////////////////////////////////////
                        /* Make this task the current.*/
                        TCT_Set_Execute_Task(task);

                  /* Check to see if the task to execute is preemptable.*/
                  /* SPR455 checks if we are in Application_Initialize */
                  else if ((TCD_Execute_Task -> tc_preemption)
                           || (INC_Initialize_State == INC_START_INITIALIZE))
                  {

                        /* Yes, the task to execute is preemptable.Replace
                           it with the new task.*/
                        TCT_Set_Execute_Task(task);

                        /* Now, check and see if the current thread is a task.
                           If so, return a status that indicates a context
                           switch is needed.*/
/////////////////////////////////////////////////////////////////
而此处TCD_Current_Thread访问个人觉得也应加以保护,因为
这个变量在中断ISR中也有所访问,
假设此处访问到一半时被中断,中断中对它改变为NULL,然后中断
返回再进行TCD_Current_Thread) -> tc_id 不就有问题了吗???
当然些没有对它修改,但是程序其它地方线程中有对TCD_Current_Thread
直接赋值的而没有使用类似 TCT_Set_Execute_Task(task)的访问方法。
//////////////////////////////////////////////////////////////////
                        if ((TCD_Current_Thread) &&
                           (((TC_TCB *) TCD_Current_Thread) -> tc_id ==
                              TC_TASK_ID))

                            /* Yes, a context switch is needed.*/
                            status =NU_TRUE;
                  }
                }

arndei 发表于 2010-6-3 21:03:01

正在学习uCOS,高手真多……

ffxz 发表于 2010-6-3 21:16:16

>> 2、如果这个OS全局变量不仅在线程之间访问,还在中OS中断ISR中有访问,那只能通过关中断来实现了,不知ffxz 还有
    什么新的方法。

与中断间的互斥,只能是关中断,没什么其他好的办法了。

>> 3、而现在的问题是NucleusPlus中在2的情况下没有做关中断处理??????
关的,一样是关中断的。

>> Protect实现是建立在中断分两部分的基础上,这样中断程序就不会共享OS内的一些数据结构,所以才可以通过线程上锁来
进行保护,除非RTT也把中断分两部分,或是ISR中不进OS服务。

protect不是把中断切成了两半,而是内部实现的一种超轻型的临界区互斥。你说的应该是HISR。使用HISR也有它的缺点,在LISR中不能再调用OS提供的其他服务,都必须放到HISR中来完成。这点以后在RT-Thread也是类似的,加入了类似HISR的实现后,中断服务程序将限制很多。另外,RT-Thread的实现和NU+不一样的是,NU+的HISR是栈单独的,而RT-Thread的栈是共享的,即是那种非抢占式的实现(但之间依然存在优先级的概念,优先级数和NU+类似,比较少)。

dr2001 发表于 2010-6-3 21:20:05

回复【5楼】clingos
-----------------------------------------------------------------------
我什么时候都没有在讨论OS 是否需要 全局变量 的问题吧。
我一直在用 共享(全局)变量 的说法在描述这件事;具体处理方式取决于 共享 这件事的冲突情况。

临界段以及类似软/硬件手段的效果,就是解决共享数据的冲突问题。无论是OS内部的共享,还是线程/任务间的共享,还是其它。有两点:
1、数据首先要被共享,不共享就没必要保护。
2、共享了,一定要有冲突,竞争(Race Condition)。在协调式调度中,不一定会有冲突,因而即便是共享变量,也不一定保护。

如果不熟悉Nu+整个的结构和代码,您贴的代码段,基本上无法说明什么问题。

clingos 发表于 2010-6-3 21:44:16

【8楼】 dr2001
协调式调度能说详细点吗?

【7楼】 ffxz
>> 3、而现在的问题是NucleusPlus中在2的情况下没有做关中断处理??????
关的,一样是关中断的。
意思是说NucleusPlus中对(不仅在线程之间访问,还在中OS中断ISR中有访问的OS全局变量)没有做关中断处理!!!

Nu+除了Protect处理中有关中断,其它地方没有任何关中断的地方,这也正是它所说的固定关中断时间的由来。
而Protect保护的也只是线程间共享,因为它的ISR(LISR)中不允许对OS一些数据结构操作。

dr2001 发表于 2010-6-3 21:47:19

>> 2、如果这个OS全局变量不仅在线程之间访问,还在中OS中断ISR中有访问
如果类似于ARMv6以后的内核,提供的LDREX,STREX的机制,那么可以使用这个机制搞定。确实不用关中断。
实际上硬件提供一个信号,让低优先级软件竞争失败的时候可以放弃处理并回退,但是其对数据处理的数量要求严格。

dr2001 发表于 2010-6-3 21:53:47

回复【9楼】clingos
-----------------------------------------------------------------------
协调式调度这个名词可能不是非常准确,核心意思是指:调度器不能剥夺被调度的对象的资源控制权;控制权只能由被调度的对象主动交出。也就是非抢占的。
典型就是调度函数A调用任务函数B和C。A不能因为某种条件,比如中断,主动喊停B的执行,而去执行C。比如等B主动退出了,才能去至执行C。

djyos 发表于 2010-6-3 22:28:07

我来说两句。
在编程中,临界操作有那么几种:
1、原子操作,可以理解为无条件原语操作,即操作过程中不允许被打断,原语操作虽然可以用在共享数据的保护中,但这不是它的主要功能。原语主要用在,某些操作有非常苛刻的时限要求,一旦开始,就必须在规定的时间内完成,比如你用指令输出一个1uS的脉冲,先输出高电平,演示5个周期(假设每周期200nS)后输出低电平,那么,输出高低电平之间就不允许任何人打断。
2、原语操作,可以理解为有限原子,比如某一段代码,受某互斥量保护,那么,该段代码是允许被中断或被高优先级线程的,但前提是,中断或高优先级线程中不去竞争该互斥量。
3、“读-修改-写”操作,这个操作无论变量是不是跟cpu字长相同,除非cpu能够在单周期内完成“读-修改-写”,这是不太可能的,因为中间的“修改”可能很复杂,否则都必须关中断(加入ISR有访问)或关调度(假设高优先级任务有访问)来使操作原子化,至于nucleus中为什么不保护,我不清楚,因为没用过nucleus。
4、单边读写操作,即每次操作要么只读,要么只写的操作,这种操作允许有限的不做临界保护,以下文字摘自djyos代码:

//以下是CPU可以用一条指令处理的数据类型,多用于需要保持数据完整性的操作,以及
//需要快速的操作。
//数据完整性和原子操作的异同:
//数据完整性是指在任何时候都能读到正确值的数据,比如下列语句:
//uint32_t    b32;
//b32++;//b32的原值是0x0000ffff
//在32位risc机上,执行过程为:
//取b32地址到寄存器0-->取b32的数值到寄存器1-->寄存器1加1-->把寄存器1写入b32.
//上述过程无论在哪里被中断或者被高优先级线程打断,在中断或高优先级线程中读b32,
//要么得到0x0000ffff,要么得到0x00010000,这两都可以认为是正确数据.
//而在16位机上,执行过程是
//1、取0x0000到寄存器r0,取0xffff到寄存器r1
//2、执行加操作,使r0=0x0001,r1=0x0000
//3、把r0写入b32的高16位。
//4、把r1写入b32的低16位。
//如果在第3和第4步骤之间被中断打断,在中断里读b32,将得到错误的0x00001ffff。
//那么原子操作呢?就是整个执行过程不被任何中断或者高优先级线程打断,还看上面代码,
//如果b32被定义为原子变量,则无论在16位还是32位机上,b32都可以得到正确的值。
typedef uint32_t      ucpu_t;
    #define cn_limit_ucpu   cn_limit_uint32
    #define cn_min_ucpu   (0)
typedef sint32_t      scpu_t;
    #define cn_limit_scpu   cn_limit_sint32
    #define cn_min_scpu   cn_min_sint32


freertos中的这段代码:
if( liSTCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > ( unsigned portBASE_TYPE ) 1 )
{
taskYIELD();
}
如果pxReadyTasksLists的长度刚好等于cpu字长的话,就属于单边读写,确实允许不加保护。

ffxz 发表于 2010-6-3 22:52:08

还没在NU+的LISR中试过protect。

一般都采用关中断的做法,因为protect内部也隐含着关中断(调用期间关闭中断,调完这个出来后就非关中断状态)。而自己只会在零星的使用数条指令时才考虑关中断,在这么数条指令的情况下,直接关中断会比protect/unprotect的机制关中断的时间要短。

clingos 发表于 2010-6-4 12:35:29

【12楼】 djyos 都江堰操作系统
说的很详细,谢谢,不知GUI什么时候和世人见面很期待啊!

djyos 发表于 2010-6-4 12:52:58

回复【14楼】clingos
-----------------------------------------------------------------------

快了,在调试中。

clingos 发表于 2010-6-4 13:24:35

【13楼】 ffxz :
想请教下,你曾帖子中有说NucleusPlus的内存管理有缺陷,能说的详细点吗?也好让俺有所提防!

ffxz 发表于 2010-6-4 17:12:24

回复【16楼】clingos
想请教下,你曾帖子中有说nucleusplus的内存管理有缺陷,能说的详细点吗?也好让俺有所提防!
-----------------------------------------------------------------------

它的管理方式很慢,当系统内存将近用光时,搜索的时间特别特别长,0.6M内存都到了ms级别了。

clingos 发表于 2010-6-4 20:46:05

非常感谢二位的相助,两位都是OS方面的大牛,也是俺的目标,对OS也是相当的追捧!希望两位的OS能为国人争光!!

aaa1982 发表于 2010-6-4 21:35:30

希望这些级别的帖子能越来越多,感谢高手的讨论。

johnwjl 发表于 2010-6-4 21:45:35

临界段与原语操作,都是高手啊!

tao_tao 发表于 2011-11-14 19:31:03

高手讨论,果然与众不同,云里来雾里去的,我的感觉,知识无止境呀。。

zzh90513 发表于 2013-8-1 15:23:30

重新温习一下临界代码处理
页: [1]
查看完整版本: 多任务临界段(原语操作)讨论!