打印
[LOOK]

RTOS(LOOK)夜话:共享与冲突及中断系统1

[复制链接]
4988|17
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
murex|  楼主 | 2011-4-18 12:35 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 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的“特点”
评分
参与人数 2威望 +11 收起 理由
hotpower + 10
dong_abc + 1

相关帖子

沙发
murex|  楼主 | 2011-4-18 19:42 | 只看该作者
本帖最后由 murex 于 2011-4-18 20:09 编辑

这个只是开头,更精彩的还在后头

使用特权

评论回复
板凳
john_lee| | 2011-4-18 20:38 | 只看该作者
勤奋的菜地,辛苦了。

使用特权

评论回复
地板
dong_abc| | 2011-4-19 01:03 | 只看该作者
明天已经过了:)

使用特权

评论回复
5
randy3418| | 2011-4-19 08:20 | 只看该作者
向整理笔记的朋友致敬!:lol

使用特权

评论回复
6
plc_avr| | 2011-4-19 10:10 | 只看该作者
谢谢共享!

使用特权

评论回复
7
john_lee| | 2011-4-20 11:25 | 只看该作者
菜地,第二夜呢?整理好就贴出来吧。

使用特权

评论回复
8
hotpower| | 2011-4-20 11:43 | 只看该作者
第二夜好像搞到二点???
够疯狂!

使用特权

评论回复
9
li923661521| | 2011-4-20 23:10 | 只看该作者
希望下次可以赶得上

使用特权

评论回复
10
hotpower| | 2011-4-21 18:05 | 只看该作者
以后让老师给大家讲

使用特权

评论回复
11
dong_abc| | 2011-4-23 13:14 | 只看该作者
都是好人啊,辛苦了~~~

使用特权

评论回复
12
hotpower| | 2011-4-27 10:41 | 只看该作者
老师很辛苦的,向老师致敬。
又仔细看了一遍。

使用特权

评论回复
13
hotpower| | 2011-5-17 17:43 | 只看该作者
再次复习

使用特权

评论回复
14
hotpower| | 2011-6-1 09:06 | 只看该作者
第四次学习了,顶起来。

使用特权

评论回复
15
murex|  楼主 | 2011-6-2 10:25 | 只看该作者
哈,又被置顶了啊,大家是要好好学习,这里的东西都是很重要的

使用特权

评论回复
16
lixupengarm| | 2011-6-13 10:12 | 只看该作者
mark!!!!!

使用特权

评论回复
17
zhyscout| | 2012-2-23 13:21 | 只看该作者
谢谢楼主!

使用特权

评论回复
18
abin0415| | 2012-4-29 01:54 | 只看该作者
学习之

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

20

主题

2174

帖子

2

粉丝