概念
信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能保证所有进程都能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。
同步:处理竞争就是同步,安排进程执行的先后顺序就是同步,每个进程都有一定的个先后执行顺序。
互斥:互斥访问不可共享的临界资源,同时会引发两个新的控制问题(互斥可以说是特殊的同步)。
竞争:当并发进程竞争使用同一个资源的时候,我们就称为竞争进程。
共享资源通常分为两类:
- 互斥共享资源,即任一时刻只允许一个进程访问该资源;
- 同步共享资源,即同一时刻允许多个进程访问该资源;信号量是解决互斥共享资源的同步问题而引入的机制。
作用
信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。二值信号量(Binary Semaphore)是最简单的一种锁(互斥锁),它只用两种状态:占用与非占用。所以它的引用计数为1。
步骤
当有进程要求使用共享资源时,需要执行以下操作:
1.系统首先要检测该资源的信号量;
2.若该资源的信号量值大于0,则进程可以使用该资源,此时,进程将该资源的信号量值减1;
3.若该资源的信号量值为0,则进程进入休眠状态,直到信号量值大于0时进程被唤醒,访问该资源;
当进程不再使用由一个信号量控制的共享资源时,该信号量值增加1,如果此时有进程处于休眠状态等待此信号量,则该进程会被唤醒。
*注:信号量通过同步与互斥保证访问资源的一致性。*
函数操作
1.创建或打开信号量
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
- (1)第一个参数key是长整型(唯一非零),系统建立IPC通讯 ( 消息队列、 信号量和 共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到,由内核变成标识符,要想让两个进程看到同一个信号集,只需设置key值不变就可以。
- (2)第二个参数nsem指定信号量集中需要的信号量数目,它的值几乎总是1。
- (3)第三个参数flag是一组标志,当想要当信号量不存在时创建一个新的信号量,可以将flag设置为IPC_CREAT与文件权限做按位或操作。设置了IPC_CREAT标志后,即使给出的key是一个已有信号量的key,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。一般我们会还或上一个文件权限
- (4)返回:成功返回信号灯集描述字
semid
,否则返回-1。
2.对信号量进行PV操作
对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf *sops, size_t nops);
sembuf 结构图
struct sembuf{
short sem_num; //除非使用一组信号量,否则它为0
short sem_op; //信号量在一次操作中需要改变的数据,通常是两个数,
//一个是-1,即P(等待)操作,
//一个是+1,即V(发送信号)操作。
short sem_flg; //通常为SEM_UNDO,使操作系统跟踪信号量,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
};
semnum: 当前需要操作的信号量在信号集中编号,从0开始
sem_flg: IPC_NOWAIT或SEM_UNDO,如果设置了SEM_UNDO标志,那么在进程结束时,相应的操作将被取消,这是比较重要的一个标志位。
sem_op: PV操作,其值为正,加到现有的信号内含值。通常用于释放所控资源的使用权;值为负,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。
blog.csdn.net/qq_44715649/article/details/88884685
semid
为semget()
返回的信号量描述符- 如果我们的信号量集只有一个信号量,此时,nsops=1,我们的sops就直接指向一个struct sembuf类型的指针。
2.对信号量其他控制操作
int semctl(int semid, int sem_num, int cmd, ...);
union semun {
int val; /* value for SETVAL */
struct semid_ds __user *buf; /* buffer for IPC_STAT & IPC_SET */
unsigned short __user *array; /* array for GETALL & SETALL */
struct seminfo __user *__buf; /* buffer for IPC_INFO */
void __user *__pad;
};
-
sem_id是由semget返回的信号量标识符
-
semnum当前信号量集的哪一个信号量,就是需要控制的信号量在信号集中的编号,如果信号集只有一个元素,该值为0。cmd为控制类型,对于有些操作,需要第四个参数,即为一个union semun联合体,根据cmd不同,使用联合体中不同的字段:
-
cmd通常是下面两个值中的其中一个
SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
IPC_RMID:用于删除一个已经无需继续使用的信号量标识符,删除的话就不需要缺省参数,只需要三个参数即可。
举例
此列子是自己完成PV操作的封装
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/sem.h>//包含信号量定义的头文件
//联合类型semun定义
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
};
//函数声明
//函数:设置信号量的值
static int set_semvalue(void);
//函数:删除信号量
static void del_semvalue(void);
//函数:信号量P操作
static int semaphore_p(void);
//函数:信号量V操作
static int semaphore_v(void);
static int sem_id;//信号量ID
int main(int argc,char *argv[])
{
int i;
int pause_time;
char op_char = 'O';
srand((unsigned int)getpid());
//创建一个新的信号量或者是取得一个已有信号量的键
sem_id = semget((key_t)1234,1,0666 | IPC_CREAT);
//如果参数数量大于1,则这个程序负责创建信号和删除信号量
if(argc > 1)
{
if(!set_semvalue())
{
fprintf(stderr,"failed to initialize semaphore\n");
exit(EXIT_FAILURE);
}
op_char = 'X';//对进程进行标记
sleep(5);
}
//循环:访问临界区
for(i = 0;i < 10;++i)
{
//P操作,尝试进入缓冲区
if(!semaphore_p())
exit(EXIT_FAILURE);
printf("%c",op_char);
fflush(stdout);//刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上
pause_time = rand() % 3;
sleep(pause_time);
printf("%c",op_char);
fflush(stdout);
//V操作,尝试离开缓冲区
if(!semaphore_v())
exit(EXIT_FAILURE);
pause_time = rand() % 2;
sleep(pause_time);
}
printf("\n %d - finished \n",getpid());
if(argc > 1)
{
sleep(10);
del_semvalue();//删除信号量
}
}
//函数:设置信号量的值
static int set_semvalue(void)
{
union semun sem_union;
sem_union.val = 1;
if(semctl(sem_id,0,SETVAL,sem_union))
return 0;
return 1;
}
//函数:删除信号量
static void del_semvalue(void)
{
union semun sem_union;
if(semctl(sem_id,0,IPC_RMID,sem_union))
fprintf(stderr,"Failed to delete semaphore\n");
}
//函数:信号量P操作:对信号量进行减一操作
static int semaphore_p(void)
{
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;
}
//函数:信号量V操作:对信号量进行加一操作
static int semaphore_v(void)
{
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;
}
————————————————
版权声明:本文为CSDN博主「Thanos Yan」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_44715649/article/details/88884685
Linux系统中已经有封装好的semaphore,无需关心PV操作
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <errno.h>
#define total 20
sem_t remain, apple, pear, mutex;
static unsigned int vremain = 20, vapple = 0, vpear = 0;
void *father(void *);
void *mather(void *);
void *son(void *);
void *daughter(void *);
void print_sem();
int main()
{
pthread_t fa, ma, so, da;
sem_init(&remain, 0, total);//总数初始化为20
sem_init(&apple, 0, 0);//盆子中苹果数, 开始为0
sem_init(&pear, 0, 0);//盆子中梨子数, 开始为0
sem_init(&mutex, 0, 1);//互斥锁, 初始为1
pthread_create(&fa, NULL, &father, NULL);
pthread_create(&ma, NULL, &mather, NULL);
pthread_create(&so, NULL, &son, NULL);
pthread_create(&da, NULL, &daughter, NULL);
for(;;);
}
void *father(void *arg)
{
while(1)
{
sem_wait(&remain);
sem_wait(&mutex);
printf("父亲: 放苹果之前, 剩余空间=%u, 苹果数=%u\n", vremain--, vapple++);
printf("父亲: 放苹果之后, 剩余空间=%u, 苹果数=%u\n", vremain, vapple);
sem_post(&mutex);
sem_post(&apple);
sleep(1);
}
}
void *mather(void *arg)
{
while(1)
{
sem_wait(&remain);
sem_wait(&mutex);
printf("母亲: 放梨子之前, 剩余空间=%u, 梨子数=%u\n", vremain--, vpear++);
printf("母亲: 放梨子之后, 剩余空间=%u, 梨子数=%u\n", vremain, vpear);
sem_post(&mutex);
sem_post(&pear);
sleep(2);
}
}
void *son(void *arg)
{
while(1)
{
sem_wait(&pear);
sem_wait(&mutex);
printf("儿子: 吃梨子之前, 剩余空间=%u, 梨子数=%u\n", vremain++, vpear--);
printf("儿子: 吃梨子之后, 剩余空间=%u, 梨子数=%u\n", vremain, vpear);
sem_post(&mutex);
sem_post(&remain);
sleep(3);
}
}
void *daughter(void *arg)
{
while(1)
{
sem_wait(&apple);
sem_wait(&mutex);
printf("女儿: 吃苹果之前, 剩余空间=%u, 苹果数=%u\n", vremain++, vapple--);
printf("女儿: 吃苹果之前, 剩余空间=%u, 苹果数=%u\n", vremain, vapple);
sem_post(&mutex);
sem_post(&remain);
sleep(3);
}
}
void print_sem()
{
int val1, val2, val3;
sem_getvalue(&remain, &val1);
sem_getvalue(&apple, &val2);
sem_getvalue(&pear, &val3);
printf("Semaphore: remain:%d, apple:%d, pear:%d\n", val1, val2, val3);
}