kylin实现(1)-----share公共组件(基础组件)

  share中主要包含了kylin中的公共代码组件,例如原子操作,互斥锁,信号量,内存分配器。

1.1 atomic

  原子操作主要包含原子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)

 

1.2 futex

  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 }

 

  1.2.1  实现信号量

  获取共享资源 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.2.2 实现条件变量

   等待条件的满足,并且循环判断

 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 }

 

  1.2.3 事件

  事件等待被触发,当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 }

 

1.3  计时器

  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.4 spinlock 自旋锁

 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的内存屏障。

 

1.5 Token

  在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 }

 

1.6 信号量,条件变量,事件,读写锁

  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 };

 

你可能感兴趣的:(kylin实现(1)-----share公共组件(基础组件))