linux 用户空间 锁,深入理解Linux用户空间的锁机制

1.缘起

随着SMP(Symmetrical Multi-Processing)架构的流行和epoll类系统调用对非阻塞fd监视的支持,高性能服务器端的开发已经能够实现CPU计算和IO的分离。为了充分发挥CPU的计算能力,服务器端的设计必须要尽量减少线程切换。引起线程切换最重要的原因之一就是对mutex和semaphor等锁的使用。本文从计算机体系架构、操作系统的支持和mutex的实现彻底分析Linux用户空间mutex的实现,分析的源码版本是glib-2.3.4和kernel-2.6.8。

2.体系结构和指令的支持

在UP(uni processor)架构下,从用户空间的角度看,中断打断了程序的正常执行。操作系统在处理完中断之后,返回用户空间的之前,重新调度系统中的线程执行。由于CPU是在执行汇编指令结束后响应中断,那么单条汇编指令的执行就是原子的。

在SMP下,由于存在CPU Local Cache和每个CPU的指令周期不同,单条汇编指令的执行不会是原子的。X86 SMP提供了一个lock指令前缀,使得某些汇编指令的执行是原子的。看如下x86_64体系结构的汇编代码,来自glibc。

Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2中对cmpxchg指令的解释如下:

This instruction can be used with a LOCK prefix to allow the instruction to be executed atomically.

所有以lock为前缀的指令都起内存栅栏的作用。内存栅栏使编译器确保对RAM中数据的改变对所有CPU都是可见的。

上述汇编对应的伪代码:

wp-display-data.php?filename=13412154761.JPG&type=p_w_picpath%2Fjpeg&width=621&height=402

Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2中对cmpxchg指令的解释如下:

This instruction can be used with a LOCK prefix to allow the instruction to be executed atomically.

by gaolingfei

所有以lock为前缀的指令都起内存栅栏的作用。内存栅栏使编译器确保对RAM中数据的改变对所有CPU都是可见的。

上述汇编对应的伪代码:

wp-display-data.php?filename=13412163021.JPG&type=p_w_picpath%2Fjpeg&width=676&height=288

3.操作系统支持

按照操作系统的经典定义,进程是资源分配的最小单位,线程是调度的最小单位。Linux操作系统提供了futex系统调用以支持mutex等锁的实现。futex的主要功能是使得线程以TASK_INTERRUPTIBLE状态等待处于进程空间的某变量的改变,或者使得某线程可以唤醒等待该变量的其他线程。

实现wait的步骤如下:

C.计算key。如果是单进程单线程,Key为用户空间地址。如果为单进程多线程,需要执行spin_lock得到用户地址对应的page,然后spin_unlock。 page_table_lock会影响相应进程的page fault的处理。(&current->mm->page_table_lock)(&current->mm->page_table_lock);

E.up_read(&current->mm->mmap_sem);

实现wake up的步骤如下:

A.执行wait的A到C。

C.唤醒在锁上的一个等待线程。

E.up_read(&current->mm->mmap_sem);

4.pthread_mutex实现分析

pthread_mutex_lock()实现在glibc-2.3.4 pthread_mutex_lock.c文件的33行,该函数会根据mutex在init的时候设置的属性,选择不同的执行路径。mutex的属性有四种:

A.PTHREAD_MUTEX_TIMED_NP:默认属性。pthread_mutex_lock()直接调用lll_mutex_lock()。

B.PTHREAD_MUTEX_RECURSIVE_NP:检查mutex owner是否为当前线程。该属性允许线程多次获取该锁。

C.PTHREAD_MUTEX_ERRORCHECK_NP:如果同一线程两次lock,会返回错误。

D.PTHREAD_MUTEX_ADAPTIVE_NP:该锁会先n次调用lll_mutex_trylock(),n为用户定义和100的最小值。如果仍然失败,则调用lll_mutex_lock()。lll_mutex_trylock()不会调用futex。

5.spin lock实现

nginx实现了spin lock以保护多进程对listen port的互斥accept。spinlock的实现如下:

wp-display-data.php?filename=13412169351.JPG&type=p_w_picpath%2Fjpeg&width=676&height=591

Spinlock本质上是一个“忙等”锁,由于其不存在下节中总结的mutex的缺点,其对于小资源是最高效的锁。相比上节中mutex的PTHREAD_MUTEX_ADAPTIVE_NP属性,nginx的spinlock是一个更完美的实现方案。

6.总结

在设置PTHREAD_MUTEX_TIMED_NP属性和单进程多线程模型下,pthread_mutex_lock()对同进程的其他线程的影响如下:

A.pthread_mutex_lock()占用的大部分CPU时间当中,直接影响其他线程调用mmap(),brk(),malloc和free()。

B.对进程处理page fault也会有影响。

C.如果整个操作系统的用户进程使用了过多的mutex之类的锁,那么所有锁共享的futex_hash_bucket将是一个瓶颈。

D.最重要的是,锁的使用会引起线程的频繁切换,导致cpu cache miss和TLB miss。

对于系统中,需要互斥访问的资源,如下建议:

A.内核中对于小资源如链表的增删,多是使用spin lock保护。

B.在设置PTHREAD_MUTEX_ADAPTIVE_NP属性下,mutex既可以是spin lock,也可以是阻塞锁。

C.使用atomic_add_return(i, v),原子对变量i增加v值,并且返回操作后的值。相反操作:atomic_sub_return(i, v)。

D.使用Per-CPU variables,例如多线程程序中要每隔1秒,统计某项操作的值。该变量最好是cache alignment。

E.对于如数据库频繁更新的操作,可以使用数据库的多版本并发控制方法减少对mutex的lock。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值