15_解决驱动中的竞态

介绍解决驱动竞态的方法

Posted by Jerry Chen on August 7, 2019

了解内容

比如我们想任意时刻只有一个系统app能正常调用驱动中open函数,就可以在open函数中实现互斥。

Linux操作系统中提供实现互斥的方法有:原子操作、自旋锁、信号量、互斥体等。

原子操作

原理是:声明一个变量(比如初始0),访问互斥资源时改值(+1),离开时还原值(-1);在访问之前判断该值是否为初始值(0),就可以知道该资源是否被占用。被占用时直接返回。

因为在文件中声明了变量(静态全局),所以原子操作在驱动程序文件的范围中生效;可以使诸如open的函数或者某内容互斥。

一些函数

1、定义一个原子变量,并初始化

1
static atomic_t v = ATOMIC_INIT(0);

2、原子变量自减1/自加1

1
2
atomic_dec(&v); //自减1
atomic_inc(&v); //自加1

3、读取原子变量的值/设置原子变量的值

1
2
atomic_read(&v);  //读取
atomic_set(&v,n); //设置值为n

4、原子变量自减1,并与0比较,如果为0则返回true,否则返回false

1
atomic_dec_and_test(&v);

#### 例程

驱动例程

atomic_demo.c

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
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/atomic.h>
#include <linux/uaccess.h>  

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("jerry");

static atomic_t  atomic_v = ATOMIC_INIT(1);
static int major;//主设备号,副设备号注册全部
static struct class *my_class;

static int demo_open(struct inode *node, struct file *filp){
    if(!atomic_dec_and_test(&atomic_v)){//减1后判断结果是否0,为0返回真
		atomic_inc(&atomic_v);			//加1
		printk(KERN_EMERG "busy!!!\n");
		return -EBUSY;
    }
	
	printk(KERN_EMERG "open success!\n");
    return 0;
}
static ssize_t demo_write (struct file *filp, const char __user *buf, size_t size, loff_t *off){
	char write_str[64];
	size_t len;
    len = copy_from_user(write_str, buf, size);
	printk(KERN_EMERG "write \"%s\" ok!\n",write_str);
	return len;
}
static int demo_release(struct inode *node, struct file *filp){
    atomic_set(&atomic_v,1);			//复原原子值,设1
	return 0;
}

static struct file_operations demo_ops = {
    .owner = THIS_MODULE,
    .open  = demo_open,
    .write = demo_write,
    .release = demo_release,
};

static int demo_init(void){
	//"register_chrdev"是逐渐废除的函数,会一次性注册256个子设备
	//推荐使用"register_chrdev_region" + "cdev_init" + "cdev_add"的模式
	//这里为了演示的简单,就使用了该函数
	major = register_chrdev(0, "demo", &demo_ops);
	my_class = class_create(THIS_MODULE, "demo_class");
	device_create(my_class, NULL, MKDEV(major, 0), NULL, "demo_dev");
	return 0;
}

static void demo_exit(void){
	unregister_chrdev(major, "demo");//逐渐废除的函数,演示暂用
}

module_init(demo_init);
module_exit(demo_exit);
系统app例程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc,char *argv[]){
	char *node = "/dev/demo_dev";
	int fd;
	
	fd = open(node,O_RDWR);
	if(fd < 0)
		goto fail;
	
	if(argc >= 2)
		write(fd,argv[1],sizeof(argv[1]));
	return 0;
fail:
	printf("Something is fail!\n");
	return 0;
}
测试结果

编写简单Makefile:参考这里

编译生成模块后,拷贝到开发板中:

  • 首先加载模块

  • 加载后会生成/dev/demo_dev设备节点

  • 顺序执行两次传参不同的测试程序,可以看到对资源的访问正常

  • 模拟并行执行两次传参不同的测试程序,可以看到互斥访问已经起作用了

其他

由于每个互斥资源都要声明一个整形原子变量,而实际该变量的值也只有两个状态,有点浪费和不易管理,所以建议使用位原子代替整形原子,也就是对整形变量的每一位单独操作。

1、定义一个整形变量,并初始化

1
static volatile unsigned long int value_bit = 0;

2、位原子置位/清零

1
2
set_bit(0,&value_bit);		//第0位置位
clear_bit(0,&value_bit);	//第0位清零

3、读取位原子的值

1
test_bit(0,&value_bit);		//读取第0位

其他(后续补充)

主要介绍原子操作、自旋锁、互斥体。