【IPC通信--共享内存】

进程间通信目的

数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

通信背景

1.由于进程是具有独立性的,进程想交互数据,成本会非常高。但是有些情况下需要多进程处理一件事情。
2.进程独立并不是彻底独立,有时候我们需要双方能够进行一定程度的信息交互。

我们要学的进程间通信,不是告诉我们如何通信,是他们两个如何先看到同一份资源。(文件,内存块…等方式)

共享内存实现进程间通信的原理

共享内存实际是操作系统在实际物理内存中开辟的一段内存。

共享内存实现进程间通信,是操作系统在实际物理内存开辟一块空间,一个进程在自己的页表中,将该空间和进程地址空间上的共享区的一块地址空间形成映射关系。另外一进程在页表上,将同一块物理空间和该进程地址空间上的共享区的一块地址空间形成映射关系。 ​ 当一个进程往该空间写入内容时,另外一进程访问该空间,会得到写入的值,即实现了进程间的通信。

要实现进程间通信需要两个进程看到同一块空间,系统开辟的共享内存就是两个进程看到的同一资源。

注意:共享内存实现进程间通信是进程间通信最快的。

如何使⽤

要使⽤⼀块共享内存,进程必须先分配它。其他需要访问这个共享内存块的每⼀个进程都必须将这个共享绑定(attach)到⾃⼰的地址空间中(系统维护⼀个对该内存的引⽤计数器,通过ipcs -s 命令可查看有⼏个进程在使⽤该共享内存块)。当通信完毕后,所有进程从共享内存块脱离,由⼀个进程释放该共享内存块。要注意的是,所有⽤户申请的共享内存块最终⼤⼩都必须是向上取整为系统页⾯⼤⼩的整数倍。在Linux系统中,内存页⾯⼤⼩默认是4KB。

注意:当⼀个进程创建⼀块共享内存后,该进程在主动去释放该共享内存之前,被kill掉时,只会使该进程脱离(detach)该共享内存块,⽽不会释放该共享内存块,这时候可以使⽤命名ipcrm -m 去释放该资源。

相关函数

1、shmget
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

函数说明:得到⼀个共享内存标识符或创建⼀个共享内存对象并返回共享内存标识符。
参数:
        key: ftok函数返回的I PC键值
        size: ⼤于0的整数,新建的共享内存⼤⼩,以字节为单位,获取已存在的共享内存块标识符时,该参数为0,
        shmflg: IPC_CREAT||IPC_EXCL 执⾏成功,保证返回⼀个新的共享内存标识符,附加参数指定IPC对象存储权限,如|0666
返回值:成功返回共享内存的标识符,出错返回-1,并设置error错误位。

key:共享内存的唯一值,这个参数需要由用户提供。
共享内存要被管理 -> struct shmid_ds -> struct ipc_perm -> key(shmget)(共享内存唯一值)

1. 为什么key值需要由用户提供?
进程间通信的前提是,先让不同的进程,看到同一份资源。如果由操作系统提供,创建共享内存的进程可以知道key值,但是使用共享内存的进程无法获取。所以key值必须由用户获取,然后在使用时标定key值,则能让使用共享内存的进程获取到。

共享内存,在内核中,让不同的进程看到同一份共享内存,做法是:让他们拥有同一个key即可。

匿名管道 --> 约定好使用同一个文件
共享内存 --> 约定好使用同一个唯一key

2.为什么key值要有唯一性?
操作系统中可能有很多个共享内存在被使用,所以我们就需要用一个唯一值来标识每一个共享内存。

3. 那么如何保证key值唯一性呢?
生成唯一key值函数:ftok函数。

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

将文件路径和一个项目标识符,转化为唯一key值。

返回值:一个整数,创建成功,返回一个合法的共享内存标识符。失败,返回 -1。


2、shmat
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void shmaddr, int shmflg);

函数说明:连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调⽤进程的地址空间
参数:
        shmid: 共享内存标识符
        shmaddr: 指定共享内存出现在进程内存地址的什么位置,通常指定为NULL,让内核⾃⼰选择⼀个合适的地址位置
        shmflg: SHM_RDONLY 为只读模式,其他参数为读写模式
返回值:成功返回附加好的共享内存地址,出错返回-1,并设置error错误位

3、shmdt
#include <sys/types.h>
#include <sys/shm.h>
void *shmdt(const void* shmaddr);

函数说明:与shmat函数相反,是⽤来断开与共享内存附加点的地址,禁⽌本进程访问此⽚共享内存,需要注意的是,该函数并不删除
所指定的共享内存区,⽽是将之前⽤shmat函数连接好的共享内存区脱离⽬前的进程
参数:shmddr 连接共享内存的起始地址
返回值:成功返回0,出错返回-1,并设置error。

4、shmctl
#include <sys/types.h>
#Include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds* buf);

函数说明:控制共享内存块
参数:
        shmid:共享内存标识符
        cmd:
                IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构赋值到buf所指向的buf中
                IPC_SET:改变共享内存的状态,把buf所指向的shmid_ds结构中的uid、gid、mode赋值到共享内存的shmid_ds结构内
                IPC_RMID:删除这块共享内存
        buf:共享内存管理结构体
返回值:成功返回0,出错返回-1,并设置error错误位。

代码演⽰

父子进程通信范例

shm.c源代码如下:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <error.h>
#define SIZE 1024
int main()
{
    int shmid ;
    char *shmaddr ;
    struct shmid_ds buf ;
    int flag = 0 ;
    int pid ;
    shmid = shmget(IPC_PRIVATE, SIZE, IPC_CREAT|0600 ) ;
    if ( shmid < 0 )
    {
            perror("get shm  ipc_id error") ;
            return -1 ;
    }
    pid = fork() ;
    if ( pid == 0 )
    {
        shmaddr = (char *)shmat( shmid, NULL, 0 ) ;
        if ( (int)shmaddr == -1 )
        {
            perror("shmat addr error") ;
            return -1 ;
        }
        strcpy( shmaddr, "Hi, I am child process!\n") ;
        shmdt( shmaddr ) ;
        return  0;
    } else if ( pid > 0) {
        sleep(3 ) ;
        flag = shmctl( shmid, IPC_STAT, &buf) ;
        if ( flag == -1 )
        {
            perror("shmctl shm error") ;
            return -1 ;
        }
        printf("shm_segsz =%d bytes\n", buf.shm_segsz ) ;
        printf("parent pid=%d, shm_cpid = %d \n", getpid(), buf.shm_cpid ) ;
        printf("chlid pid=%d, shm_lpid = %d \n",pid , buf.shm_lpid ) ;
        shmaddr = (char *) shmat(shmid, NULL, 0 ) ;
        if ( (int)shmaddr == -1 )
        {
            perror("shmat addr error") ;
            return -1 ;
        }
        printf("%s", shmaddr) ;
        shmdt( shmaddr ) ;
        shmctl(shmid, IPC_RMID, NULL) ;
    }else{
        perror("fork error") ;
        shmctl(shmid, IPC_RMID, NULL) ;
    }
    return 0 ;
}

编译 gcc shm.c –o shm。
执行 ./shm,执行结果如下:
shm_segsz =1024 bytes
shm_cpid = 9503
shm_lpid = 9504
Hi, I am child process!


