事件驱动框架(五)——框架的实现

事件驱动框架(五)——框架的实现


说明

这里先描述一下QP的一些策略和源码。
因为某原因这个系列先停更。后面主要是内核介绍。


实现

1.临界区

临界区内每次只准许一个线程(进程)进入,进入后不允许其他线程(进程)进入。因此临界区的代码不可分割。
在嵌入式系统中,保护临界区,就是在进入临界区时锁中断,在从临界区退出时解锁中断。在不支持锁中断的系统中,可采用其他底层操作系统支持的机制。
为了可以做统一,便在头文件中加入宏,后面只需根据对应的芯片特性,修改.h文件中宏对应的进出临界的方式就可以了。

临界种类

1>.第一种是保存恢复通用状态。最通用的临界区实现牵涉到在进入临界区前保存中断状态,在从临界区退出后恢复这个状态。
下为临界区的使用方法:

{
    unsigned int lock_key;    //保存中断状态变量
    . . .
    lock_key = get_int_status();   //从CPU中获得中断状态,并保存到变量
    int_lock();         //中断加锁
    . . .
    /* critical section of code */   //临界区代码
    . . .
    set_int_status(lock_key); //中断解锁
}
#define QF_INT_KEY_TYPE unsigned int
#define QF_INT_LOCK(key_) do { \
    (key_) = get_int_status(); \
    int_lock(); \
    } while (0)
#define QF_INT_UNLOCK(key_) set_int_status(key_)

主要优点是可以嵌套临界区的能力

2>.另一种是无条件解锁。较简单和较快的临界区策略是总是无条件的解锁。

#define QF_INT_LOCK(key_) int_lock()
#define QF_INT_UNLOCK(key_) int_unlock()

“无条件上锁和解锁”策略是简单和快捷的,但是不允许临界区的嵌套,因为从一个临界区退出时中断总是被解锁的,无论在进入中断时中断是否已经被上锁。不能嵌套临界区并不意味着你不能嵌套中断。许多处理器拥有基于优先级的中断控制器,比如在PC 里的Interl 8259A可编程中断控制器,或集成在ARM Cortex-M3 内的嵌套向量中断控制器NVIC 。这类中断控制器在中断到达处理器内核时处理中断优先级和嵌套。因此,你可以安全的在处理器层解锁中断,从而你可以避免在ISR 内部嵌套临界区。

2.主动对象

活动对象=控制线程+事件队列+状态机
主动对象继承了事件处理所提的状态机(也可以是PT协程),同时将事件队列和线程控制封装到一个对象里作为框架控制的基本对象。同时主动对象还进行了封装,留出初始化,构造,开始对象,调度,发送事件,停止线程,事件延时和恢复等接口。

0>.start()函数

QActive_start()函数创立活动对象的线程并提醒框架开始管理活动对象。绝大多数情况下,所有活动对象只是在系统初始化时被启动一次。

//QActive_start()伪函数
void QActive_start(QActive *me,
     uint8_t prio, /* 优先级*/
     QEvent const *qSto[], uint32_t qLen, /* 事件队列*/
     void *stkSto, uint32_t stkSize, /* 任务堆栈*/
     QEvent const *ie) /* 初始化事件*/
{
    me->prio = prio; /* 设立优先级 */
    QF_add(me); /* 将当前活动对象加入到框架中*/
    QF_ACTIVE_INIT_(me, ie); /* 执行活动对象初始化*/

    /*创建事件队列*/
}

3.事件管理

1>.事件队列

事件队列为提供了事件的缓冲,而防止一段时间内发送的事件过多,或有事件未处理完而造成的事件的丢失情况。在活动对象中事件队列需要有单读多写的功能,这就要求事件队列需要有一个合适的互斥机制来应对这种情况。
活动对象一般管理的是一个环形事件缓冲。根据前一章的策略,支持零复制事件队列,所以事件缓冲存放的事件的指针,指针指着事件池中的动态事件,也可以指着静态事件。
事件队列可以由RTOS的消息队列实现,也可以用原生的事件队列实现。
事件的机构定义

typedef struct QEventTag { 
QSignal sig;
uint8_t dynamic_; /* 动态事件编号 */
} QEvent;

dynamic为0保留给静态事件,其他代表内存池编号和累计数大小。

/**************对于事件池的分配至今很不清楚******************************/

对动态事件的事件池的初始化。一个应用程序可能最多调用这个函数3次去初始化最多3个事件池。为了可能快的事件分配,事件池数组必须根据块尺寸降序排序。

