System V IPC、LinuxThreads和NPTL对比

来源:互联网 发布:打鸡血网络语什么意思 编辑:程序博客网 时间:2024/06/05 09:44

1 Futex(NPTL核心)概述

futex ("快速用户区互斥"的简称)是一个在Linux上实现锁定和构建高级抽象锁如信号量和POSIX互斥的基本工具。它们第一次出现在内核开发的2.5.7版;其语义在2.5.40固定下来,然后在2.6.x系列稳定版内核中出现。

 

Futex 是由Hubertus Franke (IBM Thomas J. Watson 研究中心), Matthew Kirkwood, Ingo Molnar (Red Hat) Rusty Russell (IBM Linux 技术中心)等人创建的.

 

Futex 由一块能够被多个进程共享的内存空间 (一个对齐后的整型变量)组成;这个整型变量的值能够通过汇编语言调用CPU提供的原子操作指令来增加或减少,并且一个进程可以等待直到那个值变成正数。Futex 的操作几乎全部在应用程序空间完成;只有当操作结果不一致从而需要仲裁时,才需要进入操作系统内核空间执行。这种机制允许使用 futex 的锁定原语有非常高的执行效率:由于绝大多数的操作并不需要在多个进程之间进行仲裁,所以绝大多数操作都可以在应用程序空间执行,而不需要使用(相对高代价的)内核系统调用。

int sys_futex(void *futex, int op, int val, const struct timespec *timeout, void *futex2, int val3);

futex 的等待操作可以指定条件。将常量 FUTEX_WAIT 作为参数传递给 futex 作为第二个参数 op 的值,这个时候第三个参数 op 的值将作为一个比较对象。当 *(int *)futex == val 的时候,调用体将被阻塞直到 timeout 时间到达,或者被其它执行体唤醒。如果 *(int *)futex != val,则锁定过程会被直接跳过。系统通过精心编写的汇编代码,保证了这个 load-and-compare 过程的原子性。这个进行比较的过程是完全在用户态完成的,也就是说,如果值不相等,不符合等待的条件,则这个“系统调用”并不会切换进内核态执行,也就比其它的同步方式少了至少一次内核态往返切换的损耗。这也是 futex 名字(Fast Userspace muTEX)的来历。

futex wake 可以指定被唤醒的进程数。用常量 FUTEX_WAKE 传递给 op 表示唤醒操作,第三个值 val 表示了最多被唤醒的执行体数量。因此,用 futex 实现 semaphore 的“广播”和单一执行体的唤醒都是非常灵活便捷的。


2 System V IPCLinuxThreadsNPTL对比

System V IPC 信号量和fcntl锁基于重量级内核实现,复杂、耗性能。

 

内容

存在的问题

系统调用接口

实现原理

Futex

Posix Thread的互斥锁,条件变量(pthread_ )、信号量(sem_ )的实现基础。一般在NPTL库内使用。

1 尽可能在用户空间操作,以避免系统调用的巨大开销

2 避免上下文交换

int sys_futex(void *futex, int op, int val, const struct timespec *timeout, void *futex2, int val3);

系统调用号为240

Futex_wake,futex_wait等是内核函数,而不是NPTL的调用接口。

Glibc2.7/NPTL Lowlevellock.h中有最底层的函数lll_futex_wait, lll_futex_wake, lll_trylock(调用sys_futex())等,用汇编语言书写。在NPTL内部函数中实现futex的两个阶段:1 非竞争状态(当前无等等线程或没有要唤醒的线程) ,使用用户态的原子操作;2 竞争状态,需调用sys_futex,进入内核操作,入等待队列或出队列、唤醒。

System V IPC

信号量、消息队列、socket、文件锁

每次上锁都调用系统调用,对于低竞争速率的锁,开锁比较大。

更加适合于进程间的通讯。

Msgget() Msgsnd() Msgrcv() Msgctl()

Semget() Semop() Semctl() Semtimedop()

Shmat() Shmget() Shmdt() Shmctl()

基于内核同步原语(原子操作,spin lock,read/write spin lock,RCU)的混合使用。

NPTL

(Native Posix Thread Library)

基于Futex

Posix标准的互斥锁,条件变量(pthread_ )、信号量(sem_ )

更加快速,高效。

基本与Posix标准相容性。

l        有名信号量和无名信号量:它们有相同的属性,但接口有些稍微不同。信号量接口主要包括:sem_initsem_destroysem_opensem_closesem_unlinksem_waitsem_trywaitsem_postsem_getvalue

