原子操作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 ...
随机推荐
- php设置时区和strtotime转化为时间戳函数
date_default_timezone_set('PRC');//设置中华人民共和国标准时间 strtotime — 将任何英文文本的日期时间描述解析为 Unix 时间戳 格式:int strto ...
- 跨域问题The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by t
withCredentials 属性 上面说到,CORS请求默认不发送Cookie和HTTP认证信息.如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow- ...
- golang中使用gorm连接mysql操作
一.代码 package main import ( "fmt" "github.com/jinzhu/gorm" _ "github.com/go- ...
- POJ - 3294~Relevant Phrases of Annihilation SPOJ - PHRASES~Substrings POJ - 1226~POJ - 3450 ~ POJ - 3080 (后缀数组求解多个串的公共字串问题)
多个字符串的相关问题 这类问题的一个常用做法是,先将所有的字符串连接起来, 然后求后缀数组 和 height 数组,再利用 height 数组进行求解. 这中间可能需要二分答案. POJ - 3294 ...
- 面试系列18 rediscluster 原理
一.节点间的内部通信机制 1.基础通信原理 (1)redis cluster节点间采取gossip协议进行通信 跟集中式不同,不是将集群元数据(节点信息,故障,等等)集中存储在某个节点上,而是互相之间 ...
- 锋利的Jquery(Table,Checkbox)
1.Table奇数偶数行 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http: ...
- Android开发 WebView的详解
前言 WebView 是Android显示html内容的主要方式,当然TextView也可以加载html内容.但是WebView除了功能更加强大,最重要的是还能调用Html里的JavaScript语言 ...
- 以太坊geth客户端下的一些常用命令
这是一个交互式的 JavaScript 执行环境,在这里面可以执行 JavaScript 代码,其中 > 是命令提示符.在这个环境里也内置了一些用来操作以太坊的 JavaScript 对象,可以 ...
- 非旋Treap及其可持久化
平衡树这种东西,我只会splay.splay比较好理解,并且好打,操作方便. 我以前学过SBT,但并不是很理解,所以就忘了怎么打了. 许多用平衡树的问题其实可以用线段树来解决,我们真正打平衡树的时候一 ...
- 廖雪峰Java15JDBC编程-3JDBC接口-4JDBC事务
1 数据库事务:Transaction 1.1 定义 若干SQL语句构成的一个操作序列 要么全部执行成功 要么全部执行不成功 1.2 数据库事务具有ACID特性: Atomicity:原子性 一个事务 ...