本帖最后由 hotpower 于 2011-5-23 19:03 编辑  
 
John Lee 23:24:52 
那我们就研究一下look。  
murex 23:24:56 
好 
John Lee 23:25:38 
look的第一个类是signal_sink_t 
look第132行  
本来是没有这个类的,后来加上去的。  
murex 23:26:36 
那个文档的? 
John Lee 23:27:00 
include\look  
这个变化也反映出C++程序设计的多变性  
有时候改动一小点设计,就能为程序带来巨大的效应。  
signal_sink_t类先放着,以后用到的时候再回头说。  
John Lee 23:30:52 
RTOS最基本的功能是任务管理、中断管理、同步管理。  
有了这3个基石,就能跑起来了。  
look也不例外。  
murex 23:32:26 
同步管理是否主要指通信的同步 
John Lee 23:32:30 
只不过是以C++的方式来实现的。  
还不能说是通信。  
murex 23:34:18 
是不是上次在群里讲过的内容 
John Lee 23:34:30 
操作系统里面,“并发与同步”,“共享与冲突”,是关键,是永恒的话题。 \ 
上次讨论过一下。  
murex 23:35:34 
先讲下面的吧,这几个到时再说 
John Lee 23:36:15 
好,循着look的顺序来说。  
中断管理是由两个类来完成的(用户接口)  
背后还与调度器有关系。  
John Lee 23:38:06 
两个类:vector_t 和 interrupt_t  
vector_t 管理异常(中断)向量表。  
interrupt_t 定义用户中断接口。  
John Lee 23:40:17 
vector_t,在原始设计中也是没有的,后来才加上。  
John Lee 23:40:37 
在look for avr中就完全没有。  
murex 23:40:46 
是好像没的 
John Lee 23:41:29 
这是因为有个函数:dsr()  
它的需要,才导致了vector_t的出现  
你可以对比一下look for cortex 和look for avr  
看看 interrupt_t::dsr()函数的定义,有何不同。  
John Lee 23:43:56 
look for cortex: virtual void dsr(int vector, uintptr_t count) == 0; 
look for avr: virtual void dsr(uint8_t count) == 0;  
好像是这样的吧。  
可以看到,look for avr的dsr函数少了一个参数:vector !  
murex 23:45:01 
嗯,是这样的 
John Lee 23:46:19 
我详细讲讲参数vector和count.  
手册里说了,vector是中断号,而count是尚未被dsr处理的中断次数。  
vector好理解,count呢?也能理解?   
murex 23:48:02 
应该是上次菜农讲的回调函数一致  
John Lee 23:48:37 
原理上是一码事,但我这里要说的是参数:count。   
John Lee 23:49:48 
要理解count的概念,先要说isr和dsr的概念。   
John Lee 23:50:21 
你知道“中断分层处理”的概念吧?   
murex 23:50:48 
听过,感觉只有表面理解  
John Lee 23:51:03 
而这个概念又与“共享与冲突”密切相关。   
murex 23:51:17 
感觉是中断优先级的概念  
John Lee 23:51:34 
太多的概念交织在一起。   
murex 23:51:35 
同级与不同级  
John Lee 23:51:50 
不是中断优先级   
一点关系都没有   
murex 23:52:04 
那我理解错了,哈  
老师逐步来讲吧  
John Lee 23:53:15 
所以,要讲清楚一个小的应用,哪知背后牵扯出的一大堆概念。   
对于不想深入的用户,只有告诉他们:这是“定式”,你必须按“套路”出牌。 
 
