Linux 锁机制


      本文讨论了 Linux 内核中可用的大量同步或锁定机制。这些机制为 2.6 版内核的许多可用方法提供了应用程序接口(API)

。但是在深入学习 API 之前,首先需要明白将要解决的问题。

    当存在并发特性时,必须使用同步方法。当在同一时间段出现两个或更多进程并且这些进程彼此交互(例如,共

享相同的资源)时,就存在并发 现象。

        在单处理器(uniprocessor,UP)主机上可能发生并发,在这种主机中多个线程共享同一个 CPU 并且

抢占(preemption)创建竞态条件。抢占 通过临时中断一个线程以执行另一个线程的方式来实现 CPU 共享。

态条件 发生在两个或更多线程操纵一个共享数据项时,其结果取决于执行的时间。在多处理器(MP)计算机中也存

在并发,其中每个处理器中共享相同数据的线程同时执行。注意在 MP 情况下存在真正的并行(parallelism),因为

线程是同时执行的。而在 UP 情形中,并行是通过抢占创建的。两种模式中实现并发都较为困难。

Linux 内核在两种模式中都支持并发。内核本身是动态的,而且有许多创建竞态条件的方法。Linux 内核也支持多

处理(multiprocessing),称为对称多处理(SMP)。

临界段概念是为解决竞态条件问题而产生的。一个临界段 是一段不允许多路访问的受保护的代码。这段代码可以

操纵共享数据或共享服务(例如硬件外围设备)。临界段操作时坚持互斥锁(mutual exclusion)原则(当一个线

程处于临界段中时,其他所有线程都不能进入临界段)。

临界段中需要解决的一个问题是死锁条件。考虑两个独立的临界段,各自保护不同的资源。每个资源拥有一个锁,

在本例中称为 A 和 B。假设有两个线程需要访问这些资源,线程 X 获取了锁 A,线程 Y 获取了锁 B。当这些锁都

被持有时,每个线程都试图占有其他线程当前持有的锁(线程 X 想要锁 B,线程 Y 想要锁 A)。这时候线程就被死

锁了,因为它们都持有一个锁而且还想要其他锁。一个简单的解决方案就是总是按相同次序获取锁,从而使其中一

个线程得以完成。还需要其他解决方案检测这种情形。表 1 定义了此处用到的一些重要的并发术语。

术语 定义
竞争条件 两个或更多线程同时操作资源时将会导致不一致的结果。
临界状态 用于协调对共享资源的访问的代码段。
互斥锁 确保对共享资源进行排他访问的软件特性。
死锁 由两个或更多进程和资源锁导致的一种特殊情形,将会降低进程的工作效率。

Linux 同步方法

如果您了解了一些基本理论并且明白了需要解决的问题,接下来将学习 Linux 支持并发和互斥锁的各种方法。在以

前,互斥锁是通过禁用中断来提供的,但是这种形式的锁定效率比较低(现在在内核中仍然存在这种用法)。这种

方法也不能进行扩展,而且不能保证其他处理器上的互斥锁。

在以下关于锁定机制的讨论中,我们首先看一下原子运算符,它可以保护简单变量(计数器和位掩码(bitmask

))。然后介绍简单的自旋锁和读/写锁,它们构成了一个 SMP 架构的忙等待锁(busy-wait lock)覆盖。最后

,我们讨论构建在原子 API 上的内核互斥锁。

原子操作

Linux 中最简单的同步方法就是原子操作原子 意味着临界段被包含在 API 函数中。不需要额外的锁定,因为 

API 函数已经包含了锁定。由于 C 不能实现原子操作,因此 Linux 依靠底层架构来提供这项功能。各种底层

架构存在很大差异,因此原子函数的实现方法也各不相同。一些方法完全通过汇编语言来实现,而另一些方法

依靠 c 语言并且使用 local_irq_save 和 local_irq_restore 禁用中断。


要声明一个原子变量(atomic variable),首先声明一个 atomic_t 类型的变量。这个结构包含了单个 int 

元素。接下来,需确保您的原子变量使用 ATOMIC_INIT 符号常量进行了初始化。 在清单 1 的情形中,原子计

数器被设置为 0。也可以使用 atomic_set function 在运行时对原子变量进行初始化。当需要保护的数据非

常简单时,例如一个计数器,原子运算符是种理想的方法。尽管原理简单,原子 API 提供了许多针对不同情形的

运算符。下面是一个使用此 API 的示例。