QF_EPOOL_TYPE_ QF_pool_[3]; /* 分配3个事件池*/
uint8_t QF_maxPool_; /* 已初始化事件池*/
/*..........................................................................*/
void QF_poolInit(void *poolSto, uint32_t poolSize, QEventSize evtSize) {
    /* 不执行多于被支持数量的内存池*/
    Q_REQUIRE(QF_maxPool_ < (uint8_t)Q_DIM(QF_pool_));
    /* 应用程序用事件尺寸的递增次序初始化事件池 */
    Q_REQUIRE((QF_maxPool_ == (uint8_t)0)
            || (QF_EPOOL_EVENT_SIZE_(QF_pool_[QF_maxPool_ - 1]) < evtSize));
    /* 执行初始化事件池 */
    QF_EPOOL_INIT_(QF_pool_[QF_maxPool_], poolSto, poolSize, evtSize);
    ++QF_maxPool_; /* 增加事件池*/
}

从事件池中分配事件的策略:

QEvent *QF_new_(QEventSize evtSize, QSignal sig) {
    QEvent *e;
    /* 找到最适合的池ID可以适应事件尺寸 */
    uint8_t idx = (uint8_t)0;
    while (evtSize > QF_EPOOL_EVENT_SIZE_(QF_pool_[idx])) {
        ++idx;
        Q_ASSERT(idx < QF_maxPool_); /* 不能超出初始化池的范围*/
    }
    QF_EPOOL_GET_(QF_pool_[idx], e); /*从找到的池中等到分配事件*/
    Q_ASSERT(e != (QEvent *)0); /* 如果不能正常分配,说明这个事件池已经枯竭*/
    e->sig = sig; /* 设置事件信号量 */
    /* 存储事件的动态属性:
    * 事件池的ID 和计数器为0
    */
    e->dynamic_ = (uint8_t)((idx + 1) << 6);
    return e;
}

垃圾回收:

void QF_gc(QEvent const *e) {
    if (e->dynamic_ != (uint8_t)0) { /* 判断是否静态事件 */
        QF_INT_LOCK_KEY_
        QF_INT_LOCK_();/*加锁*/
        if ((e->dynamic_ & 0x3F) > 1) { /* 是否累计计数器>1 */
            --((QEvent *)e)->dynamic_; /* 递减计算器*/
            QF_INT_UNLOCK_();  /*解锁*/
        }
        else { /* 这里处理最后一次事件的回收*/
            uint8_t idx = (uint8_t)((e->dynamic_ >> 6) - 1); /*得到事件池ID*/
            QF_INT_UNLOCK_();/*解锁*/
            Q_ASSERT(idx < QF_maxPool_); 
            QF_EPOOL_PUT_(QF_pool_[idx], (QEvent *)e); /* 把事件放回池中*/
        }
    }
}

事件延时和恢复

当事件在某个特别不方便的时刻到底时,事件的延迟是很方便的,但是可以被延迟一些时间直到系统有一个比较好的状态去处理这个事件。(将事件延时和恢复也做一个的队列(AO原生)???)下为延时和恢复的实现代码:

/* 事件延时*/
void QActive_defer(QActive *me, QEQueue *eq, QEvent const *e) {
    (void)me; 
    QEQueue_postFIFO(eq, e); /*把延迟的事件发送到给定的“原始”队列,这个事件发送将递增一个
                            动态事件的引用计数器,因此这个事件在当前RTC步骤结束后不会被回收*/
}

/* 事件恢复*/
QEvent const *QActive_recall(QActive *me, QEQueue *eq) {
    QEvent const *e = QEQueue_get(eq); /* 从延时队列中获得事件*/
    if (e != (QEvent *)0) { /* 事件是否有效 */
        QF_INT_LOCK_KEY_
        QActive_postLIFO(me, e); /* LIFO的策略发送给活动对象的事件队列*/
        QF_INT_LOCK_(); /*加锁*/
        if (e->dynamic_ != (uint8_t)0) { /* 是否动态事件 */
        /*在这一刻引用计数器必须最少是2 ,因为事件被最少2 个事件队列引用*/
            Q_ASSERT((e->dynamic_ & 0x3F) > 1);
            --((QEvent *)e)->dynamic_; /* 递减计数器*/
        }
        QF_INT_UNLOCK_();
    }
    return e;
}
2>.事件的派发

直接发送事件
过QActive_postFIFO()和QActive_postLIFO()函数支持直接事件发送