⾸先,先来讲⼀下fork之后,发⽣了什么事情。
由fork创建的新进程被称为⼦进程(child process)。该函数被调⽤⼀次,但返回两次。两次返回的区别是⼦进程的返回值是0,⽽⽗进程的返回值则是新进程(⼦进程)的进程 id。将⼦进程id返回给⽗进程的理由是:因为⼀个进程的⼦进程可以多于⼀个,没有⼀个函数使⼀个进程可以获得其所有⼦进程的进程id。对⼦进程来说,之所以fork返回0给它,是因为它随时可以调⽤getpid()来获取⾃⼰的pid;也可以调⽤getppid()来获取⽗进程的id。(进程id 0总是由交换进程使⽤,所以⼀个⼦进程的进程id不可能为0 )。
fork之后,操作系统会复制⼀个与⽗进程完全相同的⼦进程,虽说是⽗⼦关系,但是在操作系统看来,他们更像兄弟关系,这2个进程共享代码空间,但是数据空间是互相独⽴的,⼦进程数据空间中的内容是⽗进程的完整拷贝,指令指针也完全相同,⼦进程拥有⽗进程当前运⾏到的位置(两进程的程序计数器pc值相同,也就是说,⼦进程是从fork返回处开始执⾏的),但有⼀点不同,如果fork成功,⼦进程中fork的返回值是0,⽗进程中fork的返回值是⼦进程的进程号,如果fork不成功,⽗进程会返回错误。
可以这样想象,2个进程⼀直同时运⾏,⽽且步调⼀致,在fork之后,他们分别作不同的⼯作,也就是分岔了。这也是fork为什么叫fork的原因,⾄于哪⼀个最先运⾏,可能与操作系统(调度算法)有关,⽽且这个问题在实际应⽤中并不重要,如果需要⽗⼦进程协同,可以通过原语的办法解决。

多进程读写范例

多进程读写即一个进程写共享内存,一个或多个进程读共享内存。下面的例子实现的是一个进程写共享内存,一个进程读共享内存。
(1)下面程序实现了创建共享内存,并写入消息。
shmwrite.c源代码如下:

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
typedef struct{
    char name[8];
    int age;
} people;
int main(int argc, char** argv)
{
    int shm_id,i;
    key_t key;
    char temp[8];
    people *p_map;
    char pathname[30] ;
    strcpy(pathname,"/tmp") ;
    key = ftok(pathname,0x03);
    if(key==-1)
    {
        perror("ftok error");
        return -1;
    }
    printf("key=%d\n",key) ;
    shm_id=shmget(key,4096,IPC_CREAT|IPC_EXCL|0600); 
    if(shm_id==-1)
    {
        perror("shmget error");
        return -1;
    }
    printf("shm_id=%d\n", shm_id) ;
    p_map=(people*)shmat(shm_id,NULL,0);
    memset(temp, 0x00, sizeof(temp)) ;
    strcpy(temp,"test") ;
    temp[4]='0';
    for(i = 0;i<3;i++)
    {
        temp[4]+=1;
        strncpy((p_map+i)->name,temp,5);
        (p_map+i)->age=0+i;
    }
    shmdt(p_map) ;
    return 0 ;
}

(2)下面程序实现从共享内存读消息。
shmread.c源代码如下:

#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct{
    char name[8];
    int age;
} people;
int main(int argc, char** argv)
{
    int shm_id,i;
    key_t key;
    people *p_map;
    char pathname[30] ;
    strcpy(pathname,"/tmp") ;
    key = ftok(pathname,0x03);
    if(key == -1)
    {
        perror("ftok error");
        return -1;
    }
    printf("key=%d\n", key) ;
    shm_id = shmget(key,0, 0);   
    if(shm_id == -1)
    {
        perror("shmget error");
        return -1;
    }
    printf("shm_id=%d\n", shm_id) ;
    p_map = (people*)shmat(shm_id,NULL,0);
    for(i = 0;i<3;i++)
    {
        printf( "name:%s\n",(*(p_map+i)).name );
        printf( "age %d\n",(*(p_map+i)).age );
    }
    if(shmdt(p_map) == -1)
    {
        perror("detach error");
        return -1;
    }
    return 0 ;
}

(3)编译与执行
①编译gcc shmwrite.c -o  shmwrite。
②执行./shmwrite,执行结果如下:
key=50453281
shm_id=688137
③编译gcc shmread.c -o shmread。
④执行./shmread,执行结果如下:
key=50453281
shm_id=688137
name:test1
age 0
name:test2
age 1
name:test3
age 2
⑤再执行./shmwrite,执行结果如下:
key=50453281
shmget error: File exists
⑥使用ipcrm -m 688137删除此共享内存。
 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux操作系统——文件详解

1.文件理解预备知识 首先&#xff0c;当我们在磁盘创建一个空文件时&#xff0c;这个文件会不会占据磁盘空间呢&#xff1f; 答案是当然会占据磁盘空间了&#xff0c;因为文件是空的&#xff0c;仅仅指的是它的内容是空的&#xff0c;但是该文件要有对应的文件名&#xff0c;…

【数据库和表的管理】

数据库和表的管理 一、实验目的 了解MySQL数据库的逻辑结构和物理结构的特点。学会使用SQL语句创建、选择、删除数据库。学会使用SQL语句创建、修改、删除表。学会使用SQL语句对表进行插入、修改和删除数据操作。了解MySQL的常用数据类型。 二、实验内容SQL语句创建、选择、删…

玩转硬件之MP3的破解

MP3播放器是一种能播放音乐文件的播放器&#xff0c;主要由存储器&#xff08;存储卡&#xff09;、显示器&#xff08;LCD显示屏&#xff09;、中央处理器MCU&#xff08;微控制器&#xff09;或解码DSP&#xff08;数字信号处理器&#xff09; 等组成。 其中微控制器是播放器…

k8s存储卷之动态

动态pv需要两个组件 1、卷插件&#xff0c;k8s本身支持的动态pv创建不包含NFS&#xff0c;需要声明和安装一个外部插件 Provisioner 存储分配器&#xff0c;动态创建pv&#xff0c;然后根据pvc的请求自动绑定和使用 2、StorageClass&#xff0c;用来定义pv的属性&#xff0c…

金蝶云星空单据转换插件-选单

文章目录 金蝶云星空单据转换插件-选单 金蝶云星空单据转换插件-选单 选单使用标识报错 应该使用实体属性

LeetCode 每日一题 Day 44 || 哑节点去重

82. 删除排序链表中的重复元素 II 给定一个已排序的链表的头 head &#xff0c; 删除原始链表中所有重复数字的节点&#xff0c;只留下不同的数字 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,3,4,4,5] 输出&#xff1a;[1,2,5] 示例 2&#x…

内存泄漏问题

内存泄漏是一种常见的问题&#xff0c;它可能导致系统内存不断增加&#xff0c;最终耗尽可用内存。解决内存泄漏问题通常需要进行调试和分析。下面是一些可能有助于解决内存泄漏问题的步骤&#xff1a; 1. 监控内存使用情况&#xff1a; a. 使用 malloc 记录日志&#xff1a;…

MyBatisPlus学习笔记二

接上&#xff1a;MyBatisPlus学习笔记一&#xff1a; MyBatisPlus学习笔记一-CSDN博客 1、条件构造器 MyBatisPlus支持各种复杂的where条件&#xff0c;可以满足日常开发的所有需求。 1.1、集成体系 1.2、实例 查询 lambda查询 更新 1.3、总结 2、自定义sql 我们可以利用MyB…

Rust-NLL(Non-Lexical-Lifetime)

Rust防范“内存不安全”代码的原则极其清晰明了。 如果你对同一块内存存在多个引用&#xff0c;就不要试图对这块内存做修改&#xff1b;如果你需要对一块内存做修改&#xff0c;就不要同时保留多个引用。 只要保证了这个原则&#xff0c;我们就可以保证内存安全。 它在实践…

入门Docker1: 容器技术的基础

目录 服务器选型 虚拟机 基于主机(物理机或虚机)的多服务实例 基于容器的服务实例 Docker Docker三要素 Docker安装 Docker基本使用 基本操作 仓库镜像 容器 服务器选型 在选择服务器操作系统时&#xff0c; Windows 附带了许多您需要付费的功能。 Linux 是开放源代…