l        消息队列主要包括以下函数:mq_openmq_receivemq_sendmq_closemq_unlinkmq_notifymq_setattrmq_getattr

l        互斥锁:pthread_mutexattr_initpthread_mutex_init,pthread_mutex_destroy,pthread_mutex_lock,pthread_mutex_trylock, pthread_mutex_unlock

l        条件变量: pthread_condattr_init, pthread_condattr_destroy, pthread_cond_init, pthread_cond_destroy, pthread_cond_signal,, pthread_cond_waitpthread_cond_timedwait

基于Futex,在内核级支持futex,同步原语的等待和唤醒在内核内实现,不再使用信号来同步。

LinuxThread

基于用户级spin lock(使用汇编代码)和信号。

Posix标准的互斥锁,条件变量, Barriers (pthread_ )、信号量(sem_ )

同步原语的等待或唤醒都使用信号。

由一个管理线程管理所有的信号,也负责线程创建,销毁,性能瓶颈所在。

Posix很不相容。

 

基于用户级spin lock和内核信号实现。

 

注:LinuxThread最大缺点:

pthread库不得不在底层依靠信号方式来实现同步,因此线程互斥中的互斥量操作和条件量操作都转换为进程的信号操作。pthread的实现中充斥了极其复杂的信号操作。大家都知道信号本身是低速的通信方式,因此势必拖慢了线程的实际性能。最后的问题就是信号处理,还有由于内核对线程的无知,必须由管理线程来接收信号后投递给相应的线程,一方面是效率低,另外一方面由于信号产生的不确定性(比如读取一个文件的时候突然出错了),要准确投递所有的信号给正确的线程难以保证。

3 内核同步原语

3.1  Memory Barriers

由于编译器会优化程序,可能改变程序的执行顺序,但当处理同步时,不希望改变指令的执行顺序,就需要使用Memory Barriers,防止顺序的改变。起到阻隔和防火墙的作用。

3.2  原子操作

3.3  Spin lock

    获取锁,或不停地试锁直至开锁。一般在Critical region是不允许内核抢占的。在单CPU情况下,Spin lock仅仅是允许或不允许内核抢占。在多CPU情况下,要复杂的多。线程在不停试开锁的时候是可以被高优先级的线程抢占的。

1.      Read/Write Spin Locks

    Rwlock_t 结构体,核心的lock32bit,0-23bit代表读计数,24bit代表是否上锁。在写的时候,读必须等待,反之,在读的时候,写必须等待读完。

2.      SeqLocks

    read/write spin locks相似,但是它给writer更多的优先级:读的时候必须等写完;但写的时候,不必等待所有读者读完。

这是个双面:好的一面是写者不需等待读者;另一面是读者可能要读多次数据,以得到一个有效的数据。只允许一个写者和多个读者。

3.      Read-Copy Update(RCU)

    Seqlocks的改进后,允许多个写者和多个读者。不限制竞争(race conditions),只适合于:

1 数据是动态分配,并使用指针引用。

2 内核在RCU保护的区间不睡眠

读者读过程:禁止内核抢占->使用指针读取数据->允许内核抢占。

写者过程:spin lock上锁->使用指针copy一份数据,并修改-> call_rcu()释放旧数据->将指针指向修改后的数据(是原子操作)->spin lock解锁

3.4 信号量

    spin lock类似,最大的区别是不像spin lock一直在spin锁,而是挂起,直到唤醒。

4 Posix Thread几个特殊的同步对象

