errseq_t 数据类型¶
errseq_t 是一种在一个地方记录错误的方式,并允许任意数量的“订阅者”判断自上次采样点以来它是否发生了变化。
此功能的最初用例是跟踪文件同步系统调用(fsync、fdatasync、msync 和 sync_file_range)的错误,但它可能适用于其他情况。
它被实现为无符号 32 位值。低位用于保存错误代码(介于 1 和 MAX_ERRNO 之间)。高位用作计数器。这是通过原子操作而不是加锁来实现的,因此这些函数可以从任何上下文调用。
请注意,如果频繁记录新错误,则存在冲突的风险,因为我们只有很少的位可以用作计数器。
为了缓解这种情况,错误值和计数器之间的位被用作标志,用于指示自记录新值以来该值是否已被采样。 这使我们能够避免在自上次记录错误以来没有人对其进行采样的情况下增加计数器。
因此,我们最终得到的值如下所示
31..13 |
12 |
11..0 |
计数器 |
SF |
errno |
总的思路是让“观察者”采样一个 errseq_t 值并将其保留为运行光标。该值稍后可用于判断自完成采样以来是否发生了任何新错误,并原子地记录检查时的状态。这使我们能够在一个地方记录错误,然后让许多“观察者”能够判断自上次检查以来该值是否发生了变化。
新的 errseq_t 应始终归零。全部为零的 errseq_t 值是一种特殊(但常见)的情况,表示从未发生过错误。因此,如果有人想知道自首次初始化以来是否设置过错误,则全零值充当“纪元”。
API 用法¶
让我给你讲一个关于工作无人机的故事。总的来说,他是一个好工人,但公司有点...管理过度。他今天必须向 77 位主管汇报工作,而且明天“大老板”要从外地来,他肯定也会测试这个可怜的家伙。
他们都在给他布置工作——太多了他都无法追踪是谁布置的什么工作,但这并不是什么大问题。主管们只想知道他何时完成了他们迄今为止交给他的所有工作,以及自上次询问以来他是否犯了任何错误。
他可能在他们实际上没有交给他的工作上犯了错误,但他无法追踪到如此详细的程度,他只能记住他犯的最近的错误。
这是我们的 worker_drone 表示
struct worker_drone {
errseq_t wd_err; /* for recording errors */
};
每天,worker_drone 都从一张白纸开始
struct worker_drone wd;
wd.wd_err = (errseq_t)0;
主管们进来并获取当天的初始读数。他们不关心他们的观察开始之前发生的任何事情
struct supervisor {
errseq_t s_wd_err; /* private "cursor" for wd_err */
spinlock_t s_wd_err_lock; /* protects s_wd_err */
}
struct supervisor su;
su.s_wd_err = errseq_sample(&wd.wd_err);
spin_lock_init(&su.s_wd_err_lock);
现在他们开始给他布置任务。每隔几分钟,他们就会要求他完成他们迄今为止交给他的所有工作。然后他们问他是否在任何一项工作中犯了任何错误
spin_lock(&su.su_wd_err_lock);
err = errseq_check_and_advance(&wd.wd_err, &su.s_wd_err);
spin_unlock(&su.su_wd_err_lock);
到目前为止,这只会返回 0。
现在,这家公司的老板非常吝啬,给他的设备很差,无法完成他的工作。偶尔会发生故障,他会犯一个错误。他重重地叹了一口气,并将其记下来
errseq_set(&wd.wd_err, -EIO);
...然后回到工作岗位。主管们最终会再次轮询,他们每个人在下次检查时都会收到错误。后续调用将返回 0,直到记录另一个错误,此时会向他们每个人报告一次。
请注意,主管无法知道他犯了多少错误,只能知道自上次检查以来是否犯了一个错误,以及记录的最新值。
有时,大老板会来抽查,并要求工人为他完成一项一次性工作。他不像主管那样全职观察工人,但他确实需要知道在他的工作处理过程中是否发生了错误。
他可以只采样工人中的当前 errseq_t,然后用它来判断以后是否发生了错误
errseq_t since = errseq_sample(&wd.wd_err);
/* submit some work and wait for it to complete */
err = errseq_check(&wd.wd_err, since);
由于他只想在那之后丢弃“since”,因此他不需要在此处对其进行推进。他也不需要任何锁,因为它对其他任何人来说都不可用。
序列化 errseq_t 光标更新¶
请注意,errseq_t API 在 check_and_advance_operation 期间不保护 errseq_t 光标。只有规范错误代码是以原子方式处理的。在多个任务可能同时使用同一个 errseq_t 光标的情况下,务必对该光标的更新进行序列化。
如果未完成此操作,则光标可能会后退,在这种情况下,可能会多次报告同一错误。
因此,通常最好先执行 errseq_check 以查看是否发生了任何变化,然后在获取锁之后再执行 errseq_check_and_advance。例如
if (errseq_check(&wd.wd_err, READ_ONCE(su.s_wd_err)) {
/* su.s_wd_err is protected by s_wd_err_lock */
spin_lock(&su.s_wd_err_lock);
err = errseq_check_and_advance(&wd.wd_err, &su.s_wd_err);
spin_unlock(&su.s_wd_err_lock);
}
这避免了在自上次检查以来没有发生任何更改的常见情况下使用自旋锁。
函数¶
-
errseq_t errseq_set(errseq_t *eseq, int err)¶
设置 errseq_t 以供稍后报告
参数
errseq_t *eseq
应设置的 errseq_t 字段
int err
要设置的错误(必须介于 -1 和 -MAX_ERRNO 之间)
描述
此函数在 eseq 中设置错误,如果上次序列在过去某个时间点被采样,则递增序列计数器。
任何设置的错误都将始终覆盖现有错误。
返回
先前的值,主要用于调试目的。 返回值不应用作后续调用中先前采样的值,因为它不会设置 SEEN 标志。
-
errseq_t errseq_sample(errseq_t *eseq)¶
获取当前 errseq_t 值。
参数
errseq_t *eseq
要采样的 errseq_t 指针。
描述
此函数允许调用者初始化他们的 errseq_t 变量。如果错误已被“看到”,则新的调用者将不会看到旧的错误。如果 eseq 中存在未看到的错误,则此函数的调用者将在下次检查错误时看到它。
上下文
任何上下文。
返回
当前 errseq 值。
-
int errseq_check(errseq_t *eseq, errseq_t since)¶
自特定采样点以来是否发生了错误?
参数
errseq_t *eseq
要检查的 errseq_t 值指针。
errseq_t since
从中检查的先前采样的 errseq_t。
描述
获取 eseq 指向的值,并查看其是否自给定值被采样以来发生了更改。since 值不会前进,因此无需将该值标记为已看到。
返回
errseq_t 中设置的最新错误,如果未更改,则为 0。
-
int errseq_check_and_advance(errseq_t *eseq, errseq_t *since)¶
检查 errseq_t 并前进到当前值。
参数
errseq_t *eseq
指向正在检查和报告的值的指针。
errseq_t *since
指向要对照检查并前进的先前采样的 errseq_t 的指针。
描述
获取 eseq 值,并查看它是否与 since 指向的值匹配。如果匹配,则只返回 0。
如果不匹配,则表示该值已更改。设置“seen”标志,并尝试将其交换到位作为新的 eseq 值。然后,将该值设置为新的“since”值,并返回设置的任何错误部分。
请注意,此处未提供用于并发更新“since”值的锁。如果需要,调用者必须提供该锁。因此,调用者可能希望在获取锁并调用此函数之前执行无锁的 errseq_check。
返回
如果已存储,则返回负 errno,如果没有发生新错误,则返回 0。