- 什么是信号量
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。
信号量的作用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程) 。
相关概念:临界资源:同时只允许一个进程使用的资源。
临界区:访问临界资源的程序段。
信号量 更正式的一个定义:它是一个特殊变量,只允许进行原子操作(p、v)操作。
2.信号量的原理
p操作(信号量变量):用于等待。如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行。
v操作(信号量变量):用于发送信号。如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1。
3.Linux的信号量机制
(1)信号量的函数定义
头文件:#include
int semtcl(int sem_id,int sem_num,int command,....);
int semget(key_t key,int num_sems,int sem_flags);
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
(2)函数参数含义
<1>semget 函数
创建一个新信号量或者获得一个已有信号量的键.
key_t key:key是一个整数值,不同的进程可以通过它访问同一个信号量。程序对所有信号量的访问都是间接的,它都是先生成一个键,再由系统生成一个信号标识符。
num_sems:需要指定de信号量数目。
sem_flags:一组标志,作用类似于文件的访问权限。当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
semget 函数成功返回时,返回一个信号量标识符,失败返回-1.
<2>semtcl函数
该函数用来直接控制信号量信息。
sem_id:semtcl返回的信号量标识符。
sem_num:信号量标号,当需要信号量组的时候,需要用到这个参数,它一般取值为0。
command:表示即将要采取的动作。
第四个参数:是一个union semum结构体
union semun{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
<3>semop函数
它的作用是改变信号量的值。
sem_id:semtcl返回的信号量标识符。
sembuf *sem_opa:一个指向结构数组的指针。
struct sembuf{
short sem_num;//除非使用一组信号量,否则它为0
short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
//一个是+1,即V(发送信号)操作。
short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
};
4.信号量的使用
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/sem.h>
union semun
{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
static int sem_id = 0;
static int set_semvalue();
static void del_semvalue();
static int semaphore_p();
static int semaphore_v();
int main(int argc, char *argv[])
{
char message = 'X';
int i = 0;
//创建信号量
sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
if(argc > 1)
{
//程序第一次被调用,初始化信号量
if(!set_semvalue())
{
fprintf(stderr, "Failed to initialize semaphore\n");
exit(EXIT_FAILURE);
}
//设置要输出到屏幕中的信息,即其参数的第一个字符
message = argv[1][0];
sleep(2);
}
for(i = 0; i < 10; ++i)
{
//进入临界区
if(!semaphore_p())
exit(EXIT_FAILURE);
//向屏幕中输出数据
printf("%c", message);
//清理缓冲区,然后休眠随机时间
fflush(stdout);
sleep(rand() % 3);
//离开临界区前再一次向屏幕输出数据
printf("%c", message);
fflush(stdout);
//离开临界区,休眠随机时间后继续循环
if(!semaphore_v())
exit(EXIT_FAILURE);
sleep(rand() % 2);
}
sleep(10);
printf("\n%d - finished\n", getpid());
if(argc > 1)
{
//如果程序是第一次被调用,则在退出前删除信号量
sleep(3);
del_semvalue();
}
exit(EXIT_SUCCESS);
}
static int set_semvalue()
{
//用于初始化信号量,在使用信号量前必须这样做
union semun sem_union;
sem_union.val = 1;
if(semctl(sem_id, 0, SETVAL, sem_union) == -1)
return 0;
return 1;
}
static void del_semvalue()
{
//删除信号量
union semun sem_union;
if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
fprintf(stderr, "Failed to delete semaphore\n");
}
static int semaphore_p()
{
//对信号量做减1操作,即等待P(sv)
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;//P()
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id, &sem_b, 1) == -1)
{
fprintf(stderr, "semaphore_p failed\n");
return 0;
}
return 1;
}
static int semaphore_v()
{
//这是一个释放操作,它使信号量变为可用,即发送信号V(sv)
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;//V()
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id, &sem_b, 1) == -1)
{
fprintf(stderr, "semaphore_v failed\n");
return 0;
}
return 1;
}
运行结果:
xxxxooxxooxxooxxooxxooooxxooxxooxxooxxoo