POSIX信号量接口,意在解决XSI信号量接口的几个不足之处:

  • POSIX信号量接口相比于XSI信号量接口,允许更高性能的实现。
  • POSIX信号量接口简单易用:没有信号量集,其中一些接口模仿了我们熟悉的文件系统操作。
  • POSIX信号量删除时的处理更加合理。XSI信号量被删除后,使用该信号量标识符的操作将会出错返回,并将errno设置为EIDRM。而对于POSIX信号量,操作可以继续正常执行,直到对该信号量的最后一个引用被释放。

POSIX信号量有两种形式可供选用:有名和无名。它们的区别在于,如何被创建和销毁,其他方面则完全相同。无名信号量只存在于内存中,并且规定能够访问该内存的进程才能够使用该内存中的信号量。这就意味着,无名信号量只能被这样两种线程使用:(1)来自同一进程的各个线程(2)来自不同进程的各个线程,但是这些进程映射了相同的内存范围到自己的地址空间。相反,有名信号量则是通过名字访问,因此,来自于任何进程的线程,只要知道该有名信号量的名字都可以访问。

调用sem_open函数可以创建一个新的有名信号量,或打开一个现存的有名信号量。

#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag, ... /* mode_t mode, unsigned int value */ );
返回值:若成功则返回指向信号量的指针,若出错则返回SEM_FAILED

如果使用一个现存的有名信号量,我们只需指定两个参数:信号量名和oflag(oflag取0)。把oflag设置为O_CREAT标志时,如果指定的信号量不存在则新建一个有名信号量;如果指定的信号量已经存在,那么打开使用,无其他额外操作发生。

