信号量描述
信号量就是控制某个进程能够对某个资源进行访问;保证某一时刻只能由一个进程对某个资源进程访问。
- 信号量是一个特殊的变量,一般去正值。它的值代表允许访问的资源数目。
- 获取资源时,需要对信号量的值进行原子减一,该操作成为P操作。当信号量值为0时,代表没有资源可用,P操作阻塞;释放资源时,需要对信号量的值进行原子加一,该操作被称为V操作。
- 信号量主要用于同步进程,如果信号量的值只取0、1,将其成为二值信号量。如果信号量的值大于一,则称之为计数信号量。
临界资源和临界区
临界资源:同一时刻,只允许被一个进程或者线程访问的资源
临界区:访问临界资源的代码段
“不加控制模拟使用打印机”示例
描述:进程a,b模拟访问打印机,a输出第一个字符A表示开始使用打印机,输出第二个A表示结束使用,b进程操作与a相同。(由于打印机同一时刻只能被一个进程使用,所以输出结果不可能出现ABAB这样交替出现的结果)
a.c
b.c
同时访问打印机指令:./a& ./b&(使a,b进程同时在后台运行)
运行结果:
信号量使用
信号量接口介绍
头文件
#include<sys/sem.h>
#include<sys/types.h>
#include<sys/ipc.h>
int semget(key_t key,int nsems,int semflg);
**semget()创建或者获取已存在的信号量;
**semget()成功返回信号量的ID,失败返回-1;
**key:两个进程使用相同的key值,就可以使用同一个信号量;
**nsems:内核维护的是一个信号量集,在新建信号量时,其指定信号量集中信号量的个数;
**semflg:IPC_CREAT IPC_EXCL
int semop(int semid,struct sembuf*sops,unsigned nsops);
**semop()对信号量进行改变,进行PV操作
**semop()成功返回0,失败返回-1、
**semid:信号量的id号,即semget的返回值,说明对哪个信号量尽心操作;
**sops:结构体指针,指向sembuf的结构体指针
**
struct sembuf
{
unsigned short sem_num;//指定信号量集中的信号量下标(信号量编号)
short sem_ops;//其值为-1,代表P操作;其值为1,代表V操作
short sem_flg;//SEM_UNDO标志位
};
int semctl(int semid,int semnum,int cmd,...);
**semctl()控制信号量,能够对信号量进行初始化和删除操作
**semid:信号量id
**semnum:信号量编号
cmd命令:
SETVAL:初始化信号量;
IPC_RMID:删除信号量;
*******************
**联合体semun
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
}
seminit()//初始化包含创建+初始化:semget()-semctl()
sem_p()//控制信号量:semop()-1
sem_v()//semop()+1
sem_destory()//删除操作semctl()
借用信号量对上述打印机出错的情况进行修正
1、封装信号量的接口
sem.h
存放头文件以及函数声明
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<string.h>
#include<sys/sem.h>
//定义联合体
union emun
{
int val;
};
void sem_init();
void sem_p();
void sem_v();
void sem_disrory();
sem.c
#include "sem.h"
static int semid=-1;
//信号量的初始化需要进行创建+初始化操作
void sem_init()
{
semid=semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);
if(semid=-1)
{
//创建失败,可能是信号量已经创建,则重新获取即可,0600属于补位
semid=semget((key_t)1234,1,0600);
if(semid==-1)
{
//持续失败,则无法创建
printf("creat semid failed\n");
}
}
else//创建成功,开始初始化
{
union semun a;//定义联合体a
a.val=1;
if(semctl(semid,0,SETVAL,a)==-1)
{
printf("semctl init failed\n");
}
}
}
void sem_p()
{
//由于只创建一个信号量,则只申请一个结构体,传入地址即可
struct sembuf a;
a.sem_num=0;//只有一个信号量,下标为0
a.sem_op=-1;//P操作
//防止获取资源进行P操作之后,进程的异常终止
//标志位可以将原来对信号量所做的操作,反过来再做一遍,即原来属于P操作,标志位将再做一遍V操作
a.sem_flg=SEM_UNDO;
if(smeop(semid,&a,1)==-1)
{
printf("sem_p failed\n");
}
}
void sem_v()
{
//由于只创建一个信号量,则只申请一个结构体,传入地址即可
struct sembuf a;
a.sem_num=0;//只有一个信号量,下标为0
a.sem_op=-1;//P操作
//防止获取资源进行P操作之后,进程的异常终止
//标志位可以将原来对信号量所做的操作,反过来再做一遍,即原来属于P操作,标志位将再做一遍V操作
a.sem_flg=SEM_UNDO;
if(smeop(semid,&a,1)==-1)
{
printf("sem_v failed\n");
}
}
void sem_distory()
{
if(semctl(semid,0,IPC_RMID)==-1)
{
//删除给定信号量的ID即可删除一组信号量
printf("sem_distory failed\n");
}
}
2、程序修改
d.c
e.c
运行截图
ipcs/ipcrm介绍
- ipcs可以查看消息队列、共享内存、信号量的使用情况
- ipcrm能够进行信号量删除
思考: