http://blog.csdn.net/guoping16/article/details/6584043
http://blog.csdn.net/ta893115871/article/details/7505560
信号量函数由semget、semop、semctl三个函数组成。下面的表格列出了这三个函数的函数原型及具体说明。
semget(得到一个信号量集标识符或创建一个信号量集对象) |
||
所需头文件 |
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> |
|
函数说明 |
得到一个信号量集标识符或创建一个信号量集对象并返回信号量集标识符 |
|
函数原型 |
int semget(key_t key, int nsems, int semflg) |
|
函数传入值 |
key |
0(IPC_PRIVATE):会建立新信号量集对象 |
大于0的32位整数:视参数semflg来确定操作,通常要求此值来源于ftok返回的IPC键值 |
||
nsems |
创建信号量集中信号量的个数,该参数只在创建信号量集时有效 |
|
msgflg |
0:取信号量集标识符,若不存在则函数会报错 |
|
IPC_CREAT:当semflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的信号量集,则新建一个信号量集;如果存在这样的信号量集,返回此信号量集的标识符 |
||
IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的信号量集,则新建一个消息队列;如果存在这样的信号量集则报错 |
||
函数返回值 |
成功:返回信号量集的标识符 |
|
出错:-1,错误原因存于error中 |
||
附加说明 |
上述semflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定信号量集的存取权限 |
|
错误代码 |
EACCESS:没有权限 EEXIST:信号量集已经存在,无法创建 EIDRM:信号量集已经删除 ENOENT:信号量集不存在,同时semflg没有设置IPC_CREAT标志 ENOMEM:没有足够的内存创建新的信号量集 ENOSPC:超出限制 |
如果用semget创建了一个新的信号量集对象时,则semid_ds结构成员变量的值设置如下:
Ÿ sem_otime设置为0。
Ÿ sem_ctime设置为当前时间。
Ÿ msg_qbytes设成系统的限制值。
Ÿ sem_nsems设置为nsems参数的数值。
Ÿ semflg的读写权限写入sem_perm.mode中。
Ÿ sem_perm结构的uid和cuid成员被设置成当前进程的有效用户ID,gid和cuid成员被设置成当前进程的有效组ID。
semop(完成对信号量的P操作或V操作) |
|
所需头文件 |
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> |
函数说明 |
对信号量集标识符为semid中的一个或多个信号量进行P操作或V操作 |
函数原型 |
int semop(int semid, struct sembuf *sops, unsigned nsops) |
函数传入值 |
semid:信号量集标识符 |
sops:指向进行操作的信号量集结构体数组的首地址,此结构的具体说明如下: struct sembuf { short semnum; /*信号量集合中的信号量编号,0代表第1个信号量*/ short val;/*若val>0进行V操作信号量值加val,表示进程释放控制的资源 */ /*若val<0进行P操作信号量值减val,若(semval-val)<0(semval为该信号量值),则调用进程阻塞,直到资源可用;若设置IPC_NOWAIT不会睡眠,进程直接返回EAGAIN错误*/ /*若val==0时阻塞等待信号量为0,调用进程进入睡眠状态,直到信号值为0;若设置IPC_NOWAIT,进程不会睡眠,直接返回EAGAIN错误*/ short flag; /*0 设置信号量的默认操作*/ /*IPC_NOWAIT设置信号量操作不等待*/ /*SEM_UNDO 选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值*/ }; |
|
nsops:进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作 |
|
函数返回值 |
成功:返回信号量集的标识符 |
出错:-1,错误原因存于error中 |
|
错误代码 |
E2BIG:一次对信号量个数的操作超过了系统限制 EACCESS:权限不够 EAGAIN:使用了IPC_NOWAIT,但操作不能继续进行 EFAULT:sops指向的地址无效 EIDRM:信号量集已经删除 EINTR:当睡眠时接收到其他信号 EINVAL:信号量集不存在,或者semid无效 ENOMEM:使用了SEM_UNDO,但无足够的内存创建所需的数据结构 ERANGE:信号量值超出范围 |
sops为指向sembuf数组,定义所要进行的操作序列。下面是信号量操作举例。
struct sembuf sem_get={0,-1,IPC_NOWAIT}; /*将信号量对象中序号为0的信号量减1*/
struct sembuf sem_get={0,1,IPC_NOWAIT}; /*将信号量对象中序号为0的信号量加1*/
struct sembuf sem_get={0,0,0}; /*进程被阻塞,直到对应的信号量值为0*/
flag一般为0,若flag包含IPC_NOWAIT,则该操作为非阻塞操作。若flag包含SEM_UNDO,则当进程退出的时候会还原该进程的信号量操作,这个标志在某些情况下是很有用的,比如某进程做了P操作得到资源,但还没来得及做V操作时就异常退出了,此时,其他进程就只能都阻塞在P操作上,于是造成了死锁。若采取SEM_UNDO标志,就可以避免因为进程异常退出而造成的死锁。
semctl (得到一个信号量集标识符或创建一个信号量集对象) |
||
所需头文件 |
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> |
|
函数说明 |
得到一个信号量集标识符或创建一个信号量集对象并返回信号量集标识符 |
|
函数原型 |
int semctl(int semid, int semnum, int cmd, union semun arg) |
|
函数传入值 |
semid |
信号量集标识符 |
semnum |
信号量集数组上的下标,表示某一个信号量 |
|
cmd |
见下文表15-4 |
|
arg |
union semun { short val; /*SETVAL用的值*/ struct semid_ds* buf; /*IPC_STAT、IPC_SET用的semid_ds结构*/ unsigned short* array; /*SETALL、GETALL用的数组值*/ struct seminfo *buf; /*为控制IPC_INFO提供的缓存*/ } arg; |
|
函数返回值 |
成功:大于或等于0,具体说明请参照表15-4 |
|
出错:-1,错误原因存于error中 |
||
附加说明 |
semid_ds结构见上文信号量集内核结构定义 |
|
错误代码 |
EACCESS:权限不够 EFAULT:arg指向的地址无效 EIDRM:信号量集已经删除 EINVAL:信号量集不存在,或者semid无效 EPERM:进程有效用户没有cmd的权限 ERANGE:信号量值超出范围 |
表15-4 semctl函数cmd形参说明表
命令 |
解 释 |
IPC_STAT |
从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中 |
IPC_SET |
设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值 |
IPC_RMID |
从内核中删除信号量集合 |
GETALL |
从信号量集合中获得所有信号量的值,并把其整数值存到semun联合体成员的一个指针数组中 |
GETNCNT |
返回当前等待资源的进程个数 |
GETPID |
返回最后一个执行系统调用semop()进程的PID |
GETVAL |
返回信号量集合内单个信号量的值 |
GETZCNT |
返回当前等待100%资源利用的进程个数 |
SETALL |
与GETALL正好相反 |
SETVAL |
用联合体中val成员的值设置信号量集合中单个信号量的值 |
4. 信号量应用程序举例
sem.c源代码如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <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 */
};
/***对信号量数组semnum编号的信号量做P操作***/
int P(int semid, int semnum)
{
struct sembuf sops={semnum,-1, SEM_UNDO};
return (semop(semid,&sops,1));
}
/***对信号量数组semnum编号的信号量做V操作***/
int V(int semid, int semnum)
{
struct sembuf sops={semnum,+1, SEM_UNDO};
return (semop(semid,&sops,1));
}
int main(int argc, char **argv)
{
int key ;
int semid,ret;
union semun arg;
struct sembuf semop;
int flag ;
key = ftok("/tmp", 0x66 ) ;
if ( key < 0 )
{
perror("ftok key error") ;
return -1 ;
}
/***本程序创建了三个信号量,实际使用时只用了一个0号信号量***/
semid = semget(key,3,IPC_CREAT|0600);
if (semid == -1)
{
perror("create semget error");
return ;
}
if ( argc == 1 )
{
arg.val = 1;
/***对0号信号量设置初始值***/
ret =semctl(semid,0,SETVAL,arg);
if (ret < 0 )
{
perror("ctl sem error");
semctl(semid,0,IPC_RMID,arg);
return -1 ;
}
}
/***取0号信号量的值***/
ret =semctl(semid,0,GETVAL,arg);
printf("after semctl setval sem[0].val =[%d]\n",ret);
system("date") ;
printf("P operate begin\n") ;
flag = P(semid,0) ;
if ( flag )
{
perror("P operate error") ;
return -1 ;
}
printf("P operate end\n") ;
ret =semctl(semid,0,GETVAL,arg);
printf("after P sem[0].val=[%d]\n",ret);
system("date") ;
if ( argc == 1 )
{
sleep(120) ;
}
printf("V operate begin\n") ;
if (V(semid, 0) < 0)
{
perror("V operate error") ;
return -1 ;
}
printf("V operate end\n") ;
ret =semctl(semid,0,GETVAL,arg);
printf("after V sem[0].val=%d\n",ret);
system("date") ;
if ( argc >1 )
{
semctl(semid,0,IPC_RMID,arg);
}
return 0 ;
}
① 编译 gcc sem.c –o sem。
② 在一窗口执行./sem,执行结果如下:
after semctl setval sem[0].val =[1]
2011年 01月 11日 星期二 10:08:11 CST
P operate begin
P operate end
after P sem[0].val=[0]
2011年 01月 11日 星期二 10:08:11 CST
V operate begin
V operate end
after V sem[0].val=0
2011年 01月 11日 星期二 10:10:11 CST
③ 然后在另一窗口中执行./sem test1,执行结果如下:
after semctl setval sem[0].val =[0]
2011年 01月 11日 星期二 10:08:36 CST
P operate begin
P operate end
after P sem[0].val=[0]
2011年 01月 11日 星期二 10:10:11 CST
V operate begin
V operate end
after V sem[0].val=1
2011年 01月 11日 星期二 10:10:11 CST
第一个semget()函数,与共享内存的shmget()函数类似。
使用格式:
#include<sys/sem.h>
int semget(key_t _key ,int _nsems,int _semflg);
功能:创建一个新的信号量或获取一个已经存在的信号量的键值。
返回值:成功返回信号量的标识码ID。失败返回-1;
参数:
_key 为整型值,用户可以自己设定。有两种情况:
1. 键值是IPC_PRIVATE,该值通常为0,意思就是创建一个仅能被进程进程给我的信号量。
2. 键值不是IPC_PRIVATE,我们可以指定键值,例如1234;也可以一个ftok()函数来取得一个唯一的键值。
_nsems 表示初始化信号量的个数。比如我们要创建一个信号量,则该值为1.,创建2个就是2。
_semflg :信号量的创建方式或权限。有IPC_CREAT,IPC_EXCL。
IPC_CREAT如果信号量不存在,则创建一个信号量,否则获取。
IPC_EXCL只有信号量不存在的时候,新的信号量才建立,否则就产生错误。
下面我们看个小例子:
#include<stdio.h>
#include<sys/sem.h>
#define MYKEY 6666
int main()
{
int semid;
semid=semget(MYKEY,1,IPC_CREAT|0666);//创建了一个权限为666的信号量
printf("semid=%d\n",semid);
return 0;
}
运行结果为:
我们可以用ipcs –s 来查看是否创建成功。
用ipcrm -s semid号来删除指定的信号量。
=============================================================================
第二个函数semctl()控制信号量的函数。
在这个函数中我们可以删除信号量或初始化信号量。
格式:
#include<sys/sem.h>
int semctl(int _semid ,int _semnum,int _cmd ……);
功能:控制信号量的信息。
返回值:成功返回0,失败返回-1;
参数:
_semid 信号量的标志码(ID),也就是semget()函数的返回值;
_semnum, 操作信号在信号集中的编号。从0开始。
_cmd 命令,表示要进行的操作。
下面列出的这些命令来源于百度!
参数cmd中可以使用的命令如下:
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设置信号量集中的一个单独的信号量的值。
Semunion ;第4个参数是可选的;semunion :是union semun的实例。
union semun {
int val;
struct semid_ds *buf;
unsigned short *arrary;
};
//下面是给一个信号量初始化的代码。
union semun sem_args;
unsigned short array[1]={1};
sem_args.array = array;
ret = semctl(semid, 0, SETALL, sem_args);//0代表对1个信号来量初始化,即有1个资源
if (-1 == ret)
{
perror("semctl");
exit(EXIT_FAILURE);
}
================================================
第三个函数 semop();信号来那个操作处理的函数。
格式:#include<sys/sem.h>
int semop(int semid ,struct sembuf *_sops ,size_t _nsops);
功能:用户改变信号量的值。也就是使用资源还是释放资源使用权。
返回值:成功返回0,失败返回-1;
参数:
_semid : 信号量的标识码。也就是semget()的返回值。
_sops是一个指向结构体数组的指针。
struct sembuf{
unsigned short sem_num;//第几个信号量,第一个信号量为0;
short sem_op;//对该信号量的操作。
short _semflg;
};
sem_num: 操作信号在信号集中的编号。第一个信号的编号为0;
sem_op : 如果其值为正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权;如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。
_semflg IPC_NOWAIT //对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
IPC_UNDO //程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。
nsops:操作结构的数量,恒大于或等于1。
下面看个例子:
源文件1:
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<sys/sem.h>
#define SEM_KEY 6666
union semun {
int setval;
struct semid_ds *buf;
unsigned short *array;
};
int main(int argc ,char *argv[])
{
int shmid;
float *addr;
float h,w;
shmid= shmget(ftok(".",1000),getpagesize(),IPC_CREAT | 0666);//创建一个共享内存,权限为0666
printf("shmid=%d\n",shmid);
if(shmid==-1)
{
perror("shmget error:");
exit(EXIT_FAILURE);
}
addr=shmat(shmid,0,0);//获取共享内存的起始地址,且为可读可写
if(-1==*addr)
{
perror("shmat error:");
exit(EXIT_FAILURE);
}
int semid;
int ret;
semid = semget(SEM_KEY, 2, IPC_CREAT | 0600);//创建2个信号量
if (-1 == semid)
{
perror("semget");
exit(EXIT_FAILURE);
}
printf("semid = %d\n", semid);
// 初始化2个信号量
union semun sem_args;
unsigned short array[2]={1,1};
sem_args.array = array;
ret = semctl(semid, 1, SETALL, sem_args);//SETALL代表设置信号集中所有的信号量的值。1,代表2个,sem_args是具体初始化的值放在共用体中。
if (-1 == ret)
{
perror("semctl");
exit(EXIT_FAILURE);
}
//对资源的使用处理操作
struct sembuf sem_opt_wait1[1] = {0, -1, SEM_UNDO};
struct sembuf sem_opt_wakeup1[1] = {0, 1, SEM_UNDO};
struct sembuf sem_opt_wait2[1] = {1, -1, SEM_UNDO};
struct sembuf sem_opt_wakeup2[1] = {1, 1, SEM_UNDO};
while(1)
{
semop(semid, sem_opt_wait2, 1);//获取进程2的资源,让进程2等待
printf(" enter you height(CM) and height(KG):\n");
scanf("%f%f",addr,addr+1);
semop(semid, sem_opt_wakeup1, 1);//唤醒进程1,即释放资源的使用权
if(*addr==1||*(addr+1)==1 ) break;//如果输入身高是1或体重是1就退出
}
return 0;
}
源程序2:
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<stdlib.h>
#include<sys/sem.h>
#include<string.h>
#include<sys/stat.h>
#define SEM_KEY 6666
union semun {
int setval;
struct semid_ds *buf;
unsigned short *array;
};
int main(int argc ,char *argv[])
{
int shmid;
float *addr;
float h,w;
shmid= shmget(ftok(".",1000),getpagesize(), IPC_CREAT | 0666);//打开刚才原程序1创建的共享内存,权限为0666
printf("shmid=%d\n",shmid);
if(shmid==-1)
{
perror("shmget error:");
exit(EXIT_FAILURE);
}
addr =shmat(shmid,0,0);//获取共享内存的起始地址,且为可读可写
if(-1== *addr)
{
perror("shmat error:");
exit(EXIT_FAILURE);
}
int semid;
int ret;
semid = semget(SEM_KEY, 0, IPC_CREAT | 0600););//取得信号量
if (-1 == semid)
{
perror("semget");
exit(EXIT_FAILURE);
}
printf("semid = %d\n", semid);
//对资源的使用处理操作
struct sembuf sem_opt_wait1[1] = {0, -1, SEM_UNDO};
struct sembuf sem_opt_wakeup1[1] = {0, 1, SEM_UNDO};
struct sembuf sem_opt_wait2[1] = {1, -1, SEM_UNDO};
struct sembuf sem_opt_wakeup2[1] = {1, 1, SEM_UNDO};
while(1)
{
semop(semid, sem_opt_wait1, 1);//获取进程1的资源好让进程1等待。
h=*addr;
w=*(addr+1);
printf("h=%f\tw=%f\n",h,w);
if(h==1.0 || w==1.0) break;;//如果输入身高是1或体重是1就退出
int ret=w/(h*h/10000);
if(ret>=20 &&ret <=25)
{
printf("ok!\n");
}else if(ret <20)
{
printf("thin!\n");
} else
{
printf("fat!\n");
}
semop(semid, sem_opt_wakeup2, 1);//释放进程2的资源,即唤醒进程2
}
if(-1==semctl(semid,1,IPC_RMID,0))//删除信号量
{
perror("semctl error:");
exit(EXIT_FAILURE);
}
if(-1==shmdt(addr))//释放共享内存,使其不再有任何指向它的指针
{
perror("shmdt error:");
exit(EXIT_FAILURE);
}
if (shmctl(shmid,IPC_RMID,0)==-1)//删除共享内存
{
perror("shctl error:");
exit(EXIT_FAILURE);
}
return 0;
}
//两个进程相互制约,而达到资源的有效使用。
运行结果: