热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[Linux]互斥机制(中断屏蔽、原子操作、自旋锁、信号量)

本文唯一地址:http:blog.csdn.netdearsqarticledetails52175401欢迎转载,转载请著名~谢谢!基本概念临界区对某段代码而言,可能

本文唯一地址:http://blog.csdn.net/dearsq/article/details/52175401
欢迎转载,转载请著名~ 谢谢!

基本概念

临界区

对某段代码而言,可能会在程序中多次被执行,每次执行的过程我们称作代码的执行路径。
当两个或多个代码路径要竞争共同的资源的时候,该代码段就是临界区。

互斥机制

访问共享资源的代码叫做临界区。共享资源被多个线程需要,但共享资源又不能被同时访问。
所以临界区需要以某种互斥机制来加以保护,确保共享资源被互斥访问。

用户空间和内核空间

为了安全考虑,Linux系统分为内核态和用户态,分别运行在内核空间和用户空间
内核态的程序可以执行特权指令,操作系统本身也在其中运行;
用户态则不允许直接访问操作系统的核心数据、设备等关键资源,必须先通过系统调用或者中断进入内核态才可以访问,当系统调用或中断返回时,重新回到用户空间运行。

Linux 的互斥机制

四种方式:中断屏蔽、原子操作、自旋锁、信号量

内核空间互斥方式:中断屏蔽、原子操作、自旋锁
用户空间互斥方式:信号量

中断屏蔽

中断是一个完全异步的事件,它的发生与正在运行的进程没有任何关系,它没有进程上下文切换
CPU具备屏蔽中断和打开中断的功能,这项功能可以保证正在执行的内核执行路径不被中断处理程序抢占,防止竞态的产生。
但是,内核的正常运行依赖于中断机制。在屏蔽中断期间,任何中断都无法得到处理,而必须等待屏蔽解除。因此长时间屏蔽中断对内核的运行起到很大的影响,其后果可能导致数据丢失,甚至系统崩溃。
实际情况是:在中断服务全过程屏蔽中断会丢失中断;如果开中断,又容易引起互斥问题。
为了解决这个问题,Linux 把中断分为顶半部TH(Top Half)和底半部BH(Bottom Half)。
TH 屏蔽中断,执行一些少量的关键性动作;BH 可以开中断,允许中断延迟执行。

原子操作

原子操作底层表现为一条汇编指令(ldrex、strex)。所以他们在执行过程中不会被别的代码路径所中断。
Linux 内核提供了两类函数来实现内核中的原子操作,分别是整型原子操作位原子操作。它们的共同点是所有的操作都是原子的,内核可以安全的调用它们而不被中断,而且它们都依赖底层CPU的原子操作实现,因此所有的这些函数都是与CPU架构相关的。

自旋锁

自旋锁是为实现保护共享资源而提出一种锁机制。

自旋锁的原理:
一个执行单元要想访问被自旋锁保护的共享资源,必须先得到锁,并且在任何时刻最多只能有一个执行单元获得锁;
而在访问完共享资源后,必须释放锁。
如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即得到锁;
如果在获取自旋锁时锁已经有保持者,那么获取锁操作将一直循环在那里,直到该自旋锁的保持者释放了锁,”自旋”一词就是因此而得名。

事实上,自旋锁的初衷是:在短期间内进行轻量级的锁定。一个被争用的自旋锁使得请求它的线程在等待锁重新可用的期间进行自旋(特别浪费处理器时间),所以自旋锁被持有的时间不应该过长。如果需要长时间锁定的话, 最好使用信号量。

信号量

在用户空间只有进程的概念。当一个临界区有多个用户态进程竞争时,最好的方法是用信号量保护这个临界区。
只有得到信号量进程才能执行临界区代码,当获取不到信号量时,进程进入休眠状态。

因此,我们可以说,信号量是进程级的互斥机制,它代表进程来争夺共享资源,如果竞争失败,就会发生进程上下文切换,当前进程进入睡眠状态,CPU运行其他进程。由于进程上下文切换的开销很大,因此,只有当进程占用资源时间较长时,用信号量才是最好的选择。

此外,信号量在SMP(对称多处理器)系统同样起作用。

浅显的比方

浅显的来说,可以理解为大家在一套房子里合租,共用一个厕所。厕所就是共享资源,去上厕所的行为被称作代码路径。
中断屏蔽就是,有一个人想要用厕所,但是呢他在上厕所前在门口贴上纸条说厕所坏了,如果他很快出来倒还不要紧,但是如果他要上很长时间,那一起住的其他人可能就要憋爆了。所以中断屏蔽最开始不会被用于处理需要耗时很长的操作。但是大家想,这样不是个解决办法啊,我有时候确实要拉很长时间怎么办呢。
于是发明了顶半部TH和底半部BH,TH用于执行少量的关键性的动作,BH用于处理中断中耗时的部分。
可以理解为,某人A特别特别想上厕所的时候,就进入TH(可以看作一个状态),此时A去应个急,拉一点点,让肚子不那么疼,此时A是不可以被打断的。如果没人用厕所他就直接慢条斯理的开始拉了(BH)。但是他这个BH状态是可以打断的,如果此时来个人B 非常非常急,B进入TH说,我受不了啦要憋死啦,A就会暂停自己的状态(保护现场)让B进来拉一会(TH),等B拉了一点点,让肚子不那么疼了就出去。此时 A继续(恢复现场)。等A 的BH部分完全结束后B再执行B的BH部分。
原子操作很好理解,就是大家每次上厕所都用时非常短,短到什么程度呢,只要一条汇编指令的时间。当然拉的量也非常少(只改变一个整型或者是位)。所以就不存在抢厕所的问题了。
自旋锁顾名思义,给这个厕所上把锁,只有拥有这个锁钥匙的人A才能进厕所。进去后把锁锁上,外面的人B急得团团转(自旋),出来后把锁释放,在门口等着的B拿了钥匙赶紧开了锁进去了。但是缺点就是,B在外面团团转,没有功夫去做别的事情,所以一旦 A 上厕所的时间很长,B就浪费了很长时间在自旋上。对系统的性能有所影响。
信号量信号量就是,我们的房子有 N 个厕所,N 不为 1, 且 N 为有限个,上厕所的人是有限的。即共享这一块资源的进程是有限个数的。这时候我们就可以在厕所门口挂上 N 吧钥匙,拿到钥匙的就可以进去,钥匙架空了,其他进程就只能在门口等待出来的人还钥匙。

