lulu爱 发表于 2013-11-10 16:44:41

高效实时操作系统原理以及实践-多对象的阻塞

本帖最后由 lulu爱 于 2013-11-10 22:10 编辑

多对象的阻塞

在多任务同步的章节里面我们看到多个任务可以阻塞在同一个内核对象上。例如多个任务可以阻塞在信号量以及队列或者事件标志上。在这个一章节会阐述单个任务如何阻塞在多个内核对象上。目前raw os支持任务同时阻塞在队列(queue)或者信号量(semaphore)上。

raw os实现多对象阻塞的功能主要是通过函数通知的功能来实现的。通过此项技术能够简洁的实现单任务阻塞在多个内核对象上的功能。

函数通知的功能能够通过注册一个回调函数,使得释放信号量以及发送消息到队列上的时候调用这个注册的函数。

怎么样注册一个函数通知功能的回调函数呢?举例如下:

raw_semphore_send_notify(&sem1, notify_function)通过此函数调用可以注册一个回调函数notify_function到信号量sem1上,当用户使用函数raw_semaphore_put的时候就可以触发此回调函数。

同样对于队列也有此功能。raw_queue_send_notify(&queue1,notify_function)通过此函数可以注册一个回调函数notify_function到queue1上。当用户使用函数raw_queue_end_post时会触发此注册的回调函数。

下图中演示的是一个实际中的案例:


上图(1)处任务1需要等待queue1或者queue2有消息才能继续运行。queue1或者queue2没有消息的话,任务1就阻塞住。(2)和(3)处说明queue1 或者queue2来消息了,任务1需要去接收,(4)和(5)处说明任务1接收了消息后继续运行。


下图演示的是raw os 对于上面问题的解决方式,不同的操作系统有不同的解决方式,无疑通过函数通知这种实现是最简洁的。


上图(1)处任务1阻塞在网关处,当sem1的内部计数器值为0时说明queue1和queue2都没有消息,当不为0时说明queue1或者queue2有消息。(2)和(3)处说明queue1 或者queue2来消息了,任务1需要去接收,(4)和(5)处说明任务1接收了消息后继续运行。

以上过程只是比前面一个图多了一个sem1的网关,确是解决问题的关键所在。下面总结描述下解决方案。

1 queue1 以及queue2上注册一个通知函数,通知函数的内容是发送一个信号量。

2 任务阻塞在sem1上,当queue1以及queue2获得消息时,会唤醒任务1,然后接收相应的消息。


具体的代码演示如下:

queue1以及queue2上注册一个通知函数notify_queue,代码如下所示:
raw_queue_send_notify(&queue1, notify_queue);
raw_queue_send_notify(&queue2, notify_queue);

通知函数内容如下:

void notify_queue(RAW_QUEUE *ptr)
{
        raw_semaphore_put(&sem1);

}

可以看到只要queue1和queue2里面一有消息就会触发notify_queue回调函数。回调函数会释放一个信号量,同时会打开网关sem1,进而唤醒任务1。

任务1的代码如下:

raw_semaphore_get(&sem1,RAW_WAIT_FOREVER);
ret = raw_queue_receive (&queue1, RAW_NO_WAIT, (RAW_VOID **)&msg_app2);

if (ret == RAW_SUCCESS) {

        /*Access queue1 msg*/;

}

ret = raw_queue_receive (&queue2, RAW_NO_WAIT, (RAW_VOID **)&msg_app2);

if (ret == RAW_SUCCESS) {

        /*Access queue2 msg*/;

}

上述代码中可以看到任务1阻塞在sem1上,当queue1以及queue2上有消息的时候会触发notify_queue这个回调函数,回调函数内会释放一个信号量,进而唤醒任务1。需要注意的是调用raw_queue_receive时需要采用没有消息立马返回的功能,即超时参数为RAW_NO_WAIT。

以上从理论到实际演示了一个完整的任务阻塞在多内核对象上的过程,这个过程是挺复杂的,具体的验证可以上VC或者keil平台验证。


官网地址为:www.raw-os.org

lulu爱 发表于 2013-11-10 16:45:41

内核高级功能之一。

electrlife 发表于 2013-11-10 17:10:36

lulu爱 发表于 2013-11-10 16:45 static/image/common/back.gif
内核高级功能之一。

这确实是一个好的且非常简单的实现方法!
但是这里有一个疑问,当任务运行时,它似乎无法知道究竟是哪个队列上的消息使它就绪的!

lulu爱 发表于 2013-11-10 17:59:17

通过判断返回值ret
ret = raw_queue_receive (&queue1, RAW_NO_WAIT, (RAW_VOID **)&msg_app2);

ret 为RAW_SUCCESS说明queue1接收到消息,为RAW_NO_PEND_WAIT是说明没消息。

只要任何一个queue1 或者queue2 有消息就能让任务1就绪。

感谢楼上的意见,这块是挺难理解的,能看懂的应该是属于高手行列了。

electrlife 发表于 2013-11-10 18:10:58

