使用者角度看bionic pthread_mutex和linux futex实现

本文详细介绍了Android的Bionic库和Linux中futex的实现,分析了futex作为快速用户态互斥体如何减少系统调用开销。通过对比glibc和Bionic中pthread_mutex_lock()和pthread_mutex_unlock()的实现,讨论了futex在用户态和内核态的角色。此外,还探讨了不同类型的pthread_mutex,包括非递归锁、递归锁和错误检查锁,以及它们在内核中的处理流程。
摘要由CSDN通过智能技术生成

使用者角度看bionic pthread_mutex和linux futex实现

本文所大篇幅引用的参考文章主要描述针对glibc和pthread实现;而本文的考察代码主要是android的bionic和pthread实现。

1.   futex引入的意义

传统的SYSTEM V IPC机制需要系统调用进入内核态去操作某个内核对象,由内核来仲裁同步,事实上大部分情况下并没有资源竞争即多个申请者不会同时去竞争同步对象,此种情况下仍然进入内核态会显得很浪费,系统开销增加进而造成性能折扣。

Futex(Fast Userspace Mutex)快速用户态互斥体,它是一种由用户态和内核态共同完成的同步机制。创建时是在用户空间通过mmap申请一片共享内存以便多进程间共同访问此futex,用户程序首先访问用户态的futex,只在判断出存在冲突(如一个进程已经拥有此futex,另一个进程申请访问,此时便存在一个冲突)时才进入内核态进行仲裁同步。

用户空间的访问和冲突判断由glibc库完成,冲突仲裁由内核的futex模块完成。

应用判断:根据futex的类型从用户空间的futex_t字中获取futex状态,看futex是否已被占用;

内核仲裁:将用户态的锁置上等待标志表明有锁的等待者存在,并调用schedule()将锁申请者挂起;当锁的拥有者释放锁(由glibc库完成)时,检查发现该锁有等待者就进入内核将等待者唤醒。

 

2.   mutex使用概述

Glibc库中实现有pthread_mutex_lock()/pthread_mutex_unlock()等用户态锁接口,以提供快速的futex机制。

Bionic 的pthread实现中也提供标准的pthread_mutex_lock()/pthread_mutex_unlock()接口,其实现也使用linux futex机制。

Android Frameworks中经常使用如下代码做线程同步,

Mutex::Autolock lock(MutexClassInstance);

Class Mutex声明在frameworks/native/include/utils/Mutex.h中。

36/*

37 * Simple mutex class.  The implementation is system-dependent.

38 *

39 * The mutex must be unlocked by the thread that locked it.  They are not

40 * recursive, i.e. the same thread can't lock it multiple times.

41 */

42class Mutex {

43public:

44    enum {

45        PRIVATE = 0,

46        SHARED = 1

47    };

48

49                Mutex();

50                Mutex(const char* name);

51                Mutex(int type, const char* name = NULL);

52                ~Mutex();

53

54    // lock or unlock the mutex

55    status_t    lock();

56    void        unlock();

57

58    // lock if possible; returns 0 on success, error otherwise

59    status_t    tryLock();

60

61    // Manages the mutex automatically. It'll be locked when Autolock is

62    // constructed and released when Autolock goes out of scope.

63    class Autolock {

64    public:

65        inline Autolock(Mutex& mutex) : mLock(mutex)  { mLock.lock(); }

66        inline Autolock(Mutex* mutex) : mLock(*mutex) { mLock.lock(); }

67        inline ~Autolock() { mLock.unlock(); }

68    private:

69        Mutex& mLock;

70    };

71

72private:

73    friend class Condition;

74

75    // A mutex cannot be copied

76                Mutex(const Mutex&);

77    Mutex&      operator = (const Mutex&);

78

79#if defined(HAVE_PTHREADS)

80    pthread_mutex_t mMutex;

81#else

82    void    _init();

83    void*   mState;

84#endif

85};

 

可以看到在有pthread支持的时候,Class Mutex使用pthread_mutex实现。pthread_mutex声明在bionic/libc/include/pthread.h中,

40typedef struct

41{

42    int volatile value;

43} pthread_mutex_t;

 

因为同一线程组使用同一用户空间,从而组内各线程是可以直接访问value变量的,不需要前文所述的open(“/lock”)和mmap(fd)。

pthread_mutex的常用接口此处只列出下面几个,用途由函数名自解释。

164int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

166int pthread_mutex_destroy(pthread_mutex_t *mutex);

167int pthread_mutex_lock(pthread_mutex_t *mutex);

168int pthread_mutex_unlock(pthread_mutex_t *mutex);

pthread_mutex类型有三种,可以在pthread_mutex创建时设定,

{

…..

pthread_mutexattr_init(&attr);

pthread_mutexattr_settype(&attr, mutex_type)

pthread_mutex_init(&mutex, &attr);

…..

}

pthread_mutex的三种类型是,

53enum {

54    PTHREAD_MUTEX_NORMAL = 0,

55    PTHREAD_MUTEX_RECURSIVE = 1,

56    PTHREAD_MUTEX_ERRORCHECK = 2,

57

58    PTHREAD_MUTEX_ERRORCHECK_NP = PTHREAD_MUTEX_ERRORCHECK,

59    PTHREAD_MUTEX_RECURSIVE_NP  = PTHREAD_MUTEX_RECURSIVE,

60

61    PTHREAD_MUTEX_DEFAULT = PTHREAD_MUTEX_NORMAL

62};

其中为PTHREAD_MUTEX_NORMAL类型时,pthread_mutex_t::value中只含有mutex是否已被线程获取的状态;后两种会含有mutex的状态,占有mutex的线程tid,重获取次数等;详见pthread.h中的注释。此处关乎到的一个使用处是当程序出现线程同步问题时,想知道哪个线程占有mutex和哪些线程在等待mutex,因此需要加以说明。

对于mutex of PTHREAD_MUTEX_NORMAL只能知道其是否被占用和是否存在竞争,但是无法知道占有锁的线程id,无论从用户空间还是内核状态的相关变量,因为这个信息并没有被保存;从内核数据futex_queues hash table可以知道该线程组内有哪些线程阻塞在该mutex上,需要注意的一点是futex_queues是个hash table。

对于后两种类型的mutex,由pthread_mutex_t::value::bit_field_tid可以知道占有线程id,从内核数据futex_queues hash table可以知道该线程组内有哪些线程阻塞在该mutex上。

在bionic/libc/bionic/pthread.c中,pthread_mutex_lock()/pthread_mutex_unlock()分别使用pthread_mutex_lock_impl(pthread_mutex_t *mutex)/ pthread_mutex_unlock_impl(pthread_mutex_t *mutex)实现,其内部根据mutex类型做不同的分支实现。

#define  MUTEX_TYPE_BITS_NORMAL      MUTEX_TYPE_TO_BITS(MUTEX_TYPE_NORMAL)

#define  MUTEX_TYPE_BITS_RECURSIVE   MUTEX_TYPE_TO_BITS(MUTEX_TYPE_RECURSIVE)

#define  MUTEX_TYPE_BITS_ERRORCHECK  MUTEX_TYPE_TO_BITS(MUTEX_TYPE_ERRORCHECK)

 

对于MUTEX_TYPE_BITS_NORMAL,走_normal_lock/ _normal_unlock分支;对于后两种尝试获取/释放,增减重占有计数,交换状态;需要和内核交互时,最后都会执行到__futex_wait_ex/ __futex_wake_ex统一入口进行系统调用,

63int  __futex_wake_ex(volatile void *ftx, int pshared, int val)

64{

65    return __futex_syscall3(ftx, pshared ? FUTEX_WAKE : FUTEX_WAKE_PRIVATE, val);

66}

67

68int  __futex_wait_ex(volatile void *ftx, int pshared, int val, const struct timespec *timeout)

69{

70    return __futex_syscall4(ftx, pshared ? FUTEX_WAIT : FUTEX_WAIT_PRIVATE, val, timeout);

71}

详细流程见下节。

#define __NR_futex              240

sys_api int futex (int *uaddr, int op, int val, const struct timespec *timeout,int *uaddr2, int val3);

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值