4.1 壁垒(Barriers

实现基于Futex

壁垒是同步机制的一种,用来栏住同时工作的线程(例如在矩阵运算中的多个线程),强迫它们在某个特定点等待直到全部线程结束,之后其中的线程才能继续运行。

pthread_join()函数一直等待所有的线程结束,而壁垒的使用则是等待线程在某特定点集合(rendezvous)。当特定数量的线程到达壁垒时,就全部解除这些线程的闭塞让它们继续运行。

壁垒的创建使用函数pthread_barrier_init()

#include <pthread.h>int pthread_barrier_init (pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count);

这个函数在传入的地址(指向壁垒对象的指针在参数barrier中)创建一个壁垒对象,并按照attr中的属性对该对象进行设定。参数count为必须调用函数pthread_barrier_wait()的线程数量。

一旦创建了壁垒,每个线程必须调用函数pthread_barrier_wait()来表明该线程已经完成:

#include <pthread.h>

int pthread_barrier_wait (pthread_barrier_t *barrier);

当线程调用函数pthread_barrier_wait()之后,它就进入闭塞状态,直到pthread_barrier_init()函数中线程数量参数中所设定的这么多线程调用了pthread_barrier_wait()函数后才解除闭塞状态。当调用 pthread_barrier_wait()函数的线程的数目正确,这些线程就会同时解除闭塞。

4.2 Spin locks (pthread)

    功能与内核版的Spin locks的类似,在用户态下,使用原子操作和精简汇编代码实现spin lock,无系统调用。

4.3 read-write lock(读写锁,pthread)

    基于Futex和原子操作汇编代码实现。

功能与内核版的Read/Write Spin Locks的类似,在用户态下,使用原子操作和精简汇编代码实现读写锁,有Futex系统调用。

5 NPTL同步原语实现伪码

5.1 Semaphores

==============================

 

       int sem_wait(sem_t * sem);

       int sem_trywait(sem_t * sem);

       int sem_post(sem_t * sem);

       int sem_getvalue(sem_t * sem, int * sval);

 

struct sem_t {

 

   unsigned int count; //       current semaphore count, also used as a futex

}

 

sem_wait(sem_t *sem)

{

  for (;;) {

 

    if (atomic_decrement_if_positive(sem->count))

      break;

 

    futex_wait(&sem->count, 0)

  }

}

 

sem_post(sem_t *sem)

{

  n = atomic_increment(sem->count);  // Pass the new value of sem->count

  futex_wake(&sem->count, n + 1);

}

 

sem_trywait(sem_t *sem)

{

  if (atomic_decrement_if_positive(sem->count)) {

    return 0;

  } else {

    return EAGAIN;

  }

}

 

sem_getvalue(sem_t *sem, int *sval)

{

  *sval = sem->count;

  read_barrier();

}

5.2 Reader Writer Locks

==============================

 

       pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

       pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

       pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

 

struct pthread_rwlock_t {

 

   unsigned int lock:

         - internal mutex

 

   unsigned int writers_preferred;

         - locking mode: 0 recursive, readers preferred

                         1 nonrecursive, writers preferred

 

   unsigned int readers;

         - number of read-only references various threads have

 

   pthread_t writer;

         - descriptor of the writer or 0

 

   unsigned int readers_wakeup;

         - 'all readers should wake up' futex.

 

   unsigned int writer_wakeup;

         - 'one writer should wake up' futex.

 

   unsigned int nr_readers_queued;

         - number of readers queued up.

 

   unsigned int nr_writers_queued;

         - number of writers queued up.

}

 

pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)

{

  lll_lock(rwlock->lock);

  for (;;) {

    if (!rwlock->writer && (!rwlock->nr_writers_queued ||

                                   !rwlock->writers_preferred))

        break;

 

    rwlock->nr_readers_queued++;

    val = rwlock->readers_wakeup;

    lll_unlock(rwlock->lock);

 

    futex_wait(&rwlock->readers_wakeup, val)

 

    lll_lock(rwlock->lock);

    rwlock->nr_readers_queued--;

  }

  rwlock->readers++;

  lll_unlock(rwlock->lock);

}

 

pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock)

{

  int result = EBUSY;

  lll_lock(rwlock->lock);

  if (!rwlock->writer && (!rwlock->nr_writers_queued ||

                                   !rwlock->writers_preferred))

    rwlock->readers++;

  lll_unlock(rwlock->lock);

  return result;

}

 

pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)

{

  lll_lock(rwlock->lock);

  for (;;) {

    if (!rwlock->writer && !rwlock->readers)

       break;

 

    rwlock->nr_writers_queued++;

    val = rwlock->writer_wakeup;

    lll_unlock(rwlock->lock);

 

    futex_wait(&rwlock->writer_wakeup, val);

 

    lll_lock(rwlock->lock);

    rwlock->nr_writers_queued--;

  }

  rwlock->writer = pthread_self();

  lll_unlock(rwlock->lock);

}

 

pthread_rwlock_unlock(pthread_rwlock_t *rwlock)

