信号量、互斥锁、自旋锁

  • 前言
  • 一、信号量
    • 1、信号量初始化api
    • 2、获取信号量
    • 3、释放信号量
    • 4、使用案例
  • 二、互斥锁
    • 互斥锁的API
  • 三、自旋锁
    • 1、初始化
    • 2、获得自旋锁
    • 3、释放自旋锁
    • 4、判断自旋锁
    • 5、自旋锁使用注意事项

前言

信号量:是一种锁机制用于协调进程之间互斥的访问临界资源。以确保某种共享资源不被多个进程同时访问。
互斥锁(Mutex):是初始值为1的信号量
自旋锁:与互斥锁类似,但在无法得到资源时,互斥锁内核线程处于睡眠阻塞状态,而自旋锁处于忙等待状态


一、信号量

Linux内核的信号量在概念和原理上与用户态的System V的IPC机制信号量(如semget,semop)是一样的,但是它绝不可能在内核之外使用,因此它与System V的IPC机制信号量毫不相干。

信号量的初始值:表示同时可以有几个任务可以访问该信号量保护的共享资源

一个任务要想访问共享资源,首先必须得到信号量,获取信号量的操作将把信号量的值减1
…若当前信号量的值为负数,表明无法获得信号量,该任务必须挂起在该信号量的等待队列,等待该信号量可用;
…若当前信号量的值为非负数,表示可以获得信号量,因而可以立刻访问被该信号量保护的共享资源。

任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加1实现,
如果信号量的值为非正数,表明有任务等待当前信号量,因此它也唤醒所有等待该信号量的任务的其中一个。

1、信号量初始化api

静态定义:
DECLARE_MUTEX(name)
该宏声明一个信号量name并初始化它的值为1,即声明一个互斥锁。
DECLARE_MUTEX_LOCKED(name)
该宏声明一个互斥锁name,但把它的初始值设置为0,即锁在创建时就处在已锁状态。因此对于这种锁,一般是先释放后获得。 动态定义:
void sema_init (struct semaphore *sem, int val);
该函用于数初始化设置信号量的初值,它设置信号量sem的值为val。
void init_MUTEX (struct semaphore *sem);
该函数用于初始化一个互斥锁,即它把信号量sem的值设置为1。
void init_MUTEX_LOCKED (struct semaphore *sem);
该函数也用于初始化一个互斥锁,但它把信号量sem的值设置为0,即一开始就处在已锁状态。

2、获取信号量

void down(struct semaphore * sem);

该函数用于获得信号量sem,它会导致睡眠,因此不能在中断上下文(包括IRQ上下文和softirq上下文)使用该函数。该函数将把sem的值减1,如果信号量sem的值非负,就直接返回,否则调用者将被挂起,直到别的任务释放该信号量才能继续运行。

int down_interruptible(struct semaphore * sem);

该函数功能与down类似,不同之处为,down不会被信号(signal)打断,但down_interruptible能被信号打断。因此该函数有返回值来区分是正常返回还是被信号中断,如果返回0,表示获得信号量正常返回,如果被信号打断,返回-EINTR。

int down_trylock(struct semaphore * sem);

该函数试着获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0。否则,表示不能获得信号量sem,返回值为非0值。因此,它不会导致调用者睡眠,可以在中断上下文使用

3、释放信号量

void up(struct semaphore * sem);

该函数释放信号量sem,即把sem的值加1,如果sem的值为非正数,表明有任务等待该信号量,因此唤醒所有这些等待者的其中一个等待任务。

4、使用案例

#define N 15
int num;
struct semaphore sem_1, sem_2;int ThreadFunc1(void *context)
{char *tmp=(char*)context;while(num<N){down(&sem_1);    printk("%s-before-num:%d\n",tmp,num);msleep(1000);num++;printk("%s-after-num:%d\n",tmp,num);up(&sem_1);}return 0;
}
int ThreadFunc2(void *context)
{char *tmp=(char*)context;while(num<N){down(&sem_1);printk("%s-before-num:%d\n",tmp,num);msleep(1000);num++;printk("%s-after-num:%d\n",tmp,num);up(&sem_1);}return 0;
}static int __init gpio_init(void)
{printk(" up. \n"); sema_init(&sem_1, 1);sema_init(&sem_2, 0);kthread_run(ThreadFunc1,"123a", "123");kthread_run(ThreadFunc2,"456a", "456");return 0;
}static void __exit gpio_exit(void)
{printk( " down.\n");
}module_init(gpio_init);
module_exit(gpio_exit);MODULE_LICENSE("Dual BSD/GPL");

