Linux学习笔记之八(进程间的共享内存)

1、引言

在之前一篇文章Linux学习笔记之六(进程之间的管道通信和信号处理)中我讲了进程间可以通过管道通信。管道通信也是在内存中某个地方开辟一块内存,让不同进程可以访问它,进而实现通信的目的。
而本文所讲的共享内存也大概是这么一个逻辑,不过相较与管道通信,内存共享实现起来更加复杂,也没有读写阻塞功能,但执行起来的效率也更加高,似乎是最快的进程通信方式。

2、实现共享内存

共享内存其任务在于从物理内存中申请一块内存区域,然后将这个区域映射到不同进程的空间中。进而进程访问该共享区域,进行读写操作。下图截取于B站,该图比较清晰明了说明了共享内存的运行逻辑。从这张图可以看出,任何进程都有自己的空间,而这些空间本质是取之于物理内存,但取的顺序则是杂乱的。共享内存无非是一块大家都可以取走的内存块而已。
在这里插入图片描述
接下来说明实现共享内存的基本步骤:
第一步:创建一个共享内存
第二步:将共享内存链接到进程空间
第三步:断开与共享内存的链接
第四步:对共享内存进行后续操作(如销毁)

2.1、创建一个共享内存

创建一个共享内存用到的函数是shmget(share memory get),其函数原型如下:

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

参数概览:

key:设置该共享内容的关键字标识。
size:指定共享内存的空间大小。
shmflag:设置共享内存的访问权限。
返回值:创建成功则返回共享内存标识符,失败则返回-1.

参数详解:
1. key参数:key 参数是一个用于标识共享内存段的关键字。它允许多个进程通过使用相同的 key 参数来访问同一个共享内存段。这就是说,如果多个进程想要访问同一个共享内存段,它们必须提供相同的 key 参数值。
通常情况下,key 参数可以是一个由 ftok() 函数生成的键值(通过给定文件路径名和一个项目标识符来生成唯一的键值),也可以是手动指定的一个键值,也可以通过一些宏指定一些key值。
关于ftok函数,其函数原型如下:

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

第一个参数是真实存在的文件路径,第二个参数是项目的id,可以自己设定。
计算机会根据这两个参数的组合,生成一个独一无二的key值,保证不同进程可以访问同一个内存空间。
2. size参数:一般而言我们会设置成4096的整数倍,因为计算机的最小内存单元就是4kB,但如果不设置成4096的整数倍也无所谓,计算机会根据自动帮你调整到合适的值。比如你设置2009,计算机也会帮你调整到4096.
2. shmflag参数:该参数用于设置共享内存的访问权限,一般由一些宏和四位整数组成,比如0777 | IPC_CREAT。权限的设置相信大家都耳熟能详了,第一位0表示后面的三位数字是八进制数字,777分别表示,用户、用户组、其他人的权限。7会被拆解位111,分别表示rwx(读写执行)三个权限。
常见的宏有:
IPC_CREAT:若共享内存存在则打开,否则创建一个。
IPC_EXCL:若共享内存不存在则创建,否则报错。
IPC_NOWAIT:若该操作需要等待,直接报错。

最后是调用该函数需要头文件(后文提到的所有函数也需要包含这几个头文件):

#include "sys/types.h"
#include "sys/ipc.h"
#include "sys/shm.h"

2.2、将共享内存链接到进程空间

在这一步用到函数是shmat,其函数原型如下:

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

参数概览:

shmid:这里输入的是共享内存的标识符,其实就是shmget函数的返回值。
void *addr:指定链接到进程中的哪个地址块,一般不需要指定,由计算机自动分配即可。
int shmflg:标志位,一般设置为0即可。
返回值:成功则返回共享内存在进程空间中的链接地址,失败则返回-1。

注:void *指的是无类型指针,无类型换一种理解方式就是,可以赋予它任意类型。
通过这个步骤,我们拿到了链接地址,再用这个地址来索引相应的内存空间之后,便可以进行读写操作,以实现进程通信。

2.3、断开与共享内存的链接

进程通信结束之后自然是要将进程与共享内存之间的链接断开。这一步用到的函数时shmdt,其函数原型如下:

int shmdt(const void *address);

void *address:该地址指的进程空间中的链接地址。一般就是shmat中的返回值了。
返回值:成功则返回0,失败则返回-1。

2.4、对共享内存进行后续操作

进程通信结束,链接也断开,下一步自然是要考虑如何回收或销毁这个共享内存了。用到的函数时shmctl,其函数原型如下:

int shmctl(int shm_id, int command, struct shmid_ds *buf);

shm_id:共享内存对象的id,即内存的标识符。一般时shmget的返回值。
command:指定对该内存的操作命令,主要有删除命令(IPC_RMID)。
shmid_ds *buf:该参数用于获取内核结构的结构值,一般也不用。故直接设置为0即可。

最后注意一点,如果使用调用shmctl(shmid, IPC_RMID, 0)这句话,最后删除的只是共享内存的标识符,使系统不再维护该内存标识符,而不是真正把共享内存释放掉。如果想要共享内存被释放掉,则需要等到该内存没有被任何进程调用时,系统才会自动被释放掉。

3、应用实例

本例子的目的:创建两个进程A和B,A进程往共享内存写入数据,B进程将数据读出来。
故,首先创建一个进程A,去往一块共享内存写入数据。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sys/types.h"
#include "sys/ipc.h"
#include "sys/shm.h"


int main()
{
    key_t key  = ftok("$HOME/shared_memory/program_A.c", 9);        //generate a unique key
    int shmid = shmget(key, 4096, 0751|IPC_CREAT);                  //create a shared memory
    char *addr = shmat(shmid, 0, 0);        //link the shared memory with this program               
    char buffer[100];           //create a buffer for writing data
    while(1)
    {
        printf("\nPlease input a character: ");
        fgets(buffer, sizeof(buffer), stdin);
        if(strcmp(buffer, "q\n") == 0)              //judge whether what I input is 'q' or not
            break;                                  
        strncpy(addr, buffer, sizeof(buffer));          //writing data into the shared memory
    }
    
    if(shmdt(addr) == 0){
        printf("disconnect shared memory successfully!\n");
    }
    else{
        printf("fail to disconnect!\n");
    }

    if(shmctl(shmid, IPC_RMID, 0) == 0)
    {
        printf("delete the identifer successfully!\n");
    }
    else{
        printf("the identifer already be deleted!\n");
    }

    return 0;
}

其次,创建一个进程B,从共享内存中读取数据。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sys/types.h"
#include "sys/ipc.h"
#include "sys/shm.h"


int main()
{
    key_t key  = ftok("$HOME/shared_memory/program_A.c", 9);      
    int shmid = shmget(key, 4096, 0751|IPC_CREAT);                  
    char *addr = shmat(shmid, 0, 0);                     
    while(1)
    {
        char input;
        printf("please input a character: ");
        scanf("%c", &input);
        if(input == 'q')
            break;
        int c;
        while ((c = getchar()) != '\n' && c != EOF) {}
        printf("what i receive is: %s\n", addr);
    }
    
    if(shmdt(addr) == 0){
        printf("disconnect successfully!\n");
    }
    else{
        printf("fail to disconnect!\n");
    }

      if(shmctl(shmid, IPC_RMID, 0) == 0)
    {
        printf("delete successfully!\n");
    }
    else{
        printf("the identifer already be deleted!\n");
    }

    return 0;
}

最后,我打开两个终端,同时运行进程A和B,运行结果如下:
在这里插入图片描述

  • 20
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

__TAT__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值