如果我们指定O_CREAT标志,那么就需要提供另外两个参数:mode和value。mode用来指定谁可以访问该信号量。它可以取打开文件时所用的权限位的取值(参考http://www.cnblogs.com/nufangrensheng/p/3502097.html中表4-5)。最终赋予信号量的访问权限,是被调用者文件创建屏蔽字所修改过的(http://www.cnblogs.com/nufangrensheng/p/3502328.html)。然而,注意通常只有读写权限对我们有用,但是接口不允许在我们打开一个现存的信号量时指定打开模式(mode)。实现通常以读写打开信号量。

value参数用来指定信号量的初始值。它可取值为:0-SEM_VALUE_MAX。

如果我们想要确保我们在创建一个新的信号量,可以把oflag参数设置为:O_CREAT|O_EXCL。如果信号量已经存在的话,这会导致sem_open调用失败。

为了提高移植性,我们在选择信号量名字的时候,必须遵循一定的约定:

  • 名字的首字符必须是斜杠(/)。
  • 除首字符外,名字中不能再包含其他斜杠(/)。
  • 名字的最长长度由实现定义,不应超过_POSIX_NAME_MAX个字符。

sem_open函数返回一个信号量指针,该指针可供其他对该信号量进行操作的函数使用。使用完成后,调用sem_close函数释放与信号量相关的资源

#include <semaphore.h>
int sem_close(sem_t *sem);
返回值:若成功则返回0,出错返回-

如果进程还没有调用sem_close就已经退出,那么内核会自动关闭该进程打开的所有信号量。注意,这并不会影响信号量值的状态——例如,如果我们增加了信号量的值,我们退出后这个值不会改变。同样,如果我们调用了sem_close,信号量值也不会受到影响。POSIX信号量机制中并没有如同XSI信号量中的SEM_UNDO标志。

调用sem_unlink函数来销毁一个有名信号量。

#include <semaphore.h>
int sem_unlink(const char *name);
返回值:若成功则返回0,出错则返回-

sem_unlink函数移除信号量的名字。如果当前没有打开的对该信号量的引用,那么就销毁它。否则,销毁被推迟到最后一个打开的引用被关闭。

与XSI信号量不同,我们只能对POSIX信号量的值进行加1或减1。对信号量值减1,就类似于对一个二值信号量加锁或请求一个与计数信号量相关的资源。

注意,POSIX信号量并没有区分信号量类型。使用二值信号量还是计数信号量,取决于我们如果对信号进行初始化和使用。如果信号量值只能取0和1,那么它就是一个二值信号量。当一个二值信号量值为1,我们则说它未加锁;若它的值为0,则说它已加锁。

调用sem_wait或sem_trywait函数,请求一个信号量(对信号量值执行减1操作)。

#include <semaphore.h>
int sem_trywait(sem_t *sem);
int sem_wait(sem_t *sem);
两个函数返回值:若成功则返回0,出错则返回-

如果信号量计数为0,这时如果调用sem_wait函数,将会阻塞。直到成功对信号量计数减1或被一个信号中断,sem_wait函数才会返回。我们可以使用sem_trywait函数以避免阻塞。当我们调用sem_trywait函数时,如果信号量计数为0,sem_trywait会返回-1,并将errno设置为EAGAIN。

第三种方法是可以阻塞一段有限的时间,这时我们使用sem_timedwait函数

#include <semaphore.h>
#include <time.h> int sem_timedwait(sem_t *restrict sem, const struct timespec *restrict tsptr);
返回值:若成功则返回0,出错则返回-

tsptr参数指定了希望等待的绝对时间。如果信号量可以被立即减1,那么超时也无所谓,即使你指定了一个已经过去的时间,试图对信号量减1的操作也会成功。如果直到超时,还不能对信号量计数减1,那么sem_timedwait函数将会返回-1,并将errno设置为ETIMEDOUT。

调用sem_post函数对信号量值加1。这类似于对一个二值信号量解锁或释放一个与计数信号量有关的资源。

#include <semaphore.h>
int sem_post(sem_t *sem);
返回值:若成功则返回0,出错则返回-

当我们调用sem_post的时,如果此时有因为调用sem_wait或sem_timedwait而阻塞的进程,那么该进程将被唤醒,并且刚刚被sem_post加1的信号量计数紧接着又被sem_wait或sem_timedwait减1。

如果我们想要在一个单一进程内使用POSIX信号量,那么使用无名信号量会更加简单。无名信号量只是创建和销毁有所改变,其他完全和有名信号量一样。我们调用sem_init函数创建一个无名信号量。

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
返回值:若成功则返回0,出错返回-

pshared参数指示我们是否要在多进程之间使用该无名信号量。如果要在多个进程之间使用,则将pshared设置为非0值。value参数指定信号量的初始值。

另外,我们需要声明一个sem_t类型的变量,并把它的地址传给sem_init,以便对该变量进行初始化。如果我们要在两个进程之间使用该无名信号量,我们需要确保sem参数指向这两个进程共享的内存范围内。

我们可以调用sem_destroy函数来销毁用完的无名信号量。

#include <semaphore.h>
int sem_destroy(sem_t *sem);
返回值:若成功则返回0,出错则返回-

调用sem_destroy后我们将不能再以sem为参数调用任何信号量函数,除非我们再次使用sem_init对sem进行初始化。

我们可以调用sem_getvalue函数来获取信号量值。

#include <semaphore.h>
int sem_getvalue(sem_t *sem, int *restrict valp);
返回值:若成功则返回0,出错则返回-

如果sem_getvalue执行成功,信号量的值将存入valp指向的整型变量中。但是,需要小心,我们刚读出来的信号量值可能会改变(因为我们随时可能会使用该信号量值)。如果不采取额外的同步机制的话,sem_getvalue函数仅仅用来调试。

实例

回忆http://www.cnblogs.com/nufangrensheng/p/3523623.html的表12-4,Single UNIX Specification并没有定义当一个线程锁定了一个normal类型的mutex,而另外一个线程试图对此mutex进行解锁会发生什么(对应于表12-4中“不占用时解锁”栏)。但是对于error-checking类型和recursive类型的mutex,在这种情况下则会出错。因为二值信号量可以像互斥量(mutex)一样使用,我们可以使用信号量创建我们自己的锁原语(primitive)来提供互斥。

假定我们要创建自己的锁:它可以被一个线程加锁,而被另外一个线程解锁。我们的锁结构可以如下:

struct slock {
sem_t *semp;
char name[_POSIX_NAME_MAX];
};

程序清单15-35显示了一种基于信号量的互斥原语的实现。

程序清单15-35 使用POSIX信号量的互斥

#include "slock.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <error.h> struct slock *
s_alloc()
{
struct slock *sp;
static int cnt; if((sp = malloc(sizeof(struct slock))) == NULL)
return(NULL);
do
{
snprintf(sp->name, sizeof(sp->name), "/%ld.%d", (long)getpid(),
cnt++);
sp->semp =sem_open(sp->name, O_CREAT|O_EXCL, S_IRWXU, );
}
while((sp->semp == SEM_FAILED) && (errno == EEXIST));
if(sp->semp == SEM_FAILED)
{
free(sp);
return(NULL);
}
sem_unlink(sp->name);
return(sp);
} void
s_free(struct slock *sp)
{
sem_close(sp->semp);
free(sp);
} int
s_lock(struct slock *sp)
{
return(sem_wait(sp->semp));
} int
s_trylock(struct slock *sp)
{
return(sem_trywait(sp->semp));
} int
s_unlock(struct slock *sp)
{
return(sem_post(sp->semp));
}

我们基于进程ID和计数counter创建一个名字。我们无需使用互斥量对counter加以保护,因为如果两个相互竞争的线程同时调用s_alloc并且分配了相同的名字,但是使用O_EXCL标志调用sem_open时只会有一个调用成功,而另一个以EEXIST出错返回,这时我们只需重新调用一次就行了。注意,我们在打开信号量后随即销毁它。这样其他进程就不能再访问它,而且简化了进程结束时的清理工作。

本篇博文内容摘自《UNIX环境高级编程》(第3版),仅作个人学习记录所用。关于本书可参考:http://www.apuebook.com/

进程间通信之POSIX信号量的更多相关文章

  1. Linux进程间通信IPC学习笔记之同步二(Posix 信号量)

    Linux进程间通信IPC学习笔记之同步二(Posix 信号量)

  2. Linux进程同步之POSIX信号量

    POSIX信号量是属于POSIX标准系统接口定义的实时扩展部分.在SUS(Single UNIX Specification)单一规范中,定义的XSI IPC中也同样定义了人们通常称为System V ...

  3. system V信号量和Posix信号量

    一.函数上的区别 信号量有两种实现:传统的System V信号量和新的POSIX信号量.它们所提供的函数很容易被区分:对于所有System V信号量函数,在它们的名字里面没有下划线.例如,应该是sem ...

  4. 第10章 Posix 信号量

    10.1 概述 10.1.1 信号量类型 Posix有名信号量:使用Posix IPC名字,可用于进程或线程间同步: Posix基于内存的信号量:也叫做无名信号量,存放在共享内存中,可用于进程或线程间 ...

  5. Linux多线程实践(5) --Posix信号量与互斥量解决生产者消费者问题

    Posix信号量 Posix 信号量 有名信号量 无名信号量 sem_open sem_init sem_close sem_destroy sem_unlink sem_wait sem_post ...

  6. POSIX信号量

    DESCRIPTION POSIX 信号量允许进程间和线程间同步他们的操作. 一个信号量是一个整型(integer),其值不能小于0. 信号量允许2中操作:给信号量的值加1(sem_post); 给信 ...

  7. linux POSIX 信号量介绍

    信号量一.什么是信号量信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)使用.多线程可以同时运行多个线程函数完成功能,但是对于共享数据如果不加以锁定,随意改变共享数据的值会发生 ...

  8. posix信号量与互斥锁

    1.简介 POSIX信号量是一个sem_t 类型的变量,但POSIX 有两种信号量的实现机制:无名信号量和命名信号量.无名信号量可以用在共享内存的情况下, 比如实现进程中各个线程之间的互斥和同步.命名 ...

  9. Linux 进程间通信(posix消息队列 简单)实例

    Linux 进程间通信(posix消息队列 简单)实例 详情见: http://www.linuxidc.com/Linux/2011-10/44828.htm 编译: gcc -o consumer ...

