1.概念(解决什么问题)
信号量是用来协调多个进程对于共享资源(临界区资源)的访问
类比刚才抢糖果的例子
特点:
特点1(核心特点) 信号量的值如果减少到0,你还要进行P操作(减法),会阻塞当前进程
如果信号量的值小于你P操作的值,会阻塞当前进程
特点2: 信号量的值不可能为负数
特点3: 信号量进行V操作不会阻塞当前进程
查看信号量:
ipcs -s
删除信号量:
ipcrm -s 信号量的ID
2.相关的接口函数
(1)申请信号量--》申请积分卡
#include <sys/sem.h>
int semget(key_t key,int nsems,int semflg);
返回值:成功 返回信号量的ID
失败 -1
参数:nsems --》你打算申请多少个信号量(申请多少张积分卡)
semflg --》IPC_CREAT IPC_EXCL 0777
(2)设置/获取信号量的值,删除信号量
int semctl(int semid,int semnum,int cmd,...);
参数:semid --》信号量的ID
semnum --》信号量的序号,序号从0开始
cmd --》GETVAL 获取信号量的值
函数的返回值就是信号量的当前值=semctl(ID号,0,GETVAL); //获取第一个信号量(序号为0)的值
SETVAL 设置信号量的值
semctl(ID号,0,SETVAL,你要设置的新值);
semctl(ID号,0,SETVAL,5) //把第一个信号量(序号为0)的值设置为5
IPC_RMID 删除信号量
semctl(ID号,0,IPC_RMID)
(3)信号量的加减法--》PV操作
int semop(int semid,struct sembuf *sops,size_t nsops)
参数:semid --》信号量的ID
sops --》
struct sembuf
{
unsigned short sem_num; //信号量的序号
short sem_op; //决定对信号量进行何种操作
P操作(减法) -1
V操作(加法) +1
short sem_flg; //一般设置SEM_UNDO 对信号量做了PV操作改变了值以后,重新运行程序,信号量的值又会自动恢复成原来的值
}
nsops --》结构体变量的个数struct sembuf,一般写1
3.总结信号量使用的套路
进程1
while(1)
{
p操作;
共享资源的访问
v操作;
}
进程2
while(1)
{
p操作;
共享资源的访问
v操作;
}
进程的生老病死
================================
1.进程的几种状态
就绪态:进程已经运行了,但是还没有取得cpu的执行权
执行态:cpu的执行权已经切换到当前进程,当前进程就处于执行态
睡眠态/挂起态:进程调用sleep() pause()函数
暂停态:外界给进程发送了暂停信号
僵尸态:进程结束的时候最开始处于僵尸态,如果父进程有回收子进程,那么子进程就会进入死亡态
如果父进程没有回收子进程,那么子进程就永远处于僵尸态
死亡态:
演示信号量p操作的特点
#include "myhead.h"
//演示信号量的特点--》理解代码细节
int main()
{
int semid;
struct sembuf pbuf;
bzero(&pbuf,sizeof(pbuf));
pbuf.sem_num=0; //对序号为0的信号量进行操作
pbuf.sem_op=-2; //P操作,减1
pbuf.sem_flg=SEM_UNDO;
//申请积分卡(申请信号量)
semid=semget(74541,1,IPC_CREAT|IPC_EXCL|0777);
if(semid==-1)
{
if(errno==EEXIST)
semid=semget(74541,1,0777);
else
{
perror("申请积分卡失败!\n");
return -1;
}
}
//给信号量赋值为5
semctl(semid,0,SETVAL,5);
//打印当前信号量的值
printf("当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
//对信号量进行P操作,做减法
semop(semid,&pbuf,1);
printf("P操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
semop(semid,&pbuf,1);
printf("P操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
semop(semid,&pbuf,1);
printf("P操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
semop(semid,&pbuf,1);
printf("P操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
semop(semid,&pbuf,1);
printf("P操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
semop(semid,&pbuf,1);
printf("P操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
}
演示信号量v操作的特点
#include "myhead.h"
//演示信号量的特点--》理解代码细节
int main()
{
int semid;
struct sembuf vbuf;
bzero(&vbuf,sizeof(vbuf));
vbuf.sem_num=0; //对序号为0的信号量进行操作
vbuf.sem_op=+1; //V操作,加1
vbuf.sem_flg=SEM_UNDO;
//申请积分卡(申请信号量)
semid=semget(74541,1,IPC_CREAT|IPC_EXCL|0777);
if(semid==-1)
{
if(errno==EEXIST)
semid=semget(74541,1,0777);
else
{
perror("申请积分卡失败!\n");
return -1;
}
}
//给信号量赋值为5
semctl(semid,0,SETVAL,5);
//打印当前信号量的值
printf("当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
//对信号量进行P操作,做减法
semop(semid,&vbuf,1);
printf("V操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
semop(semid,&vbuf,1);
printf("V操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
semop(semid,&vbuf,1);
printf("V操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
semop(semid,&vbuf,1);
printf("V操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
semop(semid,&vbuf,1);
printf("V操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
semop(semid,&vbuf,1);
printf("V操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
}
信号量的使用例子抢糖果
#include "myhead.h"
//我侄子
int main()
{
int semid;
//对第一个信号量进行p操作
struct sembuf pbuf;
bzero(&pbuf,sizeof(pbuf));
pbuf.sem_num=0; //对序号为0的信号量进行操作
pbuf.sem_op=-1; //P操作,减1
pbuf.sem_flg=SEM_UNDO;
//对第二个信号量进行v操作
struct sembuf vbuf;
bzero(&vbuf,sizeof(vbuf));
vbuf.sem_num=1; //对序号为1的信号量进行操作
vbuf.sem_op=+1; //V操作,加1
vbuf.sem_flg=SEM_UNDO;
//申请2张积分卡(申请2个信号量)
semid=semget(74541,2,IPC_CREAT|IPC_EXCL|0777);
if(semid==-1)
{
if(errno==EEXIST)
semid=semget(74541,2,0777);
else
{
perror("申请积分卡失败!\n");
return -1;
}
}
//给第一个信号量赋值为1
semctl(semid,0,SETVAL,1);
//给第二个信号量赋值为0
semctl(semid,1,SETVAL,0);
while(1)
{
//对第一个信号量进行p操作,不会阻塞当前进程
semop(semid,&pbuf,1);
printf("侄子抢到一个糖果了!\n");
sleep(3);
//对第二个信号量进行v操作,拯救p2让p2不会永远阻塞
semop(semid,&vbuf,1);
}
}
#include "myhead.h"
//我邻居小孩子
int main()
{
int semid;
//对第二个信号量进行p操作
struct sembuf pbuf;
bzero(&pbuf,sizeof(pbuf));
pbuf.sem_num=1; //对序号为1的信号量进行操作
pbuf.sem_op=-1; //P操作,减1
pbuf.sem_flg=SEM_UNDO;
//对第一个信号量进行v操作
struct sembuf vbuf;
bzero(&vbuf,sizeof(vbuf));
vbuf.sem_num=0; //对序号为0的信号量进行操作
vbuf.sem_op=+1; //V操作,加1
vbuf.sem_flg=SEM_UNDO;
//申请2张积分卡(申请2个信号量)
semid=semget(74541,2,IPC_CREAT|IPC_EXCL|0777);
if(semid==-1)
{
if(errno==EEXIST)
semid=semget(74541,2,0777);
else
{
perror("申请积分卡失败!\n");
return -1;
}
}
//给第一个信号量赋值为1
//semctl(semid,0,SETVAL,1);
//给第二个信号量赋值为0
//semctl(semid,1,SETVAL,0);
while(1)
{
//对第二个信号量进行P操作
semop(semid,&pbuf,1);
printf("邻居小孩子抢到一个糖果了!\n");
sleep(1);
//对第一个信号量进行v操作,拯救p1让p1不会永远阻塞
semop(semid,&vbuf,1);
}
}
信号量的使用协调两个进程之间对于共享内存的访问
#include "myhead.h"
int main()
{
int shmid;
int semid;
//对第一个信号量进行p操作
struct sembuf pbuf;
bzero(&pbuf,sizeof(pbuf));
pbuf.sem_num=0; //对序号为0的信号量进行操作
pbuf.sem_op=-1; //P操作,减1
pbuf.sem_flg=SEM_UNDO;
//对第二个信号量进行v操作
struct sembuf vbuf;
bzero(&vbuf,sizeof(vbuf));
vbuf.sem_num=1; //对序号为1的信号量进行操作
vbuf.sem_op=+1; //V操作,加1
vbuf.sem_flg=SEM_UNDO;
//申请2张积分卡(申请2个信号量)
semid=semget(74541,2,IPC_CREAT|IPC_EXCL|0777);
if(semid==-1)
{
if(errno==EEXIST)
semid=semget(74541,2,0777);
else
{
perror("申请积分卡失败!\n");
return -1;
}
}
//给第一个信号量赋值为1
semctl(semid,0,SETVAL,1);
//给第二个信号量赋值为0
semctl(semid,1,SETVAL,0);
//申请共享内存
shmid=shmget(54552,1024,IPC_CREAT|IPC_EXCL|0777);
if(shmid==-1) //发生错误
{
//依据刚才讲解的错误码原理,进一步分析产生错误的原因
if(errno==EEXIST) //说明错误的原因是共享内存已经存在
{
shmid=shmget(54552,1024,0777); //打开共享内存
}
else //说明错误是其他类型的错误
{
perror("申请共享内存失败了!\n");
return -1;
}
}
//映射得到共享内存的首地址
char *p=shmat(shmid,NULL,0);
//p指向的就是你刚才申请得到的1024字节的内存首地址
if(p==NULL)
{
perror("映射共享内存失败!\n");
return -1;
}
//p1通过共享内存发送信息给p2
while(1)
{
//对第一个信号量进行p操作,不会阻塞当前进程
semop(semid,&pbuf,1);
printf("请输入要发送给p2的信息!\n");
scanf("%s",p);
//对第二个信号量进行v操作,拯救p2让p2不会永远阻塞
semop(semid,&vbuf,1);
}
}
#include "myhead.h"
// p2的代码
int main()
{
int shmid;
int semid;
//对第二个信号量进行p操作
struct sembuf pbuf;
bzero(&pbuf,sizeof(pbuf));
pbuf.sem_num=1; //对序号为1的信号量进行操作
pbuf.sem_op=-1; //P操作,减1
pbuf.sem_flg=SEM_UNDO;
//对第一个信号量进行v操作
struct sembuf vbuf;
bzero(&vbuf,sizeof(vbuf));
vbuf.sem_num=0; //对序号为0的信号量进行操作
vbuf.sem_op=+1; //V操作,加1
vbuf.sem_flg=SEM_UNDO;
//申请2张积分卡(申请2个信号量)
semid=semget(74541,2,IPC_CREAT|IPC_EXCL|0777);
if(semid==-1)
{
if(errno==EEXIST)
semid=semget(74541,2,0777);
else
{
perror("申请积分卡失败!\n");
return -1;
}
}
//申请共享内存
shmid=shmget(54552,1024,IPC_CREAT|IPC_EXCL|0777);
if(shmid==-1) //发生错误
{
//依据刚才讲解的错误码原理,进一步分析产生错误的原因
if(errno==EEXIST) //说明错误的原因是共享内存已经存在
{
shmid=shmget(54552,1024,0777); //打开共享内存
}
else //说明错误是其他类型的错误
{
perror("申请共享内存失败了!\n");
return -1;
}
}
//映射得到共享内存的首地址
char *p=shmat(shmid,NULL,0);
//p指向的就是你刚才申请得到的1024字节的内存首地址
if(p==NULL)
{
perror("映射共享内存失败!\n");
return -1;
}
//从共享内存去访问p1存放的信息
while(1)
{
//对第二个信号量进行P操作
semop(semid,&pbuf,1);
printf("p1发送的信息是:%s\n",p);
//对第一个信号量进行v操作,拯救p1让p1不会永远阻塞
semop(semid,&vbuf,1);
}
}