比如一个全局变量,在任务里要访问,在中断里也要访问。   
这就是数据共享。   
John Lee 23:57:04 
然而,共享总是伴随着冲突的。   
murex 23:57:17 
修改的冲突吧  
John Lee 23:57:23 
对   
murex 23:57:38 
这个单片机中也存在  
John Lee 23:57:52 
说白了,就是要保证变量的原子访问。   
murex 23:57:57 
最主要是尽量保证只有一个地方更改这个数据  
John Lee 23:58:30 
一般的RTOS怎么处理冲突的?   
从绝对意义上来说,不可能做到“只在一个地方访问这个数据”。   
murex 23:59:35 
尽量修改数据的地方互斥  
John Lee 23:59:51 
如果做到了,这个数据就不是“共享数据”了   
murex 23:59:54 
那是的  
John Lee 0:00:17 
怎么“互斥”   
murex 0:01:15 
就是修改数据只有在一些特定的情况下发生,这些特定情况一般不会相互重复  
我是以我写单片机的程序来理解的  
John Lee 0:02:34 
在具体一些,比如信号灯(semaphore)的内部计数器。 
在任务中可以调用pend将计数器 - 1,而中断调用post将计数器 + 1.   
这个计数器,就是典型的共享数据。   
murex 0:05:34 
这个是否可以这样理解,执行掉了数据就把任务-1,产生了新任务就把任务+1  
murex 0:06:03 
就相当于是任务数  
John Lee 0:06:13 
没有新任务   
murex 0:06:51 
不是说我们实例中的任务  
John Lee 0:06:58 
信号灯,知道这个么?   
murex 0:07:24 
不是太清晰他的用途  
John Lee 0:07:28 
这个就是一个同步对象。   
可以用于控制任务同步。   
同步是指,与“异步事件”同步。   
John Lee 0:08:48 
晕没有?   
murex 0:08:51 
没有晕  
John Lee 0:09:30 
一定要理解,要说实话。   
murex 0:09:38 
我知道  
John Lee 0:09:59 
试想一下,那个“计数器”,在任务中正在进行 - 1操作,而此时中断发生了,中断调用了post将“计数器” + 1。   
John Lee 0:12:17 
让我们深入细节,来看看 - 1操作   
 
ARM属于RISC类型的CPU,这个明白?   
murex 0:13:09 
明白  
John Lee 0:14:14 
计数器是在RAM中的数据,要-1,必须先装入CPU寄存器中,然后寄存器数据-1,再然后写回到RAM.   
murex 0:14:45 
嗯,这有个时间差了  
John Lee 0:15:09 
这3步是必须的,顺序也是定死的。   
如果中间发生了中断   
中断调用了post,将计数器+1。   
murex 0:16:04 
是啊,有这个可能  
John Lee 0:17:25 
中断返回后,RAM中的值是被+1了。但继续运行任务,此时任务并不知道RAM中的数据已经发生了变化。   
murex 0:17:38 
是不知道的  
John Lee 0:18:46 
而继续使用CPU寄存器中的“临时”数据,进行-1,然后将其写回到RAM中。  \ 
那么,刚才中断改变的数据就被覆盖了。   
murex 0:19:32 
造成了原来的数据没有被减1  
John Lee 0:19:40 
错   
应该是中断的那一次+1,没有效果了。   
murex 0:20:31 
哦,也可以这样说  
要看后面的执行吧  
John Lee 0:20:48 
No.   
murex 0:20:54 
反正是与开始的那个值一样了  
John Lee 0:21:07 
错,错,错   
John Lee 0:21:41 
举例:原来的值是2,任务调用pend。   
将2装入CPU寄存器,此时中断发生。  
中断里调用post,post会从ram里取出计数器值,此时还是2。   
然后将其+1,=3,保存到ram,此时,计数器值为3。   
中断返回,继续任务的pend操作,原先的计数器值2已在CPU寄存器中,值为2。   
-1后 = 1,保存,返回。   
murex 0:25:40 
对对,看来俺还是理解错了  
John Lee 0:26:16 
如果没有冲突(按原子访问),最终的值应该为2。   
murex 0:26:32 
相当于把刚才的+1覆盖了  
John Lee 0:26:43 
但现实的结果却=1。   
直观上来看,就是中断的+1没有效果了。   
murex 0:29:04 
那是如何处理这个事情呢  
John Lee 0:29:05 
你可以看一个帖子:https://bbs.21ic.com/icview-222850-1-1.html   
里面的系统就有这种问题。   
murex 0:29:55 
就是这个coos?  
John Lee 0:30:13 
你听说过这个RTOS?   
murex 0:30:21 
嗯  
John Lee 0:30:36 
武汉大学搞的。   
你仔细看看。   
murex 0:32:12 
就是刚才说到的这个问题了  
John Lee 0:32:54 
不错,你看懂了。   
murex 0:33:14 
刚才你讲的我懂了,所以看他很轻松了 
John Lee 0:33:57 
RTOS处理冲突的办法有两种。  
第1中最简单:关中断。  
共享数据访问完后,再开中断。  
保证了原子操作。  
像ucos类的RTOS基本都这样处理。  
你可以在其代码中看到大量的ENTER_CRITICAL之类的调用。  
murex 0:36:57 
嗯,有看到过 
John Lee 0:37:45 
但这样处理有一个不算很严重的问题:增大了中断潜伏期。  
看看ucos的源代码,有些地方,从ENTER_CRITICAL到EXIT_CRITICAL的距离还不短。  
murex 0:40:36     
if (OSRunning == TRUE) {                     /* Make sure multitasking is running                  */    
        OS_ENTER_CRITICAL();    
        if (OSLockNesting  255) {               /* Prevent OSLockNesting from wrapping back to 0      */    
            OSLockNesting++;                     /* Increment lock nesting level                       */    
        }    
        OS_EXIT_CRITICAL();    
    }  
 
