Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1018387
  • 博文数量: 162
  • 博客积分: 3887
  • 博客等级: 中校
  • 技术积分: 1617
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-06 19:05
文章分类

全部博文(162)

文章存档

2015年(4)

2014年(7)

2013年(10)

2012年(16)

2011年(89)

2010年(36)

分类: LINUX

2011-03-08 14:05:13

一、概述
       信号灯主要用于同步或者互斥对共享资源的访问,它的发明来源于火车运行系统中的“信号灯”,利用信号灯可以实现 “PV操作”这种进程间同步机制。P操作是获得资源,将信号灯的值减1,如果结果不为负则执行完毕,进程获得资源,否则进程睡眠以等待资源别的进程释放资 源;V操作则是释放资源,给信号灯的值加1,释放一个因执行P操作而等待的进程。

二、信号灯的两种类型

1、二值信号灯:

      最简单的信号灯形式,信号灯的值只能取0或1,类似于互斥锁。

   虽然二值信号灯能够实现互斥锁的功能,但两者的关注内容不同。信号灯强调共享资源,只要共享资源可用,其他进程同样可以修改信号灯的值;

      互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。

2、 计数信号灯:

  信号灯的值可以取任意非负值(当然受内核本身的约束),用来统计资源,其值就代表可用资源的个数。

三、Linux下对信号灯的操作

1、 打开或创建信号灯

对应的系统调用:

#include

#include

#include

int semget(key_t key, int nsems, int sem*);

  第一个参数key是一个键值,信号灯集的描述符就由系统范围内唯一的一个键值生成。

  key可以由ftok函数生产:

key_t ftok(const char *pathname, int proj_id);

  ftok返回与系统中的路径pathname相对应的一个键值

  nsems是信号灯集中信号灯的个数,其最大值取决于具体的系统,如果是0,则代表访问已存在的信号灯集。

  sem*是一些标志位,它是IPC_CREAT、IPC_EXCL、IPC_NOWAIT三者与访问权限或的结果,访问权限一般都是,代表只有信号灯集的属主才对信号灯集有读写的权限。

  semget()如果执行成功,返回与key对应的信号灯集描述字(非负整数,存在于内存之中),失败返回-1,并将错误码置于errno全局变量中。

2、操作信号灯

  linux可以增加或减小信号灯的值,相应于对共享资源的释放和占有。

  对应的系统调用:

int semop(int semid, struct sembuf *sops, unsigned nsops);

  semop系统调用可以实现对由semid标志的信号灯集中的某一个指定信号灯的一系列操作。

  semid即是semget返回的信号灯描述字。

  sops是指向结构体sembuf的指针,可以是这种类型的结构体数组的头指针,数组的每一个sembuf结构都刻画一个在特定信号灯上的操作。

  nsops为sops指向数组的大小(有几个sembuf结构体)。

sembuf结构体定义如下:

  struct sembuf

  {

    unsigned  short sem_num;   /* semaphore number*/

    short sem_op;                       /* semaphore operation */

    short sem_*;                          /* operation flags */

  };

  sem_num对应信号灯集中的信号灯,0代表第一个信号灯。

  sem_op的值决定了对sem_num指定的信号灯的三种不同操作:

  ●sem_op = 0,调用者阻塞等待直到信号灯的值等于0时返回。可以用来测试共享资源是否已用完。

  ● sem_op > 0,代表进程要申请-sem_op个共享资源。

  如果信号灯值sem_val > abs(sem_op),则sem_val = sem_val-abs(sem_op);

  否则调用进程睡眠直到sem_val>=abs(sem_op)。当然如果sem_*指定为IPC_NOWAIT,则调用进程立即返回。

  ●      sem_op > 0,代表进程要释放sem_op数量的共享资源。也就是V操作。

  sem_*可取0,IPC_NOWAIT以及SEM_UNDO两个标志。

  ●      0代表阻塞调用

  ●      IPC_NOWAIT代表非阻塞调用

  ●      如果设置了SEM_UNDO标志,那么在进程结束时,相应的操作将被取消,这是比较重要的一个标志位。如果设置了该标志位,那么在进程没有释放共享资源就 退出时,内核将代为释放。如果为一个信号灯设置了该标志,内核都要分配一个 sem_undo结构来记录它,为的是确保以后资源能够安全释放。事实上,如果进程退出了,那么它所占用就释放了,但信号灯值却没有改变,此时,信号灯值 反映的已经不是资源占有的实际情况,在这种情况下,问题的解决就靠内核来完成。这有点像僵尸进程,进程虽然退出了,资源也都释放了,但内核进程表中仍然有 它的记录,此时就需要父进程调用waitpid来解决问题了

  semop调用成功返回0,失败返回-1,并将错误码置于errno全局变量中。

  semop可以同时操作多个信号灯,在实际应用中,对应 多种资源的申请或释放。semop保证操作的原子性,这一点尤为重要。尤其对于多种资源的申请来说,要么一次性获得所有资源,要么放弃申请,要么在不占有 任何资源情况下继续等待,这样,一方面避免了资源的浪费;另一方面,避免了进程之间由于申请共享资源而造成死锁。

  3、 获得或设置信号灯属性:

  对应的系统调用:

  #include

  #include

  #include

  int semctl(int semid, int semnum, int cmd, union semun arg);

  semctl通过具体的cmd操作由semid标志的信号灯集上的由semnum指定的信号灯。

  常用的cmd有一下几个:

  ●        IPC_STAT 获取信号灯信息,信息由arg.buf返回;

  ●        GETVAL 返回semnum所代表信号灯的值;

  ●        SETVAL 设置semnum所代表信号灯的值为arg.val;

  ●        IPC_RMID 删除semnum所代表的信号灯

  用户需要自己定义联合体semun如下:

  union semun {

  int val; /* Value for SETVAL */

  struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */

  unsigned short *; /* for GETALL, SETALL */

  struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */

  };

  semctl调用成功返回0,失败返回-1,并将错误码置于errno全局变量中。

四、利用信号灯实现PV操作

1、P操作:申请资源

  这里我们封装一个函数down():

  /*

  * function: ask for resource, P operation

  * parameter: sem_id : identifier of a semaphore set;

  sem_num : semaphore number

  * return value:

  */

  void down(int sem_id, int sem_num)

  {

  struct sembuf op;

  op.sem_num = sem_num;

  op.sem_op = -1;

  op.sem_* = 0;

  semop(sem_id, &op, 1);

  }

  2、V操作:释放资源

  这里我们封装一个函数up():

  /*

  * function: free resource, V operation

  * parameter: sem_id : identifier of a semaphore set;

  sem_num : semaphore number

  * return value: none

  */

  void up(int sem_id, int sem_num)

  {

  struct sembuf op;

  op.sem_num = sem_num;

  op.sem_op = 1;

  op.sem_* = 0;

  semop(sem_id, &op, 1);

  }

感谢:“本文由华清远见提供”

阅读(1689) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~