信号量定义

最简单的信号量是一个只有0与1两个值的变量,二值信号量。这是最为通常的形式。具有多个正数值的信号量被称之为通用信号量。在本章的其余部分,我们将会讨论二值信号量。

P与V的定义出奇的简单。假定我们有一个信号量变量sv,两个操作定义如下:

P(sv)    如果sv大于0,减小sv。如果sv为0,挂起这个进程的执行。
V(sv)    如果有进程被挂起等待sv,使其恢复执行。如果没有进行被挂起等待sv,增加sv。

信号量的另一个理解方式就是当临界区可用时信号量变量sv为true,当临界区忙时信号量变量被P(sv)减小,从而变为false,当临界区再次可用时 被V(sv)增加。注意,简单的具有一个我们可以减小或是增加的通常变量并不足够,因为我们不能用C,C++或是其他的编程语言来表述生成信号,进行原子 测试来确定变量是否为true,如果是则将其变为false。这就是使得信号量操作特殊的地方。

一个理论例子

我们可以使用一个简单的理论例子来了解一下信号量是如何工作的。假设我们有两个进程proc1与proc2,这两个进程会在他们执行的某一时刻排他的访问 一个数据库。我们定义一个单一的二值信号量,sv,其初始值为1并且可以为两个进程所访问。两个进程然后需要执行同样的处理来访问临界区代码;实际上,这 两个进程可以是同一个程序的不同调用。

这两个进程共享sv信号量变量。一旦一个进程已经执行P(sv)操作,这个进程就可以获得信号量并且进入临界区。第二个进程就会被阻止进行临界区,因为当他尝试执行P(sv)时,他就会等待,直到第一个进程离开临界区并且执行V(sv)操作来释放信号量。

所需要的过程如下:

semaphore sv = 1;
loop forever {
    P(sv);
    critical code section;
    V(sv);
    noncritical code section;
}

这段代码出奇的简单,因为P操作与V操作是十分强大的。图14-1显示了P操作与V操作如何成为进行临界区代码的门槛。

Linux信号量工具

现在我们已经了解了什么是信号量以及他们在理论上是如何工作的,现在我们可以来了解一下这些特性在Linux中是如何实现的。信号量函数接口设计十分精 细,并且提供了比通常所需要的更多的实用性能。所有的Linux信号量函数在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。乍看 起来,这似乎使得事情变得更为复杂,但是在一个进程需要锁住多个资源的复杂情况下,在信号量数组上进行操作将是一个极大的优点。在这一章,我们将会关注于 使用单一信号量,因为在大多数情况下,这正是我们需要使用的。

信号量函数定义如下:

#include <sys/sem.h>
int semctl(int sem_id, int sem_num, int command, ...);
int semget(key_t key, int num_sems, int sem_flags);
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

事实上,为了获得我们特定操作所需要的#define定义,我们需要在包含sys/sem.h文件之前通常需要包含sys/types.h与sys/ipc.h文件。而在某些情况下,这并不是必须的。

因为我们会依次了解每一个函数,记住,这些函数的设计是用于操作信号量值数组的,从而会使用其操作向比单个信号量所需要的操作更为复杂。

注意,key的作用类似于一个文件名,因为他表示程序也许会使用或是合作所用的资源。相类似的,由semget所返回的并且为其他的共享内存函数所用的标 识符与由fopen函数所返回 的FILE *十分相似,因为他被进程用来访问共享文件。而且与文件类似,不同的进程会有不同的信号量标识符,尽管他们指向相同的信号量。key与标识符的用法对于在 这里所讨论的所有IPC程序都是通用的,尽管每一个程序会使用独立的key与标识符。

semget

semget函数创建一个新的信号量或是获得一个已存在的信号量键值。

int semget(key_t key, int num_sems, int sem_flags);

