Khan's Notebook GCC/GNU/Linux Delphi/Window Java/Anywhere

路漫漫,长修远,我们不能没有钱
随笔 - 172, 文章 - 0, 评论 - 257, 引用 - 0
数据加载中……

System V信号灯(节选自unix网络编程卷2-进程间通信)

System V信号灯(节选自unix网络编程卷2-进程间通信)

System V信号灯通过定义如下概念增加了另外一级复杂度:
    计数信号灯集(set of counting semaphores): 一个或者多个信号灯(构成一个集合),其中每个都
是计算信号灯. 每个集合的信号灯数存在一个限制, 一般在25个的数量级上. 当我们谈论"System V
信号灯"时, 所指的是计数信号灯集. 当我们谈论"Posix 信号灯"时, 所指的是单个计数信号灯.

    对于系统中的每个信号灯集, 内核维护一个如下的信息结构, 它定义在<sys/sem.h>头文件中:
  struct semid_ds{
    struct ipc_perm sem_perm;   //operation permission struct
    struct sem      *sem_base;  //ptr to array of semaphores in set
    ushort          sem_nsems;  //of semaphores in set
    time_t          sem_otime;  //time of last semop()
    time_t          sem_ctime;  //time of creation or last IPC_SET
  };

    其中的ipc_perm结构描述对于当前这个特定的信号灯的访问权限.
  struct ipc_perm{
    uid_t   uid;  // owner's effective user id
    gid_t   gid;  // owner's effective group id
    uid_t   cuid; // creator's effective user id
    gid_t   cgid; // creator's effective group id
    mode_t  mode; // access modes
    ulong   seq;  // slot usage sequence number
    key_t   key;  // key
  };

    sem结构是内核用于维护某个给定信号灯的一组值的内部数据结构.一个信号灯集的每个成员由如
下结构描述:
  struct sem{
    ushort_t      semval;   //semaphore value, nonnegative
    short         sempid;   //PID of last successful semop(), SETVAL, SETALL
    ushort_t      semncnt;  //awalting semval > current value
    ushort_t      semzcnt;  //awalting semval =0
  };
    注意sem_base含有指向某个sem_base含有指向某个sem结构数组的指针, 当前信号灯集中的每个信
号灯对应其中一个数组元素.
    除了维护一个信号灯集中每个信号灯的实际值之外, 内核还给该集合中每个信号灯维护另外三个
信息, 对其值执行最后一次操作的进程的pid, 等待其值增长的进程数计数以及等待其值变为0的pid计
数.

///////////////////////////////////////////////////////////

                  semget 函数

  semget函数创建一个信号灯集或访问一个已存在的信号灯集
#include <sys/sem.h>
int semget(key_t key, int nsems, int oflag);
@return int 成功时返回非负数标识符, 出错时返回-1

    返回值是一个称为信号灯标识符(semaphore identifier)的整数, semop和semctl函数将使用它.
    指定key为IPC_PRIVATE保证创建一个唯一的IPC对象. 没有一对pahtname和id的组合会导致ftok产
生IPC_PRIVATE这个键值.
    nsems 指定集合中的信号灯数. 如果我们不创建一个新信号灯集, 而只是访问一个已存在的信号
灯集合, 那就可以指定该参数为0, 一旦创建完毕, 就不能改变其中的信号灯值.
    oflag SEM_R和SEM_A常值的组合. 其中R代表read, A代表alter. 他们还可以与
IPC_CREAT|IPC_EXCL按位或.
    设置oflag参数的IPC_CREAT位但不设置它的IPC_EXCL位时, 如果所指定的IPC对象不存在, 那就创
建一个新对象, 否则返回该对象.
    同时设置oflag的IPC_CREAT和IPC_EXCL位时, 如果所指定键的IPC对象不存在, 那么就创建一个新
的对象, 否则, 返回一个EEXIST错误, 因为该对象已存在.
    设置IPC_EXCL位, 但不设置IPC_CREAT位是没有意义的.
    当实际操作为创建一个新的信号灯集时, 相应的semid_ds结构的以下成员将被初始化:
    .sem_perm结构中的uid和cuid成员被置为调用进程的有效uid, gid和cgid成员被置为调用进程的

有效gid
    .oflag参数中的读写权限位存入sem_perm.mode
    .sem_otime 被置为0. sem_ctime则被置为当前时间.
    .sem_nsems 被置为nsems参数的值
    .与该集合中每个信号灯关联的哥哥sem结构并不初始化. 这些结构是在SETVAL或SETALL命令调用

