linux设备驱动--并发与竞态之原子操作

       原子操作指的是在执行过程中不会被别的代码路径所中断的操作。

       linux下面有两类原子操作,一类是整形原子操作,一类是位原子操作。

       原子操作目前仅仅做个测试验证学习,至于原子操作在哪些场合适合用到,还需要今后更多的驱动以及内核代码的fuck。

       整形原子操作的函数说明:

void atomic_set(atomic_t *v, int i);
atomic_t v = ATOMIC_INIT(0);
Set the atomic variable v to the integer value i. You can also initialize atomic values
at compile time with the ATOMIC_INIT macro.
int atomic_read(atomic_t *v);
Return the current value of v.
void atomic_add(int i, atomic_t *v);
Add i to the atomic variable pointed to by v. The return value is void, because
there is an extra cost to returning the new value, and most of the time there’s no
need to know it.
void atomic_sub(int i, atomic_t *v);
Subtract i from *v.
void atomic_inc(atomic_t *v);
void atomic_dec(atomic_t *v);
Increment or decrement an atomic variable.
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i, atomic_t *v);
Perform the specified operation and test the result; if, after the operation, the
atomic value is 0, then the return value is true; otherwise, it is false. Note that
there is no atomic_add_and_test.
int atomic_add_negative(int i, atomic_t *v);
Add the integer variable i to v. The return value is true if the result is negative,
false otherwise.
int atomic_add_return(int i, atomic_t *v);
int atomic_sub_return(int i, atomic_t *v);
int atomic_inc_return(atomic_t *v);
int atomic_dec_return(atomic_t *v);
Behave just like atomic_add and friends, with the exception that they return the
new value of the atomic variable to the caller.

上面是从ldd3中摘录的关于atomic的操作函数。

我们可以利用整形原子操作来控制设备只能被一个进程打开。

还是利用scull的代码,这个代码也是linux设备模型之字符设备一文中分析的代码。

/*
 * main.c -- the bare scull char module
 *
 * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
 * Copyright (C) 2001 O'Reilly & Associates
 *
 * The source code in this file can be freely used, adapted,
 * and redistributed in source or binary form, so long as an
 * acknowledgment appears in derived source files.  The citation
 * should list that the code comes from the book "Linux Device
 * Drivers" by Alessandro Rubini and Jonathan Corbet, published
 * by O'Reilly & Associates.   No warranty is attached;
 * we cannot take responsibility for errors or fitness for use.
 *
 */

//#include <linux/config.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>

#include <linux/kernel.h>	/* printk() */
#include <linux/slab.h>		/* kmalloc() */
#include <linux/fs.h>		/* everything... */
#include <linux/errno.h>	/* error codes */
#include <linux/types.h>	/* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h>	/* O_ACCMODE */
#include <linux/seq_file.h>
#include <linux/cdev.h>

#include <asm/system.h>		/* cli(), *_flags */
#include <asm/uaccess.h>	/* copy_*_user */

#include "myscull.h"		/* local definitions */

/*
 * Our parameters which can be set at load time.
 */

int scull_major =   SCULL_MAJOR;
int scull_minor =   0;
int scull_nr_devs = SCULL_NR_DEVS;	/* number of bare scull devices */
int scull_quantum = SCULL_QUANTUM;
int scull_qset =    SCULL_QSET;

module_param(scull_major, int, S_IRUGO);
module_param(scull_minor, int, S_IRUGO);
module_param(scull_nr_devs, int, S_IRUGO);
module_param(scull_quantum, int, S_IRUGO);
module_param(scull_qset, int, S_IRUGO);

MODULE_AUTHOR("Alessandro Rubini, Jonathan Corbet");
MODULE_LICENSE("Dual BSD/GPL");

struct scull_dev *scull_devices;	/* allocated in scull_init_module */


/*
 * Empty out the scull device; must be called with the device
 * semaphore held.
 */
#ifdef SCULL_DEBUG /* use proc only if debugging */
/*
 * The proc filesystem: function to read and entry
 */



/*
 * For now, the seq_file implementation will exist in parallel.  The
 * older read_procmem function should maybe go away, though.
 */

/*
 * Here are our sequence iteration methods.  Our "position" is
 * simply the device number.
 */


	
/*
 * Tie the sequence operators up.
 */

/*
 * Now to implement the /proc file we need only make an open
 * method which sets up the sequence operators.
 */

/*
 * Create a set of file operations for our proc file.
 */
