• 第20章 原子操作实验(iTOP-RK3568开发板驱动开发指南 )


    瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。


    【公众号】迅为电子

    【粉丝群】824412014(加群获取驱动文档+例程)

    【视频观看】嵌入式学习之Linux驱动(第三篇-并发与竞争-全新升级)_基于RK3568

    【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板


    第20章 原子操作实验

    在上一章节的实验中,对并发与竞争进行了实验,两个app应用程序之间对共享资源的竞争访问引起了数据传输错误,而在Linux内核中,提供了四种处理并发与竞争的常见方法,分别是原子操作、自旋锁、信号量、互斥体,在之后的几个章节中会依次对上述四种方法进行讲解。

    本章首先对四种常见方法中的原子操作进行讲解。

    20.1 原子操作

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

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

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

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

     typedef struct {
         int counter;
     } atomic_t;
     
     #ifdef CONFIG_64BIT
     typedef struct {
         long counter;
    } atomic64_t;
     #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    例如可以使用以下代码定义一个64位系统的原子整形变量:

    atomic64_t v;
    
    • 1

    在成功定义原子变量之后,必然要对原子变量进行读取、加减等动作,原子操作的部分常用API函数如下所示,定义在“内核源码/include/linux/atomic.h”文件中,所以在接下来的实验中需要加入该头文件的引用。

    函数描述
    ATOMIC_INIT(int i)定义原子变量的时候对其初始化,赋值为i
    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_dec_return(atomic_t *v)原子变量v减1,并返回v的值。
    int atomic_inc_return(atomic_t *v)原子变量v加 1,并返回v的值。
    int atomic_sub_and_test(int i, atomic_t *v)原子变量v减 i,如果结果为0就返回真,否则返回假
    int atomic_dec_and_test(atomic_t *v)原子变量v减 1,如果结果为0就返回真,否则返回假
    int atomic_inc_and_test(atomic_t *v)原子变量v加 1,如果结果为0就返回真,否则返回假
    int atomic_add_negative(int i, atomic_t *v)原子变量v加 i,如果结果为负就返回真,否则返回假

    至此,对于整型原子操作的相关API函数就讲解完成了,会在下一小节中使用上述原子整形操作API进行相应的实验。

    下面对原子位操作进行讲解,和原子整形变量不同,原子位操作没有 atomic_t 的数据结构,原子位操作是直接对内存进行操作,原子位操作相关API函数如下(图表20-2)所示:

    函数描述
    void set_bit(int nr, void *p)将 p 地址的第 nr 位置 1。
    void clear_bit(int nr,void *p)将 p 地址的第 nr 位清零。
    void change_bit(int nr, void *p)将 p 地址的第 nr 位进行翻转。
    int test_bit(int nr, void *p)获取 p 地址的第 nr 位的值。
    int test_and_set_bit(int nr, void *p)将 p 地址的第 nr 位置 1,并且返回 nr 位原来的值。
    int test_and_clear_bit(int nr, void *p)将 p 地址的第 nr 位清零,并且返回 nr 位原来的值。
    int test_and_change_bit(int nr, void *p)将 p 地址的第 nr 位翻转,并且返回 nr 位原来的值。

    对于原子位操作的知识就不再深入讲解和实验,感兴趣的同学可以到相关网站上进行自主学习。

    在下一小节中,将会使用原子整形操作对19章的并发与竞争实验进行改进。

    20.2 实验程序的编写

    20.2.1 驱动程序编写

    本实验对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\15\module。

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

    编写完成的atomic.c代码如下所示

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    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;
    	}
    	atomic64_set(&v,0);//将原子类型变量v的值设置为0
    	//printk("\nthis 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类型字符串变量kbuf
    	printk("\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);//将原子类型变量v的值赋1
    	return 0;
    }
    
    struct chrdev_test {
           dev_t dev_num;//定义dev_t类型变量dev_num来表示设备号
           int major,minor;//定义int类型的主设备号major和次设备号minor
           struct cdev cdev_test;//定义struct cdev 类型结构体变量cdev_test,表示要注册的字符设备
           struct class *class_test;//定于struct class *类型结构体变量class_test,表示要创建的类
    };
    struct chrdev_test dev1;//创建chrdev_test类型的
    struct file_operations fops_test = {
          .owner = THIS_MODULE,//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
          .open = open_test,//将open字段指向open_test(...)函数
          .read = read_test,//将read字段指向read_test(...)函数
          .write = write_test,//将write字段指向write_test(...)函数
          .release = release_test,//将release字段指向release_test(...)函数
    };
     
    static int __init atomic_init(void)
    {
    	if(alloc_chrdev_region(&dev1.dev_num,0,1,"chrdev_name") < 0 ){//自动获取设备号,设备名chrdev_name
    		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,minor is %d\n",dev1.major,dev1.minor);
    	cdev_init(&dev1.cdev_test,&fops_test);//使用cdev_init()函数初始化cdev_test结构体,并链接到fops_test结构体
    	dev1.cdev_test.owner = THIS_MODULE;//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
    	cdev_add(&dev1.cdev_test,dev1.dev_num,1);//使用cdev_add()函数进行字符设备的添加
    	dev1.class_test = class_create(THIS_MODULE,"class_test");//使用class_create进行类的创建,类名称为class_test
    	device_create(dev1.class_test,0,dev1.dev_num,0,"device_test");//使用device_create进行设备的创建,设备名称为device_test
    	return 0;
    }
    
    static void __exit atomic_exit(void)
    {
    	device_destroy(dev1.class_test,dev1.dev_num);//删除创建的设备
    	class_destroy(dev1.class_test);//删除创建的类
    	cdev_del(&dev1.cdev_test);//删除添加的字符设备cdev_test
    	unregister_chrdev_region(dev1.dev_num,1);//释放字符设备所申请的设备号
    	printk("module exit \n");
    }
    module_init(atomic_init);
    module_exit(atomic_exit)
    MODULE_LICENSE("GPL v2");
    MODULE_AUTHOR("topeet");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101

    20.2.2 编写测试 APP

    本实验应用程序对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\15\app。

    本测试app代码和上一章节相同,需要输入两个参数,第一个参数为对应的设备节点,第二个参数为“topeet”或者“itop”,分别代表向设备写入的数据,编写完成的应用程序app.c内容如下所示:

    #include 
    #include 
    #include 
    #include 
    #include 
     #include 
    int main(int argc, char *argv[])
    {
    	int fd;//定义int类型的文件描述符
    	char str1[10] = {0};//定义读取缓冲区str1
    	fd = open(argv[1],O_RDWR);//调用open函数,打开输入的第一个参数文件,权限为可读可写
    	if(fd < 0 ){
    		printf("file open failed \n");
    		return -1;
    	}
    	/*如果第二个参数为topeet,条件成立,调用write函数,写入topeet*/    
    	if (strcmp(argv[2],"topeet") == 0 ){
    		write(fd,"topeet",10);
    	}
    	/*如果第二个参数为itop,条件成立,调用write函数,写入itop*/  
    	else if (strcmp(argv[2],"itop") == 0 ){
    		write(fd,"itop",10);
    	}
    	close(fd); 
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    20.3 运行测试

    20.3.1 编译驱动程序

    在上一小节中的atomic.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示:

    export ARCH=arm64#设置平台架构
    export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
    obj-m += atomic.o    #此处要和你的驱动源文件同名
    KDIR :=/home/topeet/Linux/linux_sdk/kernel    #这里是你的内核目录                                                                                                                            
    PWD ?= $(shell pwd)
    all:
        make -C $(KDIR) M=$(PWD) modules    #make操作
    clean:
        make -C $(KDIR) M=$(PWD) clean    #make clean操作
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    对于Makefile的内容注释已在上图添加,保存退出之后,来到存放atomic.c和Makefile文件目录下,如下图(图20-4)所示:

    img

    然后使用命令“make”进行驱动的编译,编译完成如下图(图20-5)所示:

    img

    编译完生成atomic.ko目标文件,如下图(图20-6)所示:

    img

    至此驱动模块就编译成功了,下面进行应用程序的编译。

    20.3.2 编译应用程序

    来到应用程序app.c文件的存放路径如下图(图20-7)所示:

    img

    然后使用以下命令对app.c进行交叉编译,编译完成如下图(图20-8)所示:

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

    img

    生成的app文件就是之后放在开发板上运行的可执行文件,至此应用程序的编译就完成了。

    20.3.3 运行测试

    开发板启动之后,使用以下命令进行驱动模块的加载,如下图(图20-9)所示:

    insmod atomic.ko
    
    • 1

    img

    可以看到申请的主设备号和次设备号就被打印了出来,然后使用以下代码对自动生成的设备节点device_test进行查看,如下图(图20-10)所示:

     ls /dev/device_test
    
    • 1

    img

    可以看到device_test节点已经被自动创建了,然后使用以下命令运行测试app,运行结果如下图(图20-11)所示:

    ./app /dev/device_test topeet
    
    • 1

    img

    可以看到传递的buf值为topeet,然后输入以下命令在后台运行两个app,来进行竞争测试,运行结果如下图(图20-12)所示:

    ./app /dev/device_test topeet &
    
    ./app /dev/device_test itop 
    
    • 1
    • 2
    • 3

    img

    ​ 可以看到应用程序在打开第二次/dev/device_test 文件的时候,出现了“file open failed”打印,证明文件打开失败,只有在第一个应用关闭相应的文件之后,下一个应用才能打开,通过限制同一时间内设备访问数量,来对共享资源进行保护。

    最后可以使用以下命令进行驱动的卸载,如下图(图20-13)所示:

    rmmod flag.ko
    
    • 1

    img

    至此,原子操作实验就完成了。

  • 相关阅读:
    Django REST项目实战:在线中文字符识别
    Flask 学习-48.Flask-RESTX 使用api.model() 模型工厂
    PaddleX解决分类、检测两大场景问题?实战精讲教程来了!
    【Linux】在Ubuntu下安装Zotero
    数字集成电路(上)
    web:[HCTF 2018]WarmUp
    Django 做migrations时出错,解决方案
    C语言—窄字符或宽字符
    【数据结构】堆的创建
    UMA 2 - Unity Multipurpose Avatar☀️三.给UMA设置默认服饰Recipes
  • 原文地址:https://blog.csdn.net/BeiJingXunWei/article/details/132773346