第一个参数key是一个用来允许不相关的进程访问相同信号量的整数值。所有的信号量是为不同的程序通过提供一个key来间接访问的,对于每一个信号量系统 生成一个信号量标识符。信号量键值只可以由semget获得,所有其他的信号量函数所用的信号量标识符都是由semget所返回的。

还有一个特殊的信号量key值,IPC_PRIVATE(通常为0),其作用是创建一个只有创建进程可以访问的信号量。这通常并没有有用的目的,而幸运的是,因为在某些Linux系统上,手册页将IPC_PRIVATE并没有阻止其他的进程访问信号量作为一个bug列出。

num_sems参数是所需要的信号量数目。这个值通常总是1。

sem_flags参数是一个标记集合,与open函数的标记十分类似。低九位是信号的权限,其作用与文件权限类似。另外,这些标记可以与 IPC_CREAT进行或操作来创建新的信号量。设置IPC_CREAT标记并且指定一个已经存在的信号量键值并不是一个错误。如果不需 要,IPC_CREAT标记只是被简单的忽略。我们可以使用IPC_CREAT与IPC_EXCL的组合来保证我们可以获得一个新的,唯一的信号量。如果 这个信号量已经存在,则会返回一个错误。

如果成功,semget函数会返回一个正数;这是用于其他信号量函数的标识符。如果失败,则会返回-1。

semop

函数semop用来改变信号量的值:

int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

第一个参数,sem_id,是由semget函数所返回的信号量标识符。第二个参数,sem_ops,是一个指向结构数组的指针,其中的每一个结构至少包含下列成员:

struct sembuf {
    short sem_num;
    short sem_op;
    short sem_flg;
}

第一个成员,sem_num,是信号量数目,通常为0,除非我们正在使用一个信号量数组。sem_op成员是信号量的变化量值。(我们可以以任何量改变信 号量值,而不只是1)通常情况下中使用两个值,-1是我们的P操作,用来等待一个信号量变得可用,而+1是我们的V操作,用来通知一个信号量可用。

最后一个成员,sem_flg,通常设置为SEM_UNDO。这会使得操作系统跟踪当前进程对信号量所做的改变,而且如果进程终止而没有释放这个信号量, 如果信号量为这个进程所占有,这个标记可以使得操作系统自动释放这个信号量。将sem_flg设置为SEM_UNDO是一个好习惯,除非我们需要不同的行 为。如果我们确实变我们需要一个不同的值而不是SEM_UNDO,一致性是十分重要的,否则我们就会变得十分迷惑,当我们的进程退出时,内核是否会尝试清 理我们的信号量。

semop的所用动作会同时作用,从而避免多个信号量的使用所引起的竞争条件。我们可以在手册页中了解关于semop处理更为详细的信息。

semctl

semctl函数允许信号量信息的直接控制:

int semctl(int sem_id, int sem_num, int command, ...);

第一个参数,sem_id,是由semget所获得的信号量标识符。sem_num参数是信号量数目。当我们使用信号量数组时会用到这个参数。通常,如果 这是第一个且是唯一的一个信号量,这个值为0。command参数是要执行的动作,而如果提供了额外的参数,则是union semun,根据X/OPEN规范,这个参数至少包括下列参数:

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
}

许多版本的Linux在头文件(通常为sem.h)中定义了semun联合,尽管X/Open确认说我们必须定义我们自己的联合。如果我们发现我们确实需 要定义我们自己的联合,我们可以查看semctl手册页了解定义。如果有这样的情况,建议使用手册页中提供的定义,尽管这个定义与上面的有区别。

有多个不同的command值可以用于semctl。在这里我们描述两个会经常用到的值。要了解semctl功能的详细信息,我们应该查看手册页。

IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。

IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。

IPC_RMID将信号量集从内存中删除。

GETALL用于读取信号量集中的所有信号量的值。

GETNCNT返回正在等待资源的进程数目。

GETPID返回最后一个执行semop操作的进程的PID。