二、互斥锁

如果初始值为1,表示只有1个任务可以访问,信号量变成互斥锁(Mutex)。可见互斥锁是信号量的特例

互斥锁(mutex)是在原子操作API的基础上实现的信号量行为。互斥锁不能进行递归锁定或解锁,能用于进程上下文,同一时间只能有一个任务持有互斥锁。

互斥锁功能上基本上与信号量一样,互斥锁占用空间比信号量小,运行效率比信号量高。

互斥锁的API

DEFINE_MUTEX(mutexname)
静态创建和初始化互斥锁。
mutex_init(struct mutex* lock);
动态创建和初始化互斥锁。void mutex_lock(struct mutex *lock);
加锁。  若无法获得锁,则休眠
int mutex_trylock(struct mutex *lock);
尝试加锁。  若已经无法获得锁,则直接返回。用在中断void mutex_unlock(struct mutex *lock);
解锁,并唤醒其他需要锁的任务 

三、自旋锁

自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻**最多只能有一个执行单元(**进程或中断处理程序)获得锁。但是两者在调度机制上略有不同。

自旋锁实际上是忙等待,自旋锁可能导致系统死锁
死锁:简单说,就是当前任务在获得自旋锁之后,释放自旋锁之前,主动调用一些函数让出CPU的控制权(比如主动调用sleep休眠或主动调用内核调度函数schedule去调度其他任务),这时候,如果被调度进来的任务也想去获得该自旋锁,便会进入死循环轮询等待,即发生了死锁。

自旋锁不允许睡眠或阻塞
只有一个进程不会死锁,两个或两个以上的进程才会死锁

1、初始化

动态初始化:
spin_lock_init(spinlock_t *lock);
该宏用于初始化自旋锁x
静态:
DEFINE_SPINLOCK(spinlock_t x)
该宏声明一个自旋锁x并初始化它。
SPIN_LOCK_UNLOCKED
该宏用于静态初始化一个自旋锁。
//DEFINE_SPINLOCK(spinlock_t x)等同于spinlock_t x = SPIN_LOCK_UNLOCKED

2、获得自旋锁

spin_trylock(lock)
该宏尽力获得自旋锁lock,如果能立即获得锁,它获得锁并返回真,
否则不能立即获得锁,立即返回假。它不会自旋等待lock被释放。
spin_lock(lock)
该宏用于获得自旋锁lock,如果能够立即获得锁,它就马上返回,
否则,它将自旋在那里,直到该自旋锁的保持者释放,这时,它
获得锁并返回。总之,只有它获得锁才返回
spin_lock_irqsave(lock, flags)
该宏获得自旋锁的同时把标志寄存器的值保存到变量flags中并失效本地中断。
spin_lock_irq(lock)
该宏类似于spin_lock_irqsave,只是该宏不保存标志寄存器的值。
spin_lock_bh(lock)
该宏在得到自旋锁的同时失效本地软中断。
spin_trylock_irqsave(lock, flags)
该宏如果获得自旋锁lock,它也将保存标志寄存器的值到变量flags中,
并且失效本地中断,如果没有获得锁,它什么也不做。因此如果能够立即获得锁,
它等同于spin_lock_irqsave,如果不能获得锁,它等同于spin_trylock。
如果该宏获得自旋锁lock,那需要使用spin_unlock_irqrestore来释放。
spin_trylock_irq(lock)
该宏类似于spin_trylock_irqsave,只是该宏不保存标志寄存器。
如果该宏获得自旋锁lock,需要使用spin_unlock_irq来释放。
spin_trylock_bh(lock)
该宏如果获得了自旋锁,它也将失效本地软中断。如果得不到锁,
它什么也不做。因此,如果得到了锁,它等同于spin_lock_bh,
如果得不到锁,它等同于spin_trylock。如果该宏得到了自旋锁,
需要使用spin_unlock_bh来释放。

3、释放自旋锁

spin_unlock(lock)
该宏释放自旋锁lock,它与spin_trylock或spin_lock配对使用。
如果spin_trylock返回假,表明没有获得自旋锁,因此不必使用spin_unlock释放
spin_unlock_irqrestore(lock, flags)
该宏释放自旋锁lock的同时,也恢复标志寄存器的值为变量flags保存的值。
它与spin_lock_irqsave配对使用。
spin_unlock_irq(lock)
该宏释放自旋锁lock的同时,也使能本地中断。
它与spin_lock_irq配对应用。
spin_unlock_bh(lock)
该宏释放自旋锁lock的同时,也使能本地的软中断。
它与spin_lock_bh配对使用。
spin_unlock_wait(x)
该宏用于等待自旋锁x变得没有被任何执行单元保持,
如果没有任何执行单元保持该自旋锁,该宏立即返回,否则将循环在那里,
直到该自旋锁被保持者释放。

4、判断自旋锁

spin_is_locked(x)
该宏用于判断自旋锁x是否已经被某执行单元保持(即被锁),
如果是,返回真,否则返回假。
spin_can_lock(lock)
该宏用于判断自旋锁lock是否能够被锁,它实际是spin_is_locked取反。
如果lock没有被锁,它返回真,否则,返回假。

5、自旋锁使用注意事项

注意
自旋锁与中断:进程A占用锁后,内核的抢占暂时被禁止(但无法禁止中断抢占进程)。这时候突然发生中断,然后中断处理函数里想要获得这个锁时,发生了死锁。
所以需要改用spin_lock_irq或spin_lock_irqsave。解锁换成对应的spin_unlock_irq或spin_unlock_irqrestore。
进程上下文与软中断上下文
只在进程上下文访问和软中断上下文访问,那么当在进程上下文访问共享资源时,可能被软中断打断,从而可能进入软中断上下文来对被保护的共享资源访问,因此对于这种情况,对共享资源的访问必须使用spin_lock_bh和spin_unlock_bh来保护。
当然使用spin_lock_irq和spin_unlock_irq以及spin_lock_irqsave和spin_unlock_irqrestore也可以,它们失效了本地硬中断,失效硬中断,隐式地也失效了软中断。但是使用spin_lock_bh和spin_unlock_bh是最恰当的,它比其他两个快。
进程上下文 和 tasklet或timer上下文
在进程上下文和tasklet或timer上下文访问,那么应该使用与上面情况(进程上下文和软中断上下文)相同的获得和释放锁的宏,因为tasklet和timer是用软中断实现的。
进程上下文 和 中断上下文
在进程或软中断上下文需要使用spin_lock_irq和spin_unlock_irq来保护对共享资源的访问。 在中断处理句柄中使用,如果只有一个(硬),仅需要spin_lock和spin_unlock,不同的(硬)中断,使用spin_lock_irq和spin_unlock_irq
tasklet或timer上下文
不需要任何自旋锁保护,同一个tasklet或timer只能在一个CPU上运行
一个或多个软中断上下文
只在一个软中断(tasklet和timer除外)上下文访问,那么这个共享资源需要用spin_lock和spin_unlock来保护,在两个或多个软中断上下文访问,那么这个共享资源当然更需要用spin_lock和spin_unlock来保护 , 没必要使用spin_lock_bh


信号量、互斥锁、自旋锁相关推荐

  1. POSIX互斥锁自旋锁

    基础组件-POSIX互斥锁自旋锁 基础组件 基础组件-POSIX互斥锁自旋锁 前言 一.互斥锁 二.自旋锁 特点: 场景: 使用原则 自旋锁属性 三.两把锁的区别 1. 调度策略 2.使用场景 四.常 ...

  2. 第二季:5公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁【Java面试题】

    第二季:5值传递和引用传递[Java面试题] 前言 推荐 值传递 说明 题目 24 TransferValue醒脑小练习 第二季:5公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自 ...

  3. 常⻅锁策略(1. 乐观锁 悲观锁2. 公平锁 非公平锁3. 读写锁4. 可重入锁 自旋锁)

    目录 1. 乐观锁 & 悲观锁 1.1乐观锁定义 1.2 乐观锁实现 -- CAS 1.3 悲观锁定义和应⽤ 2. 公平锁 & 非公平锁 3. 读写锁 3.1 读写锁 3.2 独占锁 ...

  4. Java面试之锁-自旋锁

    Java锁之自旋锁 自旋锁:spinlock,是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁 这样的优点:是减少线程上下文切换的消耗, 缺点是循环会消耗CPU 原来提到的比较并交换, ...

  5. java多线程- 互斥锁 自旋锁

    如果一个资源会被不同的线程访问修改,那么我们把这个资源叫做临界资源(<操作系统>),那么对于该资源访问修改相关的代码就叫做临界区.引入互斥锁即解决多个线程之间共享同一个共享资源,这是多线程 ...

  6. 什么是自旋锁+自旋锁和互斥锁的区别

    文章目录 本文链接 什么是自旋锁 参考链接 自旋锁和互斥锁的区别 参考链接 本文链接 击打 什么是自旋锁 多线程对共享资源访问, 为防止并发引起的相关问题, 常引入锁的机制来处理并发问题.   获取到 ...

  7. 从 class 文件 看 synchronize 锁膨胀过程(偏向锁 轻量级锁 自旋锁 重量级锁)

    大家好,我是烤鸭: 前几天看马士兵老师的并发的课,里边讲到了 synchronize 锁的膨胀过程,今天想用代码演示一下. 1.  简单介绍 关于synchronize jdk 1.5 以后的优化,由 ...

  8. java重入锁 自旋锁_java 自旋锁(可重入且无死锁)

    java自旋锁 的实现原理:如果自旋锁被另外一个线程对象持有,那么当前获取锁的线程将陷入while循环等待,直到那个持有自旋锁的线程对象释放它所持有的自旋锁,那么那些想要获取该自旋锁的线程对象 将会有 ...

  9. java锁 -- 自旋锁

    1.概念 自旋锁:spinlock,是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU. 之前文章提到的比较并交换,底层使用 ...

  10. php 自旋锁,自旋锁、排队自旋锁、MCS锁、CLH锁(转)

    自旋锁(Spin lock) 转:http://coderbee.net/index.php/concurrent/20131115/577 自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程 ...

最新文章

  1. 2021年大数据Flink(三十四):​​​​​​​Table与SQL ​​​​​​案例一
  2. SignalR网页实时推送
  3. 13.2.虚拟化工具--jstat
  4. K8S使用filebeat统一收集应用日志
  5. 第六章 prototype和constructor
  6. Linux内核系统调用原理与实现
  7. vim 命令模式下光标移动
  8. iPhone Xs上手体验,原来用绿联的转接线就能边充电边听歌
  9. TXS0108双向电平转换芯片用于IIC时的问题
  10. 数据系统服务器更新是什么,更新客户端数据,除了轮询请求服务端,还有什么解决方案?...
  11. 使用先根序列作为插入顺序重建二叉搜索树
  12. Linux 重新加载 nginx 配置命令
  13. 如何对CAD绘图区域进行设置?
  14. 微信小程序---wxss常用属性
  15. 服务器所属文件变成nobody,NFS(expirtfs命令,NFS客户端创建新文件所属组和所属主都为nobody)(示例代码)...
  16. CHM转PDF工具综述
  17. unity 眼球效果 eyes shader
  18. 从零在FPG上实现OFDM(一)
  19. 你还不会ElasticsSearch分页查询?那你看这一篇就够了,快拿走吧
  20. Xcode7 普通icloud账号调试配置

热门文章

  1. 金融支付类APP测试方法——服务端部分
  2. java 银行管理系统
  3. 雷军的公开信:小米是谁,小米为什么而奋斗
  4. android自动关闭uvc相机服务,Android打开外部UVC摄像头代替硬件摄像头
  5. perf 常见使用方法
  6. Redis(三) -- redis简介、各数据类型应用
  7. 函授大专计算机应用和电子商务选哪个,函授大专该如何选择专业
  8. 微信公众号下载文件(避开微信浏览器的文件下载方法)
  9. 使用CTEX生成中文pdf
  10. L1-052 2018我们要赢 (5分)