John Lee 0:40:37 
那么,如何比较好的解决这个问题呢?  
murex 0:40:53 
进中断判断这个之前操作? 
John Lee 0:40:56 
这个还算短的。  
我找一个长的给你看看。  
John Lee 0:43:38 
OS_FLAGS  OSFlagPost (OS_FLAG_GRP *pgrp, OS_FLAGS flags, INT8U opt, INT8U *err) 
{ 
    OS_FLAG_NODE *pnode; 
    BOOLEAN       sched; 
    OS_FLAGS      flags_cur; 
    OS_FLAGS      flags_rdy; 
BOOLEAN       rdy; 
#if OS_CRITICAL_METHOD == 3                          /* Allocate storage for CPU status register       */ 
    OS_CPU_SR     cpu_sr; 
 
 
 
    cpu_sr = 0;                                      /* Prevent compiler warning                       */ 
#endif     
#if OS_ARG_CHK_EN > 0 
    if (pgrp == (OS_FLAG_GRP *)0) {                  /* Validate 'pgrp'                                */ 
        *err = OS_FLAG_INVALID_PGRP; 
        return ((OS_FLAGS)0); 
    } 
#endif 
    if (pgrp->OSFlagType != OS_EVENT_TYPE_FLAG) {    /* Make sure we are pointing to an event flag grp */ 
        *err = OS_ERR_EVENT_TYPE; 
        return ((OS_FLAGS)0); 
    } 
/*$PAGE*/ 
    OS_ENTER_CRITICAL(); 
    switch (opt) { 
        case OS_FLAG_CLR: 
             pgrp->OSFlagFlags &= ~flags;            /* Clear the flags specified in the group         */ 
             break; 
 
        case OS_FLAG_SET: 
             pgrp->OSFlagFlags |=  flags;            /* Set   the flags specified in the group         */ 
             break; 
 
        default: 
             OS_EXIT_CRITICAL();                     /* INVALID option                                 */ 
             *err = OS_FLAG_INVALID_OPT; 
             return ((OS_FLAGS)0); 
    } 
    sched = FALSE;                                   /* Indicate that we don't need rescheduling       */ 
    pnode = (OS_FLAG_NODE *)pgrp->OSFlagWaitList; 
    while (pnode != (OS_FLAG_NODE *)0) {             /* Go through all tasks waiting on event flag(s)  */ 
        switch (pnode->OSFlagNodeWaitType) { 
            case OS_FLAG_WAIT_SET_ALL:               /* See if all req. flags are set for current node */ 
                 flags_rdy = pgrp->OSFlagFlags & pnode->OSFlagNodeFlags; 
                 if (flags_rdy == pnode->OSFlagNodeFlags) { 
     rdy = OS_FlagTaskRdy(pnode, flags_rdy);  /* Make task RTR, event(s) Rx'd          */ 
                     if (rdy == TRUE) {                      
                         sched = TRUE;                        /* When done we will reschedule          */ 
                     } 
                 } 
                 break; 
 
            case OS_FLAG_WAIT_SET_ANY:               /* See if any flag set                            */ 
                 flags_rdy = pgrp->OSFlagFlags & pnode->OSFlagNodeFlags; 
                 if (flags_rdy != (OS_FLAGS)0) { 
     rdy = OS_FlagTaskRdy(pnode, flags_rdy);  /* Make task RTR, event(s) Rx'd          */ 
                     if (rdy == TRUE) {                       
                         sched = TRUE;                        /* When done we will reschedule          */ 
                     } 
                 } 
                 break; 
 
#if OS_FLAG_WAIT_CLR_EN > 0 
            case OS_FLAG_WAIT_CLR_ALL:               /* See if all req. flags are set for current node */ 
                 flags_rdy = ~pgrp->OSFlagFlags & pnode->OSFlagNodeFlags; 
                 if (flags_rdy == pnode->OSFlagNodeFlags) { 
     rdy = OS_FlagTaskRdy(pnode, flags_rdy);  /* Make task RTR, event(s) Rx'd          */ 
                     if (rdy == TRUE) {                        
                         sched = TRUE;                        /* When done we will reschedule          */ 
                     } 
                 } 
                 break; 
 
            case OS_FLAG_WAIT_CLR_ANY:               /* See if any flag set                            */ 
                 flags_rdy = ~pgrp->OSFlagFlags & pnode->OSFlagNodeFlags; 
                 if (flags_rdy != (OS_FLAGS)0) { 
     rdy = OS_FlagTaskRdy(pnode, flags_rdy);  /* Make task RTR, event(s) Rx'd          */ 
                     if (rdy == TRUE) {                        
                         sched = TRUE;                        /* When done we will reschedule          */ 
                     } 
                 } 
                 break; 
#endif 
            default: 
                 OS_EXIT_CRITICAL(); 
                 *err = OS_FLAG_ERR_WAIT_TYPE; 
                 return ((OS_FLAGS)0); 
        } 
        pnode = (OS_FLAG_NODE *)pnode->OSFlagNodeNext; /* Point to next task waiting for event flag(s) */ 
    } 
    OS_EXIT_CRITICAL(); 
    if (sched == TRUE) { 
        OS_Sched(); 
    } 
    OS_ENTER_CRITICAL(); 
    flags_cur = pgrp->OSFlagFlags; 
    OS_EXIT_CRITICAL(); 
    *err      = OS_NO_ERR; 
    return (flags_cur); 
}  
murex 0:44:16 
这个switch里就有长的了 
John Lee 0:45:00 
从第1个OS_ENTER_CRITICAL开始。  
最长的路径,可能到这里: 
    OS_EXIT_CRITICAL(); 
    if (sched == TRUE) { 
        OS_Sched(); 
    } 
    OS_ENTER_CRITICAL(); 
    flags_cur = pgrp->OSFlagFlags; 
    OS_EXIT_CRITICAL(); 
    *err      = OS_NO_ERR; 
    return (flags_cur); 
}  
 
倒数第3个OS_EXIT_CRITICAL.  
你看看其间中断关闭的时间,那是相当长啊。  
murex 0:47:25 
这个的话中断被推迟的时间就长了 
如第一个关之后就有中断的话 
John Lee 0:48:32 
所以,如果你用ucos,千万要看看ucos源码,估算一下中断关闭的时间。  
能不能满足你的项目要求。  
John Lee 0:50:28 
那么怎么样处理才能既避免“共享冲突”,又做到比较短的中断延时呢?  
murex 0:51:09 
以我现在的想法就是在中断中执行判断之前的操作是否有冲突的 
John Lee 0:51:13 
这就引出了“中断分层处理”这个方法。  
John Lee 0:51:37 
你怎么判断?  
murex 0:52:03 
好像是有难度,得往前判断 
John Lee 0:52:04 
判断中断前程序运行到哪里了?  
murex 0:52:54 
还是讲你的,俺是空想,哈,只是理解上应该就这样两种思维 
John Lee 0:53:16 
把中断分为两层  
第1层是立即执行的。  
当中断发生时,立即执行。  
murex 0:54:59 
这个是一般的中断方式 
John Lee 0:55:15 
在其中不能访问任何共享数据,也不能使用任何同步对象(因为同步对象里面就有共享数据)。  
murex 0:55:46 
嗯,不影响共享的可以立刻执行的 
John Lee 0:55:54 
对。  
在这层里面,只能处理最中断本身,  
murex 0:57:24 
对了,我想起刚才的了 
就是先去预读下中断返回的那个指令,是否执行回写共享数据 
如果是就要看现在的中断是否也是那个数据相关 
John Lee 0:58:45 
从广义上讲,不具备可行性。   
从理论上是可以的。   
关键是:如果判断出了会冲突,接下来应该怎么办?   
明知调用了post肯定会冲突,但不调用,则会丢失一个处理信息的。   
murex 1:01:22 
应该说寄存器  
调用这个post也行的  
John Lee 1:01:42 
哦,这倒是可以   
murex 1:02:06 
就是看这个post除了+1操作是否还有别的操作  
John Lee 1:02:09 
但如何确定是哪个寄存器?   
murex 1:02:35 
不知道,哈  
John Lee 1:02:37 
用汇编实现相关代码?   
murex 1:02:49 
那肯定不好了  
John Lee 1:04:00 
对semaphore可以特殊处理,那么其他的同步对象呢?邮箱,消息队列,事件标志,互斥体等等。   
murex 1:04:20 
哇,好多,哈,那样要乱了  
一个个判断要累的了  
John Lee 1:04:42 
所以,从广义上说,不具备可行性。   
murex 1:05:05 
那就在中断中执行一个操作semaphore标记  
John Lee 1:05:29 
干什么?   
murex 1:06:01 
那样出来看是否有这个,有的话就把值+1上去  
John Lee 1:06:39 
那这个标记又是一个共享数据了,它本身怎么办?   
murex 1:06:56 
在这个-1操作前,清标记,操作后看那标记  
所有的这个加减操作都会存在这样的问题  
John Lee 1:07:20 
请标记也是分3步的。   
有没有不会被执行3步的操作  
John Lee 1:08:11 
呵呵,你看看,要讨论,要讨论啊,才能提高。   
murex 1:08:19 
那是的了  
John Lee 1:09:01 
在RISC机器上,所有的RAM数据都必须分3步。   
murex 1:09:13 
那看来我想要的方法没法解决了  
始终存在问题  
John Lee 1:09:36 
在CISC机器上,可能有只要1步的。  
例如:x86,就可以一条指令直接对RAM加减。   
murex 1:10:23 
明白了,在我们的家用电脑上就不存在这样的问题了  
John Lee 1:10:30 
保证是原子的。   
murex 1:10:44 
单片机也是原子的  
John Lee 1:10:54 
arm也是单片机   
murex 1:11:18 
单片机有1条执行+-的吧  
John Lee 1:11:22 
不要以为只有51   
John Lee 1:12:07 
例如,通信设备(uart,spi,i2c等等),中断的第1层,就只处理需要收发的数据。  
并且收发的数据不是共享数据。   
这一点很重要。   
先放着。   
John Lee 1:14:31 
如果需要访问共享数据,或访问同步对象,则需要在中断的第2层处理。 
修正一下,应该是:如果需要访问同步对象,则需要在中断的第2层处理。   
murex 1:16:13 
访问是包含修改吗  
John Lee 1:16:26 
第2层要在时间上保证与任务错开。   
就是说执行第2层时,要保证没有任何访问共享数据的操作在执行。  
访问主要指修改   
起码是一方读,另一方修改。   
murex 1:18:35 
那这个 要保证没有任何访问共享数据的操作在执行 如何确定  
John Lee 1:20:15 
这就是关键了。   
在别的CPU架构上,这个功能的实现需要依靠“调度锁”。   
而在cortex架构上,则免了。   
这就是cortex架构的优越性:天生适合跑OS。   
不跑OS都觉得可惜。   
好了,关于这个问题,要涉及到cortex架构,要说清楚,今天的时间不够了,以后吧。   
John Lee 1:24:10 
我们先假定能错开处理时间。   
那么,一切都好解决了。   
第1层处理中断硬件直接相关的部分。   
第2层处理同步问题。   
John Lee 1:26:12 
在linux中,就是这样的处理方式。   
linux中,第1层处理,称为“top half”,第2层处理,称为“bottom half”  顶半,底半。   
look的中断处理,就是借用了这个方式。   
对应到look的相关函数。   
就是interrupt_t类的 isr()函数 和 dsr() 函数。   
这两个函数,你可以看做回调函数。   
当中断发生时,isr()立即被调用。  
注意isr的返回值。   
如果这次中断不需要操作同步对象,那么返回false即可。   
随后,中断也就会马上返回的。   
如果这次中断需要操作同步对象,例如:通信发送缓冲数据空了,或者接收数据满了。   
需要任务来填充或取走这些数据时,则可以返回true。   
返回true后,look中断管理器会将这个中断对象挂在一个链表上,在调度器可以调度的时候(没有被锁定),由调度器来访问链表,调用中断对象的dsr().   
所以,我最开始说的,中断处理背后还与调度器相关。   
好了,你应该大概知道了look的中断管理是怎样的了。   
murex 1:40:03 
嗯,知道了  
John Lee 1:40:57 
明天,在了解一下那个关键的地方(cortex)。就完全明白了。   
这只是基础。   
我们还没有谈到look的“特点” |   
     
评分
- 
查看全部评分
 
 
 
  
 |