区别分析

代码实现

中断屏蔽

  local_irq_disable() /local_irq_save(flags);
// 访问临界区
local_irq_enable() /local_irq_restore(flags);

原子操作

位原子操作:set_bit/clear_bit/change_bit/test_bit
整型原子操作:atomic_set/atomic_read/atomic_add/atomic_sub/atomic_inc/atomic_dec/
步骤:

//1. 分配整形原子变量
atomic_t v = ATOMIC_INIT(1);
//2.操作原子变量
atomic_set/atomic_read/atomic_add/atomic_sub/atomic_inc加加/atomic_dec减减/...

实例:

    static int open_cnt = 1;
open_cnt++; //不具备原子性

方法一,中断屏蔽,不适用于多核

    unsigned long flags;
local_irq_save(flags);
open_cnt++;
local_irq_restore(flags);

方法二,原子操作

    static atomic_t open_cnt = ATOMIC_INIT(1);
atomic_inc(&open_cnt); //具有原子性

自旋锁

    //1.分配自旋锁
spinlock_t lock;
//2.初始化自旋锁
spin_lock_init(&lock);
//3.访问临界区之前获取锁:
spin_lock(&lock); //获取自旋锁,立即返回,如果没有获取锁,将进行忙等待
或者
spin_trylock(&lock); //获取锁,返回true,否则返回false,所以这个函数一定要对返回值进行判断!
//4 .访问临界区
//5.释放自旋锁
spin_unlock(&lock);

衍生自旋锁

    //1.分配自旋锁
spinlock_t lock;
//2.初始化自旋锁
spin_lock_init(&lock);
//3.访问临界区前获取锁:
unsigned long flags;
spin_lock_irq(&lock); // = spin_lock() + local_irq_disable()
或者
spin_lock_irqsave(&lock, flags); // = spin_lock() local_irq_save()
//4.访问临界区
//5.释放自旋锁
spin_unlock_irq(&lock); // = spin_unlock()+ local_irq_enable()
或者
spin_unlock_irqrestore(&lock, flags); // = spin_unlock() + local_irq_restore()

信号量

//1.分配信号量对象
struct semaphore sema;
//2.初始化为互斥信号量
init_MUTEX(&sema);
或者:
DECLARE_MUTEX(sema);
//3.访问临界区之前获取信号量
down(&sema);
//如果获取信号量,立即返回
//如果信号量不可用,进程将在此休眠,并且休眠的状态是 [ 不可中断的休眠状态 TASK_UNINTERRUPTIBLE] !
或者
down_interruptible(&sema);
//如果信号量不可用,进程将进入 [ 可中断的休眠状态 TASK_INTERRUPTIBLE ],如果返回0表示正常获取信号,如果返回非0,表示接受到了信号
down_trylock();
//获取信号,如果信号量不可用,返回非0,如果信号量可用,返回0;不会引起休眠,可以在中断上下文使用。返回值也要做判断!
//4.访问临界区:临界区可以休眠
//5.释放信号量
up(&sema);
//不仅仅释放信号量,然后唤醒休眠的进程,让这个进程去获取信号量来访问临界区

参考文章:
Linux 互斥机制:http://www.cnblogs.com/jan5/articles/3351186.html
线程同步之详解自旋锁(windows平台):http://www.cnblogs.com/cposture/p/SpinLock.html 可以参考
读写自旋锁详解,第 1 部分:https://www.ibm.com/developerworks/cn/linux/l-cn-rwspinlock1/ 以自动机的观点阐述读写自旋锁的原理


推荐阅读
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • CentOS 7部署KVM虚拟化环境之一架构介绍
    本文介绍了CentOS 7部署KVM虚拟化环境的架构,详细解释了虚拟化技术的概念和原理,包括全虚拟化和半虚拟化。同时介绍了虚拟机的概念和虚拟化软件的作用。 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • Windows下配置PHP5.6的方法及注意事项
    本文介绍了在Windows系统下配置PHP5.6的步骤及注意事项,包括下载PHP5.6、解压并配置IIS、添加模块映射、测试等。同时提供了一些常见问题的解决方法,如下载缺失的msvcr110.dll文件等。通过本文的指导,读者可以轻松地在Windows系统下配置PHP5.6,并解决一些常见的配置问题。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 配置IPv4静态路由实现企业网内不同网段用户互访
    本文介绍了通过配置IPv4静态路由实现企业网内不同网段用户互访的方法。首先需要配置接口的链路层协议参数和IP地址,使相邻节点网络层可达。然后按照静态路由组网图的操作步骤,配置静态路由。这样任意两台主机之间都能够互通。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 本文介绍了OpenStack的逻辑概念以及其构成简介,包括了软件开源项目、基础设施资源管理平台、三大核心组件等内容。同时还介绍了Horizon(UI模块)等相关信息。 ... [详细]
author-avatar
大伟
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有