【问题标题】:Race condition in glibc/NPTL/Linux robust mutexes?glibc/NPTL/Linux 健壮互斥锁中的竞争条件?
【发布时间】:2012-08-10 08:08:16
【问题描述】:

在 2010 年对问题 Automatically release mutex on crashes in Unix 的评论中,jilles 声称:

glibc 强大的互斥锁是如此之快,因为 glibc 采用了危险的捷径。当内核将其标记为“将导致 EOWNERDEAD”时,不能保证互斥锁仍然存在。如果互斥锁被销毁并且内存被一个内存映射文件替换,该文件恰好在正确的位置包含最后拥有线程的 ID,并且最后一个拥有线程在写入锁定字之后终止(但在从其列表中完全删除互斥锁之前)拥有的互斥体),文件已损坏。 Solaris 和即将成为 FreeBSD9 的健壮互斥体较慢,因为它们不想冒这个风险。

我无法理解这种说法,因为销毁互斥锁​​是不合法的,除非它被解锁(因此不在任何线程的健壮列表中)。我也找不到任何搜索此类错误/问题的参考。声明是否完全错误?

我问及感兴趣的原因是,这与我自己基于相同 Linux 健壮互斥原语构建的实现的正确性有关。

【问题讨论】:

  • omg,错误名称中有 NERD
  • 似乎旧的基于 VMA 的方法至少存在一些问题; kernel.org/doc/Documentation/robust-futexes.txt 。但是,如果我正确阅读它,该列表将保留在用户空间内存中 - 那么如果该内存已损坏,您该怎么办?虽然这可能只是被视为破坏共享内存的一种特殊情况。
  • 是的,我看到如果进程运行异常并破坏它们,列表甚至互斥体内容可能会损坏。这是描述的问题吗?当有权访问互斥锁的进程调用未定义的行为时,我不担心确保正确的行为;我只是担心在使用健壮的互斥锁时可能会出现一些竞争条件。
  • 我想是我自己想出来的,但我很乐意将赏金奖励给任何有兴趣提供有关该主题的更多信息的人。

标签: c linux pthreads


【解决方案1】:

我想我找到了比赛,而且确实很丑。它是这样的:

线程 A 持有健壮的互斥体并将其解锁。基本流程是:

  1. 将它放在线程健壮列表头的“待处理”槽中。
  2. 从当前线程持有的健壮互斥锁链表中删除它。
  3. 解锁互斥锁。
  4. 清除线程健壮列表头的“待处理”槽。

问题在于,在第 3 步和第 4 步之间,同一进程中的另一个线程可以获得互斥锁,然后将其解锁,并且(正确地)相信自己是互斥锁的最终用户,销毁并释放/munmap 它。之后,如果进程中的任何线程创建了文件、设备或共享内存的共享映射,并且它恰好被分配了相同的地址,并且该位置的值恰好与仍在步骤之间的线程的 pid 匹配在解锁的第 3 和第 4 步中,如果进程被杀死,内核将通过设置它认为是互斥锁所有者 ID 的 32 位整数的高位来破坏映射文件。

解决方案是在上述第 2 步和第 4 步之间对 mmap/munmap 进行全局锁定,这与我对这个问题的回答中描述的障碍问题的解决方案完全相同:

Can a correct fail-safe process-shared barrier be implemented on Linux?

【讨论】:

  • @r 我是否理解正确,您只能在编写自己的互斥锁实现并控制 mmap/munmap 调用时解决此问题(以便您可以插入第 2 步和第 4 步之间的锁)?这意味着 POSIX (pthreads) 健壮的互斥锁总是会遇到这个问题吗?这应该被认为是 Linux 上的 pthreads 错误吗?还有,当(健壮的还是非健壮的?)全局mmap锁的持有者失败了怎么办?
  • 是的,这是 glibc 中的一个错误。见sourceware.org/bugzilla/show_bug.cgi?id=14485
【解决方案2】:

FreeBSD pthread 开发者 David Xu 对比赛的描述:http://lists.freebsd.org/pipermail/svn-src-user/2010-November/003668.html

我不认为 munmap/mmap 循环对于比赛来说是严格要求的。这块共享内存也可能有不同的用途。这不常见但有效。

正如该消息中还提到的,如果具有不同权限的线程访问一个通用的健壮互斥体,则会出现更多“乐趣”。因为拥有的健壮互斥锁列表的节点位于互斥锁本身中,所以具有低权限的线程可能会破坏高权限线程的列表。这很容易被利用来使高权限线程崩溃,并且在极少数情况下,这可能会导致高权限线程的内存被破坏。显然,Linux 强大的互斥锁仅设计用于具有相同权限的线程。这可以通过使健壮列表成为完全在线程内存中的数组而不是链表来轻松避免。

【讨论】:

  • 感谢您发现有关将内存用于其他用途而不取消映射的问题。这应该可以通过在pthread_mutex_destroy...中放置某种同步来避免...
  • 关于你的最后一句话,使用数组而不是链表的“简单”解决方案实际上并不容易。您没有阵列的存储空间。存储需求随着线程持有的健壮互斥体的数量线性增长,并且在锁定时无需额外分配(可能会失败)获得该存储的唯一方法是将其放入互斥体本身。我同意这很不幸,但似乎没有其他办法。大多数内核空间解决方案还需要在锁定时分配,这可能会失败,从而使锁定变得无用......
  • 为了避免在内核中消耗过多的资源,Linux 无论如何都会限制链表的长度。因此,如果您锁定了太多强大的互斥锁,那么除了进程终止时的解锁之外,一切都正常。在这种情况下,失败pthread_mutex_lock 似乎更有意义。此外,鉴于必须已经检查了健壮互斥锁上的 pthread_mutex_lock 的返回值(对于 [EOWNERDEAD] 和 [ENOTRECOVERABLE]),我认为由于内存不足等原因,此操作失败是可以接受的。
  • 另外,假设您的程序可以恢复受保护对象的状态,EOWNERDEAD 是您可以处理的条件。 ENOMEM 或类似情况不是您可以处理的;如果程序无法获得锁或等待它的阻塞,则程序无法继续。请记住,pthread_mutex_lock 可以从取消处理程序中调用,其中需要将互斥锁锁定为下一级取消处理程序的不变量...
  • 接受了,我可能也会奖励这个答案。我认为我在解释比赛方面做得更好,但是您找到了您最初谈论的参考资料,这正是我正在寻找的内容,并且还发现了比赛的变体(不涉及取消映射),而不是我意识到的变体。
猜你喜欢
  • 2014-11-08
  • 2012-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多