GETVAL返回信号量集中的一个单个的信号量的值。

GETZCNT返回这在等待完全空闲的资源的进程数目。

SETALL设置信号量集中的所有的信号量的值。

SETVAL设置信号量集中的一个单独的信号量的值。


这两个通常的command值为:

SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。
IPC_RMID:当信号量不再需要时用于删除一个信号量标识。

semctl函数依据command参数会返回不同的值。对于SETVAL与IPC_RMID,如果成功则会返回0,否则会返回-1。


下面给出两个例子用于说明信号量的操作

1.用semget函数编制一个创建或打开信号量级的函数

int open_semaphore_set(key_t keyval, int numsems)
{int sid;if(!numsems)return -1;if((sid=semget(keyval, numsems,IPC_CREAT | 0660))==-1)return -1;elsereturn sid;
}

2.这个程序可以同时多次运行,它所做的工作只是输出命令行参数,每个参数输出一行。用这种方式,如果这个程序同时有两个进程执行它,那么就有可能导致同一行的输出来自不同的进程,所以把输出工作的代码作为关键区(临界区),代码如下所示:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>int semaphore_P(int);
int semaphore_V(int sem_id);int main(int argc, char* argv[])
{int sem_id;int i, creat=0;int pause_time;char *cp;if(argc<=1){printf("usage: %s parameter1 parameter2 ...\n",argv[0]);exit(1);}srand((unsigned int)getpid());if((sem_id=semget((key_t)1234,1,IPC_CREAT | 0660))==-1){printf("semget failed!\n");exit(2);}if(strcmp(argv[1],"1")){semctl(sem_id, 0, SETVAL, 1);creat=1;sleep(2);}for(i=0; i<argc;i++){cp=argv[i];if(!semaphore_P(sem_id))exit(3);printf("Process %d:", getpid());fflush(stdout);while(*cp){printf("%c", *cp);fflush(stdout);pause_time=rand() %3 ;sleep(pause_time);cp++;}printf("\n");if(!semaphore_V(sem_id))exit(4);pause_time=rand() %2 ;sleep(pause_time);}printf("\n %d -finished \n",getpid());if(creat==1){sleep(10);semctl(sem_id,0,IPC_RMID,0);}return 0;
}int semaphore_P(int sem_id)
{struct sembuf sb;sb.sem_num=0;sb.sem_op=-1;sb.sem_flg=SEM_UNDO;if(semop(sem_id, &sb, 1)==-1){printf("semaphore_P failed.\n");return 0;}return 1;
}int semaphore_V(int sem_id)
{struct sembuf sb;sb.sem_num=0;sb.sem_op=1;sb.sem_flg=SEM_UNDO;if(semop(sem_id, &sb, 1)==-1){printf("semaphore_V failed. \n");return 0;}return 1;
}

