九鼎创展论坛中文版English
登录 | 立即注册 设为首页收藏本站 切换到宽版
查看: 4583|回复: 0
打印 上一主题 下一主题

x4412&ibox项目实战34-使用自旋锁避免并发竞争

[复制链接]
跳转到指定楼层
楼主
发表于 2014-10-8 12:00:25 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
自旋锁(spin lock)是一种对临界资源进行互斥手访问的典型手段,其名称来源于它的工作方式。为了获得一个自旋锁,在某CPU上运行的代码需先执行一个原子操作,该操作测试并设置(test-and-set)某个内存变量,由于它是原子操作,所以在该操作完成之前其他执行单元不可能访问这个内存变量。如果测试结果表明锁已经空闲,则程序获得这个自旋锁并继续执行;如果测试结果表明锁仍被占用,程序将在一个小的循环内重复这个“测试并设置”操作,即进行所谓的“自旋”,通俗地说就是“在原地打转”。当自旋锁的持有者通过重置该变量释放这个自旋锁后,某个等待的“测试并设置”操作向其调用者报告锁已释放。
理解自旋锁最简单的方法是把它作为一个变量看待,该变量把一个临界区或者标记为“我当前在运行,请稍等一会”或者标记为“我当前不在运行,可以被使用”。如果A执行单元首先进入例程,它将持有自旋锁;当B执行单元试图进入同一个例程时,将获知自旋锁已被持有,需等到A执行单元释放后才能进入。
使用自旋锁的方法很简单,执行以下三个步骤即可。
第一步:在程序的最开始定义一个自旋锁:
  1. spinlock_t lock; //定义一个自旋锁
复制代码
第二步:在程序的初始化函数中初始化自旋锁:
  1. spin_lock_init(lock)//初始化自旋锁
复制代码
第三步:将需要包装自旋锁的临界区用自旋锁函数封装起来:
  1. spin_lock(lock)//获取自旋锁,保护临界区
  2. …//被保护的临界区程序段
  3. spin_unlock(lock)//释放自旋锁
复制代码
       自旋锁主要针对多核CPU或单核CPU但内核可抢占的情况,对于单核CPU且内核不支持抢占的系统,自旋锁退化为空操作。在单CPU和内核可抢占的系统中,自旋锁持有期间内核的抢占将被禁止。由于内核可抢占的单CPU系统的行为实际很类似于SMP系统,因此,在这样的单CPU系统中使用自旋锁仍十分必要。
尽管用了自旋锁可以保证临界区不受别的CPU和本CPU内的抢占进程打扰,但是得到锁的代码路径在执行临界区的时候还可能受到中断和底半部(BH)的影响。为了防止这种影响,就需要用到自旋锁的衍生。spin_lock()/spin_unlock()是自旋锁机制的基础,它们和开关中断开中断 local_irq_enable()/local_irq_disable()、开关底半部开底半部local_bh_enable()/local_bh_disable()、开关中断并保存状态字 local_irq_save()/local_irq_restore()结合就形成了整套自旋锁机制,关系如下所示:
  1. spin_lock_irq() = spin_lock() + local_irq_disable()
  2. spin_unlock_irq() = spin_unlock() + local_irq_enable()
  3. spin_lock_irqsave() = spin_unlock() + local_irq_save()
  4. spin_unlock_irqrestore() = spin_unlock() + local_irq_restore()
  5. spin_lock_bh() = spin_lock() + local_bh_disable()
  6. spin_unlock_bh() = spin_unlock() + local_bh_enable()
复制代码
在使用自旋锁时,应该注意以下问题:
l  自旋锁实际上是忙等锁,当锁不可用时,CPU一直循环执行“测试并设置”该锁直到可用而取得该锁,CPU在等待自旋锁时不做任何有用的工作,仅仅是等待。因此,只有在占用锁的时间极短的情况下,使用自旋锁才是合理的。当临界区很大或有共享设备的时候,需要较长时间占用锁,使用自旋锁会降低系统的性能。
l  自旋锁可能导致系统死锁。引发这个问题最常见的情况是递归使用一个自旋锁,即如果一个已经拥有某个自旋锁的CPU想第二次获得这个自旋锁,则该CPU将死锁。此外,如果进程获得自旋锁之后再阻塞,也有可能导致死锁的发生。copy_from_user()copy_to_user() kmalloc()等函数都有可能引起阻塞,因此在自旋锁的占用期间不能调用这些函数。
将前面基于sysfs创建的LED驱动程序的LED读写函数添加自旋锁。参考源码如下:
  1. #include <linux/module.h>
  2. #include <linux/init.h>
  3. #include <linux/platform_device.h>
  4. #include <mach/hardware.h>
  5. #include <asm/mach-types.h>
  6. #include <linux/gpio.h>
  7. #include <plat/gpio-cfg.h>
  8. spinlock_t spin;
  9. /*
  10. * X4412:
  11. *
  12. * LED1 -> D22 -> XEINT14
  13. * LED2 -> D23 -> XEINT15
  14. * LED3 -> D24 -> XEINT22
  15. * LED4 -> D25 -> XEINT23
  16. */
  17. static int __x4412_led_status[4] = { 0 };
  18. static void __x4412_led_probe(void)       //初始化LED对应GPIO口
  19. {
  20.          int ret;
  21.          ret = gpio_request(EXYNOS4_GPX1(6), "GPX1");
  22.          if(ret)
  23.                    printk("x4412-led: request gpio GPX1(6) fail\n");
  24.          s3c_gpio_setpull(EXYNOS4_GPX1(6), S3C_GPIO_PULL_UP);
  25.          gpio_direction_output(EXYNOS4_GPX1(6), 1);
  26.          ret = gpio_request(EXYNOS4_GPX1(7), "GPX1");
  27.          if(ret)
  28.                    printk("x4412-led: request gpio GPX1(7) fail\n");
  29.          s3c_gpio_setpull(EXYNOS4_GPX1(7), S3C_GPIO_PULL_UP);
  30.          gpio_direction_output(EXYNOS4_GPX1(7), 1);
  31.          ret = gpio_request(EXYNOS4_GPX2(6), "GPX2");
  32.          if(ret)
  33.                    printk("x4412-led: request gpio GPX2(6) fail\n");
  34.          s3c_gpio_setpull(EXYNOS4_GPX2(6), S3C_GPIO_PULL_UP);
  35.          gpio_direction_output(EXYNOS4_GPX2(6), 1);
  36.          ret = gpio_request(EXYNOS4_GPX2(7), "GPX2");
  37.          if(ret)
  38.                    printk("x4412-led: request gpio GPX2(7) fail\n");
  39.          s3c_gpio_setpull(EXYNOS4_GPX2(7), S3C_GPIO_PULL_UP);
  40.          gpio_direction_output(EXYNOS4_GPX2(7), 1);
  41.          __x4412_led_status[0] = 0;   //定义四盏LED灯的默认状态
  42.          __x4412_led_status[1] = 0;
  43.          __x4412_led_status[2] = 0;
  44.          __x4412_led_status[3] = 0;
  45. }
  46. static void __x4412_led_remove(void)    //释放GPIO口
  47. {
  48.          gpio_free(EXYNOS4_GPX1(6));
  49.          gpio_free(EXYNOS4_GPX1(7));
  50.          gpio_free(EXYNOS4_GPX2(6));
  51.          gpio_free(EXYNOS4_GPX2(7));
  52. }
  53. static ssize_t x4412_led_read(struct device *dev, struct device_attribute *attr, char *buf)
  54. {
  55.          if(!strcmp(attr->attr.name, "led1"))           //根据从应用读取的文件名称确定操作指定LED灯
  56.          {
  57.                    spin_lock(&spin);
  58.                    if(__x4412_led_status[0] != 0)       //返回给应用LED灯当前的状态
  59.                             return strlcpy(buf, "1\n", 3);
  60.                    else
  61.                             return strlcpy(buf, "0\n", 3);
  62.                    spin_unlock(&spin);
  63.          }
  64.          else if(!strcmp(attr->attr.name, "led2"))
  65.          {
  66.                    spin_lock(&spin);
  67.                    if(__x4412_led_status[1] != 0)
  68.                             return strlcpy(buf, "1\n", 3);
  69.                    else
  70.                             return strlcpy(buf, "0\n", 3);
  71.                    spin_unlock(&spin);
  72.          }
  73.          else if(!strcmp(attr->attr.name, "led3"))
  74.          {
  75.                    spin_lock(&spin);
  76.                    if(__x4412_led_status[2] != 0)
  77.                             return strlcpy(buf, "1\n", 3);
  78.                    else
  79.                             return strlcpy(buf, "0\n", 3);
  80.                    spin_unlock(&spin);
  81.          }
  82.          else if(!strcmp(attr->attr.name, "led4"))
  83.          {
  84.                    spin_lock(&spin);
  85.                    if(__x4412_led_status[3] != 0)
  86.                             return strlcpy(buf, "1\n", 3);
  87.                    else
  88.                             return strlcpy(buf, "0\n", 3);
  89.                    spin_unlock(&spin);
  90.          }
  91.          return strlcpy(buf, "\n", 3);
  92. }
  93. static ssize_t x4412_led_write(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
  94. {
  95.          unsigned long on = simple_strtoul(buf, NULL, 10);//从应用读取需要写入的数据
  96.          if(!strcmp(attr->attr.name, "led1"))
  97.          {
  98.                    spin_lock(&spin);
  99.                    if(on)                   //如果收到1,则将对应标志位置1,同时将对应GPIO置0,对应LED灯亮
  100.                    {
  101.                             gpio_direction_output(EXYNOS4_GPX1(6), 0);
  102.                             __x4412_led_status[0] = 1;
  103.                    }
  104.                    else             //如果收到0,则将对应标志位清0,同时将对应GPIO置1,对应LED灯灭
  105.                    {
  106.                             gpio_direction_output(EXYNOS4_GPX1(6), 1);
  107.                             __x4412_led_status[0] = 0;
  108.                    }
  109.                    spin_unlock(&spin);
  110.          }
  111.          else if(!strcmp(attr->attr.name, "led2"))
  112.          {
  113.                    spin_lock(&spin);
  114.                    if(on)
  115.                    {
  116.                             gpio_direction_output(EXYNOS4_GPX1(7), 0);
  117.                             __x4412_led_status[1] = 1;
  118.                    }
  119.                    else
  120.                    {
  121.                             gpio_direction_output(EXYNOS4_GPX1(7), 1);
  122.                             __x4412_led_status[1] = 0;
  123.                    }
  124.                    spin_unlock(&spin);
  125.          }
  126.          else if(!strcmp(attr->attr.name, "led3"))
  127.          {
  128.                    spin_lock(&spin);
  129.                    if(on)
  130.                    {
  131.                             gpio_direction_output(EXYNOS4_GPX2(6), 0);
  132.                             __x4412_led_status[2] = 1;
  133.                    }
  134.                    else
  135.                    {
  136.                             gpio_direction_output(EXYNOS4_GPX2(6), 1);
  137.                             __x4412_led_status[2] = 0;
  138.                    }
  139.                    spin_unlock(&spin);
  140.          }
  141.          else if(!strcmp(attr->attr.name, "led4"))
  142.          {
  143.                    spin_lock(&spin);
  144.                    if(on)
  145.                    {
  146.                             gpio_direction_output(EXYNOS4_GPX2(7), 0);
  147.                             __x4412_led_status[3] = 1;
  148.                    }
  149.                    else
  150.                    {
  151.                             gpio_direction_output(EXYNOS4_GPX2(7), 1);
  152.                             __x4412_led_status[3] = 0;
  153.                    }
  154.                    spin_unlock(&spin);
  155.          }

  156.          return count;
  157. }
  158. //kobject目录下的四个文件对应的属性,读写函数
  159. static DEVICE_ATTR(led1, 0666, x4412_led_read, x4412_led_write);
  160. static DEVICE_ATTR(led2, 0666, x4412_led_read, x4412_led_write);
  161. static DEVICE_ATTR(led3, 0666, x4412_led_read, x4412_led_write);
  162. static DEVICE_ATTR(led4, 0666, x4412_led_read, x4412_led_write);
  163. static struct attribute * x4412_led_sysfs_entries[] = {          //对应kobject目录下的四个文件
  164.          &dev_attr_led1.attr,
  165.          &dev_attr_led2.attr,
  166.          &dev_attr_led3.attr,
  167.          &dev_attr_led4.attr,
  168.          NULL,
  169. };
  170. static struct attribute_group x4412_led_attr_group = {
  171.          .name         = NULL,
  172.          .attrs  = x4412_led_sysfs_entries,   //指定注册的kobject对应的文件属性接入点
  173. };
  174. static int x4412_led_probe(struct platform_device *pdev)
  175. {
  176.          __x4412_led_probe();                                       //初始化LED对应GPIO口
  177.          return sysfs_create_group(&pdev->dev.kobj, &x4412_led_attr_group);//注册kobject
  178. }
  179. static int x4412_led_remove(struct platform_device *pdev)
  180. {
  181.          __x4412_led_remove();                                     //释放GPIO
  182.          sysfs_remove_group(&pdev->dev.kobj, &x4412_led_attr_group);//注销kobject
  183.          return 0;
  184. }
  185. static struct platform_driver x4412_led_driver = {
  186.          .probe                  = x4412_led_probe,               //平台驱动探测函数
  187.          .remove               = x4412_led_remove,
  188.          .driver                  = {
  189.                    .name         = "x4412-led",
  190.          },
  191. };
  192. static struct platform_device x4412_led_device = {
  193.          .name      = "x4412-led",
  194.          .id        = -1,
  195. };
  196. static int __devinit x4412_led_init(void)
  197. {
  198.          int ret;
  199.          printk("x4412 led driver\r\n");
  200.          ret = platform_device_register(&x4412_led_device); //注册平台设备
  201.          if(ret)
  202.                    printk("failed to register x4412 led device\n");
  203.          ret = platform_driver_register(&x4412_led_driver);   //注册平台驱动
  204.          if(ret)
  205.                    printk("failed to register x4412 led driver\n");
  206.          spin_lock_init(&spin);
  207.          return ret;
  208. }
  209. static void x4412_led_exit(void)
  210. {
  211.          platform_driver_unregister(&x4412_led_driver);
  212. }
  213. module_init(x4412_led_init);
  214. module_exit(x4412_led_exit);
  215. MODULE_LICENSE("GPL");
  216. MODULE_AUTHOR("www.9tripod.com");
  217. MODULE_DESCRIPTION("x4412 led driver");
复制代码
       自旋锁又被衍生出多种自旋锁,如读写自旋锁,顺序锁等。自旋锁不关心锁定的临界区究竟进行怎样的操作,不管是读还是写,它都一视同仁。即便多个执行单元同时读取临界资源也会被锁住。实际上,对共享资源并发访问时,多个执行单元同时读取它是不会有问题的,自旋锁的衍生锁读写自旋锁(rwlock)可允许读的并发。
读写自旋锁是一种比自旋锁粒度更小的锁机制,它保留了“自旋”的概念,但是在写操作方面,只能最多有一个写进程,在读操作方面,同时可以有多个读执行单元。当然,读和写也不能同时进行。
读写自旋锁的使用方法和自旋锁很相似,只不过它将读和写分离开来,其相关操作如下所示。
一:定义和初始化读写自旋锁
  1. rwlock_t my_rwlock;
  2. rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* 静态初始化 */
  3. rwlock_init(&my_rwlock); /* 动态初始化 */
复制代码
二:读锁定
  1. void read_lock(rwlock_t *lock);
  2. void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
  3. void read_lock_irq(rwlock_t *lock);
  4. void read_lock_bh(rwlock_t *lock);
复制代码
三:读解锁
  1. void read_unlock(rwlock_t *lock);
  2. void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
  3. void read_unlock_irq(rwlock_t *lock);
  4. void read_unlock_bh(rwlock_t *lock);
复制代码
四:写锁定
  1. void write_lock(rwlock_t *lock);
  2. void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
  3. void write_lock_irq(rwlock_t *lock);
  4. void write_lock_bh(rwlock_t *lock);
  5. int write_trylock(rwlock_t *lock);
复制代码
五:写解锁
  1. void write_unlock(rwlock_t *lock);
  2. void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
  3. void write_unlock_irq(rwlock_t *lock);
  4. void write_unlock_bh(rwlock_t *lock);
复制代码
在对共享资源进行读取之前,应该先调用写锁定函数,完成之后应调用写解锁函数。在前面的实验中我们创建了一个字符设备驱动x4412-cdev.c,通过put_user和get_user函数和应用程序交互数据。对该驱动的读写函数添加读写自旋锁,参考源码如下:
  1. #include <linux/module.h>
  2. #include <linux/fs.h>
  3. #include <linux/errno.h>
  4. #include <linux/init.h>
  5. #include <linux/cdev.h>
  6. #include <asm/uaccess.h>
  7. #include <linux/device.h>
  8. static int x4412_cdev_major;
  9. static struct cdev x4412_cdev;
  10. static struct class *cdev_class;
  11. rwlock_t lock; //定义 rwlock
  12. int x4412_cdev_open(struct inode *inode, struct file *filp)
  13. {
  14.          return 0;
  15. }
  16. int x4412_cdev_release(struct inode *inode, struct file *filp)
  17. {
  18.          return 0;
  19. }
  20. static ssize_t x4412_cdev_read(struct file *filp, char __user *buf,size_t size,loff_t *ppos)
  21. {
  22.          int read_sizes = 0;
  23.          char *p;
  24.          char Message[]="This is x4412/ibox devboard.";
  25.          p = Message;
  26.          write_lock(&lock);
  27.          while(size && *p)
  28.          {
  29.                    if(put_user(*(p++),buf++))
  30.                             return -EINVAL;
  31.                    size--;
  32.                    read_sizes++;
  33.          }
  34.          write_unlock(&lock);
  35.          return read_sizes;
  36. }
  37. static ssize_t x4412_cdev_write(struct file *filp, const char __user *buf,size_t size, loff_t *ppos)
  38. {
  39.          int i;
  40.          char str;
  41.          read_lock(&lock);
  42.          for(i=0;i<size;i++)
  43.          {
  44.                    if(!get_user(str,buf++))
  45.                             printk("%c",str);
  46.          }
  47.          read_unlock(&lock);
  48.          printk("\r\n");
  49.          return size;
  50. }
  51. static const struct file_operations x4412_fops =
  52. {
  53.          .owner = THIS_MODULE,
  54.          .read = x4412_cdev_read,
  55.          .write = x4412_cdev_write,
  56.          .open = x4412_cdev_open,
  57.          .release = x4412_cdev_release,
  58. };
  59. static void x4412_setup_cdev(void)
  60. {
  61.          int err, devno = MKDEV(x4412_cdev_major, 0);
  62.          cdev_init(&x4412_cdev, &x4412_fops);
  63.          err = cdev_add(&x4412_cdev, devno, 1);
  64.          if (err)
  65.                    printk(KERN_NOTICE "Error %d adding x4412_cdev", err);
  66. }
  67. static void x4412_clear_cdev(void)
  68. {
  69.          cdev_del(&x4412_cdev);
  70.          unregister_chrdev_region(MKDEV(x4412_cdev_major, 0), 1);
  71. }
  72. int x4412_cdev_init(void)
  73. {
  74.          int result;
  75.          dev_t devno;
  76.          result = alloc_chrdev_region(&devno, 0, 1, "x4412-cdev");
  77.          x4412_cdev_major = MAJOR(devno);
  78.          if(result < 0)
  79.          {
  80.                    printk("register x4412-cdev error!\r\n");
  81.                    return result;
  82.          }
  83.          printk("x4412-cdev main dev number:%d\r\n",x4412_cdev_major);
  84.          x4412_setup_cdev();
  85.          cdev_class = class_create(THIS_MODULE,"x4412-cdev");
  86.          if(IS_ERR(cdev_class))
  87.          {
  88.                    x4412_clear_cdev();
  89.                    return PTR_ERR(cdev_class);
  90.          }
  91.          device_create(cdev_class,NULL,MKDEV(x4412_cdev_major,0),NULL,"x4412-cdev");
  92.          rwlock_init(&lock); //初始化 rwlock
  93.          return 0;
  94. }
  95. void x4412_cdev_exit(void)
  96. {
  97.          device_destroy(cdev_class,MKDEV(x4412_cdev_major,0));
  98.          class_destroy(cdev_class);
  99.          x4412_clear_cdev();
  100. }
  101. MODULE_AUTHOR("www.9tripod.com");
  102. MODULE_LICENSE("GPL");
  103. module_init(x4412_cdev_init);
  104. module_exit(x4412_cdev_exit);
复制代码
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|深圳市九鼎创展科技官方论坛 ( 粤ICP备11028681号-2  

GMT+8, 2024-6-23 11:08 , Processed in 0.021316 second(s), 17 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表