发行- 订阅事件发送
这种策略下的事件派发通过建立一张查找表找到某个事件的全部订阅对象,来快速发送事件。
查找表

在你可以发行任何事件前,需要初始化订阅者查找表。这部分函数的实现代码如下:

typedef struct QSubscrListTag {
    uint8_t bits[((QF_MAX_ACTIVE - 1) / 8) + 1];/*订阅对象列表*/
} QSubscrList;

QSubscrList *QF_subscrList_; 
QSignal QF_maxSignal_;

void QF_psInit(QSubscrList *subscrSto, QSignal maxSignal) {
    QF_subscrList_ = subscrSto;
    QF_maxSignal_ = maxSignal;
}

订阅信号函数(取消订阅过程类似):

void QActive_subscribe(QActive const *me, QSignal sig) {
    uint8_t p = me->prio; /* 获得优先级*/
    uint8_t i = Q_ROM_BYTE(QF_div8Lkup[p]); /*索引i 代表对多字节的字节索引,
                                        方式为QF_div8Lkup[p] = (p – 1)/8*/
    QF_INT_LOCK_KEY_
    Q_REQUIRE(((QSignal)Q_USER_SIG <= sig)
            && (sig < QF_maxSignal_)
            && ((uint8_t)0 < p) && (p <= (uint8_t)QF_MAX_ACTIVE)
            && (QF_active_[p] == me));
    QF_INT_LOCK_();
    QF_subscrList_[sig].bits[i] |= Q_ROM_BYTE(QF_pwr2Lkup[p]);/*在订阅者清单里,对应于活动对象优先级的bit被置位*/
    QF_INT_UNLOCK_();
}

事件发行函数必须确保,在这个事件的所有订阅者收到这个事件前,这个事件不会被某个订阅者回收。

void QF_publish(QEvent const *e) {
    QF_INT_LOCK_KEY_
    /* 确保发送的信号在配置范围内。*/
    Q_REQUIRE(e->sig < QF_maxSignal_);
    QF_INT_LOCK_();
    if (e->dynamic_ != (uint8_t)0) { /* 动态事件 */
        ++((QEvent *)e)->dynamic_; /* 增加计数器 */
    }
    QF_INT_UNLOCK_(); 
    #if (QF_MAX_ACTIVE <= 8)/* 使用条件编译区分单字节*/
    {
        uint8_t tmp = QF_subscrList_[e->sig].bits[0];
        while (tmp != (uint8_t)0) {
            uint8_t p = Q_ROM_BYTE(QF_log2Lkup[tmp]);
            tmp &= Q_ROM_BYTE(QF_invPwr2Lkup[p]); /* 清除subscriber位 */
            Q_ASSERT(QF_active_[p] != (QActive *)0); /* 断言必须被注册 */
            /* 如果队列溢出就使用内部断言 */
            QActive_postFIFO(QF_active_[p], e);
        }
    }
    #else
    {
        uint8_t i = Q_DIM(QF_subscrList_[0].bits);
        do { /* 遍历subscription 列表中的所有字节 */
            uint8_t tmp;
            --i;
            tmp = QF_subscrList_[e->sig].bits[i];
            while (tmp != (uint8_t)0) {
                uint8_t p = Q_ROM_BYTE(QF_log2Lkup[tmp]);
                tmp &= Q_ROM_BYTE(QF_invPwr2Lkup[p]);/* 清除 subscriber 位*/
                p = (uint8_t)(p + (i << 3)); /* 调整优先级*/
                Q_ASSERT(QF_active_[p] != (QActive *)0);/* 断言必须被注册 */
                /* 如果队列溢出就使用内部断言 */
                QActive_postFIFO(QF_active_[p], e);
            }
        } while (i != (uint8_t)0);
    }
    #endif
    QF_gc(e); /* 运行垃圾回收*/
}

4.时钟管理

(这部分的策略不是很了解)
在当前的版本里,时间事件不能是动态的,必须被静态的分配。
在时间事件中用户可自定义给时间事件添加更多成员(也就是自定义的信息)。
下面是时间结构体和操作它的函数:

typedef struct QTimeEvtTag {
    QEvent super; /* 继承时间事件*/
    struct QTimeEvtTag *prev;/* 链接到上一个时间事件*/
    struct QTimeEvtTag *next; /* 链接到下一个时间事件*/
    QActive *act; /* 时间事件的接收对象 */
    QTimeEvtCtr ctr; /* 内部时间递减器 */
    QTimeEvtCtr interval; /* 时钟周期时间 */
} QTimeEvt;

void QTimeEvt_ctor(QTimeEvt *me, QSignal sig);
#define QTimeEvt_postIn(me_, act_, nTicks_) do { \
                    (me_)->interval = (QTimeEvtCtr)0; \
                    QTimeEvt_arm_((me_), (act_), (nTicks_)); \
                    } while (0)

#define QTimeEvt_postEvery(me_, act_, nTicks_) do { \
                    (me_)->interval = (nTicks_); \
                    QTimeEvt_arm_((me_), (act_), (nTicks_)); \
                    } while (0)
uint8_t QTimeEvt_disarm(QTimeEvt *me);
uint8_t QTimeEvt_rearm(QTimeEvt *me, QTimeEvtCtr nTicks);

void QTimeEvt_arm_(QTimeEvt *me, QActive *act, QTimeEvtCtr nTicks);

系统时钟节拍和Tick()函数
为了管理时钟节拍需要加入Tick()函数。系统时钟节拍典型的运行速率是10到100Hz 。
这个函数被设计为可以从中断上下文调用。如果底层OS/RTOS不允许存取中断或者你想让ISR 非常短,也可以从任务层上下文调用。该函数不允许被嵌套和同是调用,应该仅被一个任务调用,理想地情况是被最高优先级任务调用。

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
qp官网下载qpc,移植到stm32f103单片机,在正点原子战舰V3开发板上实验成功 qp搭建步骤重要提示: #define RED_QUEUE_LEN 3 #define BLUE_QUEUE_LEN 3 #define TACKER_EVENT_POOL_LEN (RED_QUEUE_LEN + BLUE_QUEUE_LEN) static QEvt const * l_redQueueSto[RED_QUEUE_LEN]; //事件队列 static QEvt const * l_blueQueueSto[BLUE_QUEUE_LEN]; //事件队列 static LedEvt LedEvtPoolSto[TACKER_EVENT_POOL_LEN]; //事件池 static QSubscrList SubSrcSto[MAX_PUB_SIG]; //订阅列表 typedef struct LedEvtTag LedEvt; //定义事件结构 struct LedEvtTag{ QEvt super_; uint16_t uiParaH; uint16_t uiParaL; }; //定义信号枚举 enum LedSignals{ START_SIG = Q_USER_SIG, KEY0_SIG, KEY1_SIG, KEY2_SIG, KEYUP_SIG, ALL_OFF_SIG, ONLY_BULE_SIG, ONLY_RED_SIG, ALL_ON_SIG, MAX_PUB_SIG }; void PublishLedEvt(uint16_t uiSig, uint16_t uiParaH, uint16_t uiParaL) //发布事件函数 { LedEvt* peTacker; peTacker = Q_NEW(LedEvt, uiSig); peTacker->uiParaH = uiParaH; peTacker->uiParaL = uiParaL; QF_publish_((QEvt*)peTacker); } QF_init(); //时间事件列表、活动对象查找表、优先级集合 QF_psInit(SubSrcSto, Q_DIM(SubSrcSto)); //初始化事件池 QF_poolInit(LedEvtPoolSto,sizeof(LedEvtPoolSto),sizeof(LedEvtPoolSto[0])); RedLed_Start(uiPrio++, l_redQueueSto, Q_DIM(l_redQueueSto), 0, 0); //建立活动对象 BlueLed_Start(uiPrio++, l_blueQueueSto, Q_DIM(l_blueQueueSto), 0, 0); /////////////////////////////////////////////////// typedef struct RedActiveTag RedActive; //构建一个活动对象活动类型 struct RedActiveTag{ QActive super_; volatile uint16_t RedLedStateNow; uint16_t a; uint16_t b; }; extern RedActive RedLed; RedActive RedLed; void RedLed_Start(uint_fast8_t prio, QEvt const *qSto[], uint_fast16_t qLen, void *stkSto, uint_fast16_t stkSize) { RedLed_Ctor(&RedLed;); QActive_start_((QActive*)&RedLed;, prio, qSto, qLen, stkSto, stkSize, (QEvt const *)0); //创立活动对象的线程并提醒 QF 开始管理活动对象 } void RedLed_Ctor(RedActive* me) { QActive_ctor(&me;->super_, (QStateHandler)RedLed_Initial); //初始化状态机基础类(成员 super) me->RedLedStateNow = 0; }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值