uc_13_共享内存_消息队列_IPC对象

1  IPC对象

       多个IPC  ->  一个IPC对象  ->  一个IPC对象标识符  ->  一个键(整数)(即IPC对象的外部名称)

        共享内存、消息队列和信号量,这三种IPC一般被合称为XSI IPC,它们有很多相似之处。

        为了实现进程间的数据交换,系统内核会为参与通信的诸方维护一个内核对象(类似一个结构体变量),记录和通信有关的各种配置参数和运行时信息,称为IPC对象

        系统中的每个IPC对象都有唯一的、非负整数形式的标识符,所有与IPC相关的操作,都需要提供IPC对象标识符。

        与文件描述符不同,IPC对象标识符不是最小整数。当一个IPC对象被创建,以后又被销毁时,与该类型对象相关的标识符会持续加1,直到达到一个整数型的最大值,然后又回转到0。

        标识符是IPC对象的内部名。为了使多个合作进程能够在同一个IPC对象上会合,需要提供一个外部名方案。为此使用了键。每个IPC对象都与一个键相关联,于是就被作为该对象的外部名称。

        无论何时,创建或者获取一个IPC对象都必须指定一个键。键的数据类型为key_t,在<sys/ type.h>头文件中被定义为int。系统内核负责维护键与标识符的对应关系。

1.1  ftok()

        #include <sys/ipc.h>

        key_t  ftok ( const char* pathname,  int proj_id);

                功能:用于合成一个键

                pathname:一个真实存在的路径名

                proj_id:项目ID,仅低8位有效,取0~255之间的整数

                返回值:成功返回可用来创建或获取IPC对象的,失败返回-1 

        ftok()函数用pathname参数调用stat函数,将其输出的stat结构体中的st_dev(设备ID)成员、st_ino(i节点号)成员,与proj_id参数组合来生成键。

        -参与生成键的是设备ID和i节点号,而不是pathname参数字符串本身。假设当前路径为/home/t/uc,则ftok("/home/t/uc",123); 和ftok(".",123); 所返回的键是完全一样的。

        设备ID和i节点号都至少是整型字长的数据,而键也是整型字长,再加上一个字节项目ID,在合成键的过程中难免会丢失一部分信息(低8位)。因此有时候明明提供的是不同的路径,该函数返回的键却是一样的。

1.2  IPC相关命令

        查看系统中的IPC对象:

                ipcs (-a)   查看所有

                ipcs  -m   查看memory共享内存段

                ipcs  -q    查看message queue,消息队列

                ipcs  -s    查看semphore,信号量集

        删除系统中的IPC对象:

        进程中打开IPC对象,不关闭的话,进程结束后仍在。故需敲命令来结束:

                ipcrm  -m  id   移除用id标识的共享内存段

                ipcrm  -q  id    移除用id标识的消息队列

                ipcrm  -s  id    移除用id标识的信号量集

2  共享内存

        

        

        系统内核负责维护的内存区域,其他地址空间通常被映射到堆和栈之间。

        多个进程通过共享内存通信,所传输的数据通过各个进程的虚拟内存被直接反映到同一块物理内存中,这就避免了在不同进程和系统内核之间来回复制数据的开销。因此基于共享内存的进程间通信,是速度最快的进程间通信方式。

        共享内存本身缺乏足够的同步机制,这就需要程序员编写额外的代码来实现。

