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

x4412&ibox项目实战35-使用信号量避免并发竞争

[复制链接]
跳转到指定楼层
楼主
发表于 2014-10-8 17:54:01 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
信号量(semaphore)是用于保护临界区的一种常用方法,它的使用方式和自旋锁类似。与自旋锁相同,只有得到信号量的进程才能执行临界区代码。但是,与自旋锁不同的是,当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。
信号量的相关操作如下:
一:定义信号量
  1. struct semaphore sem;
复制代码
二:初始化信号量
  1. void sema_init (struct semaphore *sem, int val);
复制代码
该函数初始化信号量,并设置信号量sem的值为val。尽管信号量可以被初始化为大于1的值从而成为一个计数信号量,但是它通常不被这样使用。
  1. void init _ MUTEX(struct semaphore *sem);
复制代码
该函数用于初始化一个用于互斥的信号量,它把信号量sem的值设置为1,等同于:
  1. sema_init (struct semaphore *sem, 1)。
复制代码
以下函数也用于初始化一个信号量,但它把信号量sem的值设置为0:
  1. void init _ MUTEX _ LOCKED (struct semaphore *sem);
复制代码
它等同于:
  1. sema_init (struct semaphore *sem, 0)。
复制代码
此外,下面宏是定义并初始化信号量的“快捷方式”。
  1. DEFINE_SEMAPHORE (name)
复制代码
三:获得信号量
  1. void down(struct semaphore * sem);
复制代码
该函数用于获得信号量sem,它会导致睡眠,因此不能在中断上下文使用。
  1. int down_interruptible(struct semaphore * sem);
复制代码
该函数功能与down()类似,不同之处为,因为down()而进入睡眠状态的进程不能被信号打断,而因为down_interruptible()而进入睡眠状态的进程能被信号打断,信号也会导致该函数返回,这时候函数的返回值非0。
  1. int down_trylock(struct semaphore * sem);
复制代码
该函数尝试获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,返回非0值。它不会导致调用者睡眠,可以在中断上下文使用。
在使用down_interruptible()获取信号量时,对返回值一般会进行检查,如果非0,通常立即返回-ERESTARTSYS,如:
  1. if (down _ interruptible(&sem))
  2. {
  3. return - ERESTARTSYS;
  4. }
复制代码
四:释放信号量
  1. void up(struct semaphore * sem);
复制代码
该函数释放信号量sem,唤醒等待者。
信号量一般这样被使用,如下所示:
  1. //定义信号量
  2. DECLARE _ MUTEX(mount _ sem);
  3. down(&mount _ sem);//获取信号量,保护临界区
  4. ...
  5. critical section //临界区
  6. ...
  7. up(&mount _ sem);//释放信号量
复制代码
       编写一个驱动,用信号量的方式保证驱动只能被打开一次,当多个进程打开该驱动时,提示错误信息,并返回失败。
       kernel/drivers/char/x4412目录下新建x4412-semaphore.c文件,编辑内容如下:
  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_semaphore_major;
  9. static struct cdev x4412_semaphore;
  10. static struct class *cdev_class;
  11. DEFINE_SEMAPHORE(sem_lock);

  12. int x4412_semaphore_open(struct inode *inode, struct file *filp)
  13. {
  14.          int ret;
  15.          if(down_trylock(&sem_lock))
  16.          {
  17.                    printk("x4412-semaphore open error.\r\n");
  18.                    return -EBUSY;
  19.          }
  20.          return 0;
  21. }
  22. int x4412_semaphore_release(struct inode *inode, struct file *filp)
  23. {
  24.          printk("x4412-semaphore close.\r\n");
  25.          up(&sem_lock);
  26.          return 0;
  27. }
  28. static const struct file_operations x4412_fops =
  29. {
  30.          .owner = THIS_MODULE,
  31.          .open = x4412_semaphore_open,
  32.          .release = x4412_semaphore_release,
  33. };
  34. static void x4412_setup_cdev(void)
  35. {
  36.          int err, devno = MKDEV(x4412_semaphore_major, 0);
  37.          cdev_init(&x4412_semaphore, &x4412_fops);
  38.          err = cdev_add(&x4412_semaphore, devno, 1);
  39.          if (err)
  40.                    printk(KERN_NOTICE "Error %d adding x4412_semaphore", err);
  41. }
  42. static void x4412_clear_cdev(void)
  43. {
  44.          cdev_del(&x4412_semaphore);
  45.          unregister_chrdev_region(MKDEV(x4412_semaphore_major, 0), 1);
  46. }
  47. int x4412_semaphore_init(void)
  48. {
  49.          int result;
  50.          dev_t devno;
  51.          result = alloc_chrdev_region(&devno, 0, 1, "x4412-semaphore");
  52.          x4412_semaphore_major = MAJOR(devno);
  53.          if(result < 0)
  54.          {
  55.                    printk("register x4412-semaphore error!\r\n");
  56.                    return result;
  57.          }
  58.          x4412_setup_cdev();
  59.          cdev_class = class_create(THIS_MODULE,"x4412-semaphore");
  60.          if(IS_ERR(cdev_class))
  61.          {
  62.                    x4412_clear_cdev();
  63.                    return PTR_ERR(cdev_class);
  64.          }
  65.          device_create(cdev_class,NULL,MKDEV(x4412_semaphore_major,0),NULL,"x4412-semaphore");
  66.          return 0;
  67. }
  68. void x4412_semaphore_exit(void)
  69. {
  70.          device_destroy(cdev_class,MKDEV(x4412_semaphore_major,0));
  71.          class_destroy(cdev_class);
  72.          x4412_clear_cdev();
  73. }
  74. MODULE_AUTHOR("www.9tripod.com");
  75. MODULE_LICENSE("GPL");
  76. module_init(x4412_semaphore_init);
  77. module_exit(x4412_semaphore_exit);
