gpt4 book ai didi

linux - 没有误报风险的高可用性计算 : How to deal with a non-returning system call,?

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:15:59 35 4
gpt4 key购买 nike

我有一个进程作为高可用性系统的一部分在 Linux 计算机上运行。该进程有一个主线程,它接收来自网络上其他计算机的请求并响应它们。还有一个心跳线程定期发送多播心跳数据包,让网络上的其他进程知道这个进程仍然存在并且可用——如果他们有一段时间没有收到来自它的任何心跳数据包,其中之一他们会假设这个进程已经死亡并接管它的职责,这样整个系统就可以继续工作。

这一切都运行良好,但前几天整个系统出现故障,当我调查原因时发现以下内容:

  1. 由于(显然是)机器的 Linux 内核中的错误,此进程的主线程进行的系统调用引发了内核“oops”。
  2. 由于内核“oops”,系统调用从未返回,导致进程的主线程永久挂起。
  3. 心跳线程 OTOH 继续正常运行,这意味着网络上的其他节点从未意识到该节点发生故障,并且没有一个节点介入接管其职责......因此请求的任务没有执行,系统的操作实际上停止了。

我的问题是,是否有一个优雅的解决方案可以处理此类故障? (显然,要做的一件事是修复 Linux 内核,这样它就不会“糟糕”,但考虑到 Linux 内核的复杂性,如果我的软件也能更优雅地处理 future 的其他内核错误,那就太好了。)

我不喜欢的一个解决方案是将心跳生成器放入主线程,而不是将其作为单独的线程运行,或者以其他方式将其绑定(bind)到主线程,这样如果主线程挂起无限期地向上,心跳将不会被发送。我不喜欢这个解决方案的原因是因为主线程不是实时线程,所以这样做会引入偶尔误报的可能性,即缓慢完成的操作被误认为是节点故障。如果可以的话,我想避免误报。

理想情况下,有一些方法可以确保失败的系统调用返回错误代码,或者如果这不可能,则让我的进程崩溃;其中任何一个都会停止心跳数据包的生成并允许进行故障转移。有什么办法可以做到这一点,或者不可靠的内核是否也会使我的用户进程也变得不可靠?

最佳答案

我的第二个建议是使用 ptrace 来查找当前指令指针。您可以有一个父线程跟踪您的进程并每秒中断它以检查当前的 RIP 值。这有点复杂,所以我写了一个演示程序:(仅限 x86_64,但应该可以通过更改寄存器名称来解决。)

#define _GNU_SOURCE
#include <unistd.h>
#include <sched.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/syscall.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <linux/ptrace.h>
#include <sys/user.h>
#include <time.h>

// this number is arbitrary - find a better one.
#define STACK_SIZE (1024 * 1024)

int main_thread(void *ptr) {
// "main" thread is now running under the monitor
printf("Hello from main!");
while (1) {
int c = getchar();
if (c == EOF) { break; }
nanosleep(&(struct timespec) {0, 200 * 1000 * 1000}, NULL);
putchar(c);
}
return 0;
}

int main(int argc, char *argv[]) {
void *vstack = malloc(STACK_SIZE);
pid_t v;
if (clone(main_thread, vstack + STACK_SIZE, CLONE_PARENT_SETTID | CLONE_FILES | CLONE_FS | CLONE_IO, NULL, &v) == -1) { // you'll want to check these flags
perror("failed to spawn child task");
return 3;
}
printf("Target: %d; %d\n", v, getpid());
long ptv = ptrace(PTRACE_SEIZE, v, NULL, NULL);
if (ptv == -1) {
perror("failed monitor sieze");
exit(1);
}
struct user_regs_struct regs;
fprintf(stderr, "beginning monitor...\n");
while (1) {
sleep(1);
long ptv = ptrace(PTRACE_INTERRUPT, v, NULL, NULL);
if (ptv == -1) {
perror("failed to interrupt main thread");
break;
}
int status;
if (waitpid(v, &status, __WCLONE) == -1) {
perror("target wait failed");
break;
}
if (!WIFSTOPPED(status)) { // this section is messy. do it better.
fputs("target wait went wrong", stderr);
break;
}
if ((status >> 8) != (SIGTRAP | PTRACE_EVENT_STOP << 8)) {
fputs("target wait went wrong (2)", stderr);
break;
}
ptv = ptrace(PTRACE_GETREGS, v, NULL, &regs);
if (ptv == -1) {
perror("failed to peek at registers of thread");
break;
}
fprintf(stderr, "%d -> RIP %x RSP %x\n", time(NULL), regs.rip, regs.rsp);
ptv = ptrace(PTRACE_CONT, v, NULL, NULL);
if (ptv == -1) {
perror("failed to resume main thread");
break;
}
}
return 2;
}

请注意,这不是生产质量代码。您需要做大量的修复工作。

据此,你应该能够判断出程序计数器是否在递增,并且可以将其与其他信息(例如/proc/PID/status)结合起来查找它是否正忙于系统调用。您还可以扩展 ptrace 的使用以检查正在使用的系统调用,这样您就可以检查等待的系统调用是否合理。

这是一个 hacky 解决方案,但我认为您不会找到解决此问题的非 hacky 解决方案。尽管有 hackiness,但我不认为(这是未经测试的)它会特别慢;我的实现每秒暂停一次被监视的线程,持续很短的时间——我猜这将在 100 微秒的范围内。从理论上讲,这大约是 0.01% 的效率损失。

关于linux - 没有误报风险的高可用性计算 : How to deal with a non-returning system call,?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30061396/

35 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com