告警¶
告警(alert) 是一个内核对象,当用户感兴趣的条件发生时,它允许应用程序执行一个异步信号。
概念¶
可以定义任意数量的告警。每个告警通过其内存地址进行引用。
告警的关键属性如下:
- 告警处理者:用于指明当捕捉到告警信号时应采取的动作。这个动作可能会指示系统工作队列去执行一个处理函数,将告警标记为挂起,以使其随后能被线程处理或忽略。
- 挂起计数:记录被挂起的、未被接收的告警数量。
- 计数界限:挂起计数的最大值。
告警必须先初始化再使用。初始化时会建立一个告警处理者,并将挂机计数设为零。
告警的生命周期¶
当某个感兴趣的但又不能被检测者处理的条件发生时,ISR 或者线程可以 发送 告警信号。
每次有告警被发出时,内核都会检查它的告警处理函数,以决定采取何种动作。
K_ALERT_IGNORE
将导致告警被忽略。K_ALERT_DEFAULT
将导致挂起的告警计数递增(除非计数已达上限)。其它任何值都会被认为是一个告警处理函数的地址,并会被系统工作队列线程调用。如果该函数返回零,则认为该信号已经被消费;否则,挂起的告警计数递增(除非计数已达上限)。
内核假设,每次告警被发送时,告警处理函数会被执行一次。(即使在连续的时间内告警被多次发送)。
内核可以 接收 告警而接受一个挂起的告警。如果当前的挂起计数是零,线程可以等待该告警被挂起。多个线程可以同时等待一个挂起告警;当告警被挂起时,它会被优先级最高的、等待时间最久的线程所接受。
注解
线程在同一个时刻只能处理一个挂起告警。线程在一个操作中不能接收多个挂起告警。
与 Unix 风格的信号的比较¶
Zephyr 的告警在某种程度上类似于 Unix 风格的信号,但是也有一些显著的区别,最主要包括:
- Zephyr 告警不能被锁定;它总是立即被传递给告警处理者。
- Zephyr 告警被传递给告警处理者 后 会处于挂起状态,直到告警处理函数消费了该告警。
- Zephyr 没有预定义的告警或行为。所有的告警都是由应用程序定义的,其默认行为是挂起告警。
实现¶
定义告警¶
使用类型为 struct k_alert
的变量可以定义告警。告警在被定义后必须使用函数 k_alert_init()
进行初始化。
下面的代码定义并初始化了一个告警。该告警在开始忽略新的挂起告警前,最多能够允许 10 个未接收的告警信号被挂起。
extern int my_alert_handler(struct k_alert *alert);
struct k_alert my_alert;
k_alert_init(&my_alert, my_alert_handler, 10);
也可以使用宏 K_ALERT_DEFINE
在编译时定义并初始化一个告警。
下面的代码与上面的代码段具有系统的效果。
extern int my_alert_handler(struct k_alert *alert);
K_ALERT_DEFINE(my_alert, my_alert_handler, 10);
发出告警¶
函数 k_alert_send()
可用于发出告警。
下面的代码展示了当有新的按键发生时 ISR 是如何发出告警信号的。
extern int my_alert_handler(struct k_alert *alert);
K_ALERT_DEFINE(my_alert, my_alert_handler);
void keypress_interrupt_handler(void *arg)
{
...
k_alert_send(&my_alert);
...
}
处理告警¶
当接收到的告警信号不能被忽略或者理解挂起时,内核会使用告警处理函数。告警处理函数的格式如下:
int <function_name>(struct k_alert *alert)
{
/* catch the alert signal; return zero if the signal is consumed, */
/* or non-zero to let the alert pend */
...
}
下面的代码描述了当 ISR 检测到按键被按下时的告警处理函数。
int my_alert_handler(struct k_alert *alert_id_is_unused)
{
/* determine what key was pressed */
char c = get_keypress();
/* do complex processing of the keystroke */
...
/* signalled alert has been consumed */
return 0;
}
接受告警¶
函数 k_alert_recv()
可用于让线程接受一个挂起的告警。
下面的代码是对上一节的功能的另一种实现形式。它使用一个专用的线程去做复杂的按键处理(否则会独占系统工作队列)。告警处理函数只用于过滤未知的按键告警,这样可以使专用线程去唤醒并处理已知按键的告警。
int my_alert_handler(struct k_alert *alert_id_is_unused)
{
/* determine what key was pressed */
char c = get_keypress();
/* signal thread only if key pressed was a digit */
if ((c >= '0') && (c <= '9')) {
/* save key press information */
...
/* signalled alert should be pended */
return 1;
} else {
/* signalled alert has been consumed */
return 0;
}
}
void keypress_thread(void *unused1, void *unused2, void *unused3)
{
/* consume numeric key presses */
while (1) {
/* wait for a key press alert to pend */
k_alert_recv(&my_alert, K_FOREVER);
/* process saved key press, which must be a digit */
...
}
}