[LOOK]

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

[复制链接]
5029|15
手机看帖
扫描二维码
随时随地手机跟帖
murex|  楼主 | 2011-4-21 22:34 | 显示全部楼层 |阅读模式
本帖最后由 hotpower 于 2011-5-23 19:04 编辑

John Lee<j.y.lee@yeah.net>  21:01:09
昨天说到,如何在中断的bottom half处理部分,错开共享变量的访问。
在一般RTOS中,是利用称为“调度锁”的机制。
调度锁,在不具备中断分层处理方式的RTOS中,仅仅禁止调度。
调度任务,就是说,当系统中出现了比当前任务优先级更高的任务,调度器也不进行任务切换。
murex(344582199)  21:06:01
就是把任务切换给锁住
murex(344582199)  21:06:17
为了避免共享数据被其他任务破坏?
John Lee<j.y.lee@yeah.net>  21:06:18
而在具备中断分层处理方式的RTOS中,调度锁还禁止了bottom half调度。
这样,当在任务中需要访问共享数据时,先锁定调度锁,这样就可以禁止bottom half,待访问完毕后,再解锁调度锁。
这样就可以实现原子访问共享数据。
murex(344582199)  21:12:01
嗯,可以,但是这样的话会影响高优先级响应时间吧
John Lee<j.y.lee@yeah.net>  21:13:12
这个跟优先级没有多大关系。
murex(344582199)  21:14:44
嗯,只是争夺这个共享数据时有点影响,应该关系不大
John Lee<j.y.lee@yeah.net>  21:14:45
当调度锁被锁定时,中断还是可以响应的,其top half也会处理。
被延时的,只是bottom half。
murex(344582199)  21:17:05
这个调度锁主要是针对中断中的对共享数据的处理?
John Lee<j.y.lee@yeah.net>  21:18:11
当调度锁被锁定时,调度器不会处理中断对象链表,链表上中断对象的bottom half当然就不会被调用。这会一直延迟到调度锁被解锁。
当调度锁解锁时,调度器会进行中断对象链表处理,调用相应中断的bottom half。
调度锁的主要用途,就是使对共享数据的访问,成为原子的。
murex(344582199)  21:22:47
明白了,就是不管中断,还是其他任务需要访问这个共享数据,都得这个任务把共享数据使用权释放后
这样就保证了访问这个共享数据都是原子的了
John Lee<j.y.lee@yeah.net>  21:24:27
同步对象内部,都存在着共享数据。你可以看到,在同步原语中,大量地使用了调度锁。
murex(344582199)  21:25:10
look的源码中可以看到?
John Lee<j.y.lee@yeah.net>  21:25:24
明白是什么是原语?
murex(344582199)  21:25:54
跟原子差不多吧
John Lee<j.y.lee@yeah.net>  21:26:07
嗯,聪明。
原语,就是一个操作(一般是以调用函数的形式),其内部需要用到的共享数据都是以原子方式访问的。要么全部访问成功,要么失败后,所有的共享数据都保持在调用原语之前的状态。
murex(344582199)  21:31:38
如果失败的话,任务会被挂起等待共享数据使用权?
John Lee<j.y.lee@yeah.net>  21:33:24
挂不挂起,要看原语(具体实现)怎么处理了,例如ucos:*Accept原语,就不阻塞任务,而*Pend原语,就可能会阻塞任务。
murex(344582199)  21:35:27
就如同问时间,问出来就更好,不问出来也没事,可以继续做事
一个就是一定要等到准确时间
John Lee<j.y.lee@yeah.net>  21:37:14
中断处理是单层的RTOS,就不能用调度锁来实现共享数据的原子访问,这类RTOS,一般都用关中断的方式(中断锁),来实现原子访问。
目前,大多数RTOS都是单层的,两层的还不多见。
murex(344582199)  21:39:35
ucos就好像是中断锁来实现的
John Lee<j.y.lee@yeah.net>  21:40:02
是的,它的中断处理方式只有一层。
关于两层的具体实现,昨天已经说了个大概。
murex(344582199)  21:42:00
就是top bottom?
John Lee<j.y.lee@yeah.net>  21:42:58
对,第1层是top half,第2层是bottom half.
murex(344582199)  21:43:17
这种一层的,关中断时间如果过长,就还是挺麻烦的
John Lee<j.y.lee@yeah.net>  21:43:19
这里再说一遍,加强一下。
murex(344582199)  21:43:58
把昨天里面有部分没讲的讲下好了
John Lee<j.y.lee@yeah.net>  21:45:29
当中断发生时,top half立即被调用,进行一些需要紧急处理的工作,然后根据应用,决定是否需要调度器处理bottom half.  
如果需要,中断管理器就会将这个中断对象(数据结构)挂在一个链表上,这个链表是专门用于等待bottom half处理的中断对象的链表。
murex(344582199)  21:49:00
这个链表是怎么样的
John Lee<j.y.lee@yeah.net>  21:50:24
挂上后,中断处理器就将控制权交给调度器,调度器会查看调度锁是否被锁定,如果没有锁定,调度器就会遍历那个链表,对链表上的每一个中断对象调用其bottom half处理函数。
如果发现被锁定,则什么都不做。
然后中断返回。
链表很简单,就是个普通的双向链表,在每个中断对象(数据结构)中都有两个数据项:prev和next。
在ucos中也有很多双向链表,你应该可以看到的。
其实,调度器在调用中断对象的bottom half处理时,之中可能发生了一些关键性的共享数据变化.  
如最高优先级任务的变化。
murex(344582199)  21:58:04
嗯,这个可以理解
John Lee<j.y.lee@yeah.net>  21:58:22
好,继续。
说到look,我们就转入今晚的正题,cortex架构是如何适合rtos的。
昨晚说了一个引子,在cortex架构上,调度锁可以免了。
还记得么?
murex(344582199)  22:00:18
记得
说他天生是跑rtos的
John Lee<j.y.lee@yeah.net>  22:00:50
那是由于cortex有一个PendSV异常。
这个异常很有趣。
首先,PendSV异常时由写NVIC的一个寄存器位=1来触发的。
就是说,这个异常是一个同步中断(有程序引起的)。
但它可能并不会立即被cortex内核调用。
而是要等到CPU不在handle模式时,才会被调用。
能理解这些么?
murex(344582199)  22:04:49
基本可以
John Lee<j.y.lee@yeah.net>  22:06:06
这种机制,可以实现调度锁的功能。
哦,还必须配合SVC异常才能实现调度锁。
murex(344582199)  22:07:27
就是SVCall?
John Lee<j.y.lee@yeah.net>  22:08:29
对,关于这两个异常,你都明白它们的机制么?
murex(344582199)  22:08:40
不太了解
John Lee<j.y.lee@yeah.net>  22:11:53
其实也很简单,SVC就是立即进入svc异常处理,这也是一个同步中断。而PendSV虽然也是同步中断,但可能不会马上进入,而是要等到CPU不在handle模式时(所有的中断都已返回),才会进入处理。
记住PendSV的这个特性。
下面,通过一个实例来说明。
当任务调用pend原语时,pend原语直接执行一个svc,而pend的真正实现是在svc异常服务里面。
murex(344582199)  22:15:19
svc也是跟pend异常一样,可以通过程序写入产生中断
John Lee<j.y.lee@yeah.net>  22:18:13
假如此时中断发生,top half执行,但根据top half的行为定义,它是不允许访问共享数据的,所以我们不担心冲突,然后,中断需要进行 bottom half 处理,中断管理器将其挂入链表。
然后,在这里,一般RTOS是直接调用调度器的一个函数,将控制权交给调度器,而LOOK则与一般的RTOS有所不同,它利用了cortex架构的特点,触发了一个PendSV异常。
由于此时CPU还在中断处理中(handle模式),PendSV不会立即进入。
接下来中断返回,如果此时没有其他中断了(CPU将要返回thread模式),PendSV就会进入。
哦,不对。
此时应该还在svc处理中。
murex(344582199)  22:25:29
不是我们会执行SVC嘛
John Lee<j.y.lee@yeah.net>  22:43:59
刚才说到,中断返回时,其实我们还在svc处理中,因此,CPU还不会进入PendSV.
而所有的bottom half处理,都在PendSV中被调度器调用。这样就保证了svc处理的原子方式。
murex(344582199)  22:46:32
嗯,可以很好保护了
John Lee<j.y.lee@yeah.net>  22:47:25
等svc返回时(如果此时也没有别的中断发生),PendSV就可以进入了。在PendSV中,调度器会操作链表,调用bottom half。
简单地说,svc和hottom half在时间上是错开的。而相关的共享数据要么在svc中处理,要么在bottom half中处理,所以,就保证了不会冲突。
好了,cortex的架构问题说完了,还继续么?
好,那回到<look>头文件,interrupt_t 类就是一个中断对象,封装了所需的数据结构,提供了top half和bottom half的用户接口。
John Lee<j.y.lee@yeah.net>  22:54:33