随机推荐

  1. C++ 为什么要用覆盖(学习笔记)

    长篇大论这里就不说了,举个例子class fruit{public: void func() { printf("fruit\n"); } virtual void vfunc() ...

  2. android各种适配器的用法(转)

    ArrayAdapter_SimpleAdapter_CursorAdapter的区别 数据源不同而已1. String[]: ArrayAdapter2. List<Map<String ...

  3. Java——观察者模式实例

    观察者模式(订阅/发布模式) 作者: 代码大湿 代码大湿 Java中观察者模式中主要是Observerable类(被观察者),和Observer接口(观察者).下面是个简单的demo //被观察者 p ...

  4. 开源软交换系统 FreeSwitch 与 Asterisk 比较

    Asterisk 与freeswitch都是流行的开源软交换服务器,Asterisk出现的比较早,大概在1999年开始此项目,应该是最流行的开源软交换服务器,整个社区上下游都已经很成熟. freesw ...

  5. 能够提高开发效率的 Eclipse 实用操作

    工欲善其事,必先利其器.对于程序员来说,Eclipse便是其中的一个“器”.本文会从Eclipse快捷键和实用技巧这两个篇章展开介绍.Eclipse快捷键用熟后,不用鼠标,便可进行编程开发,避免鼠标分 ...

  6. PopupWindow添加动画效果

    1.定义PopupWindow弹出与消失的两个动画文件,放在anim文件夹下 popup_enter.xml <?xml version="1.0" encoding=&qu ...

  7. snprintf 返回值

    在平时写代码的过程中,我一个推荐带有n系列的字符串函数,如 strcat ->strncat sprintf->snprintf 我们有类似的一个函数 void dump_kid(std: ...

  8. svn备份与恢复

    公司把SVN服务器从windows迁移到linux,其主要步骤记录如下. windows上备份 "C:\Program Files\VisualSVN Server\bin\svnadmin ...

  9. javascript表单(form)序列化

    function serialize(form){ var part =[]; var field = null; var i; var j; var len; var optLen; var opt ...

  10. Angularjs 整体框架结构

    //angulajs整体代码框架结构/** * @license AngularJS v1.2.6 * (c) 2010-2014 Google, Inc. http://angularjs.org ...