并发与竞争(二)原子操作

原子操作的概念

什么是原子操作呢?

原子操作中的“原子”指的是化学反应中的最小微粒。在Linux中用原子来形容一个操作或者一个函数是最小的执行单位,是不可以被打断的。所以原子操作指的是该操作在执行完之前不会被任何事物打断。

原子操作的应用

原子操作一般用于整形变量或者位的保护。

比如,定义一个变量a,如果程序A正在给变量a赋值,此时程序B也要来操作变量a,这时候就发生了并发与竞争。程序A的操作就有可能被程序B打断。如果我们使用原子操作对变量a进行保护,就可以避免这种问题。

原子整形变量描述

Linux中定义了一个叫做atomic_tatomic64_t的结构体来描述原子变量,其中atomic_t是用于32位系统,atomic64_t是用于64位系统。代码如下:

typedef struct {
    
    
	int counter;
} atomic_t;

#ifdef CONFIG_64BIT
typedef struct {
    
    
	long counter;
} atomic64_t;
#endif

原子整形操作的API函数

32位API(32位操作系统的计算机)

原子整数操作 描述
ATOMIC_INIT(long i) 在声明一个atomic_t变量时,将它初始化为 i
long 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,返回真;否则返回假
int atomic_add_negative(int i, atomic_t *v) 原子地给 v 加 i,如果结果是负数,返回真;否则返回假
long atomic_add_return(int i, atomic_t *v) 原子地给 v 加 i,且返回结果
long atomic_sub_return(int i, atomic_t *v) 原子地从 v 减 i,且返回结果
long atomic_inc_return(int i, atomic_t *v) 原子地从 v 加 i,且返回结果
long atomic_dec_return(int i, atomic_t *v) 原子地从 v 减 i,且返回结果
int atomic_dec_and_test(atomic_t *v) 原子地从 v 减 1,如果结果等于0,返回真;否则返回假
int atomic_inc_and_test(atomic_t *v) 原子地给 v 加 1,如果结果等于0,返回真;否则返回假

64位API(64位操作系统的计算机)

原子整数操作 描述
ATOMIC64_INIT(long i) 在声明一个atomic64_t变量时,将它初始化为 i
long atomic64_read(atomic64_t *v) 原子地读取整数变量 v
void atomic64_set(atomic64_t *v, int i) 原子地设置 v 值为 i
void atomic64_add(int i, atomic64_t *v) 原子地给 v 加 i
void atomic64_sub(int i, atomic64_t *v) 原子地从 v 减 i
void atomic64_inc(atomic64_t *v) 原子地给 v 加 1
void atomic64_dec(atomic64_t *v) 原子地给 v 减 1
int atomic64_sub_and_test(int i, atomic64_t *v) 原子地从 v 减 i,如果结果等于 0,返回真;否则返回假
int atomic64_add_negative(int i, atomic64_t *v) 原子地给 v 加 i,如果结果是负数,返回真;否则返回假
long atomic64_add_return(int i, atomic64_t *v) 原子地给 v 加 i,且返回结果
long atomic64_sub_return(int i, atomic64_t *v) 原子地从 v 减 i,且返回结果
long atomic64_inc_return(int i, atomic64_t *v) 原子地从 v 加 i,且返回结果
long atomic64_dec_return(int i, atomic64_t *v) 原子地从 v 减 i,且返回结果
int atomic64_dec_and_test(atomic64_t *v) 原子地从 v 减 1,如果结果等于0,返回真;否则返回假
int atomic64_inc_and_test(atomic64_t *v) 原子地给 v 加 1,如果结果等于0,返回真;否则返回假

原子位操作API函数

除原子整数操作外,内核也提供了一组针对位的操作函数,这些位操作函数是对普通的内存地址进行操作的,参数是一个指针和一个位号。