光K8S的目录结构就够你学一天!

Kubernetes Project Layout设计 Kubernetes项目由Go语言编写。Go语言官方对项目的结构设计没有强制要求&#xff0c;早期的Go语言开发者都喜欢将包文件代码放置在项目的src/目录下&#xff0c;如nsqio开源项目&#xff0c;开发者喜欢将入口文件放入apps/目录。不同开发者的喜好…

使用WAF防御网络上的隐蔽威胁之CSRF攻击

在网络安全领域&#xff0c;除了常见的XSS&#xff08;跨站脚本&#xff09;攻击外&#xff0c;CSRF&#xff08;跨站请求伪造&#xff09;攻击也是一种常见且危险的威胁。这种攻击利用用户已经验证的身份在没有用户知情的情况下&#xff0c;执行非授权的操作。了解CSRF攻击的机…

运筹说 第45期丨多目标规划发展及其提出者—— Abraham Charnes和William W. Cooper

经过前面的学习&#xff0c;相信大家已经对运筹学的运输问题有了更加全面的了解&#xff0c;接下来小编将带你学习新一章的内容&#xff0c; 先来看看多目标规划的发展简史&#xff0c;然后再带你领略该理论两位提出者的传奇一生&#xff01; 01目标规划发展简史 Vilfredo Pa…

Vue2.组件通信

样式冲突 写在组件中的样式默认会全局生效。容易造成多个组件之间的样式冲突问题。 可以给组件加上scoped属性&#xff0c;让样式只作用于当前组件。 原理&#xff1a; 给当前组件模板的所有元素&#xff0c;加上一个自定义属性data-v-hash值&#xff0c;用以区分不同的组件。…

【Axure高保真原型】移入放大对应区域的饼图

今天和大家分享移入放大对应扇形区域的饼图的原型模板&#xff0c;鼠标移入时&#xff0c;对应扇形区域的会放大&#xff0c;并且的项目和数据弹窗&#xff0c;弹窗可以跟随鼠标移动。这个原型是用Axure原生元件制作的&#xff0c;所以不需要联网或者调用外部图表……具体效果可…

一篇教你生成密钥给自己打的exe添加密钥

一篇教你生成密钥给自己打的exe添加密钥 我这里是自己写了一个python 打包exe,说总是给我报毒什么的 文章目录 一篇教你生成密钥给自己打的exe添加密钥前言一、使用java jdk 自带的keytool&#xff1f;二、进行转换2.把证书密钥写入到你的exe 总结 前言 生成密钥并为自定义 .…

上海市税务局:买卖虚拟货币需缴税!中国仍未有放松加密政策的迹象?

自2021年央行等十部委下发禁止虚拟货币交易的通知以来&#xff0c;国内虚拟货币交易平台几乎销声匿迹。然而&#xff0c;最近一则关于个人所得税的释义再次引起了人们的关注。 1月5日&#xff0c;国家税务总局上海市税务局在官方公众号发布《个人所得税经营所得和分类所得常见误…

Win10专业版系统搭建DNS解析服务

Win10专业版 纯新手&#xff0c;也没弄过Linux的。不喜勿喷&#xff0c;有问题请指出 第一天一头雾水整了几个小时没结果&#xff0c;第二天豁然开朗&#xff0c;10分钟明白了第一天的问题所在。 Win10 安卓&#xff1a; iOS&#xff1a; 搭建DNS服务器的意义&#xff1a; 屏蔽…

关于Python —— Python教程

开始 Python 是一个易于学习、使用和高效阅读的编程语言。它具有简洁的英文语法&#xff0c;编写更少的代码&#xff0c;让程序员专注于业务逻辑而不是语言本身。 本教程将从深度、专注细节上去理解 Python 这门语言。初学者可以参考此教程理解相应的内容&#xff0c;本教程将…