share中主要包含了kylin中的公共代码组件,例如原子操作,互斥锁,信号量,内存分配器。
原子操作主要包含原子add, swap, compAndswap等,在内联汇编中,lock表示执行指令时锁总线(防止在执行该指令时内存被其他CPU的线程修改),memory则表示表示汇编语句可能修改了内存,如果有变量缓存在寄存器中,需要从内存中重新读取该变量
1 static inline int // return old value 2 atomic_add(volatile int *count, int add) 3 { 4 #ifdef __linux__ 5 __asm__ __volatile__( 6 "lock xadd %0, (%1);" 7 : "=a"(add) 8 : "r"(count), "a"(add) 9 : "memory" 10 ); 11 #else 12 #error "not done yet " 13 #endif 14 return add; 15 } 16 17 static inline int // return old value 18 atomic_swap(volatile void *lockword, int value) 19 { 20 #ifdef __linux__ 21 __asm__ __volatile__( 22 "xchg %0, (%1);" 23 : "=a"(value) 24 : "r"(lockword), "a"(value) 25 : "memory" 26 ); 27 #else 28 #error "not done yet " 29 #endif 30 return value; 31 } 32 33 static inline int // return old value 34 atomic_comp_swap(volatile void *lockword, 35 int exchange, 36 int comperand) 37 { 38 #ifdef __linux__ 39 __asm__ __volatile__( 40 "lock cmpxchg %1, (%2)" 41 :"=a"(comperand) 42 :"d"(exchange), "r"(lockword), "a"(comperand) 43 ); 44 #else 45 __asm { 46 mov eax, comperand 47 mov edx, exchange 48 mov ecx, lockword 49 lock cmpxchg [ecx], edx 50 mov comperand, eax 51 } 52 #endif /* #ifdef __linux__ */ 53 return comperand; 54 } 55 56 static inline int // return old value 57 atomic_comp_swap64(volatile void *lockword, 58 int64 exchange, 59 int64 comperand) 60 { 61 #ifdef __linux__ 62 __asm__ __volatile__( 63 "lock cmpxchg %1, (%2)" 64 :"=a"(comperand) 65 :"d"(exchange), "r"(lockword), "a"(comperand) 66 ); 67 #else 68 #error "not done yet!" 69 #endif /* #ifdef __linux__ */ 70 return comperand; 71 } 72 73 #define nop() __asm__ ("pause" ) 74 75 #ifdef __linux__ 76 #define mfence_c() __asm__ __volatile__ ("": : :"memory") 77 #else 78 #define mfence_c() _ReadWriteBarrier() 79 #endif 80 81 #define mfence_x86() __asm__ __volatile__ ("mfence": : :"memory") 82 // 封装的原子操作 83 #define AtomicGetValue(x) (atomic_comp_swap(&(x), 0, 0)) 84 #define AtomicSetValue(x, v) (atomic_swap(&(x), (v))) 85 #define AtomicSetValueIf(x, v, ifn) (atomic_comp_swap(&(x), (v), ifn)) 86 87 // not tested 88 //#define AtomicGetValue64(c) (atomic_comp_swap64(&(c), 0, 0)) 89 //#define AtomicSetValue64(c, n) (atomic_swap64(&(c), (n), (c))) 90 91 // return new value 92 #define AtomicDec(c) (atomic_add(&(c), -1) - 1) 93 #define AtomicInc(c) (atomic_add(&(c), 1) + 1)
kylin并没有使用POSIX提供的锁和信号量,而是使用futex实现了自己的锁和信号量。
int sys_futex (void *lock, int op, int val, const struct timespec *timeout);
1. futex提供了一种快速的,用户空间与内核空间协作的同步机制,其快不是因为调用sys_futex快,而是因为多数情况下可以不调用sys_futex。futex指的不(单)是sys_futex调用,futex是一种思想。
2. futex的使用包含两种操作:原子操作和sys_futex,操作的分别是一个资源变量(原子变量)一个内存地址(第一个参数)。在没有竞争的情况下,只需要进行原子操作,原子操作是不需要通过内核(系统调用)的,所以快。
a. 加锁(down):首先“原子地检查并递减”资源变量的值,如果检查的结果是没有被设置,那么返回操作成功。如果已经被设置,才需要调用sys_futex(FUTEX_WAIT)等待。
sys_futex(FUTEX_WAIT)的语义原子性的检查lock指向的值是否为val,如果是则让进程休眠,直到另一个线程调用sys_futex(FUTEX_WAKE)返回成功,或者超时返回失败。休眠的过程中,线程是被挂在lock相对应的等
待队列上的,这个等待对列是sys_futex(FUTEX_WAIT)调用时,由操作系统内核动态创建的。
b.解锁(up) 首先检查并递增资源变量,如果检查的结果是还有其他线程递减过,那就调用sys_futex(FUTEX_WAKE)后退出,否则直接退出。
3. lock指向的内存改变并不会导致唤醒操作,唤醒操作只能由sys_futex(FUTEX_WAKE)完成。
4. 同样sys_futex(FUTEX_WAIT)是否返回成功,和lock指向的内存也没有必然关系。只有在被sys_futex(FUTEX_WAKE)唤醒时,它才会返回0。超时返回ETIMEDOUT,被信号中断返回EINTR,val != *lock返回EWOULDBLOCK。
5. 在多数应用环境中,整个加锁和解锁过程中,lock指向的内存可以是一直不变的,lock提供的就是一个标识内核队列的功能。同样原因,虽然lock指向的内存可以在使用过程中一直不变,但我们要保护多个资源变量时,要用多个lock,而不能将lock指向一个全局的lock变量。
1 struct futex { 2 volatile int lock; 3 volatile int count; // 共享资源的数量 4 }; 5 6 static inline void 7 futex_init(struct futex* pf, int count) 8 { 9 pf->lock = 0; 10 pf->count = count; 11 }
获取共享资源 futex_sema_down,如果资源不足则进入等待状态
1 /* Return value: 2 * 0: okay 3 * ETIMEDOUT: timeout 4 * EINTR: interrupted 5 */ 6 static inline int 7 futex_sema_down(struct futex* pf, struct timespec* timeout, bool interruptable) 8 {
// step1 :资源数量减一 9 int n = atomic_add(&pf->count, -1); 10 if (n <= 0) { 11 retry:
// step2 : 如果原本资源数量不够,则等待其他线程释放资源 12 if (0 == sys_futex(&pf->lock, FUTEX_WAIT, 0, timeout)) { 13 return 0; 14 } 15 // step3 : 如果返回出错,则判断出错类型 16 switch (errno) { 17 case ETIMEDOUT: 18 atomic_add(&pf->count, 1); 19 return ETIMEDOUT; 20 case EINTR: 21 if (!interruptable) 22 goto retry; 23 atomic_add(&pf->count, 1); 24 return EINTR; 25 default: 26 RaiseError(IMPOSSIBLE__Can_not_lock_in_futex_sema_down); 27 } 28 } 29 return 0; 30 }
释放共享资源并唤醒等待的线程 futex_sema_up
1 /* Return value: 2 * 1: wake up some waiter 3 * 0: none is waiting 4 */ 5 static inline int 6 futex_sema_up(struct futex* pf) 7 { 8 int retry;
// step1 : 增加共享资源 9 int n = atomic_add(&pf->count, 1); 10 if (n < 0) { 11 retry = 10; 12 // step2 : sys_futex(FUTEX_WAKE) 将唤醒第三个参数数量的等待线程,这里表示它将唤醒一个等待线程,返回唤醒的等待线程数量 13 while (1 != (n=sys_futex(&pf->lock, FUTEX_WAKE, 1, NULL))) { 14 /* it means the downer decreases the count but not yet start waiting 15 * --- may be interrupted near the retry label in the above function; 16 * so we have to wait and retry.
* 如果返回值不等于1,则说明等待线程被中断或者还没开始等待,这时需要重试 17 */ 18 if (retry --) { 19 nop(); 20 } 21 else { 22 // step3 : 如果重试了十次还是没有唤醒,则当前线程放弃CPU 23 retry = 10; 24 thread_yield(); 25 } 26 } 27 return n; 28 } 29 return 0; 30 }
等待条件的满足,并且循环判断
1 /* Return value: 2 * 0: okay 3 * ETIMEDOUT: timeout 4 * EINTR: interrupted 5 */ 6 /* 7 * Cond根据count判断等待唤醒,count为0表示条件得到满足,不再等待 8 */ 9 static inline int 10 futex_cond_wait(struct futex* pf, struct timespec* timeout, bool interruptable) 11 { 12 /* I dont know whether it is a bug of linux kernel. 13 * Sometimes, sys_futex(.., FUTEX_WAIT, ..) returns 0, but the condition is not satisfied. 14 * So we have to check the condition again after return. 15 */ 16 while (0 != AtomicGetValue(pf->count)) { 17 sys_futex(&pf->lock, FUTEX_WAIT, 0, timeout); 18 19 switch (errno) { 20 case ETIMEDOUT: 21 return ETIMEDOUT; 22 case EINTR: 23 if (interruptable) { 24 return EINTR; 25 } 26 default: 27 break; 28 } 29 } 30 return 0; 31 }
唤醒条件变量
1 static inline int 2 futex_cond_signal(struct futex* pf) 3 { 4 // atomic_add获取的是操作前的数值 5 int n = atomic_add(&pf->count, -1); 6 if (1 == n) {
// step1 : 手动将lock置为1(从而让sys_futex(FUTEX_WAIT)不再等待),然后使用一个内存屏障将lock的新值从缓存刷新到内存 7 pf->lock = 1; 8 mfence_c();
// step2 : 唤醒65535个等待的线程 9 return sys_futex(&pf->lock, FUTEX_WAKE, 65535, NULL); // I hope 65535 is enough to wake up all 10 } 11 return 0; 12 }
事件等待被触发,当pf->count大于等于0时,pf->count 表示等待事件发生的线程数量
1 /* Return value: 2 * 0: okay 3 * ETIMEDOUT: timeout 4 * EINTR: interrupted 5 */ 6 static inline int 7 futex_event_wait(struct futex* pf, struct timespec* timeout, bool interruptable) 8 {
// 返回增加之前的值 9 int n = atomic_add(&pf->count, 1); 10 // n >= 0,则表明没有被事件没有被触发 11 if (0 <= n) { 12 retry:
// step1 : 事件没有被触发,等待 13 if (0 == sys_futex(&pf->lock, FUTEX_WAIT, 0, timeout)) 14 return 0; 15 16 switch (errno) { 17 case ETIMEDOUT: 18 atomic_add(&pf->count, -1); 19 return ETIMEDOUT; 20 case EINTR: 21 if (!interruptable) 22 goto retry; 23 atomic_add(&pf->count, -1); 24 return EINTR; 25 default: 26 RaiseError(IMPOSSIBLE__Can_not_lock_in_futex_sema_down); 27 } 28 } 29 // 事件已被触发过了,将pf->count置为一个非常小的负值 30 else { // else signaled 31 AtomicSetValue(pf->count, LARGE_ENOUGH_NEGATIVE); 32 } 33 return 0; 34 }
触发事件,根据参数决定是否重置事件
1 static inline int 2 futex_event_signal(struct futex* pf, bool reset) 3 { 4 int m, n, retry; 5 // 如果重置事件,则将pf->count设置为初始值0,否则置为一个足够小的负数 6 n = AtomicSetValue(pf->count, reset ? 0 : LARGE_ENOUGH_NEGATIVE); 7 if (0 < n) {
// n > 0时,pf->count表示等待事件发生的线程数量 8 retry = 10; 9 m = n; 10 do { 11 n -= sys_futex(&pf->lock, FUTEX_WAKE, n, NULL); 12 if (0 == n) 13 return m; 14 if (retry --) { 15 nop(); 16 } 17 else { 18 retry = 10; 19 thread_yield(); 20 } 21 } while (1); 22 } 23 return 0; 24 }
kylin中使用rdtsc来实现高精度的计时器,利用rdtsc指令获取时钟周期数,除以CPU频率,就得出消耗的时间
1 static inline uint64 2 rdtsc() 3 { 4 unsigned int lo, hi; 5 #ifdef __linux__ 6 /* We cannot use "=A", since this would use %rax on x86_64 */ 7 __asm__ __volatile__ ( 8 "rdtsc" 9 : "=a" (lo), "=d" (hi) 10 ); 11 #else 12 #error "not done yet " 13 #endif 14 return (uint64)hi << 32 | lo; 15 }
获取CPU频率,从/proc/cpuinfo中读取
1 static uint64 2 CalcCpuFreq() 3 { 4 uint64 t1; 5 uint64 t2; 6 7 t1 = rdtsc(); 8 Sleep(100); 9 t2 = rdtsc(); 10 return (t2-t1)*10; 11 } 12 13 uint64 GetCpuFreq() 14 { 15 static uint64 freq = 0; 16 char buf[1024], *p[2]; 17 18 if (0 != freq) { 19 return freq; 20 } 21 22 FILE* fp = fopen("/proc/cpuinfo", "rb"); 23 if (fp != NULL) { 24 while (fgets(buf, sizeof(buf), fp) != NULL) { 25 if (2==SplitString(buf, ':', p, 2) && 0==strnicmp(p[0], "cpu MHz", 7)) { 26 double f = strtod(p[1], NULL); 27 freq = (uint64)(f * 1000000.0); 28 //printf("freq=%u\n", freq); 29 break; 30 } 31 } 32 fclose(fp); 33 } 34 if (0 == freq) { 35 freq = CalcCpuFreq(); 36 } 37 return freq; 38 }
特别需要说明的是,陈硕大牛指出在多核时代,不适合再用rdtsc指令测量时间,理由有三: 1. 多核时代,每个核的TSC可能不同; 2. CPU的时钟频率在不同的时间和负载情况下可能会变;3. 指令的乱序执行可能导致测量不准确。建议使用POSIX的 clock_gettime 函数,以 CLOCK_MONOTONIC 参数调用。
1 /* Compile read-write barrier */ 2 #define barrier() asm volatile("": : :"memory") 3 4 /* Pause instruction to prevent excess processor bus usage */ 5 #define cpu_relax() asm volatile("pause\n": : :"memory") 6 7 static inline void 8 spin_lock(volatile int *lock) 9 { 10 while (1) 11 { 12 int i = 0;
// step1 : atomic_swap将lock设置为EBUSY,并返回设置之前的值,如果之前为0,则表明lock未被其他线程锁住,否则需要等待其他线程释放lock(将lock设置为0) 13 if (!atomic_swap(lock, EBUSY)) return; 14 while (*lock) { 15 i++;
// step2 : 自旋4000次其他线程还没有释放锁,则让出CPU 16 if (i == 4000) { 17 i = 0; 18 thread_yield(); 19 } 20 cpu_relax(); 21 }
// step3 : 当运行到这里的时候,表明别的线程释放了锁(lock == 0),回到最外层循环,重新尝试使用atomic_swap获取锁 22 } 23 } 24 25 static inline void 26 spin_unlock(volatile int *lock) 27 {
// 在给lock赋值为0之前加上编译期屏障 28 barrier(); 29 *lock = 0; 30 }
spinlock只使用了编译期内存屏障防止编译期指令重排列,而没有使用运行时CPU内存屏障,因为x86(包括x64)CPU采用强内存模型,只允许Store-Load重排列,不允许Load-Load,Load-Store,Store-Store重排列,所以对于
*lock = 0来说,前面的所有内存读写操作都不会被CPU重排列到*lock = 0之后执行,自然就不需要CPU的内存屏障。
在spinlock的下面,实现了令牌Token,它的语义很简单,如果token !=0 ,则表明令牌被其他对象获取,这些对象可以是pid或tid等。
1 static inline int 2 token_acquire(volatile int *token, int id) 3 { 4 return atomic_comp_swap(token, id, 0); 5 } 6 7 static inline int 8 token_release(volatile int *token, int id) 9 { 10 return atomic_comp_swap(token, 0, id); 11 } 12 13 static inline int 14 token_transfer(volatile int *token, int oldid, int newid) 15 { 16 return atomic_comp_swap(token, newid, oldid); 17 } 18 19 static inline int 20 token_set(volatile int *token, int id) 21 { 22 return atomic_comp_swap(token, id, *token); 23 }
kylin中封装的信号量,条件变量和事件基于前面的futex,对它们做了简单的封装
1 #define INFINITE ((uint32)-1) 2 // 信号量 3 class CSema 4 { 5 public: 6 // 初始化为0 7 CSema() { m_pName=NULL; Init(0); } 8 CSema(int count) { m_pName=NULL; Init(count); } 9 ~CSema() {} 10 11 int Down() { 12 return futex_sema_down(&m_futex, NULL, false); 13 } 14 int Up() { 15 return futex_sema_up(&m_futex); 16 } 17 int Count() { 18 return AtomicGetValue(m_futex.count); 19 } 20 void Init(int count) { 21 ASSERT(count >= 0); 22 futex_init(&m_futex, count); 23 } 24 void SetName(char* pName) { m_pName = pName; } 25 26 void PrintState() { TRACE0("Futex: %d, %d\n", m_futex.count, m_futex.lock); } 27 private: 28 struct futex m_futex; 29 char* m_pName; 30 };
31 // 条件变量 32 class CCond 33 { 34 public: 35 // 初始为1 36 CCond() { m_pName=NULL; Init(1); } 37 CCond(int count) { m_pName=NULL; Init(count); } 38 ~CCond() {} 39 40 int Wait(uint32 nMilliseconds=INFINITE) { 41 if (INFINITE == nMilliseconds) { 42 return futex_cond_wait(&m_futex, NULL, false); 43 } 44 else { 45 timespec ts; 46 ts.tv_sec = nMilliseconds / 1000; 47 ts.tv_nsec = (nMilliseconds % 1000) * 1000000; 48 return futex_cond_wait(&m_futex, &ts, false); 49 } 50 } 51 int Signal() { 52 return futex_cond_signal(&m_futex); 53 } 54 void Init(int count=1) { 55 ASSERT(count > 0); 56 futex_init(&m_futex, count); 57 } 58 void SetName(char* pName) { m_pName = pName; } 59 //private: 60 struct futex m_futex; 61 char* m_pName; 62 }; 63
// 事件 64 class CEvent 65 { 66 public: 67 CEvent() { Init(false, true); } 68 ~CEvent() {} 69 70 void Init(bool bSignaled=false, bool bAutoReset=true) { 71 futex_init(&m_futex, bSignaled ? LARGE_ENOUGH_NEGATIVE : 0); 72 m_bAutoReset = bAutoReset; 73 } 74 75 int Wait() { 76 return futex_event_wait(&m_futex, NULL, false); 77 } 78 79 int Signal() { 80 return futex_event_signal(&m_futex, m_bAutoReset); 81 } 82 83 void Reset() { 84 futex_event_reset(&m_futex); 85 } 86 87 void SetName(char* pName) { m_pName = pName; } 88 private: 89 struct futex m_futex; 90 bool m_bAutoReset; 91 char* m_pName; 92 };
mutex,spinlock,readwrite-lock
1 class CSpinLock 2 { 3 volatile int m_lock; 4 public: 5 CSpinLock() { Init(); } 6 ~CSpinLock() { } 7 8 void Init() { m_lock=0; } 9 void Lock() { spin_lock(&m_lock); } 10 void Unlock() { spin_unlock(&m_lock); } 11 }; 12 13 class CMutex 14 { 15 public: 16 CMutex() { Init(); } 17 ~CMutex() {} 18 19 int Lock() { 20 return futex_sema_down(&m_futex, NULL, false); 21 } 22 int Unlock() { 23 return futex_sema_up(&m_futex); 24 } 25 void Init() {
// 互斥量的共享资源数量为1 26 futex_init(&m_futex, 1); 27 } 28 bool TryLock() { return false; } // TODO:: 29 private: 30 struct futex m_futex; 31 char* m_pName; 32 }; 33 34 class CToken 35 { 36 volatile int m_token; 37 public: 38 CToken() { Init(); } 39 ~CToken() { } 40 41 void Init(int id=0) { m_token=id; } 42 int Release(int id) { return token_release(&m_token, id); } 43 int Check() { return AtomicGetValue(m_token); } 44 int Transfer(int oldid, int newid) { return token_transfer(&m_token, oldid, newid); } 45 46 void Acquire1(int id) { 47 for (int i=0; 0!=token_acquire(&m_token, id); i++) { 48 if (i < 10) { 49 thread_yield(); 50 } 51 else { 52 i = 0; 53 Sleep(100); 54 } 55 } 56 } 57 bool TryAcquire(int id) { return 0==token_acquire(&m_token, id); } 58 bool TryAcquire(int id, int* pnOld) { 59 int old = token_acquire(&m_token, id); 60 if (pnOld != NULL) { 61 *pnOld = old; 62 } 63 return 0==old; 64 } 65 66 int Add() { return atomic_add(&m_token, 1); } 67 int Set(int id) { return atomic_swap(&m_token, id); } 68 }; 69 70 class CLockedInt 71 { 72 volatile int m_int; 73 public: 74 CLockedInt() { Init(); } 75 ~CLockedInt() { } 76 77 void Init(int n=0) { m_int=n; } 78 int Get() { return AtomicGetValue(m_int); } 79 int Set(int n) { return atomic_swap(&m_int, n); } 80 int SetIf(int ifm, int n) { return atomic_comp_swap(&m_int, n, ifm); } 81 int Add() { return atomic_add(&m_int, 1); } 82 }; 83 84 class CCounter 85 { 86 volatile int m_count; 87 public: 88 CCounter() { Init(0); } 89 ~CCounter() { } 90 91 void Init(int count) { m_count=count; } 92 CCounter& operator++() { atomic_add(&m_count, 1); return *this; } 93 CCounter& operator--() { atomic_add(&m_count, -1); return *this; } 94 95 int Get() { return atomic_comp_swap(&m_count, 0, 0); } 96 int Inc(int n=1) { int m = atomic_add(&m_count, n); return m+n; } 97 int Dec(int n=1) { int m = atomic_add(&m_count, -n); return m-n; } 98 }; 99 100 class CReadWriteLock 101 { 102 CMutex m_mutexR, m_mutexW, m_mutexC; 103 int m_nReaders; 104 105 public: 106 CReadWriteLock() { m_nReaders=0; } 107 ~CReadWriteLock() {} 108 109 void ReadLock() { 110 m_mutexR.Lock(); 111 m_mutexC.Lock(); 112 m_nReaders ++; 113 // 如果是第一个读者,则加上写锁,这样之后其他线程加写锁就会等待 114 if (1 == m_nReaders) 115 m_mutexW.Lock(); 116 m_mutexC.Unlock(); 117 m_mutexR.Unlock(); 118 } 119 120 void ReadUnlock() { 121 m_mutexC.Lock(); 122 m_nReaders --;
// 如果是最后一个读者释放锁,则同时要释放写锁 123 if (0 == m_nReaders) 124 m_mutexW.Unlock(); 125 m_mutexC.Unlock(); 126 } 127 128 void WriteLock() { 129 m_mutexR.Lock(); 130 m_mutexW.Lock(); 131 } 132 133 void WriteUnlock() { 134 m_mutexW.Unlock(); 135 m_mutexR.Unlock(); 136 } 137 };