美文网首页
futex内核实现源码分析(3)

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

作者: 莒国书生 | 来源:发表于2017-05-07 14:16 被阅读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

    相关文章

      网友评论

          本文标题:futex内核实现源码分析(3)

          本文链接:https://www.haomeiwen.com/subject/pydytxtx.html