清单 1. 创建和初始化原子变量
                
atomic_t my_counter ATOMIC_INIT(0);... or ...atomic_set( &my_counter, 0 );

原子 API 支持一个涵盖许多用例的富函数集。可以使用 atomic_read 读取原子变量中的内容,也可以使用 a

tomic_add 为一个变量添加指定值。最常用的操作是使用 atomic_inc 使变量递增。也可用减号运算符,它

的作用与相加和递增操作相反。清单 2. 演示了这些函数。


清单 2. 简单的算术原子函数
                
val = atomic_read( &my_counter );atomic_add( 1, &my_counter );atomic_inc( &my_counter );atomic_sub( 1, &my_counter );atomic_dec( &my_counter );

该 API 也支持许多其他常用用例,包括 operate-and-test 例程。这些例程允许对原子变量进行操纵和测试(作

为一个原子操作来执行)。一个叫做 atomic_add_negative 的特殊函数被添加到原子变量中,然后当结果值

为负数时返回真(true)。这被内核中一些依赖于架构的信号量函数使用。

许多函数都不返回变量的值,但两个函数除外。它们会返回结果值( atomic_add_return 和 atomic_sub_r

eturn),如清单 3所示。


清单 3. Operate-and-test 原子函数
                
if (atomic_sub_and_test( 1, &my_counter )) {// my_counter is zero
}if (atomic_dec_and_test( &my_counter )) {// my_counter is zero
}if (atomic_inc_and_test( &my_counter )) {// my_counter is zero
}if (atomic_add_negative( 1, &my_counter )) {// my_counter is less than zero
}val = atomic_add_return( 1, &my_counter ));val = atomic_sub_return( 1, &my_counter ));

如果您的架构支持 64 位长类型(BITS_PER_LONG 是 64 的),那么 可以使用 long_t atomic 操作。可以在

 linux/include/asm-generic/atomic.h 中查看可用的长操作(long operation)。

原子 API 还支持位掩码(bitmask)操作。跟前面提到的算术操作不一样,它只包含设置和清除操作。许多驱动程

序使用这些原子操作,特别是 SCSI。位掩码原子操作的使用跟算术操作存在细微的差别,因为其中只有两个可用的

操作(设置掩码和清除掩码)。使用这些操作前,需要提供一个值和将要进行操作的位掩码,如清单 4 所示。


清单 4. 位掩码原子函数
                
unsigned long my_bitmask;atomic_clear_mask( 0, &my_bitmask );atomic_set_mask( (1<<24), &my_bitmask );

自旋锁

自旋锁是使用忙等待锁来确保互斥锁的一种特殊方法。如果锁可用,则获取锁,执行互斥锁动作,然后释放锁。如

果锁不可用,线程将忙等待该锁,直到其可用为止。忙等待看起来效率低下,但它实际上比将线程休眠然后当锁可

用时将其唤醒要快得多。

自旋锁只在 SMP 系统中才有用,但是因为您的代码最终将会在 SMP 系统上运行,将它们添加到 UP 系统是个

明智的做法。

自旋锁有两种可用的形式:完全锁(full lock)和读写锁。 首先看一下完全锁。

首先通过一个简单的声明创建一个新的自旋锁。这可以通过调用 spin_lock_init 进行初始化。清单 5 中

显示的每个变量都会实现相同的结果。


清单 5. 创建和初始化自旋锁
                
spinlock_t my_spinlock = SPIN_LOCK_UNLOCKED;... or ...DEFINE_SPINLOCK( my_spinlock );... or ...spin_lock_init( &my_spinlock );

定义了自旋锁之后,就可以使用大量的锁定变量了。每个变量用于不同的上下文。

清单 6 中显示了 spin_lock 和 spin_unlock 变量。这是一个最简单的变量,它不会执行

中断禁用,但是包含全部的内存壁垒(memory barrier)。这个变量假定中断处理程序和该

锁之间没有交互。


清单 6. 自旋锁 lock 和 unlock 函数
                spin_lock( &my_spinlock );// critical sectionspin_unlock( &my_spinlock );

接下来是 irqsave 和 irqrestore 对,如清单 7 所示。spin_lock_irqsave 函数需要自旋锁,并

且在本地处理器(在 SMP 情形中)上禁用中断。spin_unlock_irqrestore 函数释放自旋锁,并且

(通过 flags 参数)恢复中断。


