1、引入传真
Futex是一种用户状态和内核状态混合的同步机制。 首先,在同步的进程之间通过mmap共享内存。 futex变量位于此共享内存中,操作是原子的。 当进程要进入独占区或退出独占区时,首先要去看共享内存中的futex变量,如果没有冲突,只修改futex,不需要执行系统调用。 如果您访问futex变量以告知进程发生了冲突,则必须执行系统调用以完成相应的操作(wait或wake up )。 简单地说,futex通过对用户状态的检查,如果知道没有竞争,就不需要陷入内核,大大提高了低内容的效率。
具体的调用步骤如下。
Glibc库实现了用户状态锁定接口,如pthread _ mutex _ lock (/pthread _ mutex _ unlock ),以提供快速的futex机制。
2、传真系统调用
/include/linux/syscall.h
asmlinkagelongsys _ futex (u32 _ user * uaddr,int op,u32 val,struct timespec __user *utime,u32 _ user * _aaar )
系统呼叫号码:
#define __NR_futex191
__syscall(191,sys_futex,5 )/kernel/futex.c
syscall_define6(futex,u32 __user *,uaddr,int,op,u32,val,
struct timespec __user *,utime,u32 __user *,uaddr2,
u32,val3)
{ .
returndo_futex(uaddr,op,val,tp,uaddr2,val2,val3);
……
}
uaddr是用户状态的共享存储器的地址,内部排列存储有整数计数器。
op保存着操作类型。 定义位于/include/linux/futex.h
FUTEX_WAIT:原子检查uaddr的计数器值是否为val,如果是,则使过程中止,直到FUTEX_WAKE或超时(time-out )。 也就是说,将进程乘以与uaddr对应的队列。
FUTEX_WAKE:调用最多val个uaddr上的进程等待。
3 .传真同步机制
所有futex同步操作都必须从用户空间开始。 首先,创建共享内存中的整数计数器futex同步变量。 当进程尝试拥有锁或进入独占区域时,对futex执行“down”操作。 也就是说,从原子角度将futex同步变量减1。 如果同步变量为0,则不会发生冲突,进程将照常运行。 如果同步变量为负数,则意味着会发生冲突,需要调用futex系统调用的futex_wait操作暂停当前进程。 当进程解锁或离开独占区域时,对futex执行“up”操作。 也就是说,从原子角度将futex同步变量加1。 如果同步变量从0更改为1,则不会发生冲突,进程将照常运行。 如果预添加的同步变量为负数,则意味着存在冲突,必须调用futex系统调用的futex_wake操作来启动一个或多个等待进程。
这里的原子性加减通常在CAS(compareandswap )中进行,与平台相关。 CAS的基本形式是CAS(addr,old,new ),如果存储在addr中的值与old相等,则用new将其置换。 x86平台上有用于完成: cmpxchg的特殊命令。
可以看到:futex从用户状态开始,用户状态和核心状态协调完成。
4 .入/线程使用futex同步
进程或线程可以使用futex同步。
对于线程,情况比较简单。 因为线程共享虚拟内存空间,所以虚拟地址可以唯一地标识futex变量。 这意味着线程将在同一虚拟地址访问futex变量。
对于进程,由于进程具有独立的虚拟内存空间,因此要使用futex变量,必须与(mmap ) )共享地址空间。 每个进程用于访问futex的虚拟地址都可以不同,只要系统知道所有虚拟地址都映射到同一个物理内存地址,并且用物理内存地址唯一标识futex变量即可。
参考资料: