Linux —— 进程间通信(3)

简介: Linux —— 进程间通信(3)

5.共享内存的释放

刚刚我们已经创建好了共享内存,当我们的进程运行完毕后,申请的共享内存依旧存在,并没有被操作系统释放。实际上,管道是生命周期是随进程的,而共享内存的生命周期是随内核的,也就是说进程虽然已经退出,但是曾经创建的共享内存不会随着进程的退出而释放。


这说明,如果进程不主动删除创建的共享内存,那么共享内存就会一直存在,直到关机重启(system V IPC都是如此),同时也说明了IPC资源是由内核提供并维护的。


       此时我们若是要将创建的共享内存释放,有两个方法,一就是使用命令释放共享内存,二就是在进程通信完毕后调用释放共享内存的函数进行释放。

1.使用命令释放

[mlg@VM-20-8-centos shared_memory]$ ipcrm -m 5
//指定删除时使用的是共享内存的用户层id,即列表当中的shmid

60a6bcefe26f4b118e50f46e4d0afd1d.png

2.使用函数释放

控制共享内存我们需要用shmctl函数,shmctl函数的函数原型如下:

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

函数说明:完成对共享内存的控制


参数说明:


shmctl函数的参数说明:


shmid:共享内存标识符

cmd:表示具体的控制动作

buf:共享内存管理结构体(参考上文的共享内存的数据结构)

返回值:


shmctl调用成功,返回0

shmctl调用失败,返回-1

其中,第二个参数传入的常用的选项有以下三个:

image.png


image.pngimage.pngimage.pngimage.png

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#define PATH_NAME "./" //路径名
#define PROJ_ID 0x6666 //整数标识符
#define SIZE 4096      //共享内存的大小
int main()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);//获取key值
    if(key < 0){
        perror("ftok");
        return 1;
    }
    int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL);//创建共享内存
    if(shmid < 0){
        perror("shmget");
        return 2;
    }              
    printf("key: %u  shmid: %d\n", key, shmid);
    sleep(10);
    shmctl(shmid, IPC_RMID, NULL);//释放共享内存
    sleep(10);
    printf("key: 0x%x, shmid: %d -> shm delete success\n", key, shmid);
    return 0;
}

通过shell脚本查看共享内存的状态:

while :; do ipcs -m;echo "##############################";sleep 1;done

60a6bcefe26f4b118e50f46e4d0afd1d.png

通过监控脚本可以确定共享内存确实创建并且成功释放了。

上文我们提到ipcs是查看进程间通信设施的信息的,这里的perms是共享内存的权限,此时为0,表示没有任何权限,所以我们在创建共享内存的时候,想要获得权限可以如下操作:

int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666); //创建权限为0666的共享内存

6.共享内存的关联(挂接)

将共享内存连接到进程地址空间需要用shmat函数,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:为只读模式,其他为读写模式

返回值:

  • shmat调用成功,返回共享内存映射到进程地址空间中的起始地址
  • shmat调用失败,返回(void*) -1
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#define PATH_NAME "./" //路径名
#define PROJ_ID 0x6666 //整数标识符
#define SIZE 4096      //共享内存的大小
int main()    
{   
    key_t key = ftok(PATH_NAME, PROJ_ID); //获取key    
    if(key < 0){    
        perror("ftok");    
        return 1;    
    }    
    int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666); //创建共享内存并设置权限   
    if(shmid < 0){    
        perror("shmget");    
        return 2;    
    }    
    printf("key: %u , shmid: %d\n", key, shmid);    
    sleep(10);    
    char* mem = (char*)shmat(shmid, NULL, 0);  //休眠10s后,关联共享内存                                                         
    printf("attaches shm success\n");    
    sleep(5);    
    shmdt(mem);    //5秒后,共享内存去关联
    printf("detaches shm success\n");    
    sleep(5);    
    shmctl(shmid, IPC_RMID, NULL);    //释放共享内存
    printf("key: 0x%x, shmid: %d -> shm delete success\n", key, shmid);    
    sleep(10);    
    return 0;    
}

60a6bcefe26f4b118e50f46e4d0afd1d.png

