一个用户崩溃时共享内存中的互斥锁?

Posted

技术标签:

【中文标题】一个用户崩溃时共享内存中的互斥锁?【英文标题】:Mutex in shared memory when one user crashes? 【发布时间】:2010-12-14 14:12:13 【问题描述】:

假设一个进程正在共享内存中创建一个互斥体并锁定它并在互斥体被锁定时转储核心。

现在在另一个进程中,我如何检测互斥锁已被锁定但不属于任何进程?

【问题讨论】:

【参考方案1】:

似乎已经以强大的互斥锁的形式提供了确切的答案。

根据 POSIX,可以使用 pthread_mutexattr_setrobust() 将 pthread 互斥锁初始化为“稳健”。如果持有互斥锁的进程死了,下一个获取它的线程将收到 EOWNERDEAD(但仍然成功获取互斥锁),以便它知道执行任何清理。然后它需要使用 pthread_mutex_consistent() 通知获取的互斥体再次一致。

显然,您需要内核和 libc 支持才能正常工作。在 Linux 上,这背后的内核支持称为“强大的 futexes”,我发现对用户空间更新的引用被应用于 glibc HEAD。

实际上,对此的支持似乎还没有被过滤掉,至少在 Linux 世界中是这样。如果这些函数不可用,您可能会在那里找到 pthread_mutexattr_setrobust_np() ,据我所知,它似乎是提供相同语义的非 POSIX 前身。我在 Solaris 文档和 Debian 上的 /usr/include/pthread.h 中都找到了对 pthread_mutexattr_setrobust_np() 的引用。

可以在此处找到 POSIX 规范:http://www.opengroup.org/onlinepubs/9699919799/functions/pthread_mutexattr_setrobust.html

【讨论】:

我认为这是一个更好的答案。到目前为止,我一直在 Solaris 上成功使用强大的互斥锁。 健壮的互斥锁非常棒,但请注意,如果互斥锁是在父进程中创建的,然后分叉并且子进程在持有互斥锁时死亡,它们可能无法在 glibc 2.15 之前的 GNU/Linux 上正常工作。 bug 在 glibc 2.15 中已修复。如果共享互斥体的两个进程不是通过分叉创建的父进程和子进程,那么健壮的互斥体即使在较旧的 glibc 版本中也能正常工作。【参考方案2】:

如果您在 Linux 或类似的环境中工作,请考虑使用 named semaphores 而不是(我假设是)pthreads 互斥锁。我认为没有办法确定 pthreads 互斥锁的锁定 PID,除了建立自己的注册表并将其放入共享内存中。

【讨论】:

总体上同意信号量的建议,但 POSIX 信号量并不能真正解决问题,因为它们也不会记录锁定过程的 PID,也不会在过早死亡时解锁。尽管它们可能是 SysV 信号量但生锈且笨拙,但它们确实会跟踪 PID,并且可以在使用 SEM_UNDO 选项调用时恢复。【参考方案3】:

基于文件的锁定(使用flock(2))怎么样?当持有它的进程死亡时,它们会自动释放。

演示程序:

#include <stdio.h>
#include <time.h>
#include <sys/file.h>

void main() 
  FILE * f = fopen("testfile", "w+");

  printf("pid=%u time=%u Getting lock\n", getpid(), time(NULL));
  flock(fileno(f), LOCK_EX);
  printf("pid=%u time=%u Got lock\n", getpid(), time(NULL));

  sleep(5);
  printf("pid=%u time=%u Crashing\n", getpid(), time(NULL));
  *(int *)NULL = 1;

输出(为了清楚起见,我稍微截断了 PID 和时间):

$ ./a.out & sleep 2 ; ./a.out 
[1] 15
pid=15 time=137 Getting lock
pid=15 time=137 Got lock
pid=17 time=139 Getting lock
pid=15 time=142 Crashing
pid=17 time=142 Got lock
pid=17 time=147 Crashing
[1]+  Segmentation fault      ./a.out
Segmentation fault

发生的情况是第一个程序获得了锁并开始休眠 5 秒。 2 秒后,启动程序的第二个实例,该实例在尝试获取锁时阻塞。 3 秒后,第一个程序出现段错误(不过 bash 直到稍后才告诉你),然后第二个程序立即获得锁并继续。

【讨论】:

我认为 taht 也不会被删除,因为无论是文件还是内存,两者都是一样的。 我的意思不是在文件中写一些东西(这确实是相似的),而是使用flock(2)。当您的进程终止时,文件将自动关闭,并且应该释放它的锁定。【参考方案4】:

只有当有人有相同的想法并且会发现这个关于使用的讨论时,我才会保留这个错误的帖子!


您可以使用这种方法。 1) 锁定 POSIX 共享互斥锁 2) 将 process-id 保存在共享内存中。 3) 解锁共享互斥锁 4) 在正确退出时清理进程 ID

如果进程 coredumps 下一个进程会发现在共享内存中有一个在第 2 步保存的进程 ID。如果操作系统中没有具有此进程 ID 的进程,则没有人拥有共享互斥锁。所以只需要替换process-id即可。

更新以回答评论:

场景 1: 1. P1开始 2. P1 创建/打开一个命名互斥体(如果它不存在) 3. P1 timed_locks 命名互斥体并成功完成(必要时等待 10 秒); 4. P1核心转储 5. P2 在 coredump 之后启动 6. P2创建/打开一个命名互斥体,存在,没关系 7. P2 timed_locks 命名互斥体,锁定失败(必要时等待10秒); 8. P2 移除命名互斥体 9. P2 重新创建一个命名互斥体并锁定它

【讨论】:

我在这里没有看到解决方案。场景一:(1)P1锁; (2) P1模具; (3)僵局。场景二:(1)P1锁; (2) P1写入pid; (3) P1解锁; (4) P2获得控制并锁定并找到P1 pid。场景 3:如果切换顺序以便在解锁之前清除 pid 并且进程死亡,那么您将回到原来的问题,即死进程持有锁并死锁其他进程。我错过了什么吗? 更新不可行。对任意时间的依赖是不好的。但更糟糕的是,如果有超过 1 个进程试图执行这个公式,那么在删除、重新创建、锁定等期间,互斥锁可能会崩溃。【参考方案5】:

您应该使用操作系统提供的信号量。

操作系统释放进程已打开的所有资源,无论它是终止还是正常退出。

【讨论】:

并非在所有资源中。如果 OP 按照建议使用 POSIX 信号量并且持有锁的进程死亡,则信号量的值将不会恢复,可能会导致其他进程死锁。

以上是关于一个用户崩溃时共享内存中的互斥锁?的主要内容,如果未能解决你的问题,请参考以下文章

硬件应用程序使用共享内存 (C++) 时需要互斥锁

仅读取共享内存时的互斥锁

如果只有一个线程使用互斥锁,跨线程的共享内存会损坏吗?

在共享内存中初始化 pthread 互斥锁

忙等待中的互斥

IPC:共享内存终止进程通知