原子操作atomic
一、原子操作:即不可再细分的操作,最小的执行单位,在操作完之前都不会被任何事件中断。
整型原子操作:对int类型的操作变成原子操作。
int i = 0;
i = i + 2; <--- 转换为汇编时,不止一条语句,所以可能会被中断。
数据类型:atomic_t 在 linux/types.h 中定义。
typedef struct
{
int counter;
}atomic_t;
atomic_t.counter的变量值变化就是原子的,当然我们不能直接去读写这个变量值,要使用一些函数才能对它进行操作,
这些函数都是围绕着 atomic_t.counter 变量的修改、获取而设计的。
示例:
/* 一般定义为全局变量 */
atomic_t n = ATOMIC_INIT(3); // 将变量 n.counter 的初始值设为 3 --> n.counter = 3
n.counter = 10; // 这种写法没有意义,它并不是原子操作。
atomic_set(&n, 2); // 将变量 n.counter 的初始值设为 2 --> n.counter = 2
atomic_add(5, &n); // 将变量 n.counter 的值加上 5 --> n.counter += 5
atomic_dec(&n); // 将变量 n.counter 的值减 1 --> n.counter -= 1
printk("n = %d", atomic_read(&n)); // 读取变量 n.counter 的值 此时 n.counter == 6
接口:
32位整型原子操作的其他的函数(列出,方便查询):
ATOMIC_INIT(int i) 宏 用 i 初始化 atomic_t 类型的变量
int atomic_read(atomic_t *v) 宏 读 v 的值
void atomic_set(atomic_t *v, int i); 宏 设 v 的值为 i
void atomic_add(int i, atomic_t *v); 宏 将 v 的值加 i
void atomic_sub(int i, atomic_t *v); 宏 将 v 的值减 i
void atomic_inc(atomic_t *v); 宏 将 v 的值加 1
void atomic_dec(atomic_t *v); 宏 将 v 的值减 1
int atomic_sub_and_test(int i, atomic_t *v); 宏 将 v 的值减 i,(0==v) ? 非0值 : 0;
int atomic_inc_and_test(atomic_t *v); 宏 将 v 的值加 1,(0==v) ? 非0值 : 0;
int atomic_dec_and_test(atomic_t *v); 宏 将 v 的值减 1,(0==v) ? 非0值 : 0;
int atomic_add_negative(int i, atomic_t *v); 宏 将 v 的值加 1,(v<0) ? 非0值 : 0;
int atomic_add_return(int i, atomic_t *v); 函 将 v 的值加 i,并返回 +i 后的结果
int atomic_sub_return(int i, atomic_t *v); 函 将 v 的值减 i,并返回 -i 后的结果
int atomic_inc_return(atomic_t *v); 宏 将 v 的值加 1,并返回 +1 后的结果
int atomic_dec_return(atomic_t *v); 宏 将 v 的值减 1,并返回 -1 后的结果
int atomic_add_unless(atomic_t *v, int a, int u); 涵 ( v!=u ) ? v+a,返回非0值 : 0;
int atomic_inc_not_zero(atomic_t *v); 宏 ( v!=0 ) ? v+1,返回非0值 : 0;
64位整型原子操作:和32位整型原子操作一致,所操作的接口只是名称不同,功能一致。
ATOMIC64_INIT(int i) 宏 用 i 初始化 atomic_t 类型的变量
int atomic64_read(atomic_t *v) 宏 读 v 的值
void atomic64_set(atomic_t *v, int i); 宏 设 v 的值为 i
void atomic64_add(int i, atomic_t *v); 宏 将 v 的值加 i
void atomic64_sub(int i, atomic_t *v); 宏 将 v 的值减 i
void atomic64_inc(atomic_t *v); 宏 将 v 的值加 1
void atomic64_dec(atomic_t *v); 宏 将 v 的值减 1
...
...
注意:
32位整型原子操作在64位下执行不会有问题,但是64位整型原子操作在32位系统下执行会造成难以预料的后果。
为了让自己的驱动程序通用,若非必要则尽量使用32位整型原子操作。
位原子操作:
这种操作的数据类型是 unsigned long, 32位系统下为32bit,64位系统下为64bit。
位原子操作函数主要功能是将 unsigned long 变量中的指定位设为0或设为1。
示例:
unsigned long value = 0;
// 设置 value 的第0位为1, value = 0000000000000000 0000000000000001
set_bit(0, &value);
// 设置 value 的第2位为1, value = 0000000000000000 0000000000000101
set_bit(2, &value);
// 设置 value 的第0位为0, value = 0000000000000000 0000000000000100
clear_bit(0, &value);
// 将 value 的第0位取反,第0位为1则设为0,为0则设为1
change_bit(0, &value);
接口:都是宏
void set_bit(int nr, void *addr); 将addr的第nr位设为 1
void clear_bit(int nr, void *addr); 将addr的第nr位设为 0
void change_bit(int nr, void *addr); 将addr的第nr位取反
int test_bit(int nr, void *addr); 如果addr的第nr位为1则返回非0值,否则返回0
int test_and_set_bit(int nr, void *addr); 将addr的第nr位设为 1,设置之前该位为1则返回非0值,否则返回0
int test_and_clear_bit(int nr, void *addr); 将addr的第nr位设为 0,设置之前该位为1则返回非0值,否则返回0
int test_and_change_bit(int nr, void *addr);将addr的第nr位设取反,设置之前该位为1则返回非0值,否则返回0
整型原子操作和位原子操作都是围绕一个变量的操作做为原子操作。
可以用来限定设备能被几个进程操作,和作为计数器使用。
实例:(例如操作打印机)
#define DevNumber 1
atomic_t v = ATOMIC_INIT(DevNumber); // 限定1个
// 打开打印机设备
int OpenPrinterDevice(unsigned char *buf, unsigned int size)
{
// 将v减1后,判断v是否为0
if(atomic_dec_and_test(v))
{
// v 为 0,表示成功得到操作权限
return 0;
}
else
{
// 表示 设备已经被占用。
return -EBUSY;
}
}
// 释放打印机设备
void ClosePrinterDevice(void)
{
atomic_set(&v, DevNumber);
}
二、自旋锁
原子操作可以让指定变量的操作是原子的。很多时候我们在处理一些数据执行某些动作的时候要保证执行过程中
不能被中断,要求是原子的,而整型、位原子操作要实现这种需求就会比较复杂一些。而使用自旋锁则简单很多。
示例:
/* 一般定义为全局变量 */
spinlock_t lock; // 定义一把自旋锁
spin_lock_init(&lock); // 初始化这把自旋锁
或者使用宏来定义并初始化 DEFINE_SPINLOCK(lock)
void MyLock()
{
/* 使用场合:中断下半部与中断服务程序不会进入临界区 */
spin_lock(&lock); // 获取并上锁
// ... <--- 关闭了内核的抢占,但仍受硬中断和中断下半部的影响
// 临界区代码
// ...
spin_unlock(&lock); // 释放解锁,恢复内核的抢占
}
void MyIRQ(void) // 产生中断
{
spin_lock(&lock); // 由于该锁未被释放,所以中断服务参数就会一直自旋(双重请求)
// ... // 而中断服务未退出 就无法退回MyLock(),就无法释放锁造成死锁
// 临界区代码
// ...
spin_unlock(&lock); // 释放解锁,恢复内核的抢占
}
这种情况下需要采用以下的方式上锁:
void MyLockIrq()
{
/*使用场合:
1、中断服务函数与中断下半部都需要进入该临界区
2、中断服务函数需要进入该临界区
*/
spin_lock_irq(&lock); // 获取并上锁 临界区的内容可能会被
// ... <--- 关闭了内核的抢占以及硬件中断响应,软中断依赖硬件中断,自然也不生效。
// 临界区代码
// ...
spin_unloc_irq(&lock); // 释放解锁,恢复内核的抢占以及硬件中断,中断下半部也有效
}
如果访问临界区的资源的代码不是放在中断服务函数中,而是放在中断下半部也会出现相似的情况,
即在MyLock()上锁之后,产生一个硬件中断,当执行完中断服务函数之后就可能会继续执行中断下
半部的代码,因为它可以抢占进程上下文,而低半部要获取的锁已经被MyLock()上锁,形成死锁。
这种情况也可以用void MyLockIrq()这种方式,但是最好用一下方式,更快:
void MyLockBh()
{
/* 使用场合:中断下半部与进程上下文都需要进入该临界代码 */
spin_lock_bh(&lock); // 获取并上锁
// ... <--- 关闭了内核的抢占以及中断下半部,但受硬件中断影响
// 临界区代码
// ...
spin_unloc_bh(&lock); // 释放解锁,恢复内核的抢占以及中断下半部
}
对于一个CPU的机器来说:当有A、B进程都要执行临界区的代码时,假设A先获得锁之后,B进程不会被调度,
系统呈现假死状态, 只有当A释放锁之后,B进程才会被调度再去获取锁,此时A已经释放锁,所以B也就顺利得到锁。
对于两个CPU的机器来说:当有A、B进程都要执行临界区的代码时,假设A先获得锁之后,B进程也会去获取锁,
但是锁已经被A得到,那么B进程则会一直不停的循环检测锁是否被释放,此时系统会呈现假死状态。
(这些现象可以在VM虚拟机上验证,VM虚拟机可以调整CPU个数)
A进程:
spin_lock(&lock); // 获取并上锁
// ...
// 临界区代码 <--- A在执行临界区代码 ---cpu0
// ...
spin_unlock(&lock); // 释放解锁
B进程:
spin_lock(&lock); // <--- 阻塞这里,一直在spin_lock内部不停的循环等待 ---cpu1
// ...
// 临界区代码
// ...
spin_unlock(&lock); // 释放解锁
也就说,只有一个进程能进入临界区,其他进程要想进入临界区只能自己在原地循环旋转等待。
使用注意事项:
1、自旋锁实际上是忙等待,因为在等待锁的时候是在不停的循环等待,长时间占用锁会极大降低系统性能。
2、要避免在临界区中调用可能会产生睡眠的函数,因为此时抢占、中断已经关闭,无法被唤醒导致无法解锁。
3、若数据被软中断共享,也需要加锁,因为在不同处理器上存在软中断同时执行问题。
4、注意避免死锁,例如上述例子,A进程获得了锁之后,又继续获取该锁,因为该锁已经被A获取,
所以该锁无法再次被A获取,A就会一直循环打转等待,A没有机会释放该锁,该CPU被锁死,
对于多颗CPU来说,其他进程又无法释放该锁,形成死循环,导致死机。
A进程:
spin_lock(&lock); // 获取并上锁 关闭了内核的抢占
spin_lock(&lock); // <--- 阻塞这里,一直在spin_lock内部不停的循环 cpu被锁死
// ...
// 临界区代码 <--- 无法得到执行
// ...
spin_unlock(&lock); // 没有机会释放
<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">
原子操作atomic的更多相关文章
- java并发:线程同步机制之Volatile关键字&原子操作Atomic
volatile关键字 volatile是一个特殊的修饰符,只有成员变量才能使用它,与Synchronized及ReentrantLock等提供的互斥相比,Synchronized保证了Synchro ...
- 什么是Java中的原子操作( atomic operations)
1.啥是java的原子性 原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行. 一个很经典的例子就是银行账户转账问题: 比如从账户A向账户B转1000元,那么 ...
- 并发编程之原子操作Atomic&Unsafe
原子操作:不能被分割(中断)的一个或一系列操作叫原子操作. 原子操作Atomic主要有12个类,4种类型的原子更新方式,原子更新基本类型,原子更新数组,原子更新字段,原子更新引用.Atomic包中的类 ...
- 原子操作atomic解读
下面从一个问题引入: // ConsoleApplication5.cpp : 定义控制台应用程序的入口点. #include "stdafx.h" #include<ran ...
- golang中的原子操作atomic包
1. 概念 原子操作 atomic 包 加锁操作涉及到内核态的上下文切换,比较耗时,代价高, 针对基本数据类型我们还可以使用原子操作来保证并发的安全, 因为原子操作是go语言提供的方法,我们在用户态就 ...
- C++11并发编程:原子操作atomic
一:概述 项目中经常用遇到多线程操作共享数据问题,常用的处理方式是对共享数据进行加锁,如果多线程操作共享变量也同样采用这种方式. 为什么要对共享变量加锁或使用原子操作?如两个线程操作同一变量过程中,一 ...
- 原子操作(atomic operation)
深入分析Volatile的实现原理 引言 在多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共 ...
- 【C# 线程】 atomic action原子操作|primitive(基元、原语)
概念 原子操作(atomic action):也叫primitive(原语.基元),它是操作系统用语范畴.指由若干条指令组成的,用于完成一定功能的一个过程. 原语是由若干个机器指令构成的完成某种特定 ...
- Java中的Atomic包
Atomic包的作用 方便程序员在多线程环境下,无锁的进行原子操作 Atomic包核心 Atomic包里的类基本都是使用Unsafe实现的包装类,核心操作是CAS原子操作: 关于CAS compare ...
随机推荐
- lxhgww的奇思妙想 长链剖分板子
https://vijos.org/d/Bashu_OIers/p/5a79a3e1d3d8a103be7e2b81 求k级祖先,预处理nlogn,查询o1 //#pragma GCC optimiz ...
- System.Math.cs
ylbtech-System.Math.cs 1. 程序集 mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c56193 ...
- transient在java中的作用
java 的transient关键字的作用是需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中. trans ...
- 求一个n!中尾数有多少个零
题目描述: 输入一个正整数n,求n!(即阶乘)末尾有多少个0? 比如: n = 10; n! = 3628800,所以答案为2 输入描述: 输入为一行,n(1 ≤ n ≤ 1000) 输出描述: 输出 ...
- CSS 实现自适应正方形
在处理移动端页面时,我们有时会需要将banner图做成与屏幕等宽的正方形以获得最佳的体验效果,比如,商品详情页, 方法1.CSS3 vw单位 CSS3 中新增了一组相对于可视区域百分比的长度单位 vw ...
- 金融IT的算法要求
岗位职责 1.负责宏观经济预测的算法研究 2.负责债券.股票.基金等品种的模型研究 3.负责持仓收益分析,及绩效归因等模型研究 任职要求 1.一般数学: 线性代数与矩阵运算 随机过程 微积分 概率论 ...
- ASP.NET WEB API 特性路由
一.什么是特性路由? 特性路由是指将RouteAttribute或自定义继承自RouteAttribute的特性类标记在控制器或ACTION上,同时指定路由Url字符串,从而实现路由映射,相比之前的通 ...
- Python全栈开发:运算符
1.算数运算: 2.比较运算: 3.赋值运算: 4.逻辑运算: 5.成员运算:
- 威胁预警|首现新型RDPMiner挖矿蠕虫 受害主机易被添加恶意账户
近日,阿里云安全发现一种新型挖矿蠕虫RDPMiner,通过爆破Windows Server 3389端口RDP服务的方式进行挖矿木马传播,致使用户CPU占用率暴涨,机器卡顿,更被创建名为Default ...
- resful风格
package com.atguigu.springboot.controller; import com.atguigu.springboot.dao.DepartmentDao; import c ...