7.共享内存的去关联

取消共享内存与进程地址空间之间的关联需要用shmdt函数,shmdt函数的函数原型如下:

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

函数说明:


       与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存;(并不是释放共享内存)


参数说明:


       shmaddr:连接的共享内存的起始地址


返回值:


shmdt调用成功,返回0

shmdt调用失败,返回-1

代码同上,运行结果如下:

60a6bcefe26f4b118e50f46e4d0afd1d.png

8.用共享内存实现serve&client通信

刚刚我们是一个进程和共享内存关联的,接下来我们让两个进程通过共享内存进行通信;在线之前我们先测试一下这两个进程能否成功挂接到同一个共享内存上;

comm.h

#pragma once    
#include <stdio.h>    
#include <sys/ipc.h>    
#include <sys/shm.h>    
#include <unistd.h>    
#define PATH_NAME "./"    
#define PROJ_ID 0x6666    
#define SIZE 4097

server.c

#include "comm.h"    
int main()    
{   
    key_t key = ftok(PATH_NAME, PROJ_ID); //获取key    
    if(key < 0){    
        perror("ftok");    
        return 1;    
    }    
    int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666); //创建共享内存并设置权限   
    if(shmid < 0){    
        perror("shmget");    
        return 2;    
    }    
    printf("key: %u , shmid: %d\n", key, shmid);    
    sleep(5);    
    char* mem = (char*)shmat(shmid, NULL, 0);  //休眠10s后,关联共享内存                                                         
    printf("attaches shm success\n");    
    sleep(5);   
    /*
        通信内容(暂时不写):先测试两个进行能不能同时挂接到同一个共享内存上
    */
    shmdt(mem);    //5秒后,共享内存去关联
    printf("detaches shm success\n");    
    sleep(5);    
    shmctl(shmid, IPC_RMID, NULL);    //释放共享内存
    printf("key: 0x%x, shmid: %d -> shm delete success\n", key, shmid);    
    sleep(5);    
    return 0;    
}

client.c

#include "comm.h"    
int main()    
{      
    key_t key = ftok(PATH_NAME, PROJ_ID);    
    if(key < 0){    
        perror("ftok");    
        return 1;    
    }                                                                                                          
    //client只需要获取即可,不需要创建                                                                                        
    int shmid = shmget(key, SIZE, IPC_CREAT);//单独使用IPC_CREAT,共享内存存在就获取,反之创建                                
    if(shmid < 0){                                                                                                            
        perror("shmid");                                                                                                      
        return 1;                                                                                                             
    }                                                                                                                         
    printf("key: %u , shmid: %d\n", key, shmid);
    sleep(5);  
    char* mem = (char*)shmat(shmid, NULL, 0);                                                                                 
    sleep(5);                                                                                                                 
    printf("client process attaches success\n"); 
    /*
        通信内容(暂时不写):先测试两个进行能不能同时挂接到同一个共享内存上
    */                                                                             
    shmdt(mem);                                                                                                               
    sleep(5);                                                                                                                 
    printf("client process detaches success\n");    
    return 0;                                                                                                                    
}

从运行结果来看,两个进程确实都挂接到了共享内存;

60a6bcefe26f4b118e50f46e4d0afd1d.png

接下来我们来实现通信内容:

//server.c
while(1){
    sleep(1);                                                   
    printf("%s\n", mem);                                       
}

服务端不断的从共享内存中读数据;

//client.c
char c = 'A';
while(c < 'Z'){                                                 
    mem[c - 'A'] = c;
    c++;
    mem[c - 'A'] = 0;
    sleep(2);
}

客户端不断的向共享内存写数据;

       此时先运行服务端创建共享内存,当我们运行客户端时服务端就开始不断输出数据,说明服务端和客户端是能够正常通信的。

60a6bcefe26f4b118e50f46e4d0afd1d.png

9.共享内存的总结

共享内存:


要使用一块共享内存,进程必须首先分配它。随后需要访问这个共享内存块的每一个进程都必须将这个共享内存绑定到自己的地址空间中。

在 Linux 系统中,每个进程的虚拟内存是被分为许多页面的。这些内存页面中包含了实际的数据。每个进程都会维护一个从内存地址到虚拟内存页面之间的映射关系。尽管每个进程都有自己的内存地址,不同的进程可以同时将同一个内存页面映射到自己的地址空间中,从而达到共享内存的目的。

分配一个新的共享内存块会创建新的内存页面。因为所有进程都希望共享对同一块内存的访问,只应由一个进程创建一块新的共享内存。再次分配一块已经存在的内存块不会创建新的页面,而只是会返回一个标识该内存块的标识符。

一个进程如需使用这个共享内存块,则首先需要将它绑定到自己的地址空间中。这样会创建一个从进程本身虚拟地址到共享页面的映射关系。当对共享内存的使用结束之后,这个映射关系将被删除。当再也没有进程需要使用这个共享内存块的时候,必须有一个(且只能是一个)进程负责释放这个被共享的内存页面。

所有共享内存块的大小都必须是系统页面大小的整数倍。系统页面大小指的是系统中单个内存页面包含的字节数。在 Linux 系统中,内存页面大小是4KB,不过您仍然应该通过调用 getpagesize 获取这个值(通过man 2 getpagesize查看 )。

共享内存的生命周期是随内核的,而管道是随进程的。

共享内存不提供任何的同步和互斥机制,需要程序员自行保证数据安全。

共享内存在各种进程间通信方式中具有最高的效率。访问共享内存区域和访问进程独有的内存区域一样快,并不需要通过系统调用或者其它需要切入内核的过程来完成。同时它也避免了对数据的各种不必要的复制。

其他通信方式将会陆续补充进来


目录
相关文章
|
13天前
|
网络协议 Linux
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
87 2
|
13天前
|
Linux Python
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
29 2
|
17天前
|
消息中间件 Linux 开发者
Linux进程间通信秘籍:管道、消息队列、信号量,一文让你彻底解锁!
【8月更文挑战第25天】本文概述了Linux系统中常用的五种进程间通信(IPC)模式:管道、消息队列、信号量、共享内存与套接字。通过示例代码展示了每种模式的应用场景。了解这些IPC机制及其特点有助于开发者根据具体需求选择合适的通信方式,促进多进程间的高效协作。
46 3
|
15天前
|
消息中间件 Linux
Linux进程间通信
Linux进程间通信
31 1
|
16天前
|
C语言
Linux0.11 系统调用进程创建与执行(九)(下)
Linux0.11 系统调用进程创建与执行(九)
18 1
|
16天前
|
存储 Linux 索引
Linux0.11 系统调用进程创建与执行(九)(上)
Linux0.11 系统调用进程创建与执行(九)
34 1
|
21天前
|
域名解析 监控 安全
在Linux中,什么是守护进程,它们是如何工作的?
在Linux中,什么是守护进程,它们是如何工作的?
|
20天前
|
消息中间件 Linux
在Linux中,进程间通信方式有哪些?
在Linux中,进程间通信方式有哪些?
|
10天前
|
Unix Linux
linux中在进程之间传递文件描述符的实现方式
linux中在进程之间传递文件描述符的实现方式
|
11天前
|
开发者 API Windows
从怀旧到革新:看WinForms如何在保持向后兼容性的前提下,借助.NET新平台的力量实现自我进化与应用现代化,让经典桌面应用焕发第二春——我们的WinForms应用转型之路深度剖析
【8月更文挑战第31天】在Windows桌面应用开发中,Windows Forms(WinForms)依然是许多开发者的首选。尽管.NET Framework已演进至.NET 5 及更高版本,WinForms 仍作为核心组件保留,支持现有代码库的同时引入新特性。开发者可将项目迁移至.NET Core,享受性能提升和跨平台能力。迁移时需注意API变更,确保应用平稳过渡。通过自定义样式或第三方控件库,还可增强视觉效果。结合.NET新功能,WinForms 应用不仅能延续既有投资,还能焕发新生。 示例代码展示了如何在.NET Core中创建包含按钮和标签的基本窗口,实现简单的用户交互。
34 0