1.什么是信号量?
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。
2.信号量的工作原理
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
3.信号量操作函数
需要的库
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
1.semget函数
用于创建一个新的或者获取一个已经存在的信号量组
int semget(key_t key,int nsems, int semflg);
key 是信号量组的系统唯一的标记符,用户可以自己定义。
nsems 指明信号量组中包含多小个信号量
flags 是创建时的一些选项
一、key对应的信号量组不存在时,则创建拥有nsems个信号量,semflg中的后9位如0666的666代表访问权限。
二、key对应的信号量组存在时,返回信号量组的ID,参数nsems和semflg无意义。
三、特殊情况,
当
semflg为IPC_EXECL时,只要
key对应的信号量组存在,则segmet返回-1。作用:同时使用IPC_EXECL和IPC_CREATE,
获取新的信号量组,如果已经存在,则返回-1;
2.semop函数
用于对多个信号量进行操作
int semop(int semid, struct sembuf* sops, int nsops);
semid 对信号量组的ID。
sops 说明如何操作。
nsops 说明操作的个数。
对sembuf结构说明
struct sembuf{
short sem_num;
short sem_op;
short sem_flg;
}
sem_num 对信号量组的第sem_num个信号量进行操作
sem_op 在信号量组的第sem_num个信号量执行操作
sem _flg操作类型
sem_num是相对应的信号量集中的某一个资源,所以其值是一个从0到相应的信号量集的资源总数(ipc_perm.sem_nsems)之间的整数。除非使用一组讯号了,否则它的取值一般为0。
sem_op指明需要进行的操作,如果他取+1,相当于P操作。取-1,相当于V操作。sem_op的值实际是对资源的占有和释放。sem_op<0,占有可用资源;sem_op>0,释放可用资源。对于每个信号量都保存sem_val,初始为0.
sem_flg通常取0,如果取值为SEM_UNDO,则进程退出时,自动平衡P和V操作。
3.semctl
用于对信号量组中信号量进行控制,如删除,修改。
int semctl(int sem_id, int semnum, int cmd, union semun);
semid是信号量组的ID。
sem_num是信号量的编号
cmd控制命令
union semun{
int val;
struct semid_ds *buf;
unsigned short *arry;
}
int val由SETVAL使用, semctl将会把arg.val设置到信号量的semval中.
struct semid_ds *buf由IPC_STAT和IPC_SET使用, 读取时将信号量集合的semid_ds读取到arg.buf中. 设置时使用arg.buf的三项内容.
unsigned short *array由GETALL和SETALL使用, 将读取和设置集合中的所有信号量
参数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设置信号量集中的一个单独的信号量的值。
返回值:如果成功,则为一个正数
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设置信号量集中的一个单独的信号量的值。
返回值:如果成功,则为一个正数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sem.h>
#include <fcntl.h>
union semun{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
static int sem_id = 0;
static int init_sem();
static void del_sem();
static int sem_P();
static int sem_V();
int main(int argc, char** argv)
{
char show = 'X';
int i = 0;
sem_id = semget((key_t)1234,1,0666 | IPC_CREAT);
if(argc > 1)
{
if(!init_sem())
{
printf("init failed\n");
return -1;
}
show = argv[1][0];
sleep(2);
}
for (i = 0; i< 10; i++)
{
if (!sem_P())
exit(-1);
printf("%c", show);
fflush(stdout); sleep(rand()%3);
printf("%c", show);
fflush(stdout);
if (!sem_V())
exit(-1);
if(argc >1)
{
sleep(3);
}
else
{
sleep(2);
}
}
printf("\n%d finished \n",getpid());
if (argc > 1)
{
sleep(10);
del_sem();
}
exit(0);
}
static int init_sem()
{
union semun union_sem;
union_sem.val = 1;
if (semctl(sem_id, 0, SETVAL, union_sem) == -1)
return 0;
return 1;
}
static void del_sem()
{
union semun union_sem;
if (semctl(sem_id, 0, IPC_RMID, union_sem) == -1)
printf("delete failed\n ");
}
static int sem_P()
{
struct sembuf sem;
sem.sem_num = 0;
sem.sem_op = -1;
sem.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem, 1) == -1)
{
printf("sem_P failed\n");
return 0;
}
return 1;
}
static int sem_V()
{
struct sembuf sem;
sem.sem_num = 0;
sem.sem_op = 1;
sem.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem, 1) == -1)
{
printf("sem_V failed\n");
return 0;
}
return 1;
}
gcc sem.c -o sem
sem O & sem
此程序通过sleep控制O程序和X程序进入临界区的频率。