lulu爱 发表于 2013-11-10 17:59 static/image/common/back.gif
通过判断返回值ret
ret = raw_queue_receive (&queue1, RAW_NO_WAIT, (RAW_VOID **)&msg_app2);



ISR1和ISR2连续发生,也就是当Sem1++时,等待的任务是不可能开始运行的,
ISR2 ---> Queue2_Post -->Sem1++ --->ISR2返回 -----> ISR1 ---> Queue1_Post -->Sem1++ ---->ISR1返回 ----->等待Sem1任务开始运行,
但此时TASK是无法判断是哪个QUEUE让它就绪的!也就是说TASK无法得知两个QUEUE中数据哪个前哪个后!

electrlife 发表于 2013-11-10 18:21:06

所以我觉得LZ这里不算是真正等待多个对象。
个人觉得等待多个对象应该是一个完整的过程,即当多个对象中有一个有效那么此TASK就应该就绪并获得该就绪的对象。
此时就算其它对象也有效,但TASK已经得第一个使它就绪的对象,也就是说TASK永远获得是使它就绪的对象。
显然LZ的这种方式无法实现。

lulu爱 发表于 2013-11-10 20:54:14

本帖最后由 lulu爱 于 2013-11-10 21:04 编辑

这个你说的没错,两个不同QUEUE中数据哪个前哪个后,这个是无法得知的,但是次序先后并不重要,重要的是两点:

1queue1 或queue2 里面有数据,任务必须得唤醒。
2任务1被唤醒后,queue1 或queue2里面能获得到数据。因为实战中任务阻塞在queue1 和queue2上的时候,被唤醒了只需要确切的数据对于数据次序是不重要的。

但是对于同一个queue 里面的消息顺序先后这个是很重要的,以上的方法也能保证对同一个queue而言,消息顺序是确定的。

如果觉得queue1 和queue2这两个queue的消息顺序是重要的,可以举一个实战例子说明,或者ucos 3 能实现的例子说明也行。



lulu爱 发表于 2013-11-10 21:02:09

多对象阻塞的功能每一个操作系统实现都有自己的方式,比如threadx以及windows。只要能达到相近的目标,既可以是完成,这个没有哪家是标准的作法。上传一个threadx的多对象阻塞设计方式,raw os 的设计方式参考了它的设计。

electrlife 发表于 2013-11-10 21:50:00

本帖最后由 electrlife 于 2013-11-10 21:51 编辑

lulu爱 发表于 2013-11-10 21:02 static/image/common/back.gif
多对象阻塞的功能每一个操作系统实现都有自己的方式,比如threadx以及windows。只要能达到相近的目标,既可 ...

本身挂起在多个内核对象这种应用不是很多,多数用在网络相关上面。
我举个例子:
比如有个下位机控制板C, 下位机显示控制设为A,PC机控制设为B
A、B都可以通过CAN控制C进行相关的动作,如果AB同时发生控制命令,
此时我需要知道谁的命令触发了动作,因为AB的权限不一样,所以如果
不知先后,我程序无法仲裁!
其实讨论这个没有意义,从我个人角度来看,知道谁是真正的触发者或许更容易接受称知为挂起在多个对象上。

对于以上的问题也不是没有办法,LZ只要稍换下思路即可:
RAW_QUEUE queue1, qeueu2, queue_gateway;

raw_queue_send_notify(&queue1, notify_queue);
raw_queue_send_notify(&queue2, notify_queue);

void notify_queue(RAW_QUEUE *ptr)
{
      raw_queue_put(&queue_gateway, ptr);
}

raw_queue_receive (&queue_gateway, RAW_WAIT_FOREVER, (RAW_VOID **)&msg_gateway);
if (msg_gateway == &queue1) {
         ret = raw_queue_receive (&queue1, RAW_NO_WAIT, (RAW_VOID **)&msg_app2);
         if (ret == RAW_SUCCESS) {
                /*Access queue1 msg*/;
      }
} else if (msg_gateway == &queue2) {
         ret = raw_queue_receive (&queue2, RAW_NO_WAIT, (RAW_VOID **)&msg_app2);
         if (ret == RAW_SUCCESS) {
                /*Access queue1 msg*/;
      }
}

electrlife 发表于 2013-11-10 22:01:07

以上的写法可能不符合RAW os,主要的意思就是这个所谓的sem1可以用一个队列来替代,可有相关资源有效时
此时通知函数可以把这个有效的资源对象放入该队列中,这样TASK就绪就可以通过这个所谓的queue_gateway
来判断是哪个资源使之就绪的,这里可以在通知函数里把各个对象的地址传过去,然后在TASK中通过对比较
各个对象的地址来判断,如下判断比较严谨:
if (msg_gateway == ((RAW_VOID *))(&queue1)) {
    ............................
}

wanas 发表于 2013-11-11 17:57:51

本帖最后由 wanas 于 2013-11-11 18:00 编辑

支持electrlife,只提供基本的操作。高级功能由这些简单功能组合出来。同时等待邮箱和信号量的一个任务,不会觉得很怪异吗?
页: [1]
查看完整版本: 高效实时操作系统原理以及实践-多对象的阻塞