static struct file_operations scull_proc_ops = {
	.owner   = THIS_MODULE,
	.open    = scull_proc_open,
	.read    = seq_read,
	.llseek  = seq_lseek,
	.release = seq_release
};
	

/*
 * Actually create (and remove) the /proc file(s).
 */




#endif /* SCULL_DEBUG */





/*
 * Open and close
 */

int scull_open(struct inode *inode, struct file *filp)
{
	struct scull_dev *dev; /* device information */

	dev = container_of(inode->i_cdev, struct scull_dev, cdev);
	filp->private_data = dev; /* for other methods */

	return 0;          /* success */
}

int scull_release(struct inode *inode, struct file *filp)
{
	return 0;
}
/*
 * Follow the list
 */

/*
 * Data management: read and write
 */

ssize_t scull_read(struct file *filp, char __user *buf, size_t count,
                loff_t *f_pos)
{
	unsigned long p = *f_pos;
	int ret = 0;
	struct scull_dev *dev = filp->private_data;
	if(p >= SCULL_SIZE)
		return count ? -ENXIO : 0 ;
	if(count > SCULL_SIZE - p)
		count = SCULL_SIZE - p;

	if(copy_to_user(buf, (void *)(dev->mem + p), count))
	{
		ret = - EFAULT;
	}
	else
	{
		*f_pos += count;
		ret = count;
		printk(KERN_WARNING "read %d bytes from %d\n", count, p);
	}
	printk(KERN_WARNING "ret: %d\n",ret);
	return ret;
}

ssize_t scull_write(struct file *filp, const char __user *buf, size_t count,
                loff_t *f_pos)
{
	unsigned long p = *f_pos;
	int ret = 0;
	struct scull_dev *dev = filp->private_data;
	
	if(p >= SCULL_SIZE )
		return count ? -ENXIO : 0 ;
	if(count > SCULL_SIZE - p)
		count = SCULL_SIZE - p;

	if(copy_from_user(dev->mem + p, buf, count))
	{
		ret = -EFAULT;
	}
	else
	{
		*f_pos += count;
		ret = count;
		printk(KERN_WARNING "write %d bytes to %d\n", count , p);
	}

}

/*
 * The ioctl() implementation
 */

int scull_ioctl(struct inode *inode, struct file *filp,
                 unsigned int cmd, unsigned long arg)
{
	return 0;

}



/*
 * The "extended" operations -- only seek
 */

loff_t scull_llseek(struct file *filp, loff_t off, int whence)
{
	loff_t ret;
	struct scull_dev *dev = filp->private_data;

	switch(whence) {
	  case 0: /* SEEK_SET */
		if(off < 0)
		{
			ret = -EINVAL;
			break;
		}
		if(off > SCULL_SIZE)
		{
			ret = -EINVAL;
			break;
		}
		filp->f_pos = off;
		ret = filp->f_pos;
		break;

	  case 1: /* SEEK_CUR */
		if((off + filp->f_pos) > SCULL_SIZE)
		{
			ret = -EINVAL;
			break;
		}
		if((off + filp->f_pos) < 0)
		{
			ret = -EINVAL;
			break;
		}
		filp->f_pos += off;
		ret = filp->f_pos;
		break;

	  case 2: /* SEEK_END */
		if(off > 0)
		{
			ret = -EINVAL;
			break;
		}
		if((off + SCULL_SIZE) < 0)
		{
			ret = -EINVAL;
			break;
		}
		break;

	  default: /* can't happen */
		return -EINVAL;
	}
	
	return ret;
}



struct file_operations scull_fops = {
	.owner =    THIS_MODULE,
	.llseek =   scull_llseek,
	.read =     scull_read,
	.write =    scull_write,
	.ioctl =    scull_ioctl,
	.open =     scull_open,
	.release =  scull_release,
};

/*
 * Finally, the module stuff
 */

/*
 * The cleanup function is used to handle initialization failures as well.
 * Thefore, it must be careful to work correctly even if some of the items
 * have not been initialized
 */
void scull_cleanup_module(void)
{
	int i;
	dev_t devno = MKDEV(scull_major, scull_minor);

	/* Get rid of our char dev entries */
	if (scull_devices) {
		for (i = 0; i < scull_nr_devs; i++) {
			cdev_del(&scull_devices[i].cdev);
		}
		kfree(scull_devices);
	}

#ifdef SCULL_DEBUG /* use proc only if debugging */
	//scull_remove_proc();
#endif

	/* cleanup_module is never called if registering failed */
	unregister_chrdev_region(devno, scull_nr_devs);

	/* and call the cleanup functions for friend devices */
	//scull_p_cleanup();
	//scull_access_cleanup();

}