Linux C编程--进程间通信(IPC)5--System V IPC 机制2--信号量相关推荐

  1. Linux C编程--进程间通信(IPC)5--System V IPC 机制1--消息队列

    System V IPC 机制 1.基本概念 IPC对象一经创建,系统内核即为该对象分配相关的数据结构.为方便对IPC对象的管理,Linux提供了专门的IPC控制命令,主要包括查看IPC对象信息的ip ...

  2. 【Linux篇】第十二篇——进程间通信(管道+system V共享内存)

    进程间通信介绍 概念 目的 本质 分类 管道 什么是管道 匿名管道 匿名管道的原理 pipe函数 匿名管道使用步骤 管道读写规则 管道的特点 管道的大小 命名管道 命名管道的原理 使用命令创建命名管道 ...

  3. Linux C编程--进程间通信(IPC)1--进程间通信机制概述

    linux下进程间通信的几种主要手段简介: 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它 ...

  4. system V IPC进程间通信机制一网打尽

    目录 必备IPCS命令解析 ipcs ipcrm Linux IPC消息队列 msgget msgsnd msgrcv msgctl Linux IPC信号量 理解信号量 semget semop s ...

  5. Linux网络编程--进程间通信(一)

    进程间通信简介(摘自<Linux网络编程>p85) AT&T 在 UNIX System V 中引入了几种新的进程通讯方式,即消息队列( MessageQueues),信号量( s ...

  6. System V IPC

    1.概述 System V IPC共有三种类型:System V消息队列.System V 信号量.System V 共享内存区. System V IPC操作函数如下: 2.key_t键和ftok函 ...

  7. System V IPC POSIX IPC(一):消息队列

    System V IPC & POSIX IPC(一):消息队列 消息队列允许进程之间以消息的形式交换数据,是一种常见的进程之间的通信机制. 1. 消息队列的创建 System V IPC: ...

  8. 【C语言】【unix c】信号量集(system v ipc)

    二.信号量集(system v ipc)信号量集就是数组,数组里的每个元素都是信号量的类型1.获取键值ftok(3)2.使用键值获取信号量集的idsemget(2)#include <sys/t ...

  9. 进程间同步---system v ipc 对象信号灯集

    一.信号灯简介 Linux支持System V的信号灯(semaphore),是一种进程间通信的方式,只不过它和管道.FIFO或者共享内存不一样,信号灯主要用于同步或者互斥对共享资源的访问,它的发明来 ...

  10. System V IPC之信号灯

    信号灯也叫信号量 用于进程/线程同步或互斥的机制 信号灯的类型 1.Posix 无名信号灯 2.Posix 有名信号灯 3.System V 信号灯 信号灯的含义 计数信号灯(1和2都是) Syste ...

最新文章

  1. TCP/IP详解--第十三章
  2. 计算机专业勤学善思感悟,勤学善思作文600字
  3. 统计学习方法——CART, Bagging, Random Forest, Boosting
  4. 计算机类和数学与应用数学哪个好,数学与应用数学专业怎么样 好不好找工作...
  5. 旷视产品营销总监吕盟:构建AIoT时代的城市智慧|量子位沙龙回顾
  6. C# 处理XML的基本操作
  7. Cadence PSpice 仿真0:绘制电路图方法图文教程
  8. [work] 清华朱军 NIPS 2017对抗样本攻防竞赛总结(附学习资料)
  9. mysql集群master和salve配置mysql的master和salve详解事件
  10. 客户资料搜索软件_手机自动拨号软件APP外呼系统 电脑拨号电话营销神器 CRM客户管理系统...
  11. 廖雪峰python实战(一)
  12. 1990-2020年江苏省全省人口数、户数(常住)
  13. 无法从[C:\My Java\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\conf\server.xml]加载服务器配置
  14. 关于泛微E9 OA系统手机端无法使用的抢救过程
  15. 将Windows7屏幕外的窗口拖回
  16. 35岁+做互联网的你该何去何从呢?
  17. OpenCV 实现图片的水平投影与垂直投影,并进行行分割
  18. 李鬼见李逵——我用翟天临的论文做了分析
  19. http://www.360doc.com/content/16/0711/13/2428535_574691930.shtml
  20. 微信小程序之跳转wx.switchTab的使用

热门文章

  1. 完成动态根据类别动态填充区域颜色
  2. hibernate连接池配置
  3. DynamicData for Asp.net Mvc留言本实例 中篇 新建.删除.数据验证
  4. 喜欢 Netflix 么?你应该感谢 FreeBSD
  5. Hibernate two table same id
  6. Windows同时安装python3.0和python2.7
  7. node使用npm一句命令停止某个端口号 xl_close_port
  8. git 远程仓库版本的回退以及git reset 几种常用方式记录
  9. SVN的搭建及使用(三)用TortoiseSVN修改文件,添加文件,删除文件,以及如何解决冲突,重新设置用户名和密码等...
  10. javascript中的正则匹配函数exec(),test(),match()