什么是信号量?
它是一个计数器,用 于为多个进程提供对共享数据对象的访问,它是为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题而产生的。信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。
总得来说:用于为多个进程提供对共享数据对象的访问。
信号量工作
为了获得共享资源,进程需要执行下列操作。
(1)测试控制该资源的信号量
(2)若此信号量的值为正,则进程可以使用该资源。在这种情况下,进程会将信号量值减1, 表示它使用了一个资源单位。
(3)否则,若此信号量的值为0,则进程进入休眠状态,直至信号量值大于0。进程被唤醒 后,它返回至步骤(1)。
P/V操作
PV原子操作:(原子操作:不可中断的一个或者一系列操作, 也就是不会被线程调度机制打断的操作)
● P操作:如果有可用的资源(信号量值>0),则此操作所在的进程占用一个资源(此时信号量值减1,进入临界区代码);如果没有可用的资源(信号量值=0),则此操作所在的进程被阻塞直到系统将资源分配给该进程(进入等待队列,一直等到资源轮到该进程)。
● V操作:如果在该信号量的等待队列中有进程在等待资源,则唤醒一个阻塞进程;如果没有进程等待它,则释放一个资源(即信号量值加1)。
总的来说:P操作就是申请访问时,资源占用与否对应的操作;V操作就是有进程释放资源,是否有进程等待对应的操作。
信号量结构
内核为每个信号量都维护一个semid_ds结构
其成员如下:
struct semid_ds {
struct ipc_perm sem_perm; /*所有权 和 权限*/
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned long sem_nsems; /* 集合中信号量的数量 */
};
每个信号量由一个无名结构表示
struct{
unsigned short semval; /* 信号量值 */
unsigned short semzcnt; /* 等待信号量值为0 */
unsigned short semncnt; /* 等待信号量值增加 */
pid_t sempid; /* PID of process that last*/
}
信号量API
1.semget函数
int semget(key_t key, int num_sems, int sem_flags);
功能:创建一个新的信号量集合或获取一个已经存在的信号量集合的键值。
key: 用来判断是创建新的信号量还是用已有的信号量
num_sems: 是该集合中的信号量数。如果是创建新集合(一般在服务器进程中),则必须指定num_sems。如果是引用现有集合(一个客户进程),则将num_sems指定为0。
sem_flags: 创建的权限及特性。有IPC_CREAT、IPC_EXCL。IPC_CREAT:如果信号量集合不存在,则创建一个信号量集合,否则获取。
IPC_EXCL:只有信号量集合不存在的时候,新的信号量才建立,否则就产生错误。
2.semop函数
int semop(int semid, struct sembuf *sops, size_t nsops);
功能:用户改变信号量的值。也就是使用资源还是释放资源使用权
semid:信号集的识别码,可通过semget获取。
nsops:信号操作结构的数量,恒大于或等于1。 sops:指向存储信号操作结构的指针数组,信号操作结构的原型如下struct sembuf { unsigned short sem_num; /*操作信号在信号集中的编号,第一个信号的编号是0。*/ short sem_op; /*sem_op:如果其值为正数,用于释放所控资源的使用权。如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权;如果sem_op的值为0,则操作将暂时阻塞。具体可查看下图*/ short sem_flg; /*信号操作标志,可能的选择有两种。一般为可设置为0*/ }; IPC_NOWAIT //对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。 IPC_UNDO //程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。
3.semctl函数
int semctl(int semid, int semnum, int cmd, /*union semun arg*/);
函数有三个或四个参数,取决于cmd。 当有四个时,第四个有联合semun类型
功能:在这个函数中我们可以删除信号量或初始化信号量。
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
(Linux-specific) */
};
semnum:操作信号在信号集中的编号。从0开始,值在0~num_sems之间。
cmd:cmd参数指定下列10种命令中的一种,这些命令是运行在semid指定的信号量集合上的。
返回值:对于除 GETALL以外的所有GET命令, semctl函数都返回相应值。对于其他命令,若成功则返回值为0,若出错,则设置 errno并返回-1。
信号量操作代码步骤
- 创建信号量
- 设置信号量初值
- 改变信号量的值,进而确定是使用资源还是释放资源使用权。如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。
例程
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
int main()
{
struct sembuf set;
key_t key= ftok(".", 1);
int sem_id=semget( key, 1, IPC_CREAT|0666);
if(sem_id<0)
{
perror("semget");
printf("信号量创建失败!\n");
}
else printf("信号量创建成功\n");
if(semctl(sem_id,0,SETVAL,2)<0)
{
perror("semctl");
}
set.sem_num=0;
set.sem_op=-1;
set.sem_flg=0;
if(semop(sem_id,&set,1)<0)
{
printf("请求资源失败!!\n");
perror("semop");
semctl(sem_id,0,SETVAL,5);
}
else
printf("请求资源成功\n");
printf("再次请求资源......\n");
if(semop(sem_id,&set,1)==0)
printf("再次请求成功!\n");
else
printf("再次请求失败\n");
set.sem_num=0;
set.sem_op=1;
set.sem_flg=0;
if(semop(sem_id,&set,1)==0)
printf("资源归还成功!\n");
else
printf("资源归还失败\n");
set.sem_num=0;
set.sem_op=-1;
set.sem_flg=0;
if(semop(sem_id,&set,1)==0)
printf("再再次请求成功!\n");
else
printf("再再次请求失败\n");
return 0;
}