futex 设计成用户空间快速锁操作,由用户空间实现fastpath,以及内核提供锁竞争排队仲裁服务,由用户空间使用futex系统调用来实现slowpath。futex系统调用提供了三种配对的调用接口,满足不同使用场合的,分别为noraml futex,pi-futex,以及 requeue-pi。

futex的同步(锁)状态定义由用户空间去执行,futex系统调用并不需要理解用户空间是如何定义和使用这个地址对齐的4字节长的整型的futex,但是pi-futex除外,用户空间必须使用futex系统调用定义的锁规则。用户空间通过总线锁原子访问这个整型futex,进行状态的修改,上锁,解锁和锁竞争等。当用户空间发现futex进入了某种定义需要排队服务的状态时,用户空间就需要使用futex系统调用进行排队,待排队唤醒后再回到用户空间再次进行上锁等操作。当锁竞争时,每次的Lock和Unlock,都必需先后进行用户空间的锁操作和futex系统调用,并且两步并非原子性执行的,Lock和Unlock的执行过程可能会发生乱序。

这是我们希望的

task A futex in user futex queue in kerenl task B
  owned empty 1. own futex
1.try lock (尝试修改futex)   empty  
2.mark waiter (发现锁竞争,修改futex状态) owned -> waiters empty  
3.futex_wait 0 empty 2. unlock (修改futex,得到旧状态为waiters)
4.       enqueue 0 has waiter 3. futex_wake (发现有锁竞争)
5.       sleep and schedule 0 has waiter 4.           dequeue
  0 empty 5.           wakeup
6.       wokenup 0 empty  

7.try lock again (被唤醒后,并不知道还有没有其它任务在等待,

所以锁竞争状态来上锁,以确保自己unlock时进行slowpath,

进行内核检查有没有其它等待的任务)

0 -> waiters empty  
8. own futex waiters empty  
9. unlock (approach to slowpath) waiters    

但是总会发生我们不希望的情况,虽然总线锁原子操作使得Lock和Unlock的用户空间阶段的操作以Lock为先,让futex进行锁竞争状态,使得Lock和Unlock都要进行slowpath。然而,在它们各自调用futex系统调用时,执行futex_wait的cpu被中断了,futex_wake先于futex_wait执行了。futex_wake发现没有可唤醒的任务就离开了。然后迟到的futex_wait却一无所知,毅然排队等待在一个已经释放的锁。这样一来,如果这个锁将来不发生锁竞争,那么task A就不会被唤醒而被遗忘。

task A futex in user futex queue in kerenl task B
  owned empty 1. own futex
1.try lock (尝试修改futex)   empty  
2.mark waiter (发现锁竞争,修改futex状态) owned | waiters empty  
3.futex_wait 0 empty 2. unlock (修改futex,得到旧状态为owned | waiters)
      interupted 0 empty 3. futex_wake (发现有锁竞争)
      interupted 0 empty 4.           quit
4.       enqueue 0 has waiter  
5.       sleep and schedule 0 has waiter  
       
       
       

所以需要进行排队等待的futex系统调用,都要求将futex当前的副本作为参数传入,futex系统调用在执行排队之前都通过副本和用户空间的futex最新值进行对比,决定是否要返回用户空间,让用户空间重新判断。对于pi-futex的futex_lock_pi系统调用操作入口,并不需要用户空间传入当前futex的副本,是因为用户空间必须使用由futex系统调用对pi-futex的锁规则,futex_lock_pi 函数则以pi-futex的锁规则来判断pi-futex是否被释放。当一个用户空间的futex遵照futex.h对pi-futex锁状态规则,并使用futex系统调用的futex_lock_pi和futex_unlock_pi操作,这个futex就是一个pi-futex。

futex系统调用配对的操作入口:

1. normal futex:

static int futex_wait(u32 __user *uaddr, unsigned int flags, u32 val, ktime_t *abs_time, u32 bitset)

static int futex_wake(u32 __user *uaddr, unsigned int flags, int nr_wake, u32 bitset)

2. pi-futex:

static int futex_lock_pi(u32 __user *uaddr, unsigned int flags, int detect, ktime_t *time, int trylock)

static int futex_unlock_pi(u32 __user *uaddr, unsigned int flags)

3. requeue-pi:

static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags, u32 val, ktime_t *abs_time, u32 bitset, u32 __user *uaddr2)

static int futex_requeue(u32 __user *uaddr1, unsigned int flags, u32 __user *uaddr2, int nr_wake, int nr_requeue, u32 *cmpval, int requeue_pi)

4. robust-futex:

SYSCALL_DEFINE3(get_robust_list, int, pid, struct robust_list_head __user * __user *, head_ptr, size_t __user *, len_ptr)

SYSCALL_DEFINE2(set_robust_list, struct robust_list_head __user *, head, size_t, len)

futex_wait 应用于non-pi futex,futex的规则由用户空间定义,要求用户空间将non-pi futex副本值传入来,过滤工作是由 futex_wait_setup子函数完成,再由 futex_wait_queue_me子函数进行non-pi futex的排队和睡眠等待。

futex_wait_requeue_pi 整合了对futex从non-pi到pi的requeue,以及non-pi到non-pi的requeue。但它首先是对non-pi的futex进行futex_wait,所以它和futex_wait 一样要求用户空间将non-pi futex副本值传入来。所以futex_wait_requeue_pi 如其名字一样,拆分成两个阶段,或者说组合了两种操作,futex_wait requeue_pi 。先进行futex_wait ,待被futex_requeue 唤醒后执行requeue_pi 。可以从代码看到futex_wait_requeue_pi 前半段和 futex_wait 代码流程是差不多的。

futex_lock_pi 应用于pi-futex,futex的规则由futex系统调用(头文件)定义,用户空间必须遵从规则来使用。由于规则是由内核定义的,并不要求用户空间传入一个futex当前副本,并且还会在内核中,在使用rt_mutex代理排队等待之前,进行 futex_lock_pi_atomic上锁的尝试,失败后才进入rt_mutex代理排队等待。待排队唤醒后,通过 fixup_owner 和 fixup_pi_state_owner 对用户空间的pi-futex进行上锁。这里有两点注意,rt_mutex的上锁规则是使用task_struct的指针标记,而pi-futex的上锁规则是使用pid(tid)号标记。另外pi-futex的排队也要注意,pi-futex虽然在rt_mutex代理上进行排队,但是还要像non-pi futex一样插入到futex_hash_bucket的链表中,为的不是排队,而是让后面进来的排队,可以在futex_hash_bucket中找出futex_queue,从而得到futex_pi_state(rt_mutex代理所在)。

futex的pi-support以及robust-support都跟task_struct偶合在一起。

robust futex 依赖的是进程(或线程)的task_struct结构体中的 robust_list 链表。futex的使用者(用户空间)通过(系统调用)将 用户空间维护的 robust_list 添加进内核中task_struct->robust_list。当进程(或线程)在退出的时候,内核可以遍历用户空间的robust_list链表,并对没有释放的robust futex进行recovery处理。

进程(或线程)在退出时,exit_mm -> mm_release 会调用futex的服务,exit_robust_list 去对用户空间使用的未释放的futex进行recovery处理 handle_futex_death。

对于pi-futex,它所使用的rtmutex代理也是会被恢复的,但不必经过robust_list,mm_release 会调用futex的服务 exit_pi_state_list 进行恢复处理。

对于pthread,当使用pthread_create创建线程时,同时会调用系统调用set_robust_list将内核的task_strust->robust_list,与用户空间的pthread维护的robust_list关联起来。

linux 内核的各种futex的更多相关文章

  1. linux 内核的futex pi-support,即pi-futex使用rt_mutex委托

    futex的pi-support,也就是为futex添加pi算法解决优先级逆转的能力,使用pi-support的futex又称为pi-futex.在linux内核的同步机制中,有一个pi算法的成例,就 ...

  2. linux内核级同步机制--futex

    在面试中关于多线程同步,你必须要思考的问题 一文中,我们知道glibc的pthread_cond_timedwait底层是用linux futex机制实现的. 理想的同步机制应该是没有锁冲突时在用户态 ...

  3. linux 内核的futex

    futex是linux内核为用户空间实现锁等同步机制而设计的同步排队(队列queueing)服务.在futex.c的注释中,futex起源于"Fast Userspace Mutex&quo ...

  4. 用Qemu模拟vexpress-a9 (三)--- 实现用u-boot引导Linux内核

    环境介绍 Win7 64 + Vmware 11 + ubuntu14.04 32 u-boot 版本:u-boot-2015-04 Linux kernel版本:linux-3.16.y busyb ...

  5. linux内核数据结构学习总结

    目录 . 进程相关数据结构 ) struct task_struct ) struct cred ) struct pid_link ) struct pid ) struct signal_stru ...

  6. 戴文的Linux内核专题:07内核配置(3)

    转自Linux中国 OK,我们还继续配置内核.还有更多功能等待着去配置. 下一个问题(Enable ELF core dumps (ELF_CORE))询问的是内核是否可以生成内核转储文件.这会使内核 ...

  7. linux内核编程笔记【原创】

    以下为本人学习笔记,如有转载请注明出处,谢谢 DEFINE_MUTEX(buzzer_mutex); mutex_lock(&buzzer_mutex); mutex_unlock(& ...

  8. Linux 内核综述

    一.什么是Linux内核: 内核->操作系统中最重要的部分,内核将在系统引导时被装载进RAM,其中包含了很多关键的例程,以操作系统.内核是OS最为关键的部分,人们常将OS(操作系统)与内核等同. ...

  9. 基于tiny4412的Linux内核移植(支持device tree)(三)

    作者信息 作者: 彭东林 邮箱:pengdonglin137@163.com QQ:405728433 平台简介 开发板:tiny4412ADK + S700 + 4GB Flash 要移植的内核版本 ...

随机推荐

  1. @ModelAttribute--SpringMVC 注解系列文章(二)

    概述 在日常的开发工作中,更新操作是经常会涉及到的一个功能,但是对于一条记录的更新,往往只会更新一部分的信息,而不会整条记录都更新,比如修改用户年龄的时候,用户密码是不需要修改的,针对这种情况,处理的 ...

  2. ie旋转滤镜Matrix

    旋转一个元素算是一个比较常见的需求了吧,在支持CSS3的浏览器中可以使用transform很容易地实现,这里有介绍:http://www.css88.com/archives/2168,这里有演示ht ...

  3. vmware克隆之后网卡起不来的问题

    问题: 克隆一台主机之后,改主机的网卡起不来,只有一个本地的回环地址网卡. 使用如下的命令都无效. /etc/init.d/network restart ifup eth0 原因: 这一vmware ...

  4. flex盒子里面元素linehight对高度的影响

    那天,高高兴兴的测试flex属性.大家都知道,当父盒子display:flex的时候,子盒子即使宽高和大于父盒子,也会由于flex的特性进行收缩:然而,在给父盒子加了flex-direction:co ...

  5. table切换

    // 自己加载正确路径的jQ <!doctype html> <html><head><meta charset="utf-8">& ...

  6. git常见操作和常见错误

    最近写了个博客demo,在上传至github时,居然报错了,刚开始学习代码上传,免不了遇到一些问题,报错信息如下: fatal: remote origin already exists. (致命错误 ...

  7. .NET Core 2.0及.NET Standard 2.0

    .NET Core 2.0的发布时间,.NET Core 2.0预览版及.NET Standard 2.0 Preview大概在5月中旬或下旬发布. .NET Core 2.0正式版本发布时间大约在Q ...

  8. POI框架实现创建Excel表、添加数据、读取数据

    public class TestPOI2Excel {//创建2003版本Excel用此方法 @Test public void testWrite03Excel() throws Exceptio ...

  9. 你说你精通CSS,真的吗?

    以前做项目的时候,学习了HTML和CSS,感觉这两个比较简单,在W3school里学习了一下之后,就觉得自己已经没问题了.可是,真正要做一个好看的页面,我还是要写好久.其实,对于CSS,我并没有像我以 ...

  10. File类遍历目录及文件

    1. 构造函数 File(String args0)//使用一个表示文件或目录的路径的字符串创建一个File对象 File(URL args0)//使用一个URL对象创建File对象 File(Fil ...