信号量"/>
进程通信之共享内存,信号量
目录
1、共享内存
2、共享内存使用步骤
3、共享内存函数接口
4、共享内存示例代码
5、使用共享内存的问题
6、信号量
7、信号量使用步骤
8、信号量函数接口
9、信号量示例代码如下
10、信号量总结
1、共享内存
共享内存,顾名思义,就是通过不同进程共享一段相同的内存来达到通信的目的,由于SHM对象不再交由内核托管,因此共享内存SHM对象是众多IPC方式最高效的一种方式,但也因为这个原因,SHM一般不能单独使用,而需要配合诸如互斥锁、信号量等协同机制使用。
2、共享内存使用步骤
1)先申请key值 key_t key = ftok(".",10);
2)根据申请到的key值去申请共享内存的ID号。 -> shmget() -> man 2 shmget (share memory get)
3)根据ID号 将共享内存映射至本进程虚拟内存空间的某个区域---映射 -> man 2 shmmat
4)当不再使用时,解除映射关系 -> man 2 shmmdt
5)当没有进程再需要使用这块共享内存时,删除它-> shmctl() -> man 2 shmctl
3、共享内存函数接口
根据申请到的key值去申请共享内存的ID号
shmget() ---> man 2 shmget
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg); //shm --->share memory
参数:key: key值size: 共享内存的总字节数,必须是PAGE_SIZE的倍数 #define PAGE_SIZE 1024shmflg:IPC_CREAT -> 不存在则创建IPC_CREAT|0666
返回值:成功:共享内存ID号失败:-1根据ID号 将共享内存映射至本进程虚拟内存空间的某个区域---映射shmat() ---> man 3 shmat#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:shmid: 共享内存ID号shmaddr:NULL -> 系统自动分配地址给你 99.999%不为NULL -> 用户自己选择地址 0.001%shmflg:普通属性,填0
返回值:成功:共享内存的起始地址失败:(void *)-1
通常用法:shmat(shmid,NULL,0)当不再使用时,解除映射关系
shmdt() ---> man 2 shmdt
int shmdt(const void *shmaddr);
参数:shmaddr :你需要解除内存映射的地址
返回值:成功:0失败:-1当没有进程再需要使用这块共享内存时,删除它shmctl() ---> man 2 shmctl
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:shmid:共享内存ID号cmd:操作命令IPC_STAT --->获取共享内存的属性 --》必须填第三个参数IPC_RMID --->删除共享内存 --》填 NULLbuf:看cmd
返回值:成功:0失败:-1通常用法:
shmctl(id,IPC_RMID,NULL);//删除共享内存 说明:
#include <string.h>
char *strcpy(char *dest, const char *src); //字符串拷贝-->使用于字符串操作
void *memcpy(void *dest, const void *src, size_t n); //内存拷贝-->适用于内存操作
4、共享内存示例代码
include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<string.h>//共享内存_wr.c
int main()
{key_t key = ftok(".",10);int shmid = shmget(key,1024,IPC_CREAT|0666);if(shmid < 0){printf("shmget fail\n");return -1;}char* shm = shmat(shmid,NULL,0);if(shm == (void*) - 1){printf("shmad fail\n");return -1;}printf("%d %d\n",key,shmid);char sendbuf[1024] = "hello world";memcpy(shm,sendbuf, strlen(sendbuf)); while(1);shmdt(shm);shmctl(shmid,IPC_RMID,NULL);return 0;
}//共享内存_rd.c
int main()
{key_t key = ftok(".",10);int shmid = shmget(key,1024,IPC_CREAT|0666);if(shmid < 0){printf("shmget fail\n");return -1;}printf("%d %d\n",key,shmid);char* shm = shmat(shmid,NULL,0);if(shm == (void*) - 1){printf("shmad fail\n");return -1;}printf("%s\n",shm);while(1);printf("%s\n",shm); shmdt(shm);shmctl(shmid,IPC_RMID,NULL);return 0;
}
5、使用共享内存的问题
出现数据践踏,我们写了一次数据进去,那么就无限打印。
我们想要的是,更新一次数据,就打印一次就行。-----(用信号量来解决数据践踏)
6、信号量
信号量SEM全称Semaphore,中文也翻译为信号灯。信号量跟MSG和SHM有极大的不同,SEM不是用来传输数据的,而是作为“旗语”,用来协调各进程或者线程工作的。
信号量本质上是一个数字,用来表征一种资源的数量,当多个进程或者线程争夺这些稀缺资源的时候,信号量用来保证他们合理地、秩序地使用这些资源,而不会陷入逻辑谬误之中。
在Unix/Linux系统中常用的信号量有三种:
- IPC信号量组
- POSIX具名信号量
- POSIX匿名信号量
实际上在其内部实现中,IPC信号量组是一个数组,里面包含N个信号量元素,每个元素相当于一个POSIX信号量。这种机制可以一次性在其内部设置多个信号量。
- 临界资源(critical resources)
- 多个进程或线程有可能同时访问的资源(变量、链表、文件等等)
- 临界区(critical zone)
- 访问这些资源的代码称为临界代码,这些代码区域称为临界区
- P操作
- 程序进入临界区之前必须要对资源进行申请,这个动作被称为P操作,这就像你要把车开进停车场之前,先要向保安申请一张停车卡一样,P操作就是申请资源,如果申请成功,资源数将会减少。如果申请失败,要不在门口等,要不走人。
- V操作
- 程序离开临界区之后必须要释放相应的资源,这个动作被称为V操作,这就像你把车开出停车场之后,要将停车卡归还给保安一样,V操作就是释放资源,释放资源就是让资源数增加。
信号量组非常类似于停车场的卡牌,想象一个有N个车位的停车场,每个车位是立体的可升降的,能停n辆车,那么我们可以用一个拥有N个信号量元素,每个信号量元素的初始值等于n的信号量来代表这个停车场的车位资源——某位车主要把他的m辆车开进停车场,如果需要1个车位,那么必须对代表这个车位的信号量元素申请资源,如果n大于等于m,则申请成功,否则不能把车开进去。
7、信号量使用步骤
1)先申请共享内存的key值和id值,映射
2)由于信号量属于IPC对象,所以要申请key值 key = ftok(".",10);
3)根据key值申请信号量ID号。 -> semget() -> man 2 semget
4)控制/设置信号量值参数。 -> semctl() -> man 2 semctl
5)如何实现信号量的P/V操作? (P操作: 1->0 V操作: 0->1)
8、信号量函数接口
根据key值申请信号量ID号
semget() -> man 2 semget
#include <sys/types
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
参数:key:信号量的key值nsems: 信号量元素的个数。例如: 空间+数据 -> 2semflg: IPC_CREAT|0666 -> 不存在则创建返回值:成功: 信号量ID失败: -1控制/设置信号量值参数
semctl() -> man 2 semctl
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
参数:semid:信号量IDsemnum:需要操作的成员的下标 空间:0 数据:1cmd: SETVAL -> 用于设置信号量的起始值IPC_RMID -> 删除信号量的ID...: 空间/数据的起始值例如: 想设置空间的起始值为1,数据的起始值为0semctl(semid,0,SETVAL,1);//设置空间的起始值为1 (有车位)semctl(semid,1,SETVAL,0);//数据的起始值为0 (没车)
返回值:成功:0失败:-1实现信号量的P/V操作
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
函数参数:semid: 信号量ID号sops:进行P/V操作结构体nsops: 信号量操作结构体的个数 -> 1struct sembuf{unsigned short sem_num; //需要操作的成员的下标 空间:0 数据:1short sem_op; //P操作/V操作 P: -1 V: 1short sem_flg; //普通属性,填0.}
返回值:成功:0失败:-1
9、信号量示例代码如下
#include<stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/sem.h>//信号量_rd.c
int main()
{//1、获取key值key_t key = ftok(".",10);//2、根据key值 获取共享内存的ID号int shmid = shmget(key,1024,IPC_CREAT|0666);//3、根据ID号 将共享内存映射至本进程虚拟内存空间的某个区域char*shm_p = shmat(shmid,NULL,0);if(shm_p == (void*)-1){perror("shmat error");return -1;}//4、获取信号量的key值key_t key1 = ftok(".",20);//5、根据key值申请信号量ID号int semid = semget(key1,2,IPC_CREAT|0666);//6、初始化信号量起始值 有空间没数据semctl(semid,0,SETVAL,1);//设置空间的起始值为1semctl(semid,1,SETVAL,0);//设置数据的起始值为0//空间结构体(只是初始化,没有操作)struct sembuf space;space.sem_num = 0;space.sem_op = 1; //V操作 这个变量决定是p还是Vspace.sem_flg = 0;//数据结构体struct sembuf data;data.sem_num = 1;data.sem_op = -1;//P操作data.sem_flg = 0;//此时映射出来的shm_p 就是两个进程的共享内存while(1){//空间:0 数据:1//把车开出来之前,请问数据能不能-1?semop(semid, &data, 1);//能 -> 里面有车 -> 函数返回//不能 -> 里面没车 -> 函数阻塞//从车库里面把车开出来//从共享内存中读取数据printf("recv:%s\n",shm_p);//sleep(1);//把车开出来之后,空间+1semop(semid, &space, 1);//空间:1 数据:0//退出条件,这里要注意 应该使用strncmp 指定字节数if(strncmp(shm_p,"exit",4) == 0)break;}return 0;
}//信号量_wr.c
int main()
{//1、获取key值key_t key = ftok(".",10);//2、根据key值 获取共享内存的ID号int shmid = shmget(key,1024,IPC_CREAT|0666);//3、根据ID号 将共享内存映射至本进程虚拟内存空间的某个区域char*shm_p = shmat(shmid,NULL,0);if(shm_p == (void*)-1){perror("shmat error");return -1;}//4、获取信号量的key值key_t key1 = ftok(".",20);//5、根据key值申请信号量ID号int semid = semget(key1,2,IPC_CREAT|0666);//6、初始化信号量起始值--->有空间没数据//semctl第二个参数0--表示空间 1--表示数据semctl(semid,0,SETVAL,1);//设置空间的起始值为1 //有空间semctl(semid,1,SETVAL,0);//设置数据的起始值为0 //无数据//空间结构体(这里只是初始化,没有操作)struct sembuf space;space.sem_num = 0;//空间 (说明:通过struct sembuf里面的sem_num变量来区分结构体到底是空间还是数据)space.sem_op = -1;//P操作space.sem_flg = 0;//普通属性//数据结构体(这里只是初始化,没有操作)struct sembuf data;data.sem_num = 1;//数据data.sem_op = 1;//V操作data.sem_flg = 0;//普通属性//此时映射出来的shm_p 就是两个进程的共享内存while(1){//初始化空间:1 数据:0//开车进去之前,空间 -1 --P操作semop(semid, &space, 1);//请问空间能不能P操作?semop第三个参数1描述的是结构体的个数//能 -> 有车位 -> 函数返回//不能 -> 没车位 -> 函数阻塞//开车进去//从键盘上获取数据,存储到共享内存shm_pscanf("%s",shm_p);//开车进去之后,数据+1 --V操作semop(semid, &data, 1); //数据自动+1 这句话操作的是数据//退出条件,这里要注意 应该使用strncmp 指定字节数if(strncmp(shm_p,"exit",4) == 0)break;}//4、当不再使用时,解除映射关系shmdt(shm_p);//5、当没有进程再需要使用这块共享内存时,删除它shmctl(shmid, IPC_RMID, NULL);semctl(semid,0,IPC_RMID);return 0;
}
10、信号量总结
进程1发送端
1)共享内存的初始化
先申请共享内存的key值,根据key值获取共享内存的id号,进行共享内存的映射
2)信号量的初始化
先申请信号量的key值,根据key值获取信号量的id号,初始化信号量,空间为1,数据为0
初始化空间的结构体,初始化数据的结构体
//semctl第二个参数0--表示空间 1--表示数据
semctl(semid,0,SETVAL,1);//设置空间的起始值为1 //有空间
semctl(semid,1,SETVAL,0);//设置数据的起始值为0 //无数据
//空间结构体(这里只是初始化,没有操作)
struct sembuf space;
space.sem_num = 0;//空间 (说明:通过struct sembuf里面的sem_num变量来区分结构体到底是空间还是数据)
space.sem_op = -1;//P操作
space.sem_flg = 0;//普通属性
//数据结构体(这里只是初始化,没有操作)
struct sembuf data;
data.sem_num = 1;//数据
data.sem_op = 1;//V操作
data.sem_flg = 0;//普通属性
无论读写,空间的sem_num为0,数据的sem_num为1
3)写数据 对空间进行p操作,往共享内存写入数据,对数据进行v操作
semop(semid, &space, 1)
semop(semid, &data, 1);
semop()函数的第三个参数为结构体的数量 --> 1
无论 读写 空间数据 结构体,都先执行P操作再执行V操作
4)删除共享内存
解除共享内存的映射,删除共享内存
5)删除信号量
删除信号量
进程1接收端
1)共享内存的初始化
先申请共享内存的key值,根据key值获取共享内存的id号,进行共享内存的映射
2)信号量的初始化
先申请信号量的key值,根据key值获取信号量的id号,初始化信号量,空间为1,数据为0
初始化空间的结构体,初始化数据的结构体
//初始化信号量起始值 有空间没数据
semctl(semid,0,SETVAL,1);//设置空间的起始值为1
semctl(semid,1,SETVAL,0);//设置数据的起始值为0
//空间结构体(只是初始化,没有操作)
struct sembuf space;
space.sem_num = 0;
space.sem_op = 1; //V操作 这个变量决定是p还是V
space.sem_flg = 0;
//数据结构体
struct sembuf data;
data.sem_num = 1;
data.sem_op = -1;//P操作
data.sem_flg = 0;
无论读写,空间的sem_num为0,数据的sem_num为1
3)读数据
对数据进行p操作,在共享内存读取数据,对空间进行v操作
semop(semid, &space, 1)
semop(semid, &data, 1);
semop()函数的第三个参数为结构体的数量 --> 1
无论 读写 空间数据 结构体,都先执行P操作再执行V操作
4)删除共享内存
解除共享内存的映射,删除共享内存
5)删除信号量
删除信号量
更多推荐
进程通信之共享内存,信号量
发布评论