2.1  shmget()  创建/获取

        #include <sys/shm.h>

        int  shmget( key_t key,  size_t size,  int shmflg);

                功能:创建新获取已有的共享内存

                key:键

                size:字节数,自动按页取整

                shmflg:创建标志,可取以下值

                                0                                            获取,不存在即失败

                                IPC_CREAT                          创建,不存在即创建,已存在即获取

                                IPC_CREAT | IPC_EXCL      排他,不存在即创建,已存在即失败

                                0664                                      通过位或来组合共享内存权限

                返回值:成功返回共享内存的ID(即IPC对象的内部名称,失败返回-1  

2.2  shmat()  加载

        #include <sys/shm.h>

        void* shmat (int shmid,  void const* shmaddr,  int shmflg);

                功能:加载共享内存,将物理内存中的共享区域映射到进程用户空间的虚拟内存中。

                           同时将系统内核中共享内存对象的加载技

                shmid:共享内存的id

                shmaddr:映射到共享内存的虚拟内存起始地址,取NULL,由系统自动选择

                shmflg:加载标志,可取以下值

                                0                                以读写方式使用共享

                                SHM_RDONLY         以只读方式使用共享内存

                返回值:成功返回共享内存的起始地址,失败返回-1 

2.3  shmdt()  卸载

        #include <sys/shm.h>

        int shmdt ( void const* shmaddr );

                功能:卸载共享内存

                shmaddr:共享内存的起始地址

                返回值:成0-1 

        sgndt()负责从调用进程的虚拟内存中结束shmaddr所指向的映射区到共享内存的映射,同时将系统内核中共享内存的加载计数减1。

2.4  shmctl()  销毁

        #include <sys/shm.h>

        int shmctl ( int shmid,  IPC_RMID,  NULL);

                功能:(标记)销毁共享内存

                shmid:共享内存对象ID

                返回值:成0-1 

        销毁共享内存,并非真的销毁,而是做一个销毁标记,禁止任何进程对该共享内存形成新的加载,但已有的加载依然保留。只有当使用者们纷纷卸载,直至其加载计数降为0时,共享内存才会真的被销毁。

        共享内存一旦被创建,需要执行代码或执行命令销毁。如程序执行过程中Ctrl + C中断,使用ipcs命令可查共享内存仍在,需ipcs -m id号 销毁。

//rshm.c  读取共享内存
#include<stdio.h>
#include<unistd.h>
#include<sys/shm.h>int main(void){//合成键printf("%d进程:合成键\n",getpid());key_t key = ftok(".",123); //key,键,IPC对象--共享内存的外部名称if(key == -1){perror("ftok");return -1;}//获取共享内存printf("%d进程:获取共享内存\n",getpid());getchar();int shmid = shmget(key,0,0); //shmid,共享内存ID,IPC对象--共享内存的内部名称if(shmid == -1){perror("shmget");return -1;}//加载共享内存printf("%d进程:加载共享内存\n",getpid());getchar();char* shmaddr = shmat(shmid,NULL,0); //共享内存用来存串,就char*if(shmaddr == (void*)-1){perror("shmat");return -1;}//读取共享内存printf("%s\n",shmaddr);//卸载共享内存printf("%d进程:卸载共享内存\n",getpid());getchar();if(shmdt(shmaddr) == -1){perror("shmdt");return -1;}printf("%d进程:大功告成\n",getpid());return 0;
}//搭配wshm.c执行,再开第三个窗口,ipcs命令
//wshm.c  创建、写入、销毁共享内存
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/shm.h>// shmget shmat shmdt shmctlint main(void){//合成键printf("%d进程:合成键\n",getpid());key_t key = ftok(".",123);if(key == -1){perror("ftok");return -1;}//创建共享内存printf("%d进程:创建共享内存\n",getpid());int shmid = shmget(key,4096,IPC_CREAT | IPC_EXCL | 0664);if(shmid == -1){perror("shmget");return -1;}//加载共享内存printf("%d进程:加载共享内存\n",getpid());getchar();char* shmaddr = shmat(shmid,NULL,0);if(shmaddr == (void*)-1){perror("shmat");return -1;}//写入数据 strcpy sprintf memcpygetchar();strcpy(shmaddr,"hello world!");//卸载共享内存printf("%d进程:卸载共享内存\n",getpid());getchar();if(shmdt(shmaddr) == -1){perror("shmdt");return -1;}//销毁共享内存printf("%d进程:销毁共享内存\n",getpid());getchar();if(shmctl(shmid,IPC_RMID,NULL) == -1){perror("shmctl");return -1;}printf("%d进程:大功告成\n",getpid());return 0;
}//搭配rshm.c 执行,再开第三个窗口,ipcs命令

3  消息队列  推荐!

        消息队列是一个有系统内核负责存储和管理,并通过消息队列标识符引用的消息链表队列。链式结构,先进先出(可按消息类型提取)。

        

        

        可以通过msgget()函数创建一个新的或获取一个已有的消息队列。

        可以通过msgsnd()向消息队列的尾端追加消息,所追加的消息除了包含消息数据以外,还包含消息类型和数据长度(以字节为单位)。

        可以通过msgrcv()从消息队列中提取消息,但不一定非按先进先出的顺序提取,也可以按消息的类型提取。

        相较于其他几种IPC机制,消息队列具有明显的优势

        1)流量控制。如果系统资源(内存)短缺或者接受消息的进程来不及处理更多的消息,则发送消息的进程会在系统内核的控制下进入睡眠状态,待条件满足后再被内核唤醒,继续之前的发送过程。

        2)面向记录。每个消息都是完整的信息单元,发送端是一个消息一个消息地发,接收端也是一个消息一个消息地收。而不像管道那样手法两端所面对的都是字节流,彼此之间没有结构上的一致性。

        3)类型过滤。先进先出是队列的固有特征,但消息队列还支持按类型提取消息的做法,这就比严格先进先出的管道具有更大的灵活性。

        4)天然同步。消息队列本身就具备同步机制,空队列不可读,满队列不可写,不发则不收,无需像共享内存那样编写额外的同步代码。

        系统限制(与时俱进):

        1)可发送的消息字节数上限:8192

        2)单条队列消息总字节数上限:16384(16K)

        3)全系统总消息队列数上限:16

        4)全系统消息总字节数上限:262144(256K == 16×16K)

3.1  msgget()  创建/获取

        #include <sys/msg.h>

        int msgget ( key_t key,  int msgflg);

                功能:创建新的获取已有的消息队列

                key:键

                msgflg:创建标志,可取以下值

                                0                                            获取,不存在即失败

                                IPC_CREAT                          创建,不存在即创建,已存在即获取

                                IPC_CREAT | IPC_EXCL      排他,不存在即创建,已存在即失败

                                0664                                      通过位或来组合读写权限

                返回值:成功返回消息队列的ID,失败返回-1 

3.2  msgsnd()  发送

        #include <sys/msg.h>

        int msgsnd ( int msgid,  void const* msgp,  size_t msgsz,  int msgflg );

                功能:发送消息

                msgid:消息队列的ID

                mdgp:指向一个包含long消息类型和消息数据的内存块。是结构体的地址:

                             内存块的前4/8字节必须是大于0的整数,代表消息类型,其后紧跟消息数据

                msgsz:期望发送消息数据(不含消息类型)的字节数

                msgflg:发送标志,一般取0即可

                返回值:成0-1 

        注意,msgsnd()的msgp参数所指向的内存块中包含消息类型,其值必须大于0,但该函数的msgsz参数所表示的期望发送字节数中却不包含消息类型long所占的4个字节(64位系统是8)消息:

        

        消息队列缺省为阻塞模式,如果调用msgsnd()发送消息时,超过了系统内核有关消息的上限,该函数会阻塞,直到系统内核允许加入新消息为止。比如有消息因被接收而离开消息队列。

        如果msgflg参数中包含IPC_NOWAIT,则msgsnd()在系统内核中的消息已达上限的情况下不会阻塞,而是返回-1,同时置errno为EAGAIN。

3.3  msgrcv()  接收

        #include <sys/msg.h>

        int msgrcv ( int msgid,  void* msgp,  size_t msgsz,  long msgtye, int msgflg );

                功能:接收消息

                msgid:消息队列的ID

                msgp:指向一块包含消息类型(long)和消息数据(char[]或其他)的内存(结构体

                msgsz:期望接收消息数据(不含消息类型)的字节数,一般取  消息数据的容器量

                msgflg:接收标志,一般取0即可

                msgtyp:消息类型,可取以下值:

                                0  提取消息队列的第一条信息

                              >0  若msgflg参数不包含MSG_EXCEPT位,则提取消息队列中第一条类型

                                    为msgtyp的消息;

                                    若msgflg参数包含MSG_EXCEPT位,则提取消息队列中第一条类型

                                    不为msgtyp的消息。

                             <0  提取消息队列中类型小于等于msgtype绝对值的消息,

                                   类型越小的消息越被优先录取

                返回值:成功返回实际接收到的消息数据字节数,失败返回-1 

        注意,msgrcv()的msgp参数所指向的内存块中包含的消息类型,其值由该函数输出,但该函数的msgsz参数所表示的期望接收字节数以及该函数所返回的实际接收字节数中都不包含消息类型所占的4个字节。

        

        若存在于msgtyp参数匹配的消息,但其数据长度大于msgsz参数,且msgflg参数包含MSG_NOERROR位,则只截取该消息数据的前msgsz字节返回,剩余部分直接丢弃;但如果msgflg参数不包含MSG_NOERROR位,则不处理该消息,直接返回-1,并置errno为E2BIG。

        若消息队列中有可接收消息,则msgrcv()会将消息移除消息队列,并立即返回所接收到的消息数据的字节数,表示接收成功,否则此函数会阻塞,直到消息队列中有可接收消息位置。若msgflg参数中包含IPC_NOWAIT位,则msgrcv()在消息队列中没有可接收消息的情况下不会阻塞,而是返回-1,同时置errno为ENOMSG。

3.1  msgctl()  销毁

        #include <sys/msg.h>

        int msgctl ( int msgid,  IPC_RMID,  NULL );

                功能:销毁消息队列

                msgid:消息队列的ID

                返回值:成0-1 

//rmsg.c  读取消息队列
#include<stdio.h>
#include<unistd.h>
#include<sys/msg.h>
#include<errno.h>
int main(void){//合成键printf("%d进程:合成键\n",getpid());key_t key = ftok(".",123);if(key == -1){perror("ftok");return -1;}//获取消息队列printf("%d进程:获取消息队列\n",getpid());int msgid = msgget(key,0);if(msgid == -1){perror("msgget");return -1;}//接受数据printf("%d进程:接受数据\n",getpid());for(;;){//接受数据struct{long type;//消息类型char data[64];//消息内容}buf = {};if(msgrcv(msgid,&buf,sizeof(buf.data)-1,1234,0) == -1){if(errno == EIDRM){printf("%d进程:消息队列别销毁\n",getpid());break;}else{perror("msgrcv");return -1;}}//显示消息printf("%ld.%s",buf.type,buf.data);}return 0;
} //结合wmsg.c ,同时开第3个终端ipcs命令
//wmsg.c  写入消息队列
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/msg.h>int main(void){//合成键printf("%d进程:合成键\n",getpid());key_t key = ftok(".",123);if(key == -1){perror("ftok");return -1;}//创建消息队列printf("%d进程:创建消息队列\n",getpid());int msgid = msgget(key,IPC_CREAT | IPC_EXCL | 0664);if(msgid == -1){perror("msgget");return -1;}//发消息printf("%d进程:发送数据\n",getpid());for(;;){//通过键盘获取要发送的数据struct{                     //结构体名可以不写(区分结构体和结构体变量)long type;//消息类型char data[64];//消息内容}buf = {1234,""}; //buf是结构体变!量!名   有typedef是结构体别名!fgets(buf.data,sizeof(buf.data),stdin); //是stdin,则敲个回车,就是1条消息的结束。// ! 退出if(strcmp(buf.data,"!\n") == 0){break;}//发送数据if(msgsnd(msgid,&buf,strlen(buf.data),0) == -1){perror("msgsnd");return -1;}}//销毁消息队列printf("%d进程:销毁消息队列\n",getpid());if(msgctl(msgid,IPC_RMID,NULL) == -1){perror("msgctl");return -1;}return 0;
}//开个终端ipcs命令,执行本代码,输几个消息,看ipcs,再执行rmsg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/256033.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

dockerfile简单实践部署(jenkins,wordpress)

实现部署jenkins的流程 配置java环境&#xff0c;导入jenkins包&#xff0c;运行命令 java -jar jenkins包&#xff0c;这里为了减少进入jenkins的web端安装插件&#xff0c;将插件提前部署到容器内。 制作dockerfile 创建镜像所在的文件夹和Dockerfile文件 mkdir /test cd …

【项目日记(一)】高并发内存池项目介绍

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:项目日记-高并发内存池⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习C   &#x1f51d;&#x1f51d; 项目日记 1. 前言2. 什么是高并发内存池…

Textual Inversion

参考博客1:https://www.bilibili.com/read/cv25430752/

移动云荣获OpenInfra社区“算力基础设施技术突破奖”

近日&#xff0c;一年一度的 OpenInfra Days China在北京召开&#xff0c;大会汇聚了全球各大云厂商的技术专家&#xff0c;共同分享全球前沿基础设施技术的展望和实践经验。在本次大会上&#xff0c;移动云荣获OpenInfra社区颁发的“算力基础设施技术突破奖”&#xff0c;表明…

基于ssm实验室课程管理系统源码和论文

idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 环境&#xff1a; jdk8 tomcat8.5 摘 要 随着科学实验规模的不断扩大&#xff0c;实验室课程数量的急剧增加&#xff0c;有关实验室课程的各种信息量也在不断成倍增长。面对庞大的信息量&#xff0c;就需要有…

ChatGPT 应用开发(一)ChatGPT OpenAI API 免代理调用方式(通过 Cloudflare 的 AI Gateway)

前言 开发 ChatGPT 应用&#xff0c;我觉得最前置的点就是能使用 ChatGPT API 接口。首先我自己要能成功访问&#xff0c;这没问题&#xff0c;会魔法就可以本地调用。 那用户如何调用到我的应用 API 呢&#xff0c;我的理解是通过用户能访问到的中转服务器向 OpenAI 发起访问…

基于SSM实现的公文管理系统

一、技术架构 前端&#xff1a;jsp | jquery | bootstrap 后端&#xff1a;spring | springmvc | mybatis 环境&#xff1a;jdk1.8 | mysql | maven 二、代码及数据库 三、功能介绍 01. 登录页 02. 首页 03. 系统管理-角色管理 04. 系统管理-功能管理 05. 系统管理-用…

猫咪瘦弱的原因是什么?适合给消瘦猫咪长肉吃的猫罐头分享

很多小猫咪吃得很多&#xff0c;但是还是很瘦&#xff0c;这让很多猫主人感到困惑&#xff0c;猫咪瘦弱的原因是什么呢&#xff1f;铲屎那么多年&#xff0c;还是有点子养猫知识在身上的。那么&#xff0c;小猫咪瘦弱的原因是什么呢&#xff1f;让我们看看是不是这些原因导致的…

详细解读电力DLT698.45-2017通信规约--正向有功总电能

建立连接请看这篇&#xff1a;详细解读DLT698.45-2017通信规约--预连接响应http://mp.weixin.qq.com/s?__bizMzA3NjAwMjQzMQ&mid2652026396&idx1&sna0a17f005d23136c922a7c381ddb7e75&chksm8481f30cb3f67a1a94e66db77e61fe73c22b1904fcdbb0144108e132b265e7b4…

黑苹果之显卡篇

一、什么是显卡 显卡GPU&#xff08;Video card、Display card、Graphics card、Video adapter&#xff09;是个人计算机基础的组成部分之一&#xff0c;将计算机系统需要的显示信息进行转换驱动显示器&#xff0c;并向显示器提供逐行或隔行扫描信号&#xff0c;控制显示器的正…

springboot 在自定义注解中注入bean,解决注入bean为null的问题

问题&#xff1a; 在我们开发过程中总会遇到比如在某些场合中需要使用service或者mapper等读取数据库&#xff0c;或者某些自动注入bean失效的情况 解决方法&#xff1a; 1.在构造方法中通过工具类获取需要的bean 工具类代码&#xff1a; import org.springframework.beans…

华为OD机试 - 攀登者1(Java JS Python C)

题目描述 攀登者喜欢寻找各种地图,并且尝试攀登到最高的山峰。 地图表示为一维数组,数组的索引代表水平位置,数组的元素代表相对海拔高度。其中数组元素0代表地面。 例如:[0,1,2,4,3,1,0,0,1,2,3,1,2,1,0],代表如下图所示的地图,地图中有两个山脉位置分别为 1,2,3,4,5…