原子位操作 描述
void set_bit(int nr, void *addr) 原子地设置 addr 所指对象的第 nr 位
void clear_bit(int nr, void *addr) 原子地清空 addr 所指对象的第 nr 位
void change_bit(int nr, void *addr) 原子地翻转 addr 所指对象的第 nr 位
void test_and_set_bit(int nr, void *addr) 原子地设置 addr 所指对象的第 nr 位,并返回原先的值
void test_and_clear_bit(int nr, void *addr) 原子地清空 addr 所指对象的第 nr 位,并返回原先的值
void test_and_change_bit(int nr, void *addr) 原子地翻转 addr 所指对象的第 nr 位,并返回原先的值
void test_bit(int nr, void *addr) 原子地返回 addr 所指对象的第 nr 位

原子整形操作举例

atomic64_t v = ATOMIC64_INT(0); // 定义并初始化原子变量v=0

atomic64_set(&v,1); //设置v=1
atomic64_read(&v); //读取v的值,此时v的值是1
atomic64_inc(&v); //v的值加1,此时v的值是2

写代码验证

#include <linux/atomic.h>
#include <asm/atomic.h> 

static atomic64_t v = ATOMIC_INIT(1);

...
static int cdev_test_open(struct inode *inode, struct file *file)
{
    
    
	if(!atomic64_dec_and_test(&v)) // 第一次条件不满足
	{
    
    
		atomic64_inc(&v);
		return -EBUSY;
	}
	...
	
	return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
    
    
	atomic64_inc(&v);
	return 0;
}