class interrupt_t : public signal_sink_t {

public:

interrupt_t() __OPT_ATTR__;

interrupt_t* attach(uintptr_t irq) __OPT_ATTR__;


private:

virtual bool isr(int vector) = 0;

virtual void dsr(int vector, uintptr_t count) = 0;

};
John Lee<j.y.lee@yeah.net>  22:55:14
interrupt_t::isr()就是top half, 而interrupt_t::dsr()就是bottom half。
murex(344582199)  22:56:21
这两个函数在哪里有具体内容
John Lee<j.y.lee@yeah.net>  22:56:59
函数定义的后面有一个 = 0; 那表示这个函数是一个“纯虚函数(pure virtual function)。
在语法上就限定了,用户不允许创建interrupt_t的对象(变量)
而必须从interrupt_t类派生出一个新的子类,才能创建变量。
virtual bool isr(int vector) = 0; // = 0 表示这是一个纯虚函数。
murex(344582199)  23:00:20
纯虚函数有什么好处
John Lee<j.y.lee@yeah.net>  23:01:52
定义一个抽象的,框架性的类。
比如:我们说“汽车”,其实这是一个抽象的概念,是所有“具体”汽车的总称。
你无法制造一个抽象的“汽车”。
murex(344582199)  23:04:26
嗯,那如何来具体化它呢
John Lee<j.y.lee@yeah.net>  23:04:38
而包含有 = 0的纯虚函数的类,就是一个“抽象类”。
抽象类是不允许实例化(以这个类创建变量)的。
murex(344582199)  23:05:44
但可以通过子类来实例化?
John Lee<j.y.lee@yeah.net>  23:06:49
如果你在程序中写:
interrupt_t uart_intr;   // 创建interrupt_t类型的变量
编译器会报错的。
interrupt_t类的作用,在上面已经讲了。而要使用这个类,就必须通过“派生”(以这个类作为“基类”(父类),定义一个“子类”(派生类)。
在派生类里,要把基类的纯虚函数,重新声明为“普通的虚函数”,就可以创建这个派生类的对象了。
对象 = 变量 = 实例。
murex(344582199)  23:13:03
抽象就是把实例封装起来
John Lee<j.y.lee@yeah.net>  23:13:14
定义变量 = 创建对象 = 实例化。
实例已经是一个变量了,怎么谈得上封装?
关于 interrupt_t 类的例子,可以参考 《[菜农助学交流]基于 LOOK 系统的助学板 DS18B20 示例》。
里面有两种使用interrupt_t 例子,uart中断 和 gpio中断。
murex(344582199)  23:17:03
嗯,刚才搜索__OPT_ATTR__就找出了这个示例
bool uart0_t::isr(int vector)
{
    const char* str = buffer;
    if (str == 0) {                     // 无数据
        UART0s.IER.Bits.THRE_IEN = 0;   // 禁止发送中断
        return true;
    }
    fillfifo(str);                      // 填充 fifo
    return false;
}
// uart0 中断滞后服务例程
// 所有数据发送完成后,dsr() 被调用
void uart0_t::dsr(int vector, uintptr_t count)
{
    task->do_wakeup();              // 唤醒任务
}
John Lee<j.y.lee@yeah.net>  23:19:31
对,我现在就简单讲解一下。
这个uart只用了发送数据的功能,uart0_t 类里定义了两个数据成员:
    const char* buffer;             // 输出缓冲区
    task_t* task;                   // 正在输出的任务

buffer指向待发的数据区。task表示正在使用uart的任务(指针)(句柄)。
murex(344582199)  23:23:50
嗯,这个可以理解
John Lee<j.y.lee@yeah.net>  23:25:19
fillfifo函数的功能是:从buffer指向的数据区中,将数据一个一个填入uart的硬件fifo。
直到fifo满或数据结束。
并调整buffer指针,如果数据结束,则让buffer = 0;
好,假设uart fifo中的数据都已发出,uart发生fifo空中断,uart0_t::isr()执行。
在 isr 中,先检查 buffer 是否为 0,为0就是数据已结束,那么,禁止发送中断然后返回true,通知中断管理器,需要执行 bottom half(dsr)。
如果 != 0,则再次fillfifo,返回 false,中断管理器什么都不做,直接中断返回。
murex(344582199)  23:32:28
嗯,可以理解
John Lee<j.y.lee@yeah.net>  23:33:04
在 dsr 中,直接唤醒发送数据的任务。
那个任务在调用 uart0_t::puts()时,就被阻塞了。
do_wakeup()是一个“原语”。
哦,==,不是“原语”。
只是一个函数。
但do_wakeup由于使用的限定,从某种意义上说,也可以当做一个“原语”。
整个逻辑就是:当任务调用uart0_t::puts()输出数据时,会被阻塞。之后,当所有数据都已发送完成后,启用dsr()唤醒这个任务。
murex(344582199)  23:39:57
阻塞就是为了独占这个串口吧
John Lee<j.y.lee@yeah.net>  23:41:10
不,是为了避免“忙等待”,就是菜农常说的“0耗时”。
当任务阻塞时,调度器会将CPU资源,转让给其它任务,如果所有的任务都在阻塞,则会转到IDLE任务。
murex(344582199)  23:43:31
这个阻塞就相当于是暂时没有动作要做的了
John Lee<j.y.lee@yeah.net>  23:43:45
对。
阻塞,就意味着要发生“任务切换”。
murex(344582199)  23:44:43
但串口发送后,产生别的任务要使用串口发送怎么办
John Lee<j.y.lee@yeah.net>  23:46:40
至于这一点,我的示例,从简单的角度考虑,只用了一个任务,就无所谓“设备争用”了。
murex(344582199)  23:47:22
嗯,那是的,如果有多个争夺就需要对这个串口进行管理了
John Lee<j.y.lee@yeah.net>  23:47:32
真正的用户程序里面,如果存在“设备争用”的情况,就一定要处理了。
简单的,就在uart0_t类里再增加一个信号灯数据成员。
murex(344582199)  23:49:14
嗯,这个可以的了
John Lee<j.y.lee@yeah.net>  23:49:37
当puts时,先获取信号灯,成功后再进行下一步操作。
murex(344582199)  23:49:42
让这个任务独
John Lee<j.y.lee@yeah.net>  23:50:57
好了,回到interrupt_t
示例中的isr就展示了如何处理中断(硬件本身),访问非共享数据。
interrupt_t还有一个成员函数:attach().
这个函数是将本中断对象挂接到一个向量。
中断对象在挂接之前,是不会起作用的(有点像安装回调函数?!)
但这个与回调函数还是有本质的区别。
attach实际上将中断对象的指针(注意:这个指针是数据指针,而不是函数指针),填写到中断对象表中。
murex(344582199)  23:58:42
这个挂接就是靠这个中断对象表?
John Lee<j.y.lee@yeah.net>  23:58:57
对。
murex(344582199)  23:59:45
也就相当于安装回调函数,就会在这个终端对象表中有记录挂钩上了
John Lee<j.y.lee@yeah.net>  0:02:14
中断时,IPSR寄存器中有中断号,中断管理器会用这个中断号作为下标,去对象表(实际就一个数组)中取出相应的对象指针,然后就调用对象的isr。
这个过程的执行时间,也就是look的中断潜伏期.
有疑问么?
murex(344582199)  0:04:43
基本没有
原理理解,就是实例会是怎么样实现的
John Lee<j.y.lee@yeah.net>  0:06:19
到此,LOOK的中断系统就介绍完毕了,也附带讲解了一般RTOS对中断的处理方法。
关于中断系统,还有一点没有讲到,就是配合调度器进行任务上下文切换。
也就是中断抢占。
这个留到讲调度器时,再结合来讲。
murex(344582199)  0:10:56
嗯,好的
评分
参与人数 2威望 +11 收起 理由
hotpower + 10
dong_abc + 1

相关帖子

hotpower| | 2011-4-22 00:53 | 显示全部楼层
好!

使用特权

评论回复
dong_abc| | 2011-4-22 01:31 | 显示全部楼层
菜鸟来仰望一下~~~

使用特权

评论回复
hotpower| | 2011-4-27 13:05 | 显示全部楼层
顶起来

使用特权

评论回复
bnyuli| | 2011-5-3 13:58 | 显示全部楼层
额  看得我头晕  但应该是很用心的吧

使用特权

评论回复
tlb| | 2011-5-4 05:39 | 显示全部楼层
先顶后看

使用特权

评论回复
pa2792| | 2011-5-4 15:41 | 显示全部楼层
来顶下老同学的帖子。:lol

使用特权

评论回复
murex|  楼主 | 2011-5-4 18:42 | 显示全部楼层
哈哈,谢谢老同学

使用特权

评论回复
hotpower| | 2011-5-17 17:10 | 显示全部楼层
学习

使用特权

评论回复
hotpower| | 2011-6-1 09:06 | 显示全部楼层
第四次学习了,顶起来。

使用特权

评论回复
batsong| | 2011-6-1 10:19 | 显示全部楼层
我已经完全落后群里的兄弟们了

使用特权

评论回复
weshiluwei6| | 2011-6-2 07:48 | 显示全部楼层
经典之作 经典中的经典

使用特权

评论回复
murex|  楼主 | 2011-6-2 10:24 | 显示全部楼层
哈,又被置顶了啊,大家是要好好学习,这里的东西都是很重要的

使用特权

评论回复
lixupengarm| | 2011-6-13 10:13 | 显示全部楼层
mark!!

使用特权

评论回复
zhyscout| | 2012-2-23 13:21 | 显示全部楼层
谢谢楼主!

使用特权

评论回复
zhyscout| | 2012-2-27 13:21 | 显示全部楼层
:lol

使用特权

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

本版积分规则

20

主题

2174

帖子

2

粉丝