Futex

姓名

futex - 用于创建用户空间同步工具的基元。

摘要

futex 是一种快速用户空间 muTEX。它是一个低级同步基元,也是高阶 API(如 pthread_mutex_tpthread_cond_t)的构建块。

Futexe 旨在避免在无争议的情况下进入内核或分配内核资源。

说明

zircon futex 实现目前支持通过 6 个系统调用分布的三项操作:

    zx_status_t zx_futex_wait(const zx_futex_t* value_ptr,
                              zx_futex_t current_value,
                              zx_handle_t new_futex_owner,
                              zx_time_t deadline);
    zx_status_t zx_futex_wake(const zx_futex_t* value_ptr, uint32_t wake_count);
    zx_status_t zx_futex_wake_single_owner(const zx_futex_t* value_ptr);
    zx_status_t zx_futex_requeue(const zx_futex_t* value_ptr,
                                 uint32_t wake_count,
                                 zx_futex_t current_value,
                                 const zx_futex_t* requeue_ptr,
                                 uint32_t requeue_count,
                                 zx_handle_t new_requeue_owner);
    zx_status_t zx_futex_requeue_single_owner(const zx_futex_t* value_ptr,
                                              zx_futex_t current_value,
                                              const zx_futex_t* requeue_ptr,
                                              uint32_t requeue_count,
                                              zx_handle_t new_requeue_owner);
    zx_status_t zx_futex_get_owner(const zx_futex_t* value_ptr, uint64_t* koid);

所有这些对象共用一个 value_ptr 参数,该参数是对齐的用户空间整数的虚拟地址。此虚拟地址是内核中用于跟踪给定线程在等待的 futex 信息。内核目前不会修改 *value_ptr 的值(但请参阅下文,了解可能会进行修改的未来操作)。用户空间代码应以原子方式跨线程正确修改此值,以便构建互斥量,等等。

请注意,使用地址标记时,用户空间指针并不总是会对内核中的 futex 实例进行一对一映射。已去除特定于架构的标记信息的地址将用于 futex ID。例如,在启用了 Top-Byte-Ignore (TBI) 的 ARM 上,值为 0x0A000000FF123450 的 futex 指针与值为 0x0B000000FF123450 的 futex 指针具有相同的 futex ID,这是因为虽然它们的标记(0x0A0x0B)不同,但其地址位是相同的。

如需了解详情,请参阅 zx_futex_wait()zx_futex_wake()zx_futex_requeue()zx_futex_get_owner() 手册页面。

权利

Futex 对象没有任何相关权利。

用户空间代码只能执行 2 项原始操作:等待和唤醒(重新排队是这两者的组合)。由于 futex 严格来说是进程局部概念,因此撤消对任何一种运算的访问权限都会使 futex 毫无价值。

此外,从内核的角度来看,Futex 是临时对象,其状态仅存在,而 futex 有等候程序。如果内核中没有更持久的状态,则几乎不可能有一个持久的权利概念。

与 Linux futexe 的区别

请注意,所有 zircon futex 操作键都会脱离用户空间指针的虚拟地址。这与 Linux 实现不同,Linux 实现将私有 futex 运算(对应于仅限进程内运算)与跨地址空间共享的运算区分开来。

如上所述,我们的所有 futex 运算都未从内核修改 futex 的值。其他潜在的操作(例如 Linux 的 FUTEX_WAKE_OP)需要从内核对值进行原子操作,而我们目前的实现并不要求这样做。

所有权和优先级继承

概览

某些运行时可能需要基于表现出优先级继承行为的 futex 实现同步基元。为了支持这些用户,zircon futex 有一个“所有权”概念,可用于实现此类基元。您可以自行决定是否使用此功能。

在任何时候,futex 既可能无主,也可能归单个线程所有。当某个线程拥有一个或多个 futex 时,其有效优先级将变为其基本优先级中的最大值,而其当前拥有的所有 futex 中当前的所有 waiter 的优先级也一样高。一旦某个线程不再拥有 futex,则 futex 的 Waiter 优先级的压力就会从上述关系中消失。一旦线程不再拥有任何 futexe,其优先级就会放回到其基准优先级。

