一、什么是信号量
信号量(Semaphore),也称为信号灯,主要用来控制多个进程对共享资源的访问,信号量是进程通信的一种重要方法,但是它本身并不进行数据交换,这点和前面介绍的管道和消息队列不同。
信号量是一个整数,只能通过P、V原语进行操作,信号量大于或者等于0,表示可提供并发进程使用的资源数;小于0时表示正在等待使用资源的进程数。
如果一个进程需要访问共享资源,执行的操作如下所示:
(1)测试控制该资源的信号量。
(2)如果信号量的值大于0,则访问该资源,并将信号量减1.
(3)如果信号量的值小于或等于0,则进入休眠状态,直至信号量的值大于0时才被唤醒,返回到第一步。
(4)进程不再使用共享资源时,将信号量加1,此时如果有其它进程正在休眠等待该信号,则将其唤醒。
二、信号量的创建
1、semget函数
它的作用是创建一个新信号量或取得一个已有信号量。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflag);
参数key为信号量的键值,它可以由用户指定,也可以调用ftok函数来生成。
参数nsems指定需要的信号量数目,它的值几乎总是1。
参数semflag是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
semget函数成功返回一个相应信号标识符(非零),失败返回-1,并设置相应的错误码。
示例:
//使用semget函数创建一个信号量集然后删除
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);
int main()
{
int semid;
key_t key;
key=ftok("/home", 'a'); //生成信号量的键值
if (key<0)
{
perror("ftok error");
exit(1);
}
semid=semget(key, 1, IPC_CREAT|0666); //创建一块信号量集
if (semid<0)
{
perror("semget error");
exit(1);
}
printf("Done!\n");
sleep(5);
semid=semctl(semid, 0, IPC_RMID,0); //删除该信号量集
if (semid<0)
{
printf("semget error");
exit(1);
}
printf("removed!\n");
return 0;
}
三、信号量的控制
1、semctl函数
该函数用来直接控制信号量信息.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int sem_id, int sem_num, int cmd, union semun arg);
参数sem_id是由semget返回的信号量标识符,
参数nsems指定需要的信号量数目。
参数cmd为控制命令,它的取值可以为:
(1)IPC_RMID:删除一个信号量
(2)IPC_EXCL:只有在信号量集不存在时才创建
(3)IPC_SET:设置信号量的访问权限
(4)SETVAL:设置信号量的值
(5)GETVAL:获取指定信号量的值
(6)GETPID:获取最后操作信号量进程的标识符
(7)GETNCNT:获取等待信号量变1的进程数
(8)GETZCNT:获取等待信号量变为0的进程数
参数arg为操作值,它的类型为semun共享体。
union semun {
int val; // 信号量的值,cmd的值为SETVAL时使用
struct semid_ds *buf; // 信号量的状态,cmd的值为GETVAL、GETPID、GETNCNT、或GETZCNT时使用
unsigned short *array; // 所有信号量的值
struct seminfo *__buf; // IPC_INFO(Linux特有) 使用缓存区
};
四、信号量的操作
1、semop函数
它的作用是改变信号量的值.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int sem_id, struct sembuf *sem_opa, unsigned short nsops);
sem_id是由semget返回的信号量标识符。
参数sem_opa为指向信号量操作数组的指针。
参数nsops为数组中的元素个数。
sembuf结构的定义如下:
struct sembuf{
short sem_num;//除非使用一组信号量,否则它为0
short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
//一个是+1,即V(发送信号)操作。
short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
};
其中,sem_num为要操作的信号量在信号集中的序号;sem_op可以取为正数、负数或0,表示要执行的操作;sem_flg为操作的选项,可以设为IPC_NOWAIT或SEM_UNDO。
如果sem_op为正数,则表示进程不再使用该资源,semop函数将该值加到相应的信号量上,并唤醒等待的进程;如果为负数,表示进程希望使用资源,semop函数将该值加到相应的信号量上,如果结果为负数,则阻塞进程,如果sem_op为0,表示进程要一直等待,直到信号量的值变为0.
函数调用成功后,返回0,调用过程中发生错误或被信号中断则返回-1,并设置相应的错误码。
示例:通过信号量来实现进程间的同步。
//通过信号量实现进程间的同步
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <sys/wait.h>
int main()
{
int semid;
pid_t pid;
key_t key;
struct sembuf lock={0,-1,SEM_UNDO};
struct sembuf unlock={0,1,SEM_UNDO|IPC_NOWAIT};
int i,ret,status;
key=ftok("/home", 'a'); //生成信号量的键值
semid=semget(key, 1, IPC_CREAT|0666); //创建一个信号量集
if (semid<0)
{
perror("semget error");
exit(1);
}
ret=semctl(semid, 0, SETVAL,1); //将信号量的值设为1
if (ret<0)
{
perror("semctl error");
exit(1);
}
pid=fork(); //创建子进程
if (pid<0)
{
perror("fork error");
exit(1);
}
if (pid==0) //子进程
{
for (i=0; i<3; i++)
{
sleep(abs((int)3.0*rand()/(RAND_MAX+1)));//休眠0-3秒
ret=semop(semid, &lock, 1); //申请访问共享资源
if (ret==-1)
{
perror("lock error");
exit(1);
}
printf("child process access the resource.\n"); //开始访问共享资源
sleep(abs((int)3.0*rand()/(RAND_MAX+1)));
printf("complete!\n");
ret=semop(semid, &unlock, 1); //共享资源访问完毕
if (ret==-1)
{
perror("unlock error");
exit(1);
}
}
}
else //父进程
{
for (i=0; i<3; i++)
{
sleep(abs((int)(3.0*rand()/(RAND_MAX+1))));
ret=semop(semid, &lock, 1); //申请访问共享资源
if (ret==-1)
{
perror("lock error");
exit(1);
}
printf("father process access the resource.\n"); //开始访问共享资源
sleep(abs((int)3.0*rand()/(RAND_MAX+1)));
printf("complete!\n");
ret=semop(semid, &unlock, 1); //共享资源访问完毕
if (ret==-1)
{
perror("unlock error");
exit(1);
}
}
if (pid!=wait(&status)) //等待子进程结束
{
printf("wait error\n");
exit(1);
}
ret=semctl(semid, 0, IPC_RMID,0);//删除信号量
if (ret==-1)
{
perror("semctl error");
exit(1);
}
}
return 0;
}
五、信号量的总结
信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。我们通常通过信号来解决多个进程对同一资源的访问竞争的问题,使在任一时刻只能有一个执行线程访问代码的临界区域,也可以说它是协调进程间的对同一资源的访问权,也就是用于同步进程的。