信号量是一种用于提供不同进程间或一个给定进程的不同线程件同步手段的原语.信号量是一个特殊的整数值,主要用来控制多个进程对临界资源的互斥访问,进程根据信号量来判断是否有 访问的资源。
信号量是一个计数器,可用于同步多进程对共享数据对象得访问,为了获得共享资源,进程需要执行以下操作:
1、测试控制该资源的信号量
2、若此信号量的值为正,则进程可以使用该资源,进程将信号量值减1,表示它使用了一个资源单位
3、若此信号量的值为0,则进程进入睡眠状态,直至信号量值大于0。当进程被唤醒后,它返回至第1步。
常用的信号量一般初始值为1,只控制单个资源,有时也称互斥锁,但是,信号量得初值可以是任意一正值,该值说明有多少个共享资源单位可供共享应用,信号量有以下3个特性:
1、信号量并非是一个非负值,而必须将信号量定义为含有一个或多个信号量值得集合,当创建一个信号量时,要指定该集合中的各个值。
sreuct sem
{
ushort_t semvl;
short sempid;
ushort semncnt;
ushort semzcnt;
};
2、创建信号量对其赋初值分开,这是一个致命弱点,因为不能原子地创建一个信号量集合,并且对该集合中的所有值赋初值。
3、即使没有进程使用,但他们仍然存在,因此必须考虑在进程终止时有没有释放得信号量。
以上的三个特性就导致了信号使用的复杂性。
信号量的值通过P、V原语来进行操作改变的。(p为减操作,v为加操作)
在Linux中,系统提供了信号量的操作函数,主要有以下函数:
1、key_t ftok(char *pathname, char proj);
根据参数pathname和proj 来创建一个关键字,成功时返回与路径pathname相对应的一个键值,具有唯一性,失败时返回值为-1.
2、int semget (key_t key, int nsems , int semflg);
创建一个新信号量或者取得一个现有的信号量,key是一个关键字,是用ftok()函数创建。nsems表明创建的信号量个数,semflg是设置信号量的访问权限标志,函数调用成功时返回信号量ID,失败则返回-1.
3、int semop (int semid, struct sembuf *spos, int nspos);
对信号量进行操作的函数,用于改变信号量的键值,semid是信号量的标志,spos是指向一个结构体数组的指针,表明要进行什么操作,nspos表明数组的元素个数,调用成功则返回0,失败则返回-1.
4、semctl(int semid , int semnum , int cmd , …/* union semun arg */);
semid是信号量标志,semnum是指该信号量集合内的某个成员(用下标表示0-nsems-1)。semnum只用于GETVAL、SETVAL、GETNCNT、GETZCNT、GETPID命令。参数cmd是可选的,它支持以下命令:
GETVAL:把semval的当前值返回。
SETVAL:设置senval值
GETPID:把当前sempid当作函数返回值
GETNCNT:把semncnt的当前值当作函数的返回值
GETZCNT:把semzcnt的当前值当作函数的返回值
IPC_RMID:把由sem_id指定的信号量从系统中删除
IPC_SET:设定指定信号量集合的semid_ds结构中的三个成员
IPC_STAT:返回所指定信号量当前的semid_ds结构
SETALL:设定所指定信号量集合中每个成员的semval值
GETALL:返回所指定信号量集合中每个成员的当前semval的值
Struct sembuf
{
Unsigned short sem_num; /*sem index in array*/
Short sem_op; /* sem operation */
Short sem_flg;/* operation flags */ sem_flg&IPC_RND 0
};
其中,如果sem_op大于0,那么操作值加入到信号量的值中,并唤醒等待信号增加的进程,如果sem_op为0,当信号量的值是0的时候,函数返回,否则阻塞直到信号量的值为0,如果sem_op小于0,函数判断信号量的值加上这个负值,如果结果为0唤醒等待信号量为0的进程,如果小于0函数阻塞,如果大于0,那么从信号量里面减去这个值并返回。
4、int semctl (int semid, int semnum, int cmd, union semun arg);
该函数得作用是对信号量进行一系列得控制,semid是要操作得信号量标志,semnum是信号量得下标,cmd是操作的命令,经常用的两个命令是:SETVAL、IPC_RMID,arg用于设置或返回信号量信息。
Union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
void *__pad;
}
下面是信号量简单应用的小程序:
1、semcreate.cpp:创建一个信号量
#include "utili.h"
int main(int argc, char *argv[])
{
//通过ftok函数构造唯一的键值,路径为所传参数,构造完并检验键值是否构造成功
key_t key_id, sem_id;
key_id = ftok(argv[1], 0xFF);
if(key_id == -1){
printf("ftok error.\n");
exit(1);
}
//键值构造成功后,用所获得的键值创建一个信号量
sem_id = semget(key_id, 1, IPC_CREAT|IPC_EXCL|0666);//信号量的权限为要么创建,要么返回EEXIST错误,所属用户、所属组、其他用户都可读可写
if(sem_id == -1){ //判断信号量创建是否成功
printf("semget error.\n");
exit(1);
}
//创建成功
printf("semget ok. sem id = %d\n", sem_id);
return 0;
}
2、semgetvalue.cpp:得到信号量的值
#include "utili.h"
int main(int argc, char *argv[])
{
key_t key_id, sem_id;
key_id = ftok(argv[1], 0xFF); //获得在semcreate.cpp中产生的键值
sem_id = semget(key_id, 0, 0); //利用该建值来获得semcreate.cpp中创建的信号量
int sem = semget(sem_id, 0, GETVAL); //利用semget函数得到信号量的值
printf("sem value = %d\n", sem);
return 0;
}
3、semsetvalue.cpp
#include "utili.h"
//头文件<sys/sem.h>中含有,但如果不定义会报错,不解(?)
union semun
{
int val; /* value for SETVAL */
struct semid_ds *buf; /* buffer for IPC_STAT & IPC_SET */
unsigned short *array; /* array for GETALL & SETALL */
struct seminfo *__buf; /* buffer for IPC_INFO */
void *__pad;
};
int main(int argc, char *argv[])
{
//与semgetvalue.cpp中一样,为了获得已创建的信号量的sem_id。
key_t key_id, sem_id;
key_id = ftok(argv[1], 0xFF);
sem_id = semget(key_id, 0, 0);
if(sem_id == -1){
printf("semget error.\n");
exit(1);
}
//定义一个semun类型的共用体,将传进来的第二个参数当作内部的val值
union semun init;
init.val = atoi(argv[2]);
//通过控制函数将init.val值赋给该信号量,命令SETVAL表明该函数现在被用来执行设定值的操作
int res = semctl(sem_id, 0, SETVAL, init);
//判断是否设置成功,失败返回-1.
if(res == -1){
printf("set value fail.\n");
exit(1);
}
printf("set value ok.\n");
return 0;
}
4、semopde.cpp:信号量的减操作
#include "utili.h"
int main(int argc, char *argv[])
{
//与semgetvalue.cpp中一样,为了获得已创建的信号量的sem_id。
key_t key_id, sem_id;
key_id = ftok(argv[1], 0xFF);
sem_id = semget(key_id, 0, 0);
//定义一个sembuf类型的结构体P,并生定它内部的成员的值
struct sembuf p;
p.sem_num = 0; //表明该操作只对高信号量集合中0下标的信号量进行操作
p.sem_op = -1; //表明执行一次该操作,所对应信号量加-1
p.sem_flg = 0; //信号量的标志
//通过semop函数对某个信号量集执行一组数组(上面结构体p)的操作
int res = semop(sem_id, &p, 1);
if(res == -1){
printf("operator sem decreament fail.\n");
}
printf("operator sem decreament ok.\n");
return 0;
}
5、semopin.cpp:执行信号量集合的加操作
#include "utili.h"
int main(int argc, char *argv[])
{
//与semgetvalue.cpp中一样,为了获得已创建的信号量的sem_id。
key_t key_id, sem_id;
key_id = ftok(argv[1], 0xFF);
sem_id = semget(key_id, 0, 0);
//定义一个sembuf类型的结构体v,并生定它内部的成员的值
struct sembuf v;
v.sem_num = 0; //表明该操作只对高信号量集合中0下标的信号量进行操作
v.sem_op = 1; //表明执行一次该操作,所对应信号量加1
v.sem_flg = 0; //信号量的标志
//通过semop函数对某个信号量集执行一组数组(上面结构体p)的操作
int res = semop(sem_id, &v, 1);
if(res == -1){
printf("operator increament fail.\n");
}
printf("operator increament ok.\n");
return 0;
}
6、semrmid.cpp:删出信息量集
#include "utili.h"
int main(int argc, char *argv[])
{
//与semgetvalue.cpp中一样,为了获得已创建的信号量的sem_id
key_t key_id, sem_id;
key_id = ftok(argv[1], 0xFF);
sem_id = semget(key_id, 0, 0);
//通过semctl函数执行IPC_RMID命令删除所创建的信号量
semctl(sem_id, 0, IPC_RMID);
printf("remove ok. sem id = %d\n", sem_id);
return 0;
}
7、Makefile文件:【Makefile文件中每个执行语句前的空格必须用tale键,否则会报错】
all:semcreate semsetvalue semgetvalue semopin semopde semrmid
semcreate:semcreate.cpp
g++ -o semcreate semcreate.cpp
semsetvalue:semsetvalue.cpp
g++ -o semsetvalue semsetvalue.cpp
semgetvalue:semgetvalue.cpp
g++ -o semgetvalue semgetvalue.cpp
semopin:semopin.cpp
g++ -o semopin semopin.cpp
semopde:semopde.cpp
g++ -o semopde semopde.cpp
semrmid:semrmid.cpp
g++ -o semrmid semrmid.cpp
.PHONY:clean
clean:
rm semcreate semsetvalue semgetvalue semopin semopde semrmid
8、utili.h:头文件
#pragma once
#include<iostream>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
using namespace std;
#define ID 0xFF
上面只是信号量的简单程序的实现,并没有很好的体现信号量的同步机制。后面会继续整理相关内容。