【IPC通信--共享内存】

news2024/12/26 13:43:51

进程间通信目的

数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如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.coloradmin.cn/o/1386912.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

xlua源码分析(五) struct类型优化

xlua源码分析&#xff08;五&#xff09; struct类型优化 上一节我们分析了xlua是如何实现lua层访问C#值类型的&#xff0c;其中我们重点提到了xlua默认实现方式下&#xff0c;struct访问的效率问题。实际上&#xff0c;xlua还提供了两种优化的方式&#xff0c;可以大大提高str…

软件测试|如何使用Selenium处理隐藏元素

简介 我们在使用selenium进行web自动化测试时&#xff0c;有时候会遇到元素被隐藏&#xff0c;从而无法对元素进行操作&#xff0c;导致我们的用例报错的情况。当我们遇到元素被隐藏的情况时&#xff0c;需要先对隐藏的元素进行处理&#xff0c;才能继续进行我们的操作&#x…

一篇文章搞懂Jenkins持续集成解决的是什么问题

01 持续集成的定义 大师 Martin Fowler 是这样定义持续集成的: 持续集成是一种软件开发实战, 即团队开发成员经常集成他们的工作. 通常, 每个成员每天至少集成一次, 也就意味着每天可能发生多次集成. 持续集成并不能消除Bug, 而是让它们非常容易发现和改正. 根据对项目实战的…

第08章_面向对象编程(高级)拓展练习(关键字:static,代码块,关键字:final,抽象类和抽象方法,接口,内部类,枚举类,注解,包装类)

文章目录 第08章_面向对象编程&#xff08;高级&#xff09;拓展练习01-关键字&#xff1a;static1、银行账户类2、图形类3、数组工具类4、二分查找5、二分查找6、素数7、阅读代码&#xff0c;分析运行结果8、阅读代码&#xff0c;分析运行结果 02-代码块9、阅读代码&#xff0…

软件测试|如何使用selenium处理下拉框?

简介 下拉框是网页表单中常见的元素之一&#xff0c;通常用于选择不同的选项。对于我们的自动化测试工作来说&#xff0c;操作下拉框是我们经常需要处理的元素&#xff0c;selenium作为我们最常使用的web自动化测试框架&#xff0c;也是支持我们对下拉框进行操作的。本文我们就…

Github镜像加速器-FastGit

简介 FastGit 是一个对于 GitHub.com 的镜像加速器。使用共享资源为 GitHub 加速。 FastGit中文指南 # 基本使用 关于 FastGit 的使用&#xff0c;本质上与 git 有关。常规的面向 GitHub 的 clone 命令可能如下&#xff1a; git clone https://github.com/author/repo使用 F…

Qt 使用vs2019制作Qt静态库( *.lib )并使用

一 .创建静态库 1.创建Qt Class Library(Qt静态类库)项目 2.设置项目名以及项目路径(注意:不能有中文字符) 点击next 3.选则需要的模式以及Qt 模块 然后点击next,Finish完成创建 4. 然后手动添加Qt Widget Form File (.ui)并对设计ui 5. tpendialog.h #pragma once #includ…

VScode远程连接开发嵌入式开发板

在做嵌入式开发时&#xff0c;很多时候需要远程连接或者远程调试设备&#xff0c;这时可以通过VScode上的插件来很方便的进行远程连接和调试。 ssh远程连接嵌入式开发板&#xff1a; 1、安装vscode ssh远程插件&#xff1a;Remote-SSH。 2、点击""&#xff0c;输入…

排序算法之七:归并排序(非递归)

1.非递归实现思路 我们之前学习了递归实现的归并排序&#xff0c;是分治的思想&#xff0c;即先分解&#xff0c;再归并 这篇文章我们讲一下非递归的实现 非递归实现的思路是模拟递归的过程&#xff0c;在递归过程中&#xff0c;我们找key将数组分成左右数组&#xff0c;然后…

Peter算法小课堂—树上建模

太戈编程1720题 题目描述&#xff1a; 传说有一个大家族里共n名男性成员&#xff0c;编号1到n。其中共有n-1条父子关系。现在他们要挑选若干人组成家族护卫队抵抗外族入侵。i号成员的战斗力为z[i], 大家当然希望挑选最强护卫队。但是为了防止“父子矛盾”的魔咒应验&#xff…

【算法】如何不用中间变量交换两个数据?

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 更多算法分析与设计知识专栏&#xff1a;算法分析&#x1f525; 给大家跳…

Radzen Blazor Studio 脚手架框架解读

背景 组织管理管理准备使用Blazor这个工具实现&#xff0c;因为其有对应的 scaffold 脚手架&#xff0c;先构建数据库&#xff0c;然后通过向导&#xff0c;生成CRUD以及对应的接口&#xff0c;那么有必要看一下&#xff0c;其内部的代码结构是什么样的。 结构 接口层 有两类…

【RTOS】快速体验FreeRTOS所有常用API(2)任务管理

目录 二、任务管理2.1 任务创建&#xff08;三种方式&#xff09;1&#xff09;动态内存分配方式创建任务2&#xff09;静态内存分配方式创建任务3&#xff09;带有任务参数方式创建任务 2.2 任务删除2.3 两种delay 二、任务管理 该部分在上份代码基础上修改得来&#xff0c;代…

t2vec code

文章目录 数据预处理执行过程训练执行过程preprocess.jl 解释h5 文件结构 数据预处理执行过程 (base) zzqserver1:~/project/t2vec/preprocessing$ julia porto2h5.jl Processing 1710660 trips… 100000 200000 300000 400000 500000 600000 700000 800000 900000 1000000 11…

Halcon轮廓的处理

Halcon轮廓的处理 文章目录 Halcon轮廓的处理1. 轮廓分割2. 轮廓的筛选3. 轮廓的连接4. 轮廓的拟合 输出了目标的轮廓后&#xff0c;接下来还需要对轮廓进行处理&#xff0c;这主要基于以下3个原因。 &#xff08;1&#xff09;对于某些测量任务而言&#xff0c;并不需要分析目…

ArkTS中自定义组件

ArkTS中自定义组件 一、组件位置二、Hello.ets自定义组件自定义组件 三、Second.ets父组件 一、组件位置 一个项目下所有的自定义的组件名不可以重复&#xff0c;无论是否在一个ets文件中 二、Hello.ets自定义组件 自定义组件 1&#xff1a;组件必须使用Component装饰 2&#…

最新智能AI系统ChatGPT网站程序源码+详细图文搭建部署教程,Midjourney绘画,GPT语音对话+ChatFile文档对话总结+DALL-E3文生图

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作Ch…

Spring Boot接口请求响应慢,超过10秒以上,如无法优化SQL或代码的情况下,建议写入数据库或缓存中,请求接口时从数据库或缓存中读取返回

举例 Override public Map<String, Object> getCockpitStaffAttendanceTask() {Map<String, Object> map new HashMap<>();int chuqin 0; //出勤int queqin 0; //缺勤int chidao 0; //迟到int zaotui 0; //早退//获取所有设备卡号 并且已经绑定了人Lis…

Redis之bigkey

目录 1、什么是bigkey&#xff1f; 2、bigkey大的小 3、bigkey有哪些危害&#xff1f; 4、bigkey如何产生&#xff1f; 5、bigkey如何发现&#xff1f; 6、bigkey如何删除&#xff1f; 7、BigKey调优&#xff0c;惰性释放lazyfree 8、生产上限制keys * /flushdb/flushal…