指示 futex 所有者的信号由用户空间代码负责,就像在构建需要优先级继承行为的特定类型同步对象时正确应用所有权概念一样。

Zircon futexe 最多只能有一个所有者。不支持出于优先级继承的目的使用多个 futex。futex 的所有者不能同时担任同一 futex 的 Waiter。

指定所有权

futex 的所有权通过每个“wait”(等待)或“requeue”操作进行分配。对于重新排队操作,目标 futex 是重新排队 futex,而不是 wake_futex。用户将一个句柄传递给线程,以指明 futex 的当前所有者应该是谁,或者如果应该没有所有者,则为 ZX_HANDLE_INVALID

  • 将有效的句柄传递给线程以指明 futex 所有者是用户空间代码的责任。向非线程对象传递无效句柄或句柄将导致等待/重新排队操作失败。
  • 尚未开始的线程可能没有 futex。任何尝试将 futex 的所有权分配给尚未启动的线程都会导致等待/重新排队操作失败。
  • 退出的讨论帖可能不是 futex 的所有者。如果某个线程在拥有 futex 时退出,则 futex 将重置为无人拥有。如果用户尝试将 futex 的所有权分配给退出的线程,则等待/重新排队操作的行为就像已传递 ZX_HANDLE_INVALID 作为新的 futex 所有者一样。
  • 如果等待/重新排队操作成功,目标 futex 的所有者将始终设为指定的线程,或者在传递 ZX_HANDLE_INVALID 时不设置任何值。
  • 特别是,如果等待/重新排队操作因预期 futex 值与实际 futex 值不匹配而失败,则 futex 的所有者将保持不变,操作状态代码将为 ZX_ERR_BAD_STATE。无论为表示所有权的句柄传递的值为何,系统都将返回此错误代码,即使传递的值会导致返回 ZX_ERR_BAD_HANDLE 的状态也是如此。

转让所有权

在唤醒操作或重新排队操作期间,内核可以代表用户转移 futex 的所有权。对于重新排队操作,传输的目标是 wake_futex,而不是 requeue_futex。只有在使用唤醒/重新排队操作的 zx_futex_wake_single_owner()zx_futex_requeue_single_owner() 变体时,才会进行所有权转移。这些操作的 single_owner 变体将仅释放一个 waiter,并将 futex 的所有权分配给释放的线程。

  • 如果唤醒操作期间没有等待者,则表示不存在所有者。这项功能保持不变。
  • 如果重新排队操作由于预期 futex 值与实际 futex 值不匹配而失败,则 futex 的所有者将保持不变。
  • 如果成功调用唤醒/重新排队操作的任意非 single_owner 变体,都会导致将 target futex 的所有者设置为 nohing。

关于五彩足球的论文

  • Fuss、Futex 和 Furwocks:Linux 中的快速用户级锁定,Hubertus Franke 和 Rusty Russell

    这是描述 Linux futex 的原始白皮书。它记录了原始实现的历史和设计、之前(失败)尝试创建快速用户空间同步基元的尝试,以及性能测量结果。

  • Futexes Are Tricky,Ulrich Drepper

    此白皮书介绍了 Linux 中 futexe 的一些问题和实现细节。此外,其中还介绍了内核实现,并详细说明了互斥量、条件变量等的正确且高效的用户空间实现。

  • 使用 Futexe 的互斥和条件变量

    对“Futexe 很难处理”的进一步说明,概述了无需使用 FUTEX_CMP_REQUEUE 的简单实现

  • Locking in WebKit,Filip Pizlo

    深入介绍 WebKit 中的锁定基元,并完成基准和分析。包含对“停车区”概念的详细说明,以非常紧凑的方式表示用户空间互斥量。

系统调用