清单 7. 自旋锁变量,其中禁用了本地 CPU 中断
                spin_lock_irqsave( &my_spinlock, flags );// critical sectionspin_unlock_irqrestore( &my_spinlock, flags );

spin_lock_irqsave/spin_unlock_irqrestore 的一个不太安全的变体是 spin_lock_irq/spin

_unlock_irq。 我建议不要使用此变体,因为它会假设中断状态。

最后,如果内核线程通过 bottom half 方式共享数据,那么可以使用自旋锁的另一个变体。bottom half 方

法可以将设备驱动程序中的工作延迟到中断处理后执行。这种自旋锁禁用了本地 CPU 上的软中断。这可以阻

止 softirq、tasklet 和 bottom half 在本地 CPU 上运行。这个变体如清单 8 所示。


清单 8. 自旋锁函数实现 bottom-half 交互
                spin_lock_bh( &my_spinlock );// critical sectionspin_unlock_bh( &my_spinlock );


读/写锁

在许多情形下,对数据的访问是由大量的读和少量的写操作来完成的(读取数据比写入数据更常见)。读/写锁

的创建就是为了支持这种模型。这个模型有趣的地方在于允许多个线程同时访问相同数据,但同一时刻只允许

一个线程写入数据。如果执行写操作的线程持有此锁,则临界段不能由其他线程读取。如果一个执行读操作的

线程持有此锁,那么多个读线程都可以进入临界段。清单 9 演示了这个模型。


清单 9. 读/写自旋锁函数
                
rwlock_t my_rwlock;rwlock_init( &my_rwlock );write_lock( &my_rwlock );// critical section -- can read and writewrite_unlock( &my_rwlock );read_lock( &my_rwlock );// critical section -- can read onlyread_unlock( &my_rwlock );

根据对锁的需求,还针对 bottom half 和中断请求(IRQ)对读/写自旋锁进行了修改。显然,如果您使用的

是原版的读/写锁,那么按照标准自旋锁的用法使用这个自旋锁,而不区分读线程和写线程。

内核互斥锁

在内核中可以使用互斥锁来实现信号量行为。内核互斥锁是在原子 API 之上实现的,但这对于内核用户是不

可见的。互斥锁很简单,但是有一些规则必须牢记。同一时间只能有一个任务持有互斥锁,而且只有这个任务

可以对互斥锁进行解锁。互斥锁不能进行递归锁定或解锁,并且互斥锁可能不能用于交互上下文。但是互斥

锁比当前的内核信号量选项更快,并且更加紧凑,因此如果它们满足您的需求,那么它们将是您明智的选择。

可以通过 DEFINE_MUTEX 宏使用一个操作创建和初始化互斥锁。这将创建一个新的互斥锁并初始化其结构。

可以在 ./linux/include/linux/mutex.h 中查看该实现。

                DEFINE_MUTEX( my_mutex );

互斥锁 API 提供了 5 个函数:其中 3 个用于锁定,一个用于解锁,另一个用于测试互斥锁。首先看一下锁定函

数。在需要立即锁定以及希望在互斥锁不可用时掌握控制的情形下,可以使用第一个函数 mutex_trylock

该函数如清单 10 所示。


清单 10. 尝试使用 mutex_trylock 获得互斥锁 
                
ret = mutex_trylock( &my_mutex );
if (ret != 0) {// Got the lock!
} else {// Did not get the lock
}

如果想等待这个锁,可以调用 mutex_lock。这个调用在互斥锁可用时返回,否则,在互斥锁锁可用之前它将

休眠。无论在哪种情形中,当控制被返回时,调用者将持有互斥锁。最后,当调用者休眠时使用 mutex_lock_

interruptible。在这种情况下,该函数可能返回 -EINTR。清单 11 中显示了这两种调用。


清单 11. 锁定一个可能处于休眠状态的互斥锁
                mutex_lock( &my_mutex );// Lock is now held by the caller.if (mutex_lock_interruptible( &my_mutex ) != 0)  {// Interrupted by a signal, no mutex held}

当一个互斥锁被锁定后,它必须被解锁。这是由 mutex_unlock 函数来完成的。这个函数不能从中断上下文调用

。最后,可以通过调用 mutex_is_locked 检查互斥锁的状态。这个调用实际上编译成一个内联函数。如果互斥

锁被持有(锁定),那么就会返回 1;否则,返回 0。清单 12 演示了这些函数。


