文章目录
简介:这篇文章详细讲解了关于信号量的函数,我在CSDN上只找到了关于PV的概念知识,没有实际问题进行实际分析,仅仅通过函数介绍了解PV操作对我来说太难了,所以我写了这篇根据我遇到的实际问题运用PV操作让自己对PV操作更加深刻,希望对大家有帮助。
关键词:进程、信号量、PV操作。
1.问题分析
这是上一篇文章中在共享内存中遇到的算是程序功能上的bug,我输入的是nihao,但是输出的只有n,这种情况是因为读写之间没有一种相互制约的关系,也就是信号量所能实现进程间的“互斥和同步”的意思,这种情况就可已通过信号灯来解决。
想要看实例分析的我建议先去看看上一篇文章中的共享内存中的实例代码部分,先要遇到问题才能解决问题。
Linux应用开发基础 进程间通信篇
2.信号灯(信号量)
2.1 原子操作
原子操作意思就是程序运行某一内容的时候,不会被其他进程因为优先级或者其他一些情况而被抢占CPU,在单核操作系统,像count++这种基本机器指令就是原子操作,但是多核中就不是了。
2.2 特点
信号灯和IPC中的其他几种通信方式不同,信号灯主要实现的是进程间的同步与互斥,对访问资源的一种管理方式。
2.3 函数介绍
2.3.1 semget函数
函数原型: int semget(key_t key, int nsems, int semflg);
//创建一个新的信号量或获取一个已经存在的信号量的键值。
所需头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
函数参数:
key:和信号灯集关联的key值
nsems: 信号灯集中包含的信号灯数目。
semflg:信号灯集的访问权限
函数返回值:
成功:信号灯集ID
出错:‐1
2.3.2 semctl函数
函数原型:int semctl ( int semid, int semnum, int cmd,…union semun arg(不是地址));
//控制信号量,删除信号量或初始化信号量
所需头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
函数参数:
semid:信号灯集ID
semnum: 要修改的信号灯编号
cmd :
GETVAL:获取信号灯的值
SETVAL:设置信号灯的值
IPC_RMID:从系统中删除信号灯集合
函数返回值:
成功:0
出错:‐1
2.3.3 semop函数
int semop(int semid ,struct sembuf *sops ,size_t nsops);
//用户改变信号量的值。也就是使用资源还是释放资源使用权
包含头文件:
include<sys/sem.h>
参数:
semid : 信号量的标识码。也就是semget()的返回值
sops是一个指向结构体数组的指针。
struct sembuf{
unsigned short sem_num;//信号灯编号;
short sem_op;//对该信号量的操作。‐1 ,P操作,1 ,V操作
short sem_flg;0阻塞,1非阻塞
};
sem_op : 操作信号灯的个数
如果其值为正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权;如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。
3. PV操作实例
我们通过信号灯的方式来消除共享内存实例程序功能上的bug。
3.1 sem_write.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/sem.h>
#include <errno.h>
#define SEM_WRITE 0
#define SEM_READ 1
union semun{
int val; //信号量的值
};
void Poperation(int semid,int semnum)
{
struct sembuf sop;
sop.sem_num = semnum;
sop.sem_op = -1;
sop.sem_flg = 0;
semop(semid,&sop,1);
}
void Voperation(int semid,int semnum)
{
struct sembuf sop;
sop.sem_num = semnum;
sop.sem_op = 1;
sop.sem_flg = 0;
semop(semid,&sop,1);
}
int main()
{
int shmid;
int semid;
union semun msemun;
key_t key;
char *shmp;
key = ftok(".", 1);
if(key ==-1)
{
perror("ftok");
exit(1);
}
printf("the key is %d\n", key);
semid = semget(key, 2, IPC_CREAT|0777);
if(semid==-1)
{
perror("semget");
exit(1);
}
shmid = shmget(key,128,IPC_CREAT|0777);
if(shmid==-1)
{
perror("shmget");
exit(1);
}
printf("semid:%d\nshmid:%d\n",semid,shmid);
msemun.val = 1;
semctl(semid,SEM_WRITE,SETVAL,msemun);
msemun.val = 0;
semctl(semid,SEM_READ,SETVAL,msemun);
shmp = (char*)shmat(shmid, NULL, 0);
while(1)
{
Poperation(semid,SEM_WRITE);
printf("iput name\n");
fgets(shmp, 128, stdin);
printf("input native place\n");
fgets(shmp, 128, stdin);
Voperation(semid,SEM_READ);
if(!strcmp(shmp,"exit\n")) break;
}
shmdt(shmp);
printf("write port has exited\n");
return 0;
}
3.2 sem_read.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/sem.h>
#include <errno.h>
#define SEM_WRITE 0
#define SEM_READ 1
union semun{
int val;
};
void Poperation(int semid,int semnum)
{
struct sembuf sop;
sop.sem_num = semnum;
sop.sem_op = -1;
sop.sem_flg = 0;
semop(semid,&sop,1);
}
void Voperation(int semid,int semnum)
{
struct sembuf sop;
sop.sem_num = semnum;
sop.sem_op = 1;
sop.sem_flg = 0;
semop(semid,&sop,1);
}
int main()
{
union semun msemun;
key_t key;
int shmid;
int semid;
char *shmp;
key = ftok(".", 1);
if(key<0)
{
perror("ftok");
exit(1);
}
printf("the key is %d\n", key);
semid = semget(key, 2, 0777);
if(semid==-1)
{
perror("semget");
exit(1);
}
shmid = shmget(key,128,0777);
if(shmid==-1)
{
perror("shmget");
exit(1);
}
printf("semid:%d\nshmid:%d\n",semid,shmid);
msemun.val = 0;
semctl(semid,SEM_WRITE,SETVAL,msemun);
msemun.val = 1;
semctl(semid,SEM_READ,SETVAL,msemun);
shmp = (char*)shmat(shmid, NULL, 0);
while(1)
{
Poperation(semid,SEM_READ);
if(strlen(shmp)==0) continue;
if(!strcmp(shmp,"exit\n")) break;
printf("the shmp content is %s, length: %ld\n", shmp, strlen(shmp));
memset(shmp,0,128);
Voperation(semid,SEM_WRITE);
}
shmdt(shmp);
shmctl(shmid, IPC_RMID,NULL);
printf("read port has exited\n");
return 0;
}
3.3 终端结果
3.4 问题分析&&代码解析:
我们的目的是论证PV操作实现了write和read之间的互斥与同步,出现写入nihao,读取的结果不是我们想要的n,这种情况是概率性事件,单个输入到输出不能说明结果,“假如说我们恰好实验了999次都没出现上面那种情况,概率性事件不能保证第1000次不会出现。”所以单输入不能确切论证PV实现进程间的同步与互斥。我们通过双输入甚至更多输入,只要能保证除了最后一次写入以后,只要read能够输出最后一个就行,所以我使用了两个fgets。
printf("iput name\n");
fgets(shmp, 128, stdin);
printf("input native place\n");
fgets(shmp, 128, stdin);
我们可以看到PV操作都是一个模板,同semid和sem_num信号量编号下,只有sem_op不一样,所以我们简单理解成,P: --,V: ++,也可以这样理解,PV控制着一种资源,假设这种资源只有一个(多个资源也同样道理),P–就是得到资源,资源变成0,其他人得不到,V++就是有资源了,其他人能得到。
struct sembuf sop;
sop.sem_num = semnum;
sop.sem_op = 1; //只有这里不同,P: --,V: ++
sop.sem_flg = 0;
semop(semid,&sop,1);
如果想动态的查看信号量的值可以通过下面的代码实现,加到哪不用我多说了吧。
semctl(semid,[信号量编号], GETVAL, [semun结构体]);
通过semun.val查看
4.总结
这篇文章是通过在共享内存中实例讲解中遇到的bug,从而使用PV操作来实现进程间的互斥与同步,每个人的理解不同,我的思路不一定适合别人,但是希望能对大家有帮助。
文章中有错误的地方请各位指出,谢谢。