futex内核实现源码分析(3)

2017-05-07  本文已影响877人  莒国书生

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(……)。主要有以下几种情况:

  1. 当op挂起阻塞类型的操作时,用户传入的utime即阻塞的timeout,此时如果utime不为空,则将struct timespec类型转换为 ktime_t类型,然后传给do_futex(……)。
  2. 当op属于FUTEX_*_REQUEUE_*时,utime此时用来作为与uaddr进行条件判断的参数,则先将其转换为一个u32类型的值val2,然后传给do_futex(……),该参数即相较futex(……)多出的参数。
  3. 当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

上一篇下一篇

猜你喜欢

热点阅读