清单 12. 用 mutex_is_locked 测试互斥锁锁 
                mutex_unlock( &my_mutex );if (mutex_is_locked( &my_mutex ) == 0) {// Mutex is unlocked}

互斥锁 API 存在着自身的局限性,因为它是基于原子 API 的。但是其效率比较高,如果能满足你的需要,还是可

以使用的。

大内核锁(Big kernel lock)

最后看一下大内核锁(BLK)。它在内核中的用途越来越小,但是仍然有一些保留下来的用法。BKL 使多处理

器 Linux 成为可能,但是细粒度(finer-grained)锁正在慢慢取代 BKL。BKL 通过 lock_kernel 和 unlo

ck_kernel 函数提供。要获得更多信息,请查看 ./linux/lib/kernel_lock.c。


Linux 性能非凡,其锁定方法也一样。原子锁不仅提供了一种锁定机制,同时也提供了算术或 bitwise 操作。

自旋锁提供了一种锁定机制(主要应用于 SMP),而且读/写自旋锁允许多个读线程且仅有一个写线程获得给定

的锁。最后,互斥锁是一种新的锁定机制,提供了一种构建在原子之上的简单 API。不管你需要什么,Linux 都

会提供一种锁定方案保护您的数据。

转载自:http://www.ibm.com/developerworks/cn/linux/l-linux-synchronization.html

转载于:https://www.cnblogs.com/int32bit/archive/2011/08/16/3187806.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/362041.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSS中越界问题经典解决方案

8.CSS相关知识 (1)如何解决父元素的第一个子元素的margin-top越界问题 1)为父元素加border-top: 1px;——有副作用 2)为父元素指定padding-top: 1px;——有副作用 3)为父元素指定overflow:hidden;——有副作用 4)为父元素添加前置内容生成——推荐使用 .parent:before { conten…

用可编写脚本的终结点遍历REST应用程序

我喜欢JDK附带ScriptEngine的事实。 当您要评估服务器环境中已经部署的应用程序并进行故障排除时&#xff0c;它非常灵活。 将此REST端点添加到Java EE应用程序中&#xff0c;它将使您可以立即访问该应用程序的内部状态。 package myrestapp;import java.io.StringReader; imp…

win7笔记本为手机共享wifi

1、cmd netsh wlan set hostednetwork modeallow ssidyourname keyyourpassword 开启win7的虚拟wifi&#xff0c;让电脑变成无线路由器 这时&#xff0c;网络连接中会多出一个网卡为“Microsoft Virtual WiFi Miniport Adapter”的无线连接2。如果没有&#xff0c;需要更新无线…

createjs中shape的属性regX和regY

官方文档说regX和regY是图形与注册点的距离。 那么注册点是什么呢&#xff1f; 我理解注册点就是图形的x/y对应的点图形动效的原点就是注册点 如果修改图形的regX和regY值图形在画布上的位置是会被改变的&#xff0c;但是注册点其实并没有被改变。因为图形的x/y值并没有被改…

CSS3里的display

默认值&#xff1a;inline 适用于&#xff1a;所有元素 继承性&#xff1a;无 动画性&#xff1a;否 none&#xff1a;隐藏对象。与visibility属性的hidden值不同&#xff0c;其不为被隐藏的对象保留其物理空间inline&#xff1a;指定对象为内联元素。block&#xff1a;指定…

H3C 单区域OSPF配置示例二

转载于:https://www.cnblogs.com/fanweisheng/p/11163688.html

身份反模式:联邦筒仓和意大利面条身份

分析公司Quocirca的最新研究证实&#xff0c;现在许多企业的外部用户比内部用户更多&#xff1a;在欧洲&#xff0c;有58&#xff05;的企业直接与其他企业和/或消费者的用户进行交易&#xff1b; 仅在英国&#xff0c;这一数字就达到了65&#xff05;。 如果您回顾历史&#x…

OpenSSL命令

Openssl Windows下编译过程1、下载openssl源代码以及相应的vc工程2、下载perl工具&#xff0c;如&#xff1a;ActivePerl-5.8.8.820-MSWin32-x86-274739.msi3、安装ActivePerl4、打开控制台程序&#xff0c;在openssl解压后的目录下执行Perl Configure VC-WIN32命令&#xff0c…

修改webpack的publicPath为动态设置以适配公司活动平台

背景&#xff1a; 我们需要将React开发的应用部署到一个活动搭建平台上&#xff0c;这意味我们只需要上传源码&#xff0c;没有搭建服务器的环节&#xff0c;没有配置Nginx的环节。具体步骤就是在该平台新建一个活动&#xff0c;然后将自己的源码传到这个活动下&#xff0c;然…

多个气泡向上冒出!

这里展示白色半透明气泡如下图&#xff1a;实际是动态 思路&#xff1a;HTML里只需要一个CANVAS元素&#xff0c;Javascript里操作canvas 1、给canvas里绘制背景图片 2、在绘制半径为0-10px的圆形&#xff0c;x坐标屏幕水平随机&#xff0c;y所标竖直大于屏幕高度。 圆形背景…

注入域对象而不是基础结构组件

依赖注入是Java&#xff08;以及许多其他编程语言&#xff09;中广泛使用的软件设计模式&#xff0c;用于实现控制反转 。 它提高了可重用性&#xff0c;可测试性&#xff0c;可维护性&#xff0c;并有助于构建松耦合的组件。 如今&#xff0c;依赖注入是将Java对象连接在一起的…

分享25个优秀的网站底部设计案例

相对于网站头部来说&#xff0c;关注网站底部设计的人很少。我们平常也能碰到有些网站的底部设计得很漂亮&#xff0c;给网站的呈现来一个完美的结尾。这篇文章收集了25个优秀的网站底部设计案例&#xff0c;一起欣赏。 me & oli La Bubbly Poogan’s Porch GiftRocket Lin…

wbepack中output.filename和output.chunkFilename

对于webpack配置中filename和chunkFilename在使用中有些不懂的地方&#xff0c;研究之后记录如下。 filename: string | function 此选项决定了每个输出 bundle 的名称。这些 bundle 将写入到 output.path 选项指定的目录下。 对于单个入口起点&#xff0c;filename 会是一个…

页面添加锚点后如何点击不改变URL?

直接奔主题&#xff0c;前端简单地锚点实现方法大家都会&#xff0c;无非就是在把 a 标签的 href 写成想要跳到的元素的id &#xff0c;比如点击 <a href"#box"></a> 页面就会自动滚动到 <div id"box"></div> 元素的位置。 那么这…

OP AMP - 反馈理论在运放中的应用

实际应用的系统绝大多数是闭环的&#xff0c;运放更是如此。 关于反馈理论&#xff0c;需要了解极点&#xff0c;零点&#xff0c;波特图&#xff0c;以及如何用相位裕度&#xff0c;幅值裕度 确定系统的稳定性&#xff0c;这里不作赘述 &#xff0c;不懂的部分可以到网上搜点…

具有Spring Boot和Java配置的Spring Batch教程

我一直在努力将Podcastpedia.org的一些批处理作业迁移到Spring Batch。 以前&#xff0c;这些工作是以我自己的方式开发的&#xff0c;我认为现在是时候使用一种更“标准化”的方法了。 因为我以前从未在Java配置中使用过Spring&#xff0c;所以我认为通过在Java中配置Spring B…

阅读react-redux源码 - 零

阅读react-redux源码 - 零阅读react-redux源码 - 一阅读react-redux源码(二) - createConnect、match函数的实现 react的技术栈一定会遇到redux&#xff0c;而在react中使用redux需要使用react-redux&#xff0c;那么react-redux是怎么包装redux已适用react的呢&#xff1f; …

kali linux 开启配置ssh服务

1. 一、配置SSH参数 修改sshd_config文件&#xff0c;命令为&#xff1a;vi /etc/ssh/sshd_config将#PasswordAuthentication no的注释去掉&#xff0c;并且将NO修改为YES //kali中默认是yes2. 将PermitRootLogin without-password修改为PermitRootLogin yes3. 然后&#xf…

CORS(跨域资源共享)

CORS&#xff08;跨域资源共享&#xff09;使用额外的HTTP头部来告诉浏览器&#xff0c;允许运行在origin(domain)上的Web应用访问来自不同源服务器上的指定资源。 浏览器访问一个web应用&#xff0c;这个web应用会发很多的跨域请求&#xff0c;例如加载不同源的JS/CSS脚本&am…

[转]jQuery设计思想

转自&#xff1a;http://kb.cnblogs.com/page/109875/ jQuery是目前使用最广泛的javascript函数库。 据统计&#xff0c;全世界排名前100万的网站&#xff0c;有46%使用jQuery&#xff0c;远远超过其他库。微软公司甚至把jQuery作为他们的官方库。 对于网页开发者来说&#xff…