我正在阅读本书中关于事件驱动编程的内容:
Practical UML Statecharts in C/C++, 2nd Edition: Event-Driven Programming for Embedded Systems
在页面上。 xxviii简介,作者说:
......事件驱动的应用程序必须在处理后返回控制 每个事件,所以执行上下文不能保留在 基于堆栈的变量和程序计数器,因为它是顺序的 程序。相反,事件驱动的应用程序成为一种状态 机器,或实际上是一组协作状态机 保留静态中从一个事件到下一个事件的上下文 变量
我无法理解为什么在处理事件后返回控件后,执行上下文无法在基于堆栈的变量和程序计数器中保留?
答案 0 :(得分:1)
让我们从传统顺序编程范例的工作原理开始。假设您想要使嵌入式电路板上的LED闪烁。一个常见的解决方案是编写这样的程序(例如,参见Arduino Blink tutorial):
while (1) { /* RTOS task or a "superloop" */
turn_LED_on(); /* turn the LED on (computation) */
delay(500); /* wait for 500 ms (polling or blocking) */
turn_LED_off(); /* turn the LED off (computation) */
delay(1000); /* wait for 1000 ms (polling or blocking) */
}
这里的关键点是delay()
函数,它会一直等待直到延迟过去。这种等待被称为“阻塞”,因为调用程序被阻塞,直到delay()
返回。
请注意,Blinky程序会在两个不同的上下文中调用delay()
:turn_LED_on()
之后的第一次和turn_LED_off()
之后的第二次。每次delay()
返回代码中的其他位置。这意味着当程序被阻止时,代码中的位置信息(调用的上下文)将自动保留。
琐碎的Blinky程序非常简单,但原则上可以从其他函数调用阻塞函数,如delay()
,
复杂的if-else-while代码。仍然,delay()
将能够返回到调用的确切位置,因为C编程语言保留了调用的上下文(在调用堆栈和程序计数器中)。
但阻止会使整个程序对任何其他事件都没有反应,因此人们想出了event-driven programming。
事件驱动程序围绕event-loop构建。示例事件驱动代码可能如下所示:
while (1) { /* event-loop */
Event *e = queue_get(); /* block when event queue is empty */
dispatch(e); /* handle the event, cannot block! */
}
重点是dispatch()
“事件处理程序”函数无法调用阻塞函数,如delay()
。相反,dispatch()
只能执行一些立即操作,必须快速返回回到事件循环。这样,事件循环始终保持响应。
但是,通过返回dispatch()
函数从调用堆栈中删除自己的堆栈帧。因此,与调用dispatch()
相关联的调用堆栈和程序计数器始终是相同的,并且无法“记住”执行上下文。
相反,要使LED闪烁,dispatch()
功能必须依赖于记住LED状态(开/关)的某个变量(state
)。您可以编写此类dispatch()
函数的示例如下:
static enum {OFF, ON } state = OFF; /* start in the OFF state */
timer_arm(1000); /* arm a timer to generate TIMEOUT event in 1000 ms */
void dispatch(Event *e) {
switch (state) {
case OFF:
if (e->sig == TIMEOUT) {
turn_LED_on();
timer_arm(500);
state = ON; /* transition to "ON" state */
}
break;
case ON:
if (e->sig == TIMEOUT) {
turn_LED_off();
timer_arm(1000);
state = OFF; /* transition to "OFF" state */
}
break;
}
}
我希望你能看到dispatch()
通过一个事件TIMEOUT驱动状态ON和OFF来实现state machine。