futex同步机制包括用户态的原子操作和内核态的futex系统调用两部分组成,其调用原型如下:
int futex (int *uaddr, int op, int val, const struct timespec *timeout,
int *uaddr2, int val3);
在futex系统调用内部是通过do_futex()完成具体操作
long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
u32 __user *uaddr2, u32 val2, u32 val3)
futex系统调用的参数很多,而do_futex的参数比futex还要多出一个来。这是由于同一个futex调用要根据不同的操作类型来完成不同的操作,而具体的操作所需的参数因目的不同而有所差异,每种具体的操作类型所需的参数数目以及具体参数的含义由其参数op决定。具体op操作类型的定义具体如下:
//最基本的挂起唤醒操作,将进程(线程)阻塞在uaddr所指向的futex变量上(仅当*uaddr==val)和
//唤醒阻塞在*uaddr所指向的futex变量上的val个进程(线程)
#define FUTEX_WAIT 0
#define FUTEX_WAKE 1
// 不清楚 ?
#define FUTEX_FD 2
//跟基本的唤醒操作类似,但不仅仅是唤醒val个等待在uaddr的进程(线程),
//而更进一步,将val3个等待uaddr的进程(线程)移到uaddr2的等待队列中
//(相当于先它们,然后强制让它们阻塞在uaddr2上面)
#define FUTEX_REQUEUE 3
//在FUTEX_REQUEUE的基础上,FUTEX_CMP_REQUEUE 多了一个判断,
//仅当*uaddr与val2相等时才执行操作,否则直接返回,让用户态去重试。
#define FUTEX_CMP_REQUEUE 4
//在这种操作类型中做了很多动作。它尝试在uaddr1的等待队列中唤醒val个进程,
//然后修改uaddr2的值,并且在uaddr2的值满足条件的情况下,唤醒uaddr2队列中的val2个进程。
//uaddr2的值如何修改?又需要满足什么样的条件才唤醒uaddr2?这些逻辑都pack在val3参数中。
#define FUTEX_WAKE_OP 5
//带优先级继承的futex锁操作
#define FUTEX_LOCK_PI 6
#define FUTEX_UNLOCK_PI 7
#define FUTEX_TRYLOCK_PI 8
//在基本的挂起唤醒操作基础上,额外使用一个bitset参数val3,
//使用特定bitset进行wait的进程,只能被使用它的bitset超集的wake调用所唤醒。
#define FUTEX_WAIT_BITSET 9
#define FUTEX_WAKE_BITSET 10
//FUTEX_WAIT_REQUEUE_PI是带优先级继承版本的FUTEX_WAIT_REQUEUE,
//FUTEX_WAIT_REQUEUE_PI是与之配套使用的,用于替代普通的FUTEX_WAIT
#define FUTEX_WAIT_REQUEUE_PI 11
#define FUTEX_CMP_REQUEUE_PI 12
具体的futex系统调用如下,在futex(……)中根据操作类型op对参数进行调整,然后调用do_futex(……)。主要有以下几种情况:
- 当op挂起阻塞类型的操作时,用户传入的utime即阻塞的timeout,此时如果utime不为空,则将struct timespec类型转换为 ktime_t类型,然后传给do_futex(……)。
- 当op属于FUTEX_*_REQUEUE_*时,utime此时用来作为与uaddr进行条件判断的参数,则先将其转换为一个u32类型的值val2,然后传给do_futex(……),该参数即相较futex(……)多出的参数。
- 当op的操作类型为FUTEX_WAKE_OP时,utime与情况2做同样的类型转换,但此时代表的含义是将要唤醒的进程数目。
linux/kernel/futex.c
SYSCALL_DEFINE6(futex, u32 __user *, uaddr, int, op, u32, val,
struct timespec __user *, utime, u32 __user *, uaddr2,
u32, val3)
{
struct timespec ts;
ktime_t t, *tp = NULL;
u32 val2 = 0;
int cmd = op & FUTEX_CMD_MASK;
if (utime && (cmd == FUTEX_WAIT || cmd == FUTEX_LOCK_PI ||
cmd == FUTEX_WAIT_BITSET ||
cmd == FUTEX_WAIT_REQUEUE_PI)) {
if (copy_from_user(&ts, utime, sizeof(ts)) != 0)
return -EFAULT;
if (!timespec_valid(&ts))
return -EINVAL;
t = timespec_to_ktime(ts);
if (cmd == FUTEX_WAIT)
t = ktime_add_safe(ktime_get(), t);
tp = &t;
}
/*
* requeue parameter in 'utime' if cmd == FUTEX_*_REQUEUE_*.
* number of waiters to wake in 'utime' if cmd == FUTEX_WAKE_OP.
*/
if (cmd == FUTEX_REQUEUE || cmd == FUTEX_CMP_REQUEUE ||
cmd == FUTEX_CMP_REQUEUE_PI || cmd == FUTEX_WAKE_OP)
val2 = (u32) (unsigned long) utime;
return do_futex(uaddr, op, val, tp, uaddr2, val2, val3);
}
在do_futex(……)中,主要根据op代表的具体操作类型进行不同分支的操作。例如FUTEX_WAIT执行futex_wait(uaddr, flags, val, timeout, val3),FUTEX_WAKE则执行futex_wake(uaddr, flags, val, val3),这是最基本futex阻塞唤醒操作。可以看到FUTEX_WAIT_BITSET和FUTEX_WAKE_BITSET最终调用的具体操作函数也是futex_wait(uaddr, flags, val, timeout, val3)和futex_wake(uaddr, flags, val, val3),只不过FUTEX_WAIT和FUTEX_WAKE在执行具体操作之前将bitset参数val3设置为全匹配。另外操作函数中flag参数指明该futex变量时进程间共享的还进程私有的,该参数具体值根据op的值设定。
linux/kernel/futex.c
long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,
u32 __user *uaddr2, u32 val2, u32 val3)
{
int cmd = op & FUTEX_CMD_MASK;
unsigned int flags = 0;
if (!(op & FUTEX_PRIVATE_FLAG))
flags |= FLAGS_SHARED;
if (op & FUTEX_CLOCK_REALTIME) {
flags |= FLAGS_CLOCKRT;
if (cmd != FUTEX_WAIT_BITSET && cmd != FUTEX_WAIT_REQUEUE_PI)
return -ENOSYS;
}
switch (cmd) {
case FUTEX_LOCK_PI:
case FUTEX_UNLOCK_PI:
case FUTEX_TRYLOCK_PI:
case FUTEX_WAIT_REQUEUE_PI:
case FUTEX_CMP_REQUEUE_PI:
if (!futex_cmpxchg_enabled)
return -ENOSYS;
}
switch (cmd) {
case FUTEX_WAIT:
val3 = FUTEX_BITSET_MATCH_ANY;
case FUTEX_WAIT_BITSET:
return futex_wait(uaddr, flags, val, timeout, val3);
case FUTEX_WAKE:
val3 = FUTEX_BITSET_MATCH_ANY;
case FUTEX_WAKE_BITSET:
return futex_wake(uaddr, flags, val, val3);
case FUTEX_REQUEUE:
return futex_requeue(uaddr, flags, uaddr2, val, val2, NULL, 0);
case FUTEX_CMP_REQUEUE:
return futex_requeue(uaddr, flags, uaddr2, val, val2, &val3, 0);
case FUTEX_WAKE_OP:
return futex_wake_op(uaddr, flags, uaddr2, val, val2, val3);
case FUTEX_LOCK_PI:
return futex_lock_pi(uaddr, flags, val, timeout, 0);
case FUTEX_UNLOCK_PI:
return futex_unlock_pi(uaddr, flags);
case FUTEX_TRYLOCK_PI:
return futex_lock_pi(uaddr, flags, 0, timeout, 1);
case FUTEX_WAIT_REQUEUE_PI:
val3 = FUTEX_BITSET_MATCH_ANY;
return futex_wait_requeue_pi(uaddr, flags, val, timeout, val3,
uaddr2);
case FUTEX_CMP_REQUEUE_PI:
return futex_requeue(uaddr, flags, uaddr2, val, val2, &val3, 1);
}
return -ENOSYS;
}
后面会主要对futex_wait(……)和futex_wake(……)进行详细分析。
参考:
http://www.tuicool.com/articles/feUR73
http://blog.csdn.net/jianchaolv/article/details/7544316
网友评论