使用者角度看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);