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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容