目录
一、概念
1.定义
- 用于管理对资源的访问。相当于红绿灯:管理进程间争夺的使用权,未得到使用权的一方只能等待。
- 信号量是一个原子操作。它是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目,获取资源时,需要对信号量的值进行原子-1,该操作称为P操作。当信号量的值为0时,代表没有资源可用,p操作会阻塞。释放资源时,需要对信号量的值进行原子加一,该操作被称为V操作。信号量主要用来同步进行。
- P:用于等待---获取资源--- -1
- V:用于操作---释放资源--- +1
-
PV操作:
假设一个信号量变量sv,则如下表的两个操作定义:
2.目的
保证同一时刻只能有一个进程对某个资源进行访问,防止因多个程序同时访问一个共享资源而引发的问题,使得在任意时刻只有一个执行线程访问代码的临界区域。而一个进程要持续不断的运行以等待某个内存位置被改变。
3.专业名词
- 二值信号量:信号量的值如果只取0,1。
- 计数信号量:信号量的值大于1.
- 临界资源:计算机的软硬件资源;同一时刻只允许进程或者线程访问的资源(打印机)
- 临界区::访问临界资源的代码段。
二、信号量接口
头文件:#include<sys/sem.h>
(1)semget
- 作用:创建一个新信号量或者获取一个已有的信号量的键:
int semget(key_t key,int num_sems,int sem_flags);
- 第一个参数key是整数值,不相关的进程可以通过它访问同一个信号量。程序对所有信号量的访问都是间接的,它先提供一个键,再由系统生成一个相应的信号量标识符。只有semget函数才直接使用信号量键,所有其他的信号量函数都是由semget函数返回信号量的标识符。
- 第二个参数num_sems:创建信号量的数目,一般取值为1。
- 第三个参数sem_flags:标志位,如果未创建时IPC_CREAT;如果为全新创建,不知道是否有人创建过,则为IPC_CREAT|IPC_EXCL。
- 返回值:成功返回一个整数(非零)值,是其他信号量函数将用到的信号量标识符。如果失败,返回-1。
(2)semop
- 作用:对信号量进行改变,做p或者v操作
int semop(int sem_id,struct sembuf * sem_ops,sieze_t num_sem_ops);
- 第一个参数是返回的信号量标识符。表明是p操作还是v操作,p:-1,v:+1。
- 第二个参数是结构体指针,指向sembuf,sembuf内有三个结构体成员:
struct sembuf{ short sem_num; short sem_op; short sem_flg; }
- sem_flg一般等于SEM_UNDO。这样的目的是:不论当前进程是否正常退出,都将还原此操作的 sem_op 值。用于以防万一异常结束的进程。
- 第三个参数是数组长度
(3)semctl
- 作用:用来直接控制信号量信息
int semctl(int sem_id,int sem_num,int command,...);
- 第一个参数是信号量标识符
- 第二个参数是信号量编号
- 第三个信号是将要采取的动作
- SETVAL:初始化信号量,该值通过下面union semun中的val成员设置,其作用是再信号量第一次使用之前对它进行设置。
- IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。
- 如果有第四个参数,它会是一个union semun的联合体:
该结构体一般需要我们程序员自己定义union semun{ int val; struct semid_ds *buf; unsigned short * array; }
- 返回值:成功返回0,失败返回-1。
(4)删除信号量
ipcrm -s id
三、信号量使用
信号量的使用只需用一个最简单的二进制信号量即可,在下面例子讲述用完整编程接口为二进制信号量创建一个非常简单的pv类型接口。
例子:
用进程a和进程b模拟访问打印机,进程a输出第一个字符”A“表示开始使用打印机,输出第二个字符”A“表示结束使用,b进程操作与a进程相同。如下图示:
sleep----开始使用
printf-----使用结束
1.编写a和b的代码:
- n=rand()%3; sleep(n);//随机睡眠3秒钟
- 在后台可以实现ab进程同时运行(后台&实现同时运行多进程),执行结果如下:
- 如果不加控制,AB交替出现,说明AB是在交替使用,A在sleep时B会打印出B,就不符合信号量的要求——A在使用时B不能使用。
由于打印机在同一时刻只能被一个进程使用,所以输出结果不应该出现abab这样交替的结果,下面使用信号量对其进行控制,使AB成对出现:
2.实现A在sleep时B不能使用,A和B是成对出现的:
代码
头文件:seem.h
#include<sys/sem.h>
#include<unistd.h>
#include<stdio.h>
//程序员自己定义semun联合体
union semun
{
int val;
};
void sem_init();
void sem_p(); //P操作
void sem_v(); //V操作
void sem_destory();//销毁
~
seem.cpp文件:
//初始化
void sem_init()
{
//semget:创建一个新信号量或者获取一个已有的信号量的键
semid=semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);//1234:房间号,0600->文件权限
if(semid==-1) //全新创建失败,获取已经存在的信号量
{
semid=semget((key_t)1234,1,0600);
if(semid==-1)
{
perror("semget error");
}
else
{
//初始化
union semun a;//定义联合体
a.val=1;//初始化设置为1,假设刚开始可以使用
//semctl:用来直接控制信号量信息
if(semctl(semid,0,SETVAL,a)==-1)//编号从0开始
{
perror("semctl init error");
}
}
}
}
void sem_p()//p操作
{
struct sembuf buf;
buf.sem_num=0;
buf.sem_op=-1;
buf.sem_flg=SEM_UNDO;//一般都这样设置,是让操作系统记住使用了P操作
//semop:对信号量进行改变,做p或者v操作
if(semop(semid,&buf,1)==-1)//1:长度,只有一个信号量
perror("p perror");
}
void sem_v()//v操作
{
struct sembuf buf;
buf.sem_num=0;
buf.sem_op=1;
buf.sem_flg=SEM_UNDO;//一般都这样设置,是让操作系统记住使用了P操作
if(semop(semid,&buf,1)==-1)//1:长度,只有一个信号量
perror("v perror");
}
//销毁
void sem_destory()
{
if(semctl(semid,0,IPC_RMID)==-1)
perror("destory sem error");
}
a.c:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<assert.h>
#include"seem.h"
int main()
{
int i=0;
//信号量初始化
sem_init();
for(;i<5;i++)
{
sem_p();
printf("A");
fflush(stdout);
int n=rand()%3;
sleep(n);
printf("A");
fflush(stdout);
sem_v();
n=rand()%3;
sleep(n);
}
sleep(10);
sem_destory();
}
b.c:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<assert.h>
#include"seem.h"
int main()
{
int i=0;
sem_init();
for(;i<5;i++)
{
sem_p();
printf("B");
fflush(stdout);
int n=rand()%3;
sleep(n);
printf("B");
fflush(stdout);
sem_v();
n=rand()%3;
sleep(n);
}
}
编译执行结果
此时,AB是成对打印的。
四、查看信号量信息
- ipcs:查看信号量和消息队列以及共享内存
- ipcs -s:查看信号量
- ipcs -m:查看共享内存
- ipcs -q:只查看消息队列