解释:

  • 上面的是一个驱动的模块,当程序A调用该驱动时没有问题此时v=1(第一次调用if(!atomic64_dec_and_test(&v)) // 第一次条件不满足)打开成功,此时原子变量v=0了,在程序A调用期间程序B也调用该模块,此时c=0条件if(!atomic64_dec_and_test(&v)会满足会返回-EBUSY即驱动调用将会不成功!
  • 程序A成功调用完毕以后再释放驱动模块时会将原子变量加一。这样下一个程序就可以调用成功了。

实例

led.c

#include <linux/init.h>         //初始化头文件
#include <linux/module.h>       //最基本的文件,支持动态添加和卸载模块。
#include <linux/miscdevice.h>   //包含了miscdevice结构的定义及相关的操作函数。
#include <linux/fs.h>           //文件系统头文件,定义文件表结构(file,buffer_head,m_inode等)
#include <linux/uaccess.h>      //包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义。
#include <linux/io.h>           //包含了ioremap、iowrite等内核访问IO内存等函数的定义。
#include <linux/kernel.h>		//驱动要写入内核,与内核相关的头文件

#include <linux/atomic.h>
#include <asm/atomic.h> 

#define GPIO_DR 0xfdd60000     //LED物理地址,通过查看原理图得知
unsigned int *vir_gpio_dr;     //存放映射完的虚拟地址的首地址

static atomic64_t v = ATOMIC_INIT(1); // 初始化一个原子变量v等于1

ssize_t misc_read (struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
    
    
	printk("misc_read\n ");
	return 0;
}

ssize_t misc_write (struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
    
    	
    /*应用程序传入数据到内核空间,然后控制蜂鸣器的逻辑,在此添加*/
	// kbuf保存的是从应用层读取到的数据
    char kbuf[64] = {
    
    0};
    // copy_from_user 从应用层传递数据给内核层
	if(copy_from_user(kbuf,ubuf,size)!= 0) 
	{
    
    
        // copy_from_user 传递失败打印
		printk("copy_from_user error \n ");
		return -1;
	}
    //打印传递进内核的数据
    //printk("kbuf is %d\n ",kbuf[0]); 
	if(kbuf[0]==1) //传入数据为1 ,LED亮
	{
    
    
		*vir_gpio_dr = 0x80008000; 
	}
	else if(kbuf[0]==0) //传入数据为0,LED灭
		*vir_gpio_dr = 0x80000000;
	return 0;
}

int misc_release(struct inode *inode,struct file *file)
{
    
    
	atomic64_inc(&v); // 释放的时候给v加1,便于下一个应用打开该设备

	printk("hello misc_relaease bye bye \n ");
	return 0;
}

int misc_open(struct inode *inode,struct file *file)
{
    
    
	if(!atomic64_dec_and_test(&v)) // 只会允许被一个设备打开,不会允许多个设备同时打开的逻辑
	{
    
    
		atomic64_inc(&v);
		return -EBUSY;
	}

	printk("hello misc_open\n ");
	return 0;
}
//文件操作集
struct file_operations misc_fops={
    
    
	.owner = THIS_MODULE,
	.open = misc_open,
	.release = misc_release,
	.read = misc_read,
	.write = misc_write,
};
//miscdevice结构体
struct miscdevice  misc_dev = {
    
    
	.minor = MISC_DYNAMIC_MINOR,
	.name = "hello_misc",
	.fops = &misc_fops,
};
static int misc_init(void)
{
    
    
	int ret;
    //注册杂项设备
	ret = misc_register(&misc_dev);
	if(ret<0)
	{
    
    
		printk("misc registe is error \n");
	}
	printk("misc registe is succeed \n");
    //将物理地址转化为虚拟地址
	vir_gpio_dr = ioremap(GPIO_DR,4);
	if(vir_gpio_dr == NULL)
	{
    
    
	printk("GPIO_DR ioremap is error \n");
	return EBUSY;
	}
	printk("GPIO_DR ioremap is ok \n");	
	return 0;
}
static void misc_exit(void){
    
    
    //卸载杂项设备
	misc_deregister(&misc_dev);
	iounmap(vir_gpio_dr);
	printk(" misc gooodbye! \n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");

Makefile

obj-m += led.o
KDIR =/home/liefyuan/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:
	make -C  $(KDIR) M=$(PWD) modules modules ARCH=arm64 CROSS_COMPILE=/usr/local/arm64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
clean:
	rm -rf modules.order *.o workqueue.o  Module.symvers *.mod.c *.ko

编译模块:

扫描二维码关注公众号,回复: 14807718 查看本文章
$ export ARCH=arm64
$ export CROSS_COMPILE=aarch64-linux-gnu-
$ make

测试应用:

app.c:2秒钟闪烁

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc,char *argv[])
{
    
    
	int fd;
	char buf[64] = {
    
    0};//定义buf缓存
	char val[1];
	//打开设备节点
	fd = open("/dev/hello_misc",O_RDWR);
	if(fd < 0)
	{
    
    
		//打开设备节点失败
		perror("open error \n"); 
		return fd;
	}
	//把缓冲区数据写入文件中
	while(1)
	{
    
    
		val[0] = 1;
		write(fd, val, sizeof(val));
		sleep(2);
		val[0] = 0;
		write(fd, val, sizeof(val));
		sleep(2);
	}
	close(fd);
	return 0;
}

app2.c:1秒钟闪烁

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc,char *argv[])
{
    
    
	int fd;
	char buf[64] = {
    
    0};//定义buf缓存
	char val[1];
	//打开设备节点
	fd = open("/dev/hello_misc",O_RDWR);
	if(fd < 0)
	{
    
    
		//打开设备节点失败
		perror("open error \n"); 
		return fd;
	}
	//把缓冲区数据写入文件中
	while(1)
	{
    
    
		val[0] = 1;
		write(fd, val, sizeof(val));
		sleep(1);
		val[0] = 0;
		write(fd, val, sizeof(val));
		sleep(1);
	}
	close(fd);
	return 0;
}

编译:

aarch64-linux-gnu-gcc app.c -o app.armelf
aarch64-linux-gnu-gcc app2.c -o app2.armelf

测试

安装模块,设备节点出现:

[root@RK356X:/opt]# insmod led.ko
[   57.789237] led: loading out-of-tree module taints kernel.
[root@RK356X:/opt]# [   57.790784] misc registe is succeed
[   57.791188] GPIO_DR ioremap is ok

[root@RK356X:/opt]# ls /dev/hello_misc
/dev/hello_misc

先后台运行app.armelf

 ./app.armelf &

OK,灯已经按2秒频率闪烁起来了。

再在前台运行app2.armelf

[root@RK356X:/opt]# ./app2.armelf
open error
: Device or resource busy

目的达到了,打开出错。

这就是原子操作运用到实际的一个简单应用!

猜你喜欢

转载自blog.csdn.net/qq_28877125/article/details/128210852
今日推荐