原子操作指的是在执行过程中不会被别的代码路径所中断的操作。
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测试,效果一致。