linux驱动并发与竞态(方法一:原子操作)

本文介绍了Linux驱动程序中解决并发与竞态问题的方法之一——原子操作。详细讲解了整型原子操作和位原子操作的各种函数及宏定义,并通过一个实际案例展示了如何使用整型原子变量来保护驱动程序不被多次打开。

竞态的解决方法

  • 1. 原子操作
  • 2. 自旋锁
  • 3. 信号量
  • 4. 互斥体

1. 原子操作

使用场景:整型原子变量保护驱动程序不被多次打开,既驱动打开一次之后,在退出之前其他进程无法再次打开驱动。

切记不能自行使用“+”、“-”等等操作原子操作数

1.1 整型原子操作函数(宏定义)

以下例举几个常用的。

1.1.1 定义整型原子变量并设置初始值
//atomic_t类型结构体
typedef struct {
   int counter;
} atomic_t;

//例子:定义初始值 a=0
atomic_t a = ATOMIC_INIT(0);
1.1.2 设置整型原子变量的值
#define atomic_set(v, i)             ((v)->counter = (i))

//code 例子:
atomic_set(&buffer->size, 0);
1.1.3 获取原子变量的值
#define atomic_read(v) ACCESS_ONCE((v)->counter)


static inline int atomic_read(const atomic_t *v)
{
	int temp;

	asm volatile (
		"LNKGETD %0, [%1]\n"
		: "=da" (temp)
		: "da" (&v->counter));

	return temp;
}

//code 例子
if (atomic_read(&waiting_for_crash_ipi) > 0)
	printk(KERN_WARNING "Non-crashing CPUs did not react to IPI\n");
1.1.4 整型原子变量的加/减
void atomic_add(int i, atomic_t *v); //整型原子变量加i
void atomic_sub(int i, atomic_t *v); //整型原子变量减i

//例子
atomic_add(1, &vfe_dev->irq_cnt);
1.1.5 整型原子变量自增/自减
#define atomic_inc(v) atomic_add(1,(v))
#define atomic_dec(v) atomic_sub(1,(v))

void atomic_inc(atomic_t *v); //整型原子变量自增1
void atomic_dec(atomic_t *v);//整型原子变量自减1

//例子
atomic_inc(&irq_err_count);
1.1.6 整型原子变量操作返回函数(宏定义)
#define atomic_inc_return(v) atomic_add_return(1, (v))//返回自增1的结果
#define atomic_dec_return(v) atomic_sub_return(1, (v)) //返回自减1的结果

//例子
if (atomic_inc_return(&kdump_in_progress) != 1)
	unw_init_running(kdump_cpu_freeze, NULL);
1.1.7 整型原子变量测试函数(宏定义)
/*整型原子变量减i后是否为0*/
#define atomic_sub_and_test(i, v) (atomic_sub_return((i), (v)) == 0)
/*整型原子变量自减1后是否为0*/
#define atomic_dec_and_test(v) (atomic_dec_return(v) == 0)
/*整型原子变量自加1后是否为0*/
#define atomic_inc_and_test(v) (atomic_inc_return(v) == 0)

//例子
	if (!atomic_inc_and_test(&ah->intr_ref_cnt)) {
		ath_dbg(common, INTERRUPT, "Do not enable IER ref count %d\n",
			atomic_read(&ah->intr_ref_cnt));
		return;
	}

1.2 位原子操作函数(宏定义)

以下例举几个常用的。

1.2.1 设置位
/*
* nr:指定设置哪一位 ,如果地址指定的数据是32位的,那么nr可取0~31。
* addr:指定要设置的地址。
* 返回值:无
*/
void set_bit(int nr, unsigned long *addr)

//例子
set_bit(PACKET_MERGE_SEGS, &pd->flags);
1.2.2 清除位
/*
* 作用:将某一位清0。
* nr:指定设置哪一位 ,如果地址指定的数据是32位的,那么nr可取0~31。
* addr:指定要设置的地址。
* 返回值:无
*/
void clear_bit(int nr, unsigned long *addr)

//例子
clear_bit(PACKET_MERGE_SEGS, &pd->flags);
1.2.3 改变位
/*
* 作用:翻转某一位的值,是0就变为1,是1就变为0
* nr:指定设置哪一位 ,如果地址指定的数据是32位的,那么nr可取0~31。
* addr:指定要设置的地址。
* 返回值:无
*/
void change_bit(int nr, unsigned long *addr);

//例子
change_bit(ATM_VF_ADDR,&vcc->flags);
1.2.4 测试位
/*
* 作用:获取某一位的值
* nr:指定设置哪一位 ,如果地址指定的数据是32位的,那么nr可取0~31。
* addr:指定要设置的地址。
* 返回值:返回nr位的值
*/
int test_bit(int nr, const volatile unsigned long *addr)

//例子
if (test_bit(usage->code, asc->pressed_fn))
	do_translate = 1;
1.2.5 测试并操作位
/*测试并设置位*/
/*
* nr:指定设置哪一位 ,如果地址指定的数据是32位的,那么nr可取0~31。
* addr:指定要设置的地址。
* 返回值:返回nr位的值
*/
int test_and_set_bit(int nr, volatile unsigned long *addr);
/*测试并清除位*/
int test_and_clear_bit(int nr, volatile unsigned long *addr);
/*测试并反转位*/
int test_and_change_bit(int nr, volatile unsigned long *addr);


1.3 整型原子操作实验

本实验使用整型原子变量保护驱动程序不被多次打开,既驱动打开一次之后,在退 出之前其他进程无法再次打开驱动。

只需要在驱动程序的.open和.release函数中添加整型原子相关操作即可

1.代码驱动修改

   /*---------------修改内容 1 -----------------*/
static atomic_t a = ATOMIC_INIT(1);

static int chrdevbase_open(struct inode *inode, struct file *filp)
{
   /*---------------修改内容 2 -----------------*/

    if(atomic_read(&a))    //if a=1。2种情况a=1:   1)初始值;2)进入过release函数
    {
        atomic_set(&a,0);  //set a=0
    }
    else                   //else if a = 0
    {
        printk("\n driver on using!  open failed !!!\n");   //fail
        return - EBUSY;
    }


    printk("open \n");
    return 0;
}
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
    /*---------------修改内容 3 -----------------*/
    atomic_set(&a,1);        //set a=1
    printk("chrdevbase release!\r\n");
    return 0;
}

2. 应用app代码修改

int main(int argc, char *argv[])
{
   int fd, error;   
   char buf[128];

   /*判断输入的命令是否合法*/
   if(argc != 1)
   {
        printf(" commend error ! \n");
        return -1;
   }
   /*打开文件*/
   printf(" going to open file \n");  //为了方便看code运行进度
   fd = open("/dev/chrdevbase", O_RDWR);
   if(fd < 0)
   {
      printf("open file : %s failed !\n", argv[0]);
      return -1;
   }
   /*判断命令的有效性*/
   /*写入命令*/
   memcpy(buf, wbuf, sizeof(wbuf));
   error = write(fd,buf,sizeof(buf));
   if(error < 0)
   {
      printf("write file error! \n");
      close(fd);
      /*判断是否关闭成功*/
   }

   /*--------------修改内容 1 ---------------*/
   sleep(15);  //休眠15秒

   /*关闭文件*/
   printf(" going to close file \n");   //为了方便看code运行进度
   error = close(fd);
   if(error < 0)
   {
      printf("close file error! \n");
      return -1;
   }
   return 0;   
}

3.编译后测试代码:
前面一次open file,若没过15s,无法再次打开。必须过了15s,才能再次打开。

msm8909w:/data/app # ./chrdevbaseApp2 &         ->加& 在后台运行
[1] 18473
 going to open file                             ->open file前,在app code中加入此打印内容
msm8909w:/data/app # ./chrdevbaseApp2           ->在15s前,再次运行app
 going to open file
open file : ./chrdevbaseApp2 failed !           ->open file失败
255|msm8909w:/data/app #  going to close file   ->在close file前,在app code中加入此打印内容
255|msm8909w:/data/app # ./chrdevbaseApp2       ->过了15s后,再次运行app
 going to open file  
 going to close file
[1] + Done                 ./chrdevbaseApp2     ->不报错,成功open file
msm8909w:/data/app #

参考

[野火]i.MX Linux开发实战指南

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值