Futex-4—Futex实现总结

发布时间 2023-04-20 21:34:11作者: Hello-World3

一、上层实现概述

1. 概述

在2.5.7版本的内核中引入,虽然名字中有互斥锁(mutex)的含义,但实际它是一种用于用户空间应用程序的通用同步工具(基于futex可以在userspace实现互斥锁、读写锁、condition variable等同步机制)。Futex组成包括:

(1) 内核空间的等待队列
(2) 用户空间层的32-bit futex word(所有平台都是32bit,包括64位平台)

在没有竞争的场景下,锁的获取和释放性能都非常高,不需要内核的参与,仅仅是通过用户空间的原子操作来修改futex word的状态即可。在有竞争的场景下,如果线程无法获取futex锁,那么把自己放入到 wait queue中(陷入内核,
有系统调用的开销)。而在用户空间的owner task释放锁的时候,如果检测到有竞争(等待队列中有阻塞任务),就会通过系统调用来唤醒等待队列中的任务,使其恢复执行,继续去持锁。如果没有竞争,那么也无需陷入内核。


2. 上层实现位置

art/runtime/base/mutex.cc,在 art/runtime/Android.bp 中编译为 libart.so。此库在Android机器中的位置:

/ # find / -name libart.so 2>/dev/null
/apex/com.android.art/lib64/libart.so
/apex/com.android.art/lib/libart.so
/apex/com.android.art@331314010/lib64/libart.so
/apex/com.android.art@331314010/lib/libart.so

3. trace其函数调用路径

root@localhost:/# bpftrace -l 'u:/apex/com.android.art/lib64/libart.so:*' | grep ExclusiveLock //查看libart库中所有可被探测的函数
root@localhost:/# bpftrace -e 'uprobe:/apex/com.android.art/lib64/libart.so:_ZN3art17ReaderWriterMutex13ExclusiveLockEPNS_6ThreadE {printf("tid%d, comm=%s, ustack: %s\n", tid, comm, ustack());}' //看其持锁调用栈

 

二、futex用户空间接口

1. futex接口函数原型

#include <linux/futex.h>
#include <sys/time.h>

int futex(int *uaddr, int futex_op, int val, const struct timespec *timeout, /* or: uint32_t val2 */ int *uaddr2, int val3);

//注意glibc没有对此系统调用进行封装,需要自己封装后使用,如下:
static int futex(int *uaddr, int futex_op, int val, const struct timespec *timeout, int *uaddr2, int val3)
{
    return syscall(SYS_futex, uaddr, futex_op, val, timeout, uaddr2, val3);
}

2. Futex系统调用的复杂性体现在其参数上,需要充分理解其参数

(1) uaddr: 表示 32bit Futex word 的地址。对于 normal futex, 内核并不关心 futex word 表示什么含义,对于 PI futex 内核空间和用户空间按照下面的方式解释 32bit Futex word:owner tid(lsb 30-bit), flag (msb 2-bit)

Futex word 等于0的时候表示空锁状态。当 owner tid 设置为具体 thread id 时表示持锁状态。如果有任务持锁失败进入等待队列,那么需要或一个 FUTEX_WAITERS 标志。

(2) futex_op: Futex op包括两部分,futex操作码(LSB 7-bit,最大支持128个op-code)和futex flag(其它bit)。futex系统调用支持各种各样的操作码,下面会详细介绍。futex flag用来定义futex操作码的行为,具体包括:
a. FUTEX_PRIVATE_FLAG: 如果futex word只在一个进程的不同线程间共享,此时它是 process-private 的。若是在进程间共享(放在共享内存里),则是 process-share 的。
b. FUTEX_CLOCK_REALTIME: 有些操作码,如 FUTEX_WAIT,会有 timeout 参数(val2) 用来定义阻塞的超时时间。这个bit用来指定 timeout 参数使用哪一个基准clock的,若设置了此bit则使用realtime clock,否则使用monotonic clock.

(3) val: 和 futex opcode 有关。

(4) timeout: 和 futex opcode 有关。

(5) uaddr2: 和 futex opcode 有关。

(6) val3: 和 futex opcode 有关。

futex系统调用支持各种各样的操作码(futex_op),如下:

a. FUTEX_WAIT:如果futex word(uaddr)中仍然保存着参数val给定的值(表示没有发生其它意外情况),那么当前线程则进入睡眠,等待 FUTEX_WAKE 的操作唤醒它。

b. FUTEX_WAKE:最多唤醒 val 个等待在 futex word(uaddr) 上的线程。val或者等于1(唤醒1个等待线程)或者等于 INT_MAX(唤醒全部等待线程)。

c. FUTEX_WAIT_BITSET:同 FUTEX_WAIT,只不过多提供一个 mask 的参数

d. FUTEX_WAKE_BITSET:同 FUTEX_WAKE,只不过多提供一个 mask 参数用来选择唤醒哪一个 waiter。

e. FUTEX_LOCK_PI:PI版本的 FUTEX_WAIT

f. FUTEX_UNLOCK_PI:PI版本的 FUTEX_WAKE

g. FUTEX_REQUEUE:这个操作包括唤醒和移动队列两个动作。唤醒 val 个等待在 uaddr 上的waiter,如果还有其他的waiter,那么将这些等待在 uaddr 的waiter转移到 uaddr2 的等待队列上去(最多转移 val2 个waiter)。

h. FUTEX_CMP_REQUEUE:同上,不过需要对比 val3 这个 uaddr 的期望值。

除了futex wait和wake这样的基本操作,futex还有其他应用在复杂场景的操作码,由于在手机场景没有使用,本文不再介绍。

我们整理各个操作码的参数如下: