欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 养生 > 驱动-原子操作

驱动-原子操作

2025/4/22 3:33:11 来源:https://blog.csdn.net/ItJavawfc/article/details/147305794  浏览:    关键词:驱动-原子操作

前面 对并发与竞争进行了实验, 两个 app 应用程序之间对共享资源的竞争访问引起了数据传输错误, 而在 Linux 内核中, 提供了四种处理并发与竞争的常见方法:

分别是原子操作、 自旋锁、 信号量、 互斥体, 这里了解下原子操作

文章目录

  • 参考资料
    • 原子整数操作
    • 原子位操作
  • 测试实验-驱动字符设备运用原子操作
    • 驱动程序源码 atomic.c
    • 编译文件 Makefile
    • 测试程序 app.c
    • 加载驱动 insmod
    • 查看dev 下设备是否生成 ls /dev/device_test
    • 驱动传参测试
  • 总结


参考资料

驱动-并发与竞争
Linux驱动之 原子操作
Linux驱动—原子操作

#原子操作的基本概念
原子操作是指不可中断的一个或一系列操作,即在执行过程中不会被线程调度机制打断,保证在多核/多线程环境下对共享数据的操作是安全的。

“原子” 是化学世界中不可再分的最小微粒, 一切物质都由原子组成。 在 Linux 内核中的原子操作可以理解为“不可被拆分的操作” , 就是不能被更高等级中断抢夺优先的操作。 在 C语言中可以使用以下代码对一个整形变量赋值。

int v;//定义一个 int 类型的变量 v
v = 1;//将 int 类型的变量 v 赋值为 1

而上述代码仍然不是“不可拆分的操作”, C 语言程序仍然需要翻译成汇编指令, 在汇编指令的执行过程中仍可能会有竞争的产生。 而原子操作会将整形变量的操作当成一个整体, 不可再进行分割。 而原子操作又可以进一步细分为“整型原子操作”和“位原子操作”

在 Linux 内核中使用 atomic_t 和 atomic64_t 结构体分别来完成 32 位系统和 64 位系统的
整形数据原子操作, 两个结构体定义在“内核源码/include/linux/types.h”

原子整数操作


#include <linux/types.h>
#include <asm/atomic.h>// 定义原子变量
atomic_t v = ATOMIC_INIT(0);// 常用操作
atomic_read(&v);        // 读取原子变量的值
atomic_set(&v, i);      // 设置原子变量的值为i
atomic_add(i, &v);      // 原子加i
atomic_sub(i, &v);      // 原子减i
atomic_inc(&v);         // 原子加1
atomic_dec(&v);         // 原子减1
atomic_sub_and_test(i, &v); // 减i后测试是否为0
atomic_inc_and_test(&v);    // 加1后测试是否为0
atomic_dec_and_test(&v);    // 减1后测试是否为0

原子位操作


#include <asm/bitops.h>unsigned long word = 0;set_bit(nr, &word);     // 设置第nr位为1
clear_bit(nr, &word);   // 清除第nr位
change_bit(nr, &word);  // 翻转第nr位
test_bit(nr, &word);    // 测试第nr位是否为1
test_and_set_bit(nr, &word);    // 测试并设置位
test_and_clear_bit(nr, &word);  // 测试并清除位
test_and_change_bit(nr, &word); // 测试并翻转位

测试实验-驱动字符设备运用原子操作

为了解决前面实验中并发与竞争的问题, 加入原子整形操作相关实验代码, 在 open()函数和 release()函数中加入原子整形变量 v 的赋值代码, 并且在 open()函数中加入原子整形变量 v 的判断代码, 从而实现同一时间内只允许一个应用打开该设备节点, 以此来
防止共享资源竞争的产生。

这里的open 操作可以看一下简要代码,方便理解。


static atomic64_t v = ATOMIC_INIT(1);//初始化原子类型变量 v,并设置为 1
static int open_test(struct inode *inode,struct file *file)
{
if(atomic64_read(&v) != 1){//读取原子类型变量 v 的值并判断是否等于 1
return -EBUSY;
} a
tomic64_set(&v,0);//将原子类型变量 v 的值设置为 0
//printk("\nthis is open_test \n");
return 0;
}

就是设置了一个原子操作的变量,初始化的默认值是1,在这个数值为1的时候才能进行操作,当一个程序执行后 改变值,那么其它程序就无法执行了。 这个程序执行完成后再改变为默认值,其它程序就能访问执行自己逻辑了。

驱动程序源码 atomic.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/atomic.h>
#include <linux/errno.h>static atomic64_t v=ATOMIC_LONG_INIT(1);static int open_test(struct  inode  *inode,struct file *file){if(atomic64_read(&v)!=1){return -EBUSY;}atomic64_set(&v,0);printk("\n this is open_test \n");return 0;};static ssize_t read_test(struct file *file,char __user *ubuf,size_t len,loff_t *off)
{int ret;char kbuf[10] = "topeet";//定义char类型字符串变量kbufprintk("\nthis is read_test \n");ret = copy_to_user(ubuf,kbuf,strlen(kbuf));//使用copy_to_user接收用户空间传递的数据if (ret != 0){printk("copy_to_user is error \n");}printk("copy_to_user is ok \n");return 0;
}
static char kbuf[10] = {0};//定义char类型字符串全局变量kbuf
static ssize_t write_test(struct file *file,const char __user *ubuf,size_t len,loff_t *off)
{int ret;ret = copy_from_user(kbuf,ubuf,len);//使用copy_from_user接收用户空间传递的数据if (ret != 0){printk("copy_from_user is error\n");}if(strcmp(kbuf,"topeet") == 0 ){//如果传递的kbuf是topeet就睡眠四秒钟ssleep(4);}else if(strcmp(kbuf,"itop") == 0){//如果传递的kbuf是itop就睡眠两秒钟ssleep(2);}printk("copy_from_user buf is %s \n",kbuf);return 0;
}
static int release_test(struct inode *inode,struct file *file)
{//printk("\nthis is release_test \n");atomic64_set(&v,1);  //将原子变量赋值为1 return 0;
}struct chrdev_test
{dev_t  dev_num;  //定义dev_t类型变量来表示设备号int major,minor; //定义int 类型的主设备号和次设备号struct cdev cdev_test;   //定义字符设备struct class *class_test;   //定义结构体变量class 类
};struct chrdev_test dev1; //创建chardev_test类型结构体变量static struct file_operations fops_test = {.owner=THIS_MODULE,//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块.open = open_test,//将open字段指向chrdev_open(...)函数.read = read_test,//将open字段指向chrdev_read(...)函数.write = write_test,//将open字段指向chrdev_write(...)函数.release = release_test,//将open字段指向chrdev_release(...)函数
};//定义file_operations结构体类型的变量cdev_test_opsstatic int __init chrdev_fops_init(void)//驱动入口函数
{if(alloc_chrdev_region(&dev1.dev_num,0,1,"chrdev_name") < 0){printk("alloc_chrdev_region is error\n");}   printk("alloc_chrdev_region is ok\n");dev1.major=MAJOR(dev1.dev_num);//通过MAJOR()函数进行主设备号获取dev1.minor=MINOR(dev1.dev_num);//通过MINOR()函数进行次设备号获取printk("major is %d\n",dev1.major);printk("minor is %d\n",dev1.minor);使用cdev_init()函数初始化cdev_test结构体,并链接到cdev_test_ops结构体cdev_init(&dev1.cdev_test,&fops_test);dev1.cdev_test.owner = THIS_MODULE;//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块 cdev_add(&dev1.cdev_test,dev1.dev_num,1);printk("cdev_add is ok\n");dev1.class_test  = class_create(THIS_MODULE,"class_test");//使用class_create进行类的创建,类名称为class_testdevice_create(dev1.class_test,NULL,dev1.dev_num,NULL,"device_test");//使用device_create进行设备的创建,设备名称为device_testreturn 0;
}
static void __exit chrdev_fops_exit(void)//驱动出口函数
{cdev_del(&dev1.cdev_test);//使用cdev_del()函数进行字符设备的删除unregister_chrdev_region(dev1.dev_num,1);//释放字符驱动设备号 device_destroy(dev1.class_test,dev1.dev_num);//删除创建的设备class_destroy(dev1.class_test);//删除创建的类printk("module exit \n");}
module_init(chrdev_fops_init);//注册入口函数
module_exit(chrdev_fops_exit);//注册出口函数
MODULE_LICENSE("GPL v2");//同意GPL开源协议
MODULE_AUTHOR("wang fang chen "); //作者信息

编译文件 Makefile

#!/bin/bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
obj-m += atomic.o
KDIR :=/home/wfc123/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:make -C $(KDIR) M=$(PWD) modulesclean:make -C $(KDIR) M=$(PWD) clean

测试程序 app.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main(int argc,char *argv[])
{int fd;//定义int类型的文件描述符char str1[10]={0};//定义读取缓冲区str1fd=open(argv[1],O_RDWR,0666);//调用open函数,打开输入的第一个参数文件,权限为可读可写//fd=open("/dev/device_test",O_RDWR,0666);//调用open函数,打开输入的第一个参数文件,权限为可读可写if(fd<0){printf("open is error\n");return -1;}printf("open is ok\n");if(strcmp(argv[2],"topeet")==0){write(fd,"topeet",sizeof(str1)); }else if(strcmp(argv[2],"itop")==0){write(fd,"itop",sizeof(str1)); }close(fd);//调用close函数,对取消文件描述符到文件的映射return 0;
}

对测试程序进行编译

aarch64-linux-gnu-gcc -o app app.c -static

加载驱动 insmod

如下,测试实验结果
在这里插入图片描述

查看dev 下设备是否生成 ls /dev/device_test

[root@topeet:/mnt/sdcard]# ls /dev/device_test
/dev/device_test
[root@topeet:/mnt/sdcard]#

驱动传参测试

分别执行 测试传参命令如下,结果如下:

[root@topeet:/mnt/sdcard]# ./app /dev/device_test topeet
open is ok
[root@topeet:/mnt/sdcard]# ./app /dev/device_test itop
open is ok
[root@topeet:/mnt/sdcard]# ./app /dev/device_test topeet &
ice_test i[root@topeet:/mnt/sdcard]# ./app /dev/device_test iopen is ok
top
open is error
[root@topeet:/mnt/sdcard]#

在这里插入图片描述

单独执行传参命令验证实验,是没有问题的。两个不同参数同步执行 时候,第二个就执行失败了。 这里起作用的就是 原子操作的效果。

总结

  • 原子操作可以理解为一个计数器,一个原子变量,更具原子变量的值是否是预设的值来保护程序,防止程序资源竞争

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词