semctl时初始化的.
    System V信号灯设计中, 创建一个信号灯集(semget)并将它初始化(semctl)需两次函数调用是一
个致命的缺陷. 一个不完备的解决方案是: 在调用semget时指定IPC_CREATE|IPC_EXCL标志, 只有一个
进程(首先调用semget的那个进程)创建所需信号灯. 该进程随后初始化该信号灯, 其他进程会收到来
自semget的一个EEXIST错误, 于是再次调用semget, 不过这次调用既不指定IPC_CREATE, 也不指定
IPC_EXCL.

    然而竞争状态依然存在, 假设有俩个进程几乎同时尝试创建并初始化一个只有单成员信号灯集,
两者都执行如下几行标了号的代码:
1   oflag = IPC_CREAT | IPC_EXCL | SVSEM_MODE;
2   if( (semid = semget(key, 1, oflag))>=0 ) { // success, we are the first, so initialze
3     arg.val=1;
4     semctl(semid, 0, SETVAL, arg);
5   } else if(errno == EEXIST){
      //already exists, just open
6     semid = semget(key, 1, SVSEM_MODE);
7   } else
8     err_sys("semget error");
9   semop(semid, ....);  // decrement the semaphore by 1

那么如下情况可能发生:
 1.第一个进程执行1-3行, 然后被内核阻止执行.
 2.内核启动第二个进程, 执行1 2 5 6 9行

    尽管成功创建该信号灯的第一个进程将是初始化该信号灯的唯一进程, 但是由于它完成创建和初
始化操作需要两个步骤, 因此, 内核有可能在俩步骤之间把上下文切换到另一个进程. 这个新切换来
运行的进程可以随后使用该信号灯(第9行), 但是该信号灯的值尚未由第一个进程初始化, 当第二个进
程执行第9行时, 该信号灯的值是不确定的.
    幸运的是存在绕过这个竞争状态的方法. 当semget创建一个新的信号灯集时, 其semid_ds结构的
sem_otime成员保证被置为0. (System V手册, XPG3和Unix98标准也这么说) 该成员只是在semop调用
成功时才被置为当前值. 因此, 上面的例子中的第二个进程成功的再次调用semget(第6行)后, 必须以
ICP_STAT命令调用semctl. 然后等待sem_otime变为非零值, 到时就可以判定该信号灯已被初始化, 而
且对他初始化的那个进程已经成功调用semop. 这意味着创建该信号灯的那个进程必须初始化它的值,
而且必须在任何其他进程可以使用该信号灯之前调用semop.

#include "unpipc.h"
#include "semaphore.h"
#include <stdarg.h>
#define  MAX_TRIES 10

union semun {
  int              val;       /* for SETVAL */
  struct semid_ds  *buf;      /* for IPC_STAT and IPC_SET */
  unsigned short   *array;    /* for GETALL and SETALL */
};

sem_t* sem_open( const char *pathname, int oflag, ...){
  int i, fd, semflag, semid, save_errno;
  key_t            key;
  mode_t           mode;
  va_list          ap
  sem_t            *sem;
  union semun      arg;
  unsigned int     value;
  struct semid_ds  seminfo;
  struct sembuf    initop;
 
  // no mode for sem_open() w/out O_CREAT; guess
  semflag = SVSEM_MODE;
  semid  = -1;
  if(oflag & CREAT){
    va_start(ap, oflag); // init ap to final named argument
    mode = va_arg(ap, va_mode_t);
    value = va_arg(ap, unsigned int);
    va_end(ap);

    // convert to key that will identify System V semaphore
    if( (fd = open(pathname, oflag, mode)) == -1 )
      return(SEM_FAILED);
    close(fd);

    if( (key = ftok(pathname, 0)) ==(key_t)-1 )
      return(SEM_FAILED);
    
    semflag = IPC_CREATE | (mode & 0777);
    if(oflag & O_EXCL)
      semflag = semflag | IPC_EXCL;

    //create the System V semaphore with IPC_EXCL
    if( (semid = semget(key, 1, semflag|IPC_EXCL)) >= 0 ){
      //success, we are the first so initialize to 0
      arg.val = 0;
      if( semctl(semid, 0, SETVAL, arg) == -1)
        goto err;
        // then increment by value set sem_otime nonzore
      if(value > SEMVMX){
        errno = EINVAL;
        got err;
      }
      initop.sem_num = 0;
      initop.sem_op = value;
      initop.sem_flag = 0;
      if( semop(semid, &initop, 1) == -1 )
        goto err;
      goto finish;
    } else if( errno != EEXIST || (semflag & IPC_EXCL) != 0 )
      goto err;
      // else fall through
  }
  .....
}
// sem_open函数, 前半部分[my_pxsem_svsem/sem_open.c]


/////////////////////////////////////////////////////
             semop 函数

    使用semget打开一个信号灯集后, 对其中一个或多个信号灯的操作就使用semop函数来执行
#include <sys/sem.h>
int semop(int semid, struct sembuf *opsptr, size_t nops);
@return int 成功时返回0, 出错时返回-1

  opsstr指向一个如下结构的数组:
  struct sembuf{
    short sem_num;  // semaphore number : 0, 1..., nsems-1
    short sem_op;   // semaphore operation <0,0>0
    short sem_flg;  // operation flags: 0, IPC_NOWAIT, SEM_UNDO
  }

  nops 指出由opsptr指向的sembuf结构数组中元素的数目. 该数组中的每个元素给目标信号灯集中某
个特定的信号灯指定一个操作. 这个特定的信号灯由sembuf.sem_num指定, 0代表第一个元素, 1代表
第二个元素, 依次类推, 一直到nsems-1, 其中nsems是目标信号灯集中成员信号灯的数目(创建时
semget的第二个参数).
    我们仅仅保证sembuf结构含有所给出的三个成员. 它可能还含有其他成员, 而哥哥成员并不保证
以我们给出的顺序排序. 这意味着我们不能静态初始化这种结构, 例如:
  struct sembuf ops = {0, 1, SEM_UNDO}; //error
    而是必须使用运行时初始化的方法, 给每个成员赋值.
    传递给semop函数的操作数组由内核保证原子的执行, 内核或者完成所有指定操作, 或者什么操作
都不做.
    每个特定的操作是由sem_op的值确定的, 它可以是负数, 0 或者正数, 在稍后的讨论中, 我们将
使用如下术语:
  .semval : 信号灯的当前值
  .semncnt: 等待semval变为大于其当前值的线程数
  .semzcnt: 等待semval变为0的线程数
  .semadj : 所指定信号灯针对调用进程的调整值. 只有在对应本操作的sembuf结构的sem_flg成员中
指定SEM_UNDO标志后, semadj才会更新. 这是一个概念性的变量, 它由内核为再其某个信号灯操作中
指定了SEM_UNDO标志的每个进程维护;具有semadj这个名字的结构成员不必存在(调用进程终止时,
semadj加到相应信号灯的semval上, 要是调用进程对某个信号灯的全部操作都指定SEM_UNDO标志, 那
么该进程终止后, 该信号灯的值就会变得像根本没有运行过进程一样, 这就是UNDO的本意)
  .使得一个给定信号灯操作非阻塞的方法是, 再对应的sembuf.sem_flg成员中指定IPC_NOWAIT标志.

指定了该标志的情况下, 如果不把调用线程投入睡眠, 就完成不了这个给定操作, 那么semop将返回一
个EAGAIN错误.
  .当一个线程被投入睡眠以等待某个信号灯操作的完成时(我们将看到该线程既可等待这个信号灯值
变为0, 也可等待它变为大于0), 如果它捕获了一个信号, 那么其信号处理程序的返回将中断引起睡眠
的semop函数, 该函数于是返回了一个EINTR错误, semop是需被捕获的信号中断的慢系统调用(slow
system call).
  .当一个线程被投入睡眠以等待某个信号灯操作的完成时, 如果该信号灯被另外某个线程或进程从进
程从系统中删除, 那么引起睡眠的semop函数将返回一个EIDRM错误, 表示identifier removed(标识符
已删除)"

    现在描述semop的操作, 它基于每个具体指定的sem_op操作的三个可能值: 正数, 0, 负数.
  1.如果sem_op是正数, 其值就加到semval上, 这对应于释放由某个信号灯控制的资源
    如果指定了SEM_UNDO标志, 那就从相应信号灯的semadj值中减掉sem_op的值
  2.如果sem_op是0, 那么调用者希望等待到semval变为0. 如果semval已经是0, 那就立即返回
    如果semval不为0, 相应信号灯的semzcnt值就加1, 调用线程则阻塞到semval变为0(到那时, 相应

信号灯的semzcnt值再减1). 前面已经提到, 如果指定了IPC_NOWAIT标志, 调用线程就不会被投入睡眠
. 如果某个被捕获的信号中断了引起睡眠的semop函数, 或者相应的信号灯被删除, 那么该函数将过早
的返回一个错误.
  3.如果sem_op是负数, 那么调用者希望等待semval变为大于或等于sem_op的绝对值.这对应于分配资
