Linux/GLIBC中存在多种解决方案,但没有一个允许在用户空间和内核空间之间显式共享信号量。内核提供了暂停线程/进程的解决方案,最有效的是 futex。以下是有关同步用户空间应用程序的当前实现的最新技术的一些细节。
SYSV 服务
Linux System V (SysV) 信号量是同名 Unix 操作系统的遗产。它们基于锁定/解锁信号量的系统调用。对应的服务有:
GLIBC(例如2.31 版本)在这些服务之上不提供任何附加值。图书馆服务直接调用同名的系统调用。例如,semop()(在sysdeps/unix/sysv/linux/semtimedop.c 中)直接调用相应的系统调用:
int
__semtimedop (int semid, struct sembuf *sops, size_t nsops,
const struct timespec *timeout)
{
/* semtimedop wire-up syscall is not exported for 32-bit ABIs (they have
semtimedop_time64 instead with uses a 64-bit time_t). */
#if defined __ASSUME_DIRECT_SYSVIPC_SYSCALLS && defined __NR_semtimedop
return INLINE_SYSCALL_CALL (semtimedop, semid, sops, nsops, timeout);
#else
return INLINE_SYSCALL_CALL (ipc, IPCOP_semtimedop, semid,
SEMTIMEDOP_IPC_ARGS (nsops, sops, timeout));
#endif
}
weak_alias (__semtimedop, semtimedop)
如今,SysV 信号量(以及其他 SysV IPC,如共享内存和消息队列)被认为已弃用,因为它们需要对每个操作进行系统调用,它们会通过系统的上下文切换减慢调用进程。新应用程序应使用可通过 GLIBC 获得的符合 POSIX 标准的服务。
POSIX 服务
POSIX 信号量基于快速用户互斥体 (FUTEX)。该原则包括在用户空间中使用原子操作递增/递减信号量计数器,只要没有争用。但是当存在争用时(多个线程/进程想要同时“锁定”信号量),一个futex()系统调用要么在信号量“解锁”时唤醒等待的线程/进程,要么暂停等待释放信号量的线程/进程。从性能的角度来看,这与上述 SysV 服务相比有很大的不同,后者系统地需要系统调用来执行任何操作。POSIX 服务在 GLIBC 中针对操作(原子操作)的用户空间部分实现,只有在存在争用时才会切换到内核空间。
例如,在 GLIBC 2.31中,锁定信号量的服务位于nptl/sem_waitcommon.c中。它检查信号量的值以使用原子操作(在 __ new_sem_wait_fast()中)递减它,并调用futex()系统调用(在 __ new_sem_wait_slow()中)仅当信号量之前等于 0时才挂起调用线程试图减少它。
static int
__new_sem_wait_fast (struct new_sem *sem, int definitive_result)
{
[...]
uint64_t d = atomic_load_relaxed (&sem->data);
do
{
if ((d & SEM_VALUE_MASK) == 0)
break;
if (atomic_compare_exchange_weak_acquire (&sem->data, &d, d - 1))
return 0;
}
while (definitive_result);
return -1;
[...]
}
[...]
static int
__attribute__ ((noinline))
__new_sem_wait_slow (struct new_sem *sem, clockid_t clockid,
const struct timespec *abstime)
{
int err = 0;
[...]
uint64_t d = atomic_fetch_add_relaxed (&sem->data,
(uint64_t) 1 << SEM_NWAITERS_SHIFT);
pthread_cleanup_push (__sem_wait_cleanup, sem);
/* Wait for a token to be available. Retry until we can grab one. */
for (;;)
{
/* If there is no token available, sleep until there is. */
if ((d & SEM_VALUE_MASK) == 0)
{
err = do_futex_wait (sem, clockid, abstime);
[...]
基于 futex 的 POSIX 服务例如:
要管理互斥体(即二进制信号量),可以使用 pthread 服务。它们也是基于 futex 的。举些例子: