linux 共享内存动态扩展的问题(链表数据保存到共享内存)

14 篇文章 2 订阅


前言

由于这段内容审核为通过,于是我把它们删除了。从此,这篇文章没有前言了。


提示:以上内容毫无用处,仅仅发表个人意见。下面言归正传

一、问题描述

共享内存作为一种进程间通信的手段在linux应用程序开发过程中应用广泛。广泛到我轻而易举就遇到了,我的应用场景是这样的。我在为一个设备开发一个网络管理代理端的应用程序(我在此也不会解释什么是网络管理代理端),这里有两个进程,一个在收集设备的参数信息,另一个进程对管理端提供管理的网络接口。于是我打算用共享内存实现两个进程之间设备参数的共享。但是在开发的过程中遇到了一个问题就是我的设备是是板卡插槽式的,这就导致我收集设备参数的进程收集到的参数类型和数据量是变化的。正常进程中的内存我们用malloc函数可以动态分配和扩展,那共享内存如何做到呢?

二、Linux共享内存深入了解

之前也使用过共享内存,但是都是分配固定大小的内存空间。然后映射到自己的数据结构中。对相关的内容自认为了解是比较深刻的。但是,在解决上面问题之前,我觉得我之前并没有了解共享内存的全部。

1.了解问题的本质才能找到解决问题的最佳方案

我们总是在解决各种各样的问题,无论是生活还是工作中的问题。当我们试图找到解决某个问题的最佳方案的时候我们必须了解该问题的本质。下面让我们试图找到我上面提出问题的本质。
问:如果我们解决了上面的问题,最终达到的目的是什么?
答:把我们的参数信息保存到一块内存中,而这块内存多个进程都可以看到。

问:影响我们达到上面目的的因素是什么?
答:因为参数信息的内容和类型不确定,需要根据实际情况动态分配存储空间

问:linux下共享内存可以动态分配吗?
当看到这个问题,我想我们似乎找到了问题的本质。男人的直觉告诉我,这个问题的回答将直接影响到我通向成功能否实现。于是我百度了一下,这方面的文章很少,很多的答案是不可以。我们时候进入了牛角尖,为了走出这个牛角尖,清回答我下面的问题:
问:有获得一块任意合理大小的共享内存的方法吗?
答:有,linux提供了系统调用来获得一块共享内存。
问:shmat获得的内存和malloc获得的内存对于程序员存储数据有区别吗?
答:没有。
于是问题解决了,我们似乎可以分装一个函数实现和malloc一样动态的分配共享内存空间。

2.封装一个mallocshm函数实现连表的进程间共享

下面我们以存一个连表数据到共享内存中为目的继续这篇文章的讨论,既然是要操作共享内存,我们必须先了解一下共享内存的接口就像了解malloc函数一样。所谓知己知彼方能百战不殆。
使用共享内存我们永远都离不开ftok、shmget、shmat、shmdt、shmctl这五个系统调用。其实这五个函数怎么使用已经有很多文章讲过了,随意百度一下就能找到,而且还有很多实例。所以我在这里就不重复了,什么函数原型、参数说明什么的就交给巨人吧,我只是站在巨人的肩膀上谈谈我对共享内存的理解。下面是重点:

我们都知道linux每个用户进程都有自己独立的内存空间,进程与进程之间的内存空间不可以相互访问。可是在软件开发的过程中我们的进程间有时候又不得不交换数据,于是操作系统不得不给应用程序开发者提供一个进程间交互的接口。于是各种IPC工具应用而生,我们讨论的共享内存就是其中一种。
本质上共享内存也是一块内存空间,只是她比较特殊,她存在于内核空间。我们每个linux程序员都知道用户空间的进程无法之间访问内核空间的内存(连其他进程的内存都无法访问更不要说内核了)。这就需要内核开发这给出接口,使内核中的这块内存空间能够被多个进程访问。于是就产生了我们前面提到的五个系统调研。
在使用这五个系统调用时我们会用到几个概念:

  1. key_t的key值:shmget会根据这个值在内核中给你分配一块内存空间。
  2. int型的shmid值:这个整数之唯一标识一块共享内存空间,我们只要得到了这个之就可以得到访问共享内存的访问权利了。拥有了shmid就等于拥有了共享内存。
  3. 一个指向共享内存的void *型的指针,他是shmat的返回值。
    所以我们如何获得一块共享内存内?答案是我们获得shmid就ok啦
int get_a_shmid(const char *path, int id, size_t size)
{
	//第一步获得一个key,我们最好是用ftok这个系统调用来让内核分配,反正内核
	//分配的肯定是没有问题的,这样内核分配共享内存也就没有问题了
	//key_t ftok(const char *path ,int id)
	//参数path:一个文件系统的路径,这个可以随意。好像共享内存的实现是依据虚拟文件系
	//统的,ftok根据某个文件的inode生成一个键值;
	//参数id:0-255随意一个值,相当于一个路径下最多可以分配256个shmid
	key_t key = ftok(path, id);
	//判断一下key值是否分配成功,linux大多数系统调用失败都返回-1
	if(key < 0)
		return -1;
	//shmget第三个参数要说明一下,他根open的flag产生类似控制了共享内存的访问权限
	return shmget(key, size, IPC_CREAT|0666);
}

上面我说了,只要得到了shmid我们就得到了共享内存了,我们如何能够像访问普通内存一样访问共享内存呢?内核总是想在我们前面,shmat系统调用正是用来解决这个问题的。既然如此,让我们实现mallocshm函数吧。

void* mallocshm(const char *path, int id, size_t size)
{
	int shmid = get_a_shmid(path, id, size);
	if(shmid < 0)
		return NULL;
	void *ptr = shmat(shmid, 0, 0);
	return ptr;
}

好的,现在我们可以像malloc一样分配一块共享内存来使用了,但是我们得到的内存只是内核为特定进程开了一个通道来访问某个shmid的共享内存块。当这个进程退出后这个通道就关闭了,所以维护一块共享内存的话似乎用shmid更加合适。

3.用共享内存存储一个链表

我知道我写的文章又臭又长,有很多废话,但是如果你认真读的话我相信总能有所得。我始终相信,如果我可以把一个链表的数据保存到共享内存而且多个进程可以成功访问那有关共享内存的其他问题都可以迎刃而解。经过前面的探讨,我想我可以做到这一点。说干就干吧。。。。。。
下面我只给出把链表数据存入共享内存的程序历程:

#include <stdio.h>
#include <sys/shm.h>
#include <sys/ipc.h>

//作为获取键值的路径
#define SHM_KEY_PATH "/home"
/*
 * 定义一个链表头
 */ 
typedef struct head
{
    int firstid;
    int tailid;
    int nodenum;
} Head;

/*
 * 定义一个链表节点,这个链表节点有些特殊
 * 我们用nextid这个变量来保存下一个节点的shmid
 * 这样我们就可以根据这个shmid找到下一个节点的共享内存
 */ 
typedef struct node
{
    int nextid;
    int data;
} Node;

/*
 * 向某个链表中添加一个节点
 */ 
void add_a_node(Head *head, int dat)
{
    if(head->nodenum >= 0)
    {
	key_t key = ftok(SHM_KEY_PATH, head->nodenum);
        if(key < 0)
	    return;
	int shmid = shmget(key, sizeof(Node), IPC_CREAT | 0666);
	if(shmid < 0)
	    return;
	Node *node = (Node *)shmat(shmid, 0, 0);
	if(node == NULL)
	    return;
	node->nextid = -1;
	node->data = dat;
	if(head->nodenum == 0)
	{
	    head->firstid = shmid;
	    head->tailid = shmid;
	}
	else
	{
	    if(head->tailid < 0)
	    {
		head->tailid = shmid;
		shmdt((void *)node);
		return;
	    }
	    Node *tail = (Node *)shmat(head->tailid, 0, 0);
	    if(tail != NULL)
	    {
		tail->nextid = shmid;
		head->tailid = shmid;
		shmdt((void *)tail);
	    }
	    shmdt((void *)node);
	}
	head->nodenum++;
    }
}

int main()
{
    Head head;
    int i = 0;

    head.firstid = -1;
    head.tailid = -1;
    head.nodenum = 0;
    for(i = 0; i < 5; i++)
    {
	add_a_node(&head, i);
    }
}

将上面的程序编译运行结果如下:
在这里插入图片描述
shmid为98327-98331的共享内存就是我们建立的,其他的进程就可以访问了。


总结

我写了很多,实例程序也值给出了建立链表的过程,没有数据访问、插入、删除等操作。但是,我觉得这已经足够了。因为,我们发现与动态申请内存的malloc相比,动态申请共享内存唯一的区别就是多了一步操作就四将shmid对应的共享内存映射到操作进程中。其实唯一的区别就是普通内存申请获得的是指向某块内存的指针,而共享内存是shmid。只要我们拥有了shmid就拥有了共享内存,就像我们只要拥有了金钱就拥有了一切一样。

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Linux内核链表是一个非常基础的数据结构,在Linux内核中被广泛使用。Linux内核链表通过指针连接节点,每个节点都包含下一个节点的指针,从而形成链表。 在多核处理器架构下,由于不同核心的缓存可能不一致,当多个核心同时对链表进行修改时,可能会出现数据不一致的情况。为了解决这个问题Linux内核采用了一种叫做“cache一致性”的技术,即每次修改链表节点时,都需要将该节点所在的缓存行标记为无效,这样其他核心访问该节点时,就会重新从内存中读取数据,保证数据一致性。 而内存屏障则是保证代码执行顺序的关键技术。在多核处理器架构下,由于不同核心的指令可能乱序执行,因此需要内存屏障来确保指令的执行顺序。内存屏障分为读屏障、写屏障和全屏障三种。 读屏障(rmb)用于确保所有先于读屏障的读操作都完成后,才能执行读屏障之后的操作;写屏障(wmb)用于确保所有先于写屏障的写操作都完成后,才能执行写屏障之后的操作;全屏障(mb)则是同时执行读屏障和写屏障的作用。 在Linux内核链表中,内存屏障被广泛应用。例如,在向链表中添加节点时,需要先将新节点的指针指向下一个节点,再将上一个节点的指针指向新节点。此时就需要使用内存屏障来确保指针的修改顺序正确。具体来说,需要在修改新节点指针之后、修改上一个节点指针之前,加入写屏障;在读取新节点指针之前、读取上一个节点指针之后,加入读屏障。这样就可以确保指针的修改顺序正确,从而避免了数据不一致的情况。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

南波儿万

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

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

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

打赏作者

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

抵扣说明:

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

余额充值