{

  lll_lock(rwlock->lock);

 

  if (rwlock->writer)

    rwlock->writer = 0;

  else

    rwlock->readers--;

 

  if (!rwlock->readers) {

    if (rwlock->nr_writers_queued) {

      ++rwlock->writer_wakeup;

      lll_unlock(rwlock->lock);

      futex_wake(&rwlock->writer_wakeup, 1);

      return;

    } else

      if (rwlock->nr_readers_queued) {

        ++rwlock->readers_wakeup;

        lll_unlock(rwlock->lock);

        futex_wake(&rwlock->readers_wakeup, MAX_INT);

        return;

      }

  }

 

  lll_unlock(rwlock->lock);

}

5.3 Conditional Variable

.

================================

 

       int pthread_cond_timedwait (pthread_cond_t *cv, pthread_mutex_t *mutex);

       int pthread_cond_signal    (pthread_cond_t *cv);

       int pthread_cond_broadcast (pthread_cond_t *cv);

 

struct pthread_cond_t {

 

   unsigned int cond_lock;       //         internal mutex

 

   uint64_t total_seq; //     Total number of threads using the conditional variable.

 

   uint64_t wakeup_seq;   //     sequence number for next wakeup.

 

   uint64_t woken_seq;    //     sequence number of last woken thread.

 

   uint32_t broadcast_seq;

 

}

 

 

struct cv_data {

 

   pthread_cond_t *cv;

 

   uint32_t bc_seq

 

}

 

cleanup_handler(cv_data)

{

  cv = cv_data->cv;

  lll_lock(cv->lock);

 

  if (cv_data->bc_seq == cv->broadcast_seq) {

    ++cv->wakeup_seq;

    ++cv->woken_seq;

  }

 

  /* make sure no signal gets lost.  */

  FUTEX_WAKE(cv->wakeup_seq, ALL);

 

  lll_unlock(cv->lock);

}

 

 

cond_timedwait(cv, mutex, timeout):

{

   lll_lock(cv->lock);

   mutex_unlock(mutex);

 

   cleanup_push

 

   ++cv->total_seq;

   val = seq =  cv->wakeup_seq;

   cv_data.bc = cv->broadcast_seq;

   cv_data.cv = cv;

 

   while (1) {

 

     lll_unlock(cv->lock);

 

     enable_async(&cv_data);

 

     ret = FUTEX_WAIT(cv->wakeup_seq, val, timeout);

 

     restore_async

 

     lll_lock(cv->lock);

 

     if (bc != cv->broadcast_seq)

       goto bc_out;

 

     val = cv->wakeup_seq;

 

     if (val != seq && cv->woken_seq != val) {

       ret = 0;

       break;

     }

 

     if (ret == TIMEDOUT) {

       ++cv->wakeup_seq;

       break;

     }

   }

 

   ++cv->woken_seq;

 

 bc_out:

   lll_unlock(cv->lock);

 

   cleanup_pop

 

   mutex_lock(mutex);

 

   return ret;

}

 

cond_signal(cv)

{

   lll_lock(cv->lock);

 

   if (cv->total_seq > cv->wakeup_seq) {

     ++cv->wakeup_seq;

     FUTEX_WAKE(cv->wakeup_seq, 1);

   }

 

   lll_unlock(cv->lock);

}

 

cond_broadcast(cv)

{

   lll_lock(cv->lock);

 

   if (cv->total_seq > cv->wakeup_seq) {

     cv->wakeup_seq = cv->total_seq;

     cv->woken_seq = cv->total_seq;

     ++cv->broadcast_seq;

     FUTEX_WAKE(cv->wakeup_seq, ALL);

   }

 

   lll_unlock(cv->lock);

}

5.4 Barriers

===================

 

    int pthread_barrier_wait(barrier_t *barrier);

 

struct barrier_t {

 

   unsigned int lock:   //         - internal mutex

 

   unsigned int left;     //         - current barrier count, # of threads still needed.

 

   unsigned int init_count;  //         - number of threads needed for the barrier to continue.

 

   unsigned int curr_event; //         - generation count

}

 

pthread_barrier_wait(barrier_t *barrier)

{

  unsigned int event;

  result = 0;

 

  lll_lock(barrier->lock);

  if (!--barrier->left) {

    barrier->curr_event++;

    futex_wake(&barrier->curr_event, INT_MAX)

 

    result = BARRIER_SERIAL_THREAD;

  } else {

    event = barrier->curr_event;

    lll_unlock(barrier->lock);

    do {

      futex_wait(&barrier->curr_event, event)

    } while (event == barrier->curr_event);

  }

 

  if (atomic_increment_val (barrier->left) == barrier->init_count)

    lll_unlock(barrier->lock);

 

  return result;

}

原创粉丝点击