• IPC-信号量 以及pthread-mutex


    https://ty-chen.github.io/linux-kernel-shm-semaphore/

    Linux提供两种信号量:

    • 内核信号量,由内核控制路径使用

    • 用户态进程使用的信号量,这种信号量又分为POSIX信号量和SYSTEM V信号量。

      1. 对POSIX来说,信号量是个非负整数。
      2. 而SYSTEM V信号量则是一个或多个信号量的集合,它对应的是一个信号量结构体,这个结构体是为SYSTEM V IPC服务的,信号量只不过是它的一部分。常用于进程间同步。

      3. POSIX信号量的引用头文件是<semaphore.h>,而SYSTEM V信号量的引用头文件是<sys/sem.h>

      Linux内核的信号量在概念和原理上与用户态的System V的IPC机制信号量是一样的,但是它绝不可能在内核之外的中断使用,它是一种睡眠锁。

    如果有一个任务想要获得已经被占用的信号量时,信号量会将其放入一个等待队列(它不是站在外面痴痴地等待而是将自己的名字写在任务队列中)然后让其睡眠。

    当持有信号量的进程将信号释放后,处于等待队列中的一个任务将被唤醒(因为队列中可能不止一个任务),并让其获得信号量

    ps:在有的系统中Binary semaphore与Mutex是没有差异的。在有的系统上,主要的差异是mutex一定要由获得锁的进程来释放。而semaphore可以由其它进程释放(这时的semaphore实际就是个原子的变量,大家可以加或减),因此semaphore可以用于进程间同步。Semaphore的同步功能是所有 系统都支持的,而Mutex能否由其他进程释放则未定,因此建议mutex只用于保护criticalsection。而semaphore则用于保护某变量,或者同步。

    平时用的不多!! 有时间继续看

    信号量的核心思想是:内核空间分配一个data管理一个“文件id”, 进程A 和进程B 通过“文件id” 访问数据,同时加上原子操作等实现通信!!

    PS:在信号量这种常用的同步互斥手段方面,POSIX在无竞争条件下是不会陷入内核的,而SYSTEM V则是无论何时都要陷入内核,因此性能稍差

    • 1.System V信号量每次执行PV操作时,都需要进行用户态和内核态的切换。
    • 2. POSIX pthread库实现的信号量执行PV操作时,仅当需要时才进行用户态和内核态的切换。具体表述如下:

            2.1 P操作:a) 在用户态“信号量值减一,且值大于等于0”,则无需陷入内核;

                                    b) 在用户态“信号量值减一,且值小于0”,则需要陷入内核,并将调用进程插入到该信号量的等待队列,睡眠;

            2.2 V操作:a) 在用户态“信号量值加一,且值大于0”,则无需陷入内核;

                                    b) 在用户态“信号量值加一,且值小于等于0”,则需要陷入内核,并唤醒该信号量等待队列上的一个进程;

     来看看POSIX 的pthread_mutex 是怎么实现的!!毕竟用的多 性能要好!

    这个用的比较多!!

    pthread_mutex_t {
        int __lock; // 锁变量, 传给系统调用futex,用作用户空间的锁变量 mutex状态,0表示未占用,1表示占用
        usigned int __count;  // 可重入的计数  记录owner线程持有锁的次数
        int __owner;   // 被哪个线程占有了  owner线程ID
        int __kind;  // 记录mutex的类型,有以下几个取值  PTHREAD_MUTEX_TIMED_NP 等----
    ------------------------
    }

    pthread_mutex_lock

      pthread_mutex_lock调用LLL_UNLOCK(基于Linux的futex), 去拿到锁或阻塞自己. 另外,对可重入的锁进行计数(也叫做递归锁,是指在一个线程中可以多次获取同一把锁).

    代码见:https://code.woboq.org/userspace/glibc/nptl/pthread_mutex_lock.c.html

    int
    __pthread_mutex_lock (pthread_mutex_t *mutex)
    {
      /* See concurrency notes regarding mutex type which is loaded from __kind
         in struct __pthread_mutex_s in sysdeps/nptl/bits/thread-shared-types.h.  */
      unsigned int type = PTHREAD_MUTEX_TYPE_ELISION (mutex);
      LIBC_PROBE (mutex_entry, 1, mutex);
      if (__builtin_expect (type & ~(PTHREAD_MUTEX_KIND_MASK_NP
                                     | PTHREAD_MUTEX_ELISION_FLAGS_NP), 0))
        return __pthread_mutex_lock_full (mutex);
      if (__glibc_likely (type == PTHREAD_MUTEX_TIMED_NP))//一般默认缺省值,即普通锁
        {
          FORCE_ELISION (mutex, goto elision);
        simple:
          /* Normal mutex.  */
          LLL_MUTEX_LOCK (mutex);
          assert (mutex->__data.__owner == 0);
        }
      else if (__builtin_expect (PTHREAD_MUTEX_TYPE (mutex)
                                 == PTHREAD_MUTEX_RECURSIVE_NP, 1))//可重入
        {
          /* Recursive mutex.  */
          pid_t id = THREAD_GETMEM (THREAD_SELF, tid);
          /* Check whether we already hold the mutex.  */
          if (mutex->__data.__owner == id)
            {
              /* Just bump the counter.  */
              if (__glibc_unlikely (mutex->__data.__count + 1 == 0))
                /* Overflow of the counter.  */
                return EAGAIN;
              ++mutex->__data.__count; /* 若已经持有了此锁, 增加计数, 无需block此线程 */
              return 0;
            }
          /* We have to get the mutex.  */
          LLL_MUTEX_LOCK (mutex);// 去判断锁变量, 如果不行, 被OS休眠掉
          assert (mutex->__data.__owner == 0);
          mutex->__data.__count = 1;// 拿到了锁, 锁变量是ok的,则设置count
        }
      else if (__builtin_expect (PTHREAD_MUTEX_TYPE (mutex)
                              == PTHREAD_MUTEX_ADAPTIVE_NP, 1))
        {
          if (! __is_smp)
            goto simple;
          if (LLL_MUTEX_TRYLOCK (mutex) != 0)
            {
              int cnt = 0;
              int max_cnt = MIN (max_adaptive_count (),
                                 mutex->__data.__spins * 2 + 10);
              do
                {
                  if (cnt++ >= max_cnt)
                    {
                      LLL_MUTEX_LOCK (mutex);
                      break;
                    }
                  atomic_spin_nop ();
                }
              while (LLL_MUTEX_TRYLOCK (mutex) != 0);
              mutex->__data.__spins += (cnt - mutex->__data.__spins) / 8;
            }
          assert (mutex->__data.__owner == 0);
        }
      else
        {
          pid_t id = THREAD_GETMEM (THREAD_SELF, tid);
          assert (PTHREAD_MUTEX_TYPE (mutex) == PTHREAD_MUTEX_ERRORCHECK_NP);
          /* Check whether we already hold the mutex.  */
          if (__glibc_unlikely (mutex->__data.__owner == id))
            return EDEADLK;
          goto simple;
        }
      pid_t id = THREAD_GETMEM (THREAD_SELF, tid);
      /* Record the ownership.  */
      mutex->__data.__owner = id;
    
      LIBC_PROBE (mutex_acquired, 1, mutex);
      return 0;
    }

    2

    pthread_mutex_unlock (pthread_mutex_t *mutex) {
        if (type == PTHREAD_MUTEX_TIMED_NP) {
            mutex->__data.__owner = 0;
            lll_unlock (mutex->__data.__lock, PTHREAD_MUTEX_PSHARED (mutex));
            return 0;
      }
        else {
             if (type == PTHREAD_MUTEX_RECURSIVE_NP) 
            -------------------
        }

      pthread_mutex_lock/unlock主要是调用底层的lll_lock/lll_unlock, 其实就是调用futex的FUTEX_WAIT/FUTEX_WAKE操作, 来实现线程的休眠和唤醒工作

    # define LLL_MUTEX_LOCK(mutex) 
      lll_lock ((mutex)->__data.__lock, PTHREAD_MUTEX_PSHARED (mutex))
    
    define __lll_lock(futex, private)                                      
      ((void)                                                               
       ({                                                                   
         int *__futex = (futex);                                            
         if (__glibc_unlikely                                               
             (atomic_compare_and_exchange_bool_acq (__futex, 1, 0)))        
           {                                                                
             if (__builtin_constant_p (private) && (private) == LLL_PRIVATE) 
               __lll_lock_wait_private (__futex);                           
             else                                                           
               __lll_lock_wait (__futex, private);                          
           }                                                                
       }))

    atomic_compare_and_exchange_bool_acq 用于将_futex从0原子变为1,成功则返回0,从而获得锁退出。

    失败则返回值>0(对应我们这里是1或者2),然后继续走分支。

    futex变量的值有3种:0代表当前锁空闲,1代表有线程持有当前锁,2代表存在锁冲突。

      futex的值初始化时是0;当调用mutex-lock的时候会利用cas操作改为1;

      当调用lll_lock时,如果不存在锁冲突,则将其改为1,否则改为2。

    根据值, 走__lll_lock_wait:

    /* Note that we need no lock prefix.  */
    #define atomic_exchange_acq(mem, newvalue) 
      ({ __typeof (*mem) result;                              
         if (sizeof (*mem) == 1)                              
           __asm __volatile ("xchgb %b0, %1"                      
                 : "=q" (result), "=m" (*mem)                  
                 : "0" (newvalue), "m" (*mem));                  
         else if (sizeof (*mem) == 2)                          
           __asm __volatile ("xchgw %w0, %1"                      
                 : "=r" (result), "=m" (*mem)                  
                 : "0" (newvalue), "m" (*mem));                  
         else if (sizeof (*mem) == 4)                          
           __asm __volatile ("xchgl %0, %1"                          
                 : "=r" (result), "=m" (*mem)                  
                 : "0" (newvalue), "m" (*mem));                  
         else                                      
           __asm __volatile ("xchgq %q0, %1"                      
                 : "=r" (result), "=m" (*mem)                  
                 : "0" ((atomic64_t) cast_to_integer (newvalue)),     
                   "m" (*mem));                          
         result; })
    void
    __lll_lock_wait (int *futex, int private)
    {
      if (*futex == 2)
        lll_futex_wait (futex, 2, private); /* Wait if *futex == 2.  */
     
      while (atomic_exchange_acq (futex, 2) != 0)
        lll_futex_wait (futex, 2, private); /* Wait if *futex == 2.  */
    }

      同时附上老版本的ll_lock实现:

    /*
     * CAS操作的核心宏,cas操作判断(mutex)->__data.__lock的值是否为0,如果为0,则置为1,ZF=1
     * 1.判断是否在多线程环境下
     * 2.如果不是多线程环境则直接调用cmpxchgl指令进行cas操作,如果是多线程则需要在cmpxchgl指令前
     * 加上lock指令
     * 3.如果cas成功则跳到标号18,如果cas失败则调用__lll_lock_wait子程序
     */
    # define __lll_lock_asm_start "cmpl $0, %%gs:%P6
    	"                 
                      "je 0f
    	"                     
                      "lock
    "                             //!!!!!!lock 指令!!!!!!!!!!!
                      "0:	cmpxchgl %1, %2
    	"
    //LLL_PRIVATE为0,所以不会走第一个分支,走第二个分支
    #define lll_lock(futex, private) 
      (void)                                      
        ({ int ignore1, ignore2;                              
           if (__builtin_constant_p (private) && (private) == LLL_PRIVATE)        
         __asm __volatile (__lll_lock_asm_start                   
                   "jz 18f
    	"                   
                   "1:	leal %2, %%ecx
    "                 
                   "2:	call __lll_lock_wait_private
    "           
                   "18:"                          
                   : "=a" (ignore1), "=c" (ignore2), "=m" (futex)     
                   : "0" (0), "1" (1), "m" (futex),           
                     "i" (MULTIPLE_THREADS_OFFSET)            
                   : "memory");                         //!!!!!!指令影响内存!!!!!!!!!!!
           else                                   
         {                                    
           int ignore3;                               
           __asm __volatile (__lll_lock_asm_start                 
                     "jz 18f
    	"                     
                     "1:	leal %2, %%edx
    "               
                     "0:	movl %8, %%ecx
    "               
                     "2:	call __lll_lock_wait
    "             
                     "18:"                        
                     : "=a" (ignore1), "=c" (ignore2),            
                       "=m" (futex), "=&d" (ignore3)              
                     : "1" (1), "m" (futex),                  
                       "i" (MULTIPLE_THREADS_OFFSET), "0" (0),        
                       "g" ((int) (private))                  
                     : "memory");                     
         }                                    
        })
    View Code

    可知:xchgb 是一个原子操作在多核cpu 下也是的!!多核/多线程则需要在cmpxchgl指令前 加上lock指令 锁住地址总线 防止被修改:
    具体分析可见:smp-volatile

      futex(&__lock)的值从0原子变为2就成功。否则调用lll_futex_wait,阻塞。这里的atomic_exchange_acq是一个返回旧值的原子操作,直接采用了内敛汇编(xchg)的方式,并且根据变量类型从而选取linux下不同的汇编指令。

    到了这里,只要这个原子xchg的是正确的,并且阻塞与唤醒(wake up)之间的协议是正确的,那么这个mutex的语义就得到保证

     /* Wait while *FUTEXP == VAL for an lll_futex_wake call on FUTEXP.  */
     #define lll_futex_wait(futexp, val, private) 
      lll_futex_timed_wait (futexp, val, NULL, private)
     #define lll_futex_timed_wait(futexp, val, timeout, private)     
       lll_futex_syscall (4, futexp,                                 
                  __lll_private_flag (FUTEX_WAIT, private),  
                  val, timeout)

    glibc/nptl/lowlevellock.c 文件以及其头文件

    所以:lll_futex_syscall调用简化为

    lll_futex_syscall (4, futexp,   0,     2,   NULL)

    1.根据锁类型进行对应的操作,如果是普通锁PTHREAD_MUTEX_TIMED_NP则进行CAS操作,如果CAS失败则进行sys_futex系统调用挂起当前线程

    2.如果是递归锁PTHREAD_MUTEX_RECURSIVE_NP,则判断当前线程id是否和持有锁的线程id是否相等,如果相等说明是重入的,则将加锁次数加一。否则进行CAS操作获取锁,如果CAS失败则进行sys_futex系统调用挂起当前线程。

    3.如果是适配/自适应 锁PTHREAD_MUTEX_ADAPTIVE_NP,在获取锁的时候会进行自旋操作,当自旋的次数超过最大值时,则进行sys_futex系统调用挂起当前线程。

    4.如果是检错锁PTHREAD_MUTEX_ERRORCHECK_NP,则判断锁是否已经被当前线程获取,如果是则返回错误,否则进行CAS操作获取锁,检错锁可以避免普通锁出现的死锁情况。

    在获取锁失败后,会将互斥量设置为2,然后进行系统调用进行挂起,这是为了让解锁线程发现有其它等待互斥量的线程需要被唤醒

    pthread_mutex_unlock 

    其核心如下:

     #define __lll_unlock(futex, private)  
     ((void) ({   int *__futex = (futex);   
     int __val = atomic_exchange_rel (__futex, 0);     
     if (__builtin_expect (__val > 1, 0))  
     lll_futex_wake (__futex, 1, private);   })) 
     
     #define lll_unlock(futex, private) __lll_unlock(&(futex), private) 
     
     #define lll_futex_wake(ftx, nr, private)  ({  
     DO_INLINE_SYSCALL(futex, 3, (long) (ftx), 
     __lll_private_flag (FUTEX_WAKE, private),   
     (int) (nr));   _r10 == -1 ? -_retval : _retval;  }) 

    lll_unlock分为两个步骤:

    1. 将futex设置为0并拿到设置之前的值(用户态操作)
    2. 如果futex之前的值>1,代表存在锁冲突,也就是说有线程调用了FUTEX_WAIT在休眠,所以通过调用系统函数FUTEX_WAKE唤醒休眠线程

    futex变量的值有3种:0代表当前锁空闲,1代表有线程持有当前锁,2代表存在锁冲突。

      futex的值初始化时是0;当调用mutex-lock的时候会利用cas操作改为1;

      当调用lll_lock时,如果不存在锁冲突,则将其改为1,否则改为2。

    对比就知道为什么futex>1  就需要 futex_wake了

    /**
     * futex_wait_setup() - 准备等待在一个futex变量上
     * @uaddr: futex用户空间地址
     * @val: 期望的值
     * @flags: futex标识 (FLAGS_SHARED, etc.)
     * @q:  关联的futex_q
     * @hb:  hash_bucket的指针 返回给调用者
     *
     * Setup the futex_q and locate the hash_bucket.  Get the futex value and
     * compare it with the expected value.  Handle atomic faults internally.
     * Return with the hb lock held and a q.key reference on success, and unlocked
     * with no q.key reference on failure.
     *
     * Return:
     *  0 - uaddr contains val and hb has been locked;
     * <1 - -EFAULT or -EWOULDBLOCK (uaddr does not contain val) and hb is unlocked
     */
    static int futex_wait_setup(u32 __user *uaddr, u32 val, unsigned int flags,
          struct futex_q *q, struct futex_hash_bucket **hb)
    {
     u32 uval;
     int ret;
    
     /*
      * Access the page AFTER the hash-bucket is locked.
      * Order is important:
      *
      *   Userspace waiter: val = var; if (cond(val)) futex_wait(&var, val);
      *   Userspace waker:  if (cond(var)) { var = new; futex_wake(&var); }
      *
      * The basic logical guarantee of a futex is that it blocks ONLY
      * if cond(var) is known to be true at the time of blocking, for
      * any cond.  If we locked the hash-bucket after testing *uaddr, that
      * would open a race condition where we could block indefinitely with
      * cond(var) false, which would violate the guarantee.
      *
      * On the other hand, we insert q and release the hash-bucket only
      * after testing *uaddr.  This guarantees that futex_wait() will NOT
      * absorb a wakeup if *uaddr does not match the desired values
      * while the syscall executes.
      */
    retry:
       //初始化futex_q 填充q->key
     ret = get_futex_key(uaddr, flags & FLAGS_SHARED, &q->key, VERIFY_READ);
     if (unlikely(ret != 0))
      return ret;
    
    retry_private:
      //1.对q->key进行hash 然后通过&futex_queues[hash & ((1 << FUTEX_HASHBITS)-1)]找到futex_hash_bucket
      //2. spin_lock(&hb->lock)获得自旋锁
     *hb = queue_lock(q);
     //原子的将uaddr的值拷贝到uval中
     ret = get_futex_value_locked(&uval, uaddr);
     if (ret) { //拷贝操作失败 重试
        ...
      goto retry;
     }
     // 如果uval的值(即uaddr)不等于期望值val 则表明其他线程在修改
      // 直接返回无需等待
     if (uval != val) {
      queue_unlock(q, *hb);
      ret = -EWOULDBLOCK;
     }
    
    out:
     if (ret)
      put_futex_key(&q->key);
     return ret;
    }

    futex_wait_setup()方法主要是为阻塞在uaddr上做准备(uaddr 地址 为pthread_mutex_t  结构中lock值对应的地址),主要步骤:

    1. 初始化futex_q,并初始化futex_q.key的引用,
    2. futex_q.key进行hash通过&futex_queues[hash & ((1 << FUTEX_HASHBITS)-1)]找到futex_hash_bucket
    3. 调用spin_lock(&hb->lock)尝试获得自旋锁 失败则进行重试回到步骤1
    4. 判断*uaddr的值跟val是否相等,不相等说明其他线程在修改则释放持有的hb.lock自旋锁,返回-EWOULDBLOCK(#define EWOULDBLOCK 246 /* Operation would block (Linux returns EAGAIN) */即linux中的EAGAIN)
    **
     * futex_wait_queue_me() - queue_me() and wait for wakeup, timeout, or signal
     * @hb:  the futex hash bucket, must be locked by the caller
     * @q:  the futex_q to queue up on
     * @timeout: the prepared hrtimer_sleeper, or null for no timeout
     */
    static void futex_wait_queue_me(struct futex_hash_bucket *hb, struct futex_q *q,
        struct hrtimer_sleeper *timeout)
    {
      // task状态保证在另一个task唤醒它之前被设置,set_current_state利用set_mb()实现
      // 设置task状态为TASK_INTERRUPTIBLE CPU只会调度状态为TASK_RUNNING的任务
     set_current_state(TASK_INTERRUPTIBLE);
      //将q插入到等待队列中然后释放自旋锁
     queue_me(q, hb);
     //启动定时器
     if (timeout) {
      hrtimer_start_expires(&timeout->timer, HRTIMER_MODE_ABS);
      if (!hrtimer_active(&timeout->timer))
       timeout->task = NULL;
     }
    
     /*
      * If we have been removed from the hash list, then another task
      * has tried to wake us, and we can skip the call to schedule().
      */
     if (likely(!plist_node_empty(&q->list))) {
      /*
       * If the timer has already expired, current will already be
       * flagged for rescheduling. Only call schedule if there
       * is no timeout, or if it has yet to expire.
       */
        // 未设置过期时间 或过期时间还未到期才进行调度
      if (!timeout || timeout->task)
          //系统重新进行调度,此时CPU会去执行其他任务,当前任务会被阻塞
       schedule();
     }
      // 走到这里说明当前任务被CPU选中执行
     __set_current_state(TASK_RUNNING);
    }

    futex_wait_queue_me主要做了几件事:

    1. 将设置task状态为TASK_INTERRUPTIBLE (CPU只会调度状态为TASK_RUNNING的任务)
    2. 调用queueme()futex_q插入到等待队列
    3. 启动定时任务
    4. 若未设置过期时间或过期时间未到期 重新调度进程
    5. 获的执行资格 设置task状态为TASK_RUNNING

      总结一下futex_wait的工作机制:futex_wait_setup()方法会根据futex_q.keyhash找到对应futex_hash_bucket,并通过spin_lock(futex_hash_bucket.lock)获取自旋锁;futex_wait_queue_me()方法先是将task设置为TASK_INTERRUPTIBLE然后调用queue_me()futex_q入队,然后才spin_unlock(&hb->lock);释放持有的自旋锁。也就是说检查*uaddr值和进程/线程挂起放在了一个临界区中,保证了条件和等待之间的原子性。

    以上信息来自:https://jishuin.proginn.com/p/763bfbd55ad5

    futex_wake

    futex_wake()唤醒阻塞在*uaddr上的任务

    static int
    futex_wake(u32 __user *uaddr, unsigned int flags, int nr_wake, u32 bitset)
    {
     struct futex_hash_bucket *hb;
     struct futex_q *this, *next;
     struct plist_head *head;
     union futex_key key = FUTEX_KEY_INIT;
     int ret;
    
     if (!bitset)
      return -EINVAL;
      //根据uaddr的值填充&key的内容
     ret = get_futex_key(uaddr, flags & FLAGS_SHARED, &key, VERIFY_READ);
     if (unlikely(ret != 0))
      goto out;
     //根据&key获取对应uaddr所在的futex_hash_bucket
     hb = hash_futex(&key);
      //对该hb加自旋锁
     spin_lock(&hb->lock);
     head = &hb->chain;
     //遍历该hb的链表 注意链表中存储的节点时plist_node类型,而这里的this却是futex_q类型
      //这种类型转换是通过c中的container_of机制实现的
     plist_for_each_entry_safe(this, next, head, list) {
      if (match_futex (&this->key, &key)) {
       if (this->pi_state || this->rt_waiter) {
        ret = -EINVAL;
        break;
       }
    
       /* Check if one of the bits is set in both bitsets */
       if (!(this->bitset & bitset))
        continue;
       //唤醒对应进程
       wake_futex(this);
       if (++ret >= nr_wake)
        break;
      }
     }
     //释放自旋锁
     spin_unlock(&hb->lock);
     put_futex_key(&key);
    out:
     return ret;
    }
    static void wake_futex(struct futex_q *q)
    {
     struct task_struct *p = q->task;
    
     if (WARN(q->pi_state || q->rt_waiter, "refusing to wake PI futex
    "))
      return;
    
      // 在唤醒任务之前将q->lock_ptr设置为NULL
      // 如果另一个CPU发生了非futex方式的唤醒,则任务可能退出。p解引用则是一个不存在的task结构体
      // 为避免这种case,需要持有p引用来进行唤醒
     get_task_struct(p);
     //将q出队列
     __unqueue_futex(q);
     /*
      * 只要写入q-> lock_ptr = NULL,等待的任务就可以释放futex_q,而无需获取任何锁。
       * 这里需要一个内存屏障,以防止lock_ptr的后续存储超越plist_del。
      */
     smp_wmb();
     q->lock_ptr = NULL;
     // 将TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE状态的task唤醒
     wake_up_state(p, TASK_NORMAL);
      // task释放futex_q
     put_task_struct(p);
    }

    futex_wake()流程如下:

    1. 根据*uaddr填充futex_q->key,调用hash_futex(&key);查找对应的futex_hash_bucket
    2. 调用spin_lock(&hb->lock);尝试获取hb的自旋锁
    3. 遍历futex_hash_bucket挂载的链表,找到uaddr对应的节点
    4. 调用wake_futex唤起等待的进程,其实就是将task设置为TASK_RUNNING并放入调度队列中等待CPU调度同时释放futex_q
    5. 释放自旋锁

    此外futex同步机制即可用于线程之间同步,也可用于进程之间同步。

    • 用于线程比较简单,因为线程共享虚拟内存空间,futex变量由唯一的虚拟地址表示,线程即可用虚拟地址访问futex变量
    • 用于进程稍微复杂一些,因为进程间是分配的独立的虚拟内存地址空间。需要通过mmap让进程可以共享一段地址空间来使用futex变量。故而每个进程访问futex的虚拟地址不一样,但是操作系统知道所有这些虚拟地址映射到同一个表示futex变量的物理地址

    思考一下:

      在目前的mutex实现前提下, 用lock-free一般不会更快. 它们都只能同时让一个线程做有用的事, 在性能上不会有质的区别. 由于lock-free的代码会明显复杂, 性能还未必更高. 所以除非场景特殊, 用lock-free并不是个好主意.
    更好的方法是减少数据共享, 比如你这里分多个桶加锁就行了, 性能会比单纯用一套lock-free的数据结构更好. 在追求极限性能的场景中,一般考虑的是wait-free的算法和容器, 这能让所有线程同时做有用的事, 在关键代码上相比加锁和lock-free才有较大的区别.

    来自:https://www.zhihu.com/question/53303879
     
    http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!! 但行好事 莫问前程 --身高体重180的胖子
  • 相关阅读:
    jquery 应用
    SQL Server表分区
    .NET Framework 各版本区别
    后台添加控件时,必须每次重画控件,才能从前台获取控件数据。
    SVN文件库移植(转)
    C# WebService 的缓存机制
    OpenGL C#绘图环境配置
    java 调用webservice的各种方法总结
    SQLServer锁的概述
    C# Word 类库
  • 原文地址:https://www.cnblogs.com/codestack/p/14692940.html
Copyright © 2020-2023  润新知