futex机制介绍「建议收藏」

2022-08-02 13:22:12 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

1、概念 futex: a sort of fast, user-space mutual exclusion primitive. Futex是一种用户态和内核态混合的同步机制。首先,同步的进程间通过mmap共享一段内存,futex变量就位于这段共享的内存中且操作是原子的,当进程尝试进入互斥区或者退出互斥区的时候,先去查看共享内存中的futex变量,如果没有竞争发生,则只修改futex,而不用再执行系统调用了。当通过访问futex变量告诉进程有竞争发生,则还是得执行系统调用去完成相应的处理(wait 或者 wake up)。简单的说,futex就是通过在用户态的检查,(motivation)如果了解到没有竞争就不用陷入内核了,大大提高了low-contention时候的效率。 https://lwn.net/Articles/172149/ https://lwn.net/Articles/360699/ 2、futex的由来 为什么要有futex,他解决什么问题?何时加入内核的?我们来看下 经研究发现,很多同步是无竞争的,即某个进程进入互斥区,到再从某个互斥区出来这段时间,常常是没有进程也要进这个互斥区或者请求同一同步变量的。但是在这种情况下,这个进程也要陷入内核去看看有没有人和它竞争,退出的时侯还要陷入内核去看看有没有进程等待在同一同步变量上。这些不必要的系统调用(或者说内核陷入)造成了大量的性能开销。为了解决这个问题,Futex就应运而生。 前面的概念已经说了,futex是一种用户态和内核态混合同步机制,为什么会是用户态 内核态,听起来有点复杂,由于我们应用程序很多场景下多线程都是非竞争的,也就是说多任务在同一时刻同时操作临界区的概率是比较小的,大多数情况是没有竞争的,在早期内核同步互斥操作必须要进入内核态,由内核来提供同步机制,这就导致在非竞争的情况下,互斥操作扔要通过系统调用进入内核态。 我们来看一下程序 程序1: pthread_mutex_t lock; int count = 0; void thread1() { while(1) { pthread_mutex_lock(&lock); /* do something */ count ; pthread_mutex_unlock(&lock); } } void thread2() { while(1) { sleep(60); pthread_mutex_lock(&lock); count = 0; pthread_mutex_unlock(&lock); } } pthread_create(&tid1, NULL, thread1, NULL); pthread_create(&tid2, NULL, thread1, NULL);

假设系统中有2个程序,一个线程在滴答自增,一个线程周期性清除计数器。显然两个线程同时进入临界区的几率相当小,在未实现futex机制之前,每次调用pthread_mutex_lock和unlock都要通过syscall进入内核,内核检查该锁的拥有者,发现没有人持则返回到用户态,因为大部分时间2个线程并没有争抢互斥锁。显然大部分时间在做无用功,时间浪费在user->kernel和kernel→usr的切换,显然这个锁的性能不太好,因为存在大量user→kernel、kernel→usr的切换,例子可能不太恰当,get到点就好。

那么如何解决这个问题? 就像前面说的,采用用户态 内核态混合机制,在用户态使用原子操作,对持有锁的持有情况进行判断,如果锁可以占用,那么更新锁的状态并直接占用,不需要进入内核态,如果锁已经占用,则进入内核态挂起当前任务,事实上这些操作对程序员不可见的,一般都是由C库提实现好了。 Glibc中常用的线程同步方式举例: Semaphore 变量定义: sem_t sem; 初始化: sem_init(&sem,0,1); 进入加锁: sem_wait(&sem); 退出解锁: sem_post(&sem); Mutex 变量定义: pthread_mutex_t mut; 初始化: pthread_mutex_init(&mut,NULL); 进入加锁: pthread_mutex_lock(&mut); 退出解锁: pthread_mutex_unlock(&mut); 这些用于同步的函数和futex有什么关系?下面让我们来看一看: 以Semaphores为例, 进入互斥区的时候,会执行sem_wait(sem_t *sem),sem_wait的实现如下: int sem_wait (sem_t *sem) { int *futex = (int *) sem; if (atomic_decrement_if_positive (futex) > 0) return 0; int err = lll_futex_wait (futex, 0); return -1; ) atomic_decrement_if_positive()的语义就是如果传入参数是正数就将其原子性的减一并立即返回。如果信号量为正,在Semaphores的语义中意味着没有竞争发生,如果没有竞争,就给信号量减一后直接返回了。 如果传入参数不是正数,即意味着有竞争,调用lll_futex_wait(futex,0),lll_futex_wait是个宏,展开后为: #define lll_futex_wait(futex, val) ({ … __asm __volatile (LLL_EBX_LOAD LLL_ENTER_KERNEL LLL_EBX_LOAD : “=a” (__status) : “0″ (SYS_futex), LLL_EBX_REG (futex), “S” (0), “c” (FUTEX_WAIT), “d” (_val), “i” (offsetof (tcbhead_t, sysinfo)) : “memory”); … }) 可以看到当发生竞争的时候,sem_wait会调用SYS_futex系统调用,并在val=0的时候执行FUTEX_WAIT,让当前线程休眠。 2002年的ols文档,在linux-2.5.7引入了futex。 https://www.kernel.org/doc/ols/2002/ols2002-pages-479-495.pdf

2、内核配置: 在内核配置选项中可以看到,默认是勾选的。 CONFIG_FUTEX=Y [*] Enable futex support CONFIG_FUTEX: │ │ │ │ Disabling this option will cause the kernel to be built without │ │ support for “fast userspace mutexes”. The resulting kernel may not │ │ run glibc-based applications correctly. 从CONFIG_FUTEX的注释中我们可以发现,我们会发现一个问题,注释说如果CONFIG_FUTEX=N,则生成的内核可能不能正常运行glibc应用程序。为什么会这样? 从上面futex例子我们可以看出,在Semaphores和mutex的实现过程中使用了futex,说明glibc库使用了futex,而其他的常用的同步手段也是建立在futex机制上,包括用户态下的操作和核心态下的操作。 未完–待续

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/125323.html原文链接:https://javaforall.cn

0 人点赞