/*
 * Set up the char_dev structure for this device.
 */
static void scull_setup_cdev(struct scull_dev *dev, int index)
{
	int err, devno = MKDEV(scull_major, scull_minor + index);
    
	cdev_init(&dev->cdev, &scull_fops);
	dev->cdev.owner = THIS_MODULE;
	dev->cdev.ops = &scull_fops;
	err = cdev_add (&dev->cdev, devno, 1);
	/* Fail gracefully if need be */
	if (err)
		printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}


int scull_init_module(void)
{
	int result, i;
	dev_t dev = 0;

/*
 * Get a range of minor numbers to work with, asking for a dynamic
 * major unless directed otherwise at load time.
 */
	if (scull_major) {
		dev = MKDEV(scull_major, scull_minor);
		result = register_chrdev_region(dev, scull_nr_devs, "scull");
	} else {
		result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,
				"scull");
		scull_major = MAJOR(dev);
	}
	if (result < 0) {
		printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
		return result;
	}

        /* 
	 * allocate the devices -- we can't have them static, as the number
	 * can be specified at load time
	 */
	scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
	if (!scull_devices) {
		result = -ENOMEM;
		goto fail;  /* Make this more graceful */
	}
	memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));

        /* Initialize each device. */
	for (i = 0; i < scull_nr_devs; i++) {
		scull_setup_cdev(&scull_devices[i], i);
	}

        /* At this point call the init function for any friend device */
	dev = MKDEV(scull_major, scull_minor + scull_nr_devs);
	//dev += scull_p_init(dev);
	//dev += scull_access_init(dev);

#ifdef SCULL_DEBUG /* only when debugging */
	//scull_create_proc();
#endif

	return 0; /* succeed */

  fail:
	scull_cleanup_module();
	return result;
}

module_init(scull_init_module);
module_exit(scull_cleanup_module);

为了实现设备只能被一个进程打开,从而避免竞态的出现。

做以下改动:

在文件开头定义并初始化一个整形原子变量,初始化为1。

static atomic_t scull_available = ATOMIC_INIT(1);  //init atomic

在scull_open 函数和scull_close函数中:

int scull_open(struct inode *inode, struct file *filp)
{
    struct scull_dev *dev; /* device information */

    dev = container_of(inode->i_cdev, struct scull_dev, cdev);
    filp->private_data = dev; /* for other methods */
    if(!atomic_dec_and_test(&scull_available)){
        atomic_inc(&scull_available);
        return -EBUSY;
    }
    return 0;          /* success */
}

int scull_release(struct inode *inode, struct file *filp)
{
    atomic_inc(&scull_available);
    return 0;
}



在scull_open函数中,当第一个进程打开scull设备时,因为scull_available原子变量的初值为1,atomic_dec_and_test做自减后测试为0,返回true,所以open就成功。

那第二个进程打开scull设备时,原子变量已经减到0了,所以atomic_dec_and_test返回false,所以open返回EBUSY。

不管是第二个进程打开失败,还是第一个进程执行结束,都不要忘了恢复原子变量的值.(atomic_inc(&scull_available);)


在应用层写一个小的测试程序:

app.c:

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

#include <unistd.h>
void main(void)
{
	int fd;
	fd = open("/dev/scull",O_RDWR);
	if( fd < 0 )
	{
		perror("open");
		return;
	}
	sleep(10);
	close(fd);
	exit(0);
}

开始运行./app时是正常的,在./app &后我们立即再./app(手速够快,小于10s),会有设备忙的提示。

如果在./app &后过了10s,我们再./app还是可以正常打开的。

这样看来,通过整形原子操作实现了设备只能被一个进程打开。


同样的道理,我们可以利用位操作来实现这一点:

static int i = 1;
static void *addr = &i ;

int scull_open(struct inode *inode, struct file *filp)
{
	struct scull_dev *dev; /* device information */

	dev = container_of(inode->i_cdev, struct scull_dev, cdev);
	filp->private_data = dev; /* for other methods */
	if(test_and_set_bit(1,addr) != 0){
		clear_bit(1,addr);
		return -EBUSY;
	}
	return 0;          /* success */
}

int scull_release(struct inode *inode, struct file *filp)
{
	clear_bit(1,addr);
	return 0;
}

利用app.c测试,效果一致。













  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值