源.
    如果semval大于或等于sem_op的绝对值, 那就从semval中减掉sem_op的绝对值.
    如果指定了SEM_UNDO标志, 那么sem_op的绝对值就加到相应信号灯的semadj值上.
    如果semval小于sem_op的绝对值, 相应信号灯的semncnt值就加1, 调用线程则被阻塞到semval变
为大于或等于sem_op的绝对值. 到那时,该线程将被解阻塞, semval中减掉sem_op的绝对值, 相应信号
灯的semncnt值将减1. 如果指定了SEM_UNDO标志, 那么sem_op的绝对值将加到相应信号灯的semadj值
上. 前面已经提到, 如果指定了IPC_NOWAIT标志, 调用线程就不会投入睡眠. 另外, 如果某个被捕获
的信号中断了引起睡眠的sem_op函数, 或者相应的信号灯被删除, 那么该函数将过早的返回一个错误.


/////////////////////////////////////////////////////
                semctl 函数

    semctl函数对一个信号灯执行各种控制操作.

#include <sys/sem.h>
int semctl( int semid, int semnum, int cmd, .../* union semun arg*/ );
@return int 成功时为非负值, 出错时为-1

  semid  标识需控制其操作的信号灯集
  semnum 标示该信号灯集中某个成员(0, 1, ....nsems-1). semnum值仅用于GETVAL,

SETVAL,GETNCNT,GETZCNT,SETPID命令
  第四个参数为可选, 取决于第三个参数cmd(参见下面给出的联合中的注释).
union semun {
  int              val;       /* used for SETVAL only*/
  struct semid_ds  *buf;      /* used for IPC_STAT and IPC_SET */
  unsigned short   *array;    /* used for GETALL and SETALL */
};
    这个union并没有出现再任何系统头文件中, 因而必须由应用程序声明.它是以值传递的, 而不是
以引用传递. 也就是说作为参数的是这个union的值, 而不是指向它的指针.
    (不幸的是, 有些系统(FreeBSD和Linux)在<sys/sem.h>头文件中定义了这个union, 从而造成编写
可移植性代码的困难. 尽管由这个系统头文件来声明union semun确实有足够的理由, Unix 98还是声
称它必须由应用程序显式声明).

    System V支持下列cmd值, 除非另外声明, 否则返回值为0表示成功, 返回值为-1 表示失败.

  GETVAL   把semval的当前值作为函数返回值返回, 既然信号灯绝不会是负数(semval被声明为
unsigned short), 因此成功的返回值总是非负数.

  SETVAL   把semval值设置为arg.val. 如果操作成功, 那么相应信号灯在所在进程中的调整值
(semadj)将被置为0;

  GETPID   把sempid的当前值作为函数返回值返回.
  GETNCNT  把semncnt的当前值作为函数返回值返回.
  GETZCNT  把semzcnt的当前值作为函数返回值返回.

  GETALL   返回指定信号灯集中每个成员的semval值. 这些值通过arg.arry指针返回, 函数本身的返
回值为0, 注意, 调用者必须分配足够容纳所指定信号灯集的所有成员的semval值的一个unsigned
short[], 然后把arg.array设置成指向这个数组.

  SETALL   设置所指定信号灯集中每个成员的semval值. 这些值是通过arg.array数组指定的
  IPC_RMID 把由semid指定的信号灯集从系统中删除掉.

  IPC_SET  设置所指定信号灯集的semid_ds结构中的以下三个成员: sem_perm.uid, sem_perm.gid,
sem_perm.mode, 这些值由arg.buf参数指向的结构中的相应成员. semid_ds.sem_ctime也被设置成当
前值.

  IPC_STAT (通过arg.buf参数) 返回所指定信号灯集的当前semid_ds结构. 注意, 调用者必须首先分
配一个semid_ds结构, 并把arg.buf设置成指向这个结构.



posted on 2009-11-27 00:57 Khan 阅读(1916) 评论(0)  编辑 收藏 引用 所属分类: GCC/G++


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理