本帖最后由 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的“特点” |