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查看 )。

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

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

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

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


目录
相关文章
|
2月前
|
资源调度 Linux 调度
Linux c/c++之进程基础
这篇文章主要介绍了Linux下C/C++进程的基本概念、组成、模式、运行和状态,以及如何使用系统调用创建和管理进程。
40 0
|
1天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
26天前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
99 4
linux进程管理万字详解!!!
|
17天前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
58 8
|
14天前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?
|
26天前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
66 4
|
27天前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
28天前
|
消息中间件 存储 Linux
|
2月前
|
运维 Linux
Linux查找占用的端口,并杀死进程的简单方法
通过上述步骤和命令,您能够迅速识别并根据实际情况管理Linux系统中占用特定端口的进程。为了获得更全面的服务器管理技巧和解决方案,提供了丰富的资源和专业服务,是您提升运维技能的理想选择。
49 1
|
2月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
【10月更文挑战第9天】本文将深入浅出地介绍Linux系统中的进程管理机制,包括进程的概念、状态、调度以及如何在Linux环境下进行进程控制。我们将通过直观的语言和生动的比喻,让读者轻松掌握这一核心概念。文章不仅适合初学者构建基础,也能帮助有经验的用户加深对进程管理的理解。
27 1