信号量、互斥锁、自旋锁、原子操作
linux内核中有多种内核锁,内核锁的作用是:
多核处理器下,会存在多个进程处于内核态的情况,而在内核态下,进程是可以访问所有内核数据的,因此要对共享数据进行保护,即互斥处理;
linux内核锁机制有信号量、互斥锁、自旋锁还有原子操作。
一、信号量(struct semaphore):
是用来解决进程/线程之间的同步和互斥问题的一种通信机制,是用来保证两个或多个关键代码不被并发调用。
信号量(Saphore)由一个值和一个指针组成,指针指向等待该信号量的进程。信号量的值表示相应资源的使用情况。信号量S>=0时,S表示可用资源的数量。执行一次P操作意味着请求分配一个资源,因此S的值减1;当S<0时,表示已经没有可用资源,S的绝对值表示当前等待该资源的进程数。请求者必须等待其他进程释放该类资源,才能继续运行。而执行一个V操作意味着释放一个资源,因此S的值加1;若S<0,表示有某些进程正在等待该资源,因此要唤醒一个等待状态的进程,使之运行下去。
信号量是选择睡眠的方式来对共享工作停止访问的。
也就是说信号量通过PV操作同步解决了进程/线程对临界资源利用的冲突问题;
二、互斥锁:(mutex_lock)
互斥锁同样也是对线程间(不能对进程)同步和互斥的一种另一种机制。
互斥锁更多的是强调对共享资源的锁定作用,当一个线程占用了当前共享资源,使用互斥锁将其lock住之后,其他线程就无法访问,必须等到unlock之后,其他线程才能利用共享资源里面的内容;
互斥锁是选择睡眠的方式来对共享工作停止访问的。
也就是说互斥锁通过对共享资源的锁定和互斥解决利用资源冲突问题;
三、自旋锁(spin_lock):
是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。
四、原子操作:
Reference:
http://blog.sina.com.cn/s/blog_6d7fa49b01014q7p.html
http://blog.csdn.net/vividonly/article/details/6599502
http://blog.csdn.net/williamwang2013/article/details/8517380
http://blog.csdn.net/yikai2009/article/details/8650221
4.1、Linux原子概念:
所谓原子操作,就是“不可中断的一个或一系列操作”。
原子操作,就是不能被更高等级中断抢夺优先的操作。你既然提这个问题,我就说深一点。由于操作系统大部分时间处于开中断状态,所以,一个程序在执行的时候可能被优先级更高的线程中断。而有些操作是不能被中断的,不然会出现无法还原的后果,这时候,这些操作就需要原子操作。就是不能被中断的操作。
硬件级的原子操作:在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都可以认为是“原子操作”,因为中断只发生在指令边缘。在多处理器结构中(Symmetric Multi-Processor)就不同了,由于系统中有多个处理器独立运行,即使能在单条指令中完成的操作也有可能受到干扰。在X86平台生,CPU提供了在指令执行期间对总线加锁的手段。CPU上有一根引线#HLOCK pin连到北桥,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。对于其他平台的CPU,实现各不相同,有的是通过关中断来实现原子操作(sparc),有的通过CMPXCHG系列的指令来实现原子操作(IA64)。本文主要探讨X86平台下原子操作的实现。
4.2、Linux内核两组原子操作接口:
1、原子整数操作
原子操作通常针对int或bit类型的数据,但是Linux并不能直接对int进行原子操作,而只能通过atomic_t的数据结构来进行。
定义于#include<asm/atomic.h>
图1.1 内核中的整数原子操作函数
2、内核中提供的一些主要位原子操作函数。同时内核还提供了一组与上述操作对应的非原子位操作函数,名字前多两下划线。由于不保证原子性,因此速度可能执行更快。
定义于#include<asm/bitops.h>
图1.2 内核中的位原子操作函数
1 void atomic_set(atomic_t *v,int i); //设置原子变量v的值为i
2 atomic_t v = ATOMIC_INIT(0); //定义原子变量v,并初始化为0;
3
4 atomic_read(atomic_t* v); //返回原子变量v的值;
5
6 void atomic_add(int i, atomic_t* v); //原子变量v增加i;
7 void atomic_sub(int i, atomic_t* v);
8
9 void atomic_inc(atomic_t* v); //原子变量增加1;
10 void atomic_dec(atomic_t* v);
11
12 int atomic_inc_and_test(atomic_t* v); //先自增1,然后测试其值是否为0,若为0,则返回true,否则返回false;
13 int atomic_dec_and_test(atomic_t* v); //先自减1,然后测试其值是否为0,若为0,则返回true,否则返回false
14 int atomic_sub_and_test(int i, atomic_t* v); //先减i,然后测试其值是否为0,若为0,则返回true,否则返回false;
15 //注意:只有自加,没有加操作
16
17 int atomic_add_return(int i, atomic_t* v); //v的值加i后返回新的值;
18 int atomic_sub_return(int i, atomic_t* v);
19 int atomic_inc_return(atomic_t* v); //v的值自增1后返回新的值;
20 int atomic_dec_return(atomic_t* v);
实例代码:
在scull_open 函数和scull_close函数中:
如果没有进程在使用该驱动 ,原子变量值 为 1 ,将原子变量减 一 为 0 ,函数返回 true ,再 !true 为 假 ,if 里面的代码不执行;这样打开了、并使用该驱动, 原子变量变为 0;
如果再有进程来打开驱动程序,0-1 = 负1,返回 false ,if 条件成立,运行里面的代码,将原子变量加一恢复到 0,程序返回;
最后, 在应用程序退出时 close 函数, 自增 恢复原子变量值为 1:
1 static atomic_t scull_available = ATOMIC_INIT(1); //init atomic
2
3 int scull_open(struct inode *inode, struct file *filp)
4 {
5 struct scull_dev *dev; // device information
6
7 dev = container_of(inode->i_cdev, struct scull_dev, cdev);
8 filp->private_data = dev; // for other methods
9 if(!atomic_dec_and_test(&scull_available)){
10 atomic_inc(&scull_available);
11 return -EBUSY;
12 }
13 return 0; // success
14 }
15
16 int scull_release(struct inode *inode, struct file *filp)
17 {
18 atomic_inc(&scull_available);
19 return 0;
20 }
以上总结几点:
互斥锁与信号量的区别:
1、信号量一般以同步的方式对共享资源进行控制,而互斥锁通过互斥的方式对共享资源对其进行控制;
2、信号量可以对进程的共享资源进行控制,而互斥锁不行;
3、信号量的值为非负整数,而互斥锁的值只能为0或1;
4、互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到;
自旋锁与互斥锁的区别:
1、因为自旋锁不会引起调用者睡眠,所以效率比较高
2、自旋锁比较适用于锁使用者保持锁时间比较短的情况。
3、自旋锁容易造成死锁,所以需要安全使用它;
- HDU 3032 Nim or not Nim?(Multi-Nim)
- POJ 2311 Cutting Game(二维SG+Multi-Nim)
- js去掉html标签和去掉字符串文本的所有的空格
- php操作memcache的使用测试总结
- linux awk命令详解
- php str_split 解决中文
- PHP汉字转拼音函数
- 51NOD 1185 威佐夫游戏 V2(威佐夫博弈)
- HDU 1527 取石子游戏(威佐夫博弈)
- PHP文件操作类
- Linux添加/删除用户和用户组
- 次小生成树
- HDU 4786Fibonacci Tree(最小生成树)
- HDU 1847 Good Luck in CET-4 Everybody!(找规律版巴什博奕)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- redis实战第七篇 使用redis工具(redis-cli)搭建redis cluster
- 使用ROS2机器人操作系统进行多机器人编程技术实践(Multi-Robot Programming Via ROS2 )
- 自动化运维实践 | Ansible playbook重用
- 手把手教你打造高效的 Kubernetes 命令行终端
- docker save load export import的区别
- 使用ABAC控制数据访问
- linux 压力测试工具之ab
- 什么是Python Wheels?为什么要关心它?
- Nginx fastcgi_cache权威指南
- 玩转dockerfile
- redis实战第六篇 手动创建redis cluster
- docker容器入门最佳教程
- redis实战第五篇 jedis 连接 redis sentinel详解
- redis实战第四篇 手动容灾故障转移记录
- 10条很棒的Python一行代码