进程通信之共享内存,信号量

编程入门 行业动态 更新时间:2024-10-07 20:25:03

进程通信之共享内存,<a href=https://www.elefans.com/category/jswz/34/1768721.html style=信号量"/>

进程通信之共享内存,信号量

目录

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)删除信号量

删除信号量

更多推荐

进程通信之共享内存,信号量

本文发布于:2024-02-28 08:08:24,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1769130.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:信号量   进程   内存   通信

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!