复制代码
       驱动在open函数中通过down_trylock函数尝试获取信号量,若获取成功,函数将返回0,对应驱动打开成功,若获取失败,函数将返回失败,并打印出错提示信息。release函数通过up函数释放信号量,释放之后,其他进程将允许访问本驱动了。
       编辑kernel/drivers/char/x4412/Kconfig文件,添加如下语句:
  1. config X4412_SEMAPHORE_DRIVER
  2.          tristate "x4412 semaphore driver"
  3.          default m
  4.          help
  5.          compile for x4412 semaphore driver,y for kernel,m for module.
复制代码
       编辑kernel/drivers/char/x4412/Makefile文件,添加如下语句:
  1. obj-$(CONFIG_X4412_SEMAPHORE_DRIVER) += x4412-semaphore.o
复制代码
       编译内核,在kernel/drivers/char/x4412目录将会生成目标文件x4412-semaphore.ko
       ubuntu用户目录或samba目录下新建x4412-semaphore-app.c文件,编辑内容如下:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <fcntl.h>
  4. #define DEVICE_NAME  "/dev/x4412-semaphore"
  5. #define SEM_DEBUG

  6. int main(int argc,char **argv)
  7. {
  8.         int fd;
  9.         fd = open(DEVICE_NAME,O_RDWR);
  10.         if(fd < 0)
  11.         {
  12.                   printf("open device %s error \n",DEVICE_NAME);
  13.         }
  14.         else
  15.         {
  16. #ifdef SEM_DEBUG
  17.                    fd = open(DEVICE_NAME,O_RDWR);
  18.                    if(fd < 0)
  19.                    {
  20.                             printf("repeat open device %s error!\r\n",DEVICE_NAME);
  21.                    }
  22.                    usleep(5000);
  23. #endif
  24.                    close(fd);
  25.         }
  26.         return 0;
  27. }
复制代码
       该测试程序两次调用open函数打开驱动,模拟多个进程同时调用一个驱动的情况。使用如下指令编译测试应用程序:
  1. arm-none-linux-gnueabi-gcc x4412-semaphore-app.c -o x4412-semaphore-app
复制代码
       这时在当前目录下将会生成目标映像文件x4412-semaphore-app。将驱动模块和应用映像拷贝到开发板,加载模块测试,观察是否和期望的效果一样。
  1. [root@x4412 mnt]# insmod x4412-semaphore.ko
  2. [root@x4412 mnt]# ./x4412-semaphore-app
  3. [ 1385.583258] x4412-semaphore open error.
  4. repeat open device /dev/x4412-semaphore error!
  5. [ 1385.591150] x4412-semaphore close.
复制代码
       从前面的示例可以看出,自旋锁和信号量都是解决互斥问题的基本手段,面对特定的情况,应该如何进行选择呢?选择的依据是临界区的性质和系统的特点。从严格意义上说,信号量和自旋锁属于不同层次的互斥手段,前者的实现依赖于后者。在信号量本身的实现上,为了保证信号量结构存取的原子性,在多CPU 中需要自旋锁来互斥。
信号量是进程级的,用于多个进程之间对资源的互斥,虽然也是在内核中,但是该内核执行路径是以进程的身份,代表进程来争夺资源的。如果竞争失败,会发生进程上下文切换,当前进程进入睡眠状态,CPU 将运行其他进程。鉴于进程上下文切换的开销也很大,因此,只有当进程占用资源时间较长时,用信号量才是较好的选择。
当所要保护的临界区访问时间比较短时,用自旋锁是非常方便的,因为它节省上下文切换的时间。但是 CPU 得不到自旋锁会在那里空转直到其他执行单元解锁为止,所以要求锁不能在临界区里长时间停留,否则会降低系统的效率。由此,可以总结出自旋锁和信号量选用的 3 项原则。
l  当锁不能被获取时,使用信号量的开销是进程上下文切换时间Tsw,使用自旋锁的开销是等待获取自旋锁(由临界区执行时间决定)Tcs,若Tcs比较小,应使用自旋锁,若Tcs很大,应使用信号量。
l  信号量所保护的临界区可包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护包含这样代码的临界区。因为阻塞意味着要进行进程的切换,如果进程被切换出去后,另一个进程企图获取本自旋锁,死锁就会发生。
l  信号量存在于进程上下文,因此,如果被保护的共享资源需要在中断或软中断情况下使用,则在信号量和自旋锁之间只能选择自旋锁。当然,如果一定要使用信号量,则只能通过 down_trylock()方式进行,不能获取就立即返回以避免阻塞。

在x4412开发板和ibox卡片电脑上直接能用的映像文件:
x4412-semaphore.ko (3.82 KB, 下载次数: 7)
x4412-semaphore-app (6.07 KB, 下载次数: 8)
回复

使用道具 举报

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

本版积分规则

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

GMT+8, 2024-6-23 11:40 , Processed in 0.021527 second(s), 20 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

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