进程间链表实现
Posted rtoax
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进程间链表实现相关的知识,希望对你有一定的参考价值。
《老酒馆》
对比起,踩您脚了;
老二两:是我对不起您,耽误您脚落地了。
隔离在家,第四天。2021年8月2日13:13:02。
--共享内存--
在文章《mmap从低向高增长的legacy模式和从高向低增长的modern模式》我介绍过进程地址空间的内存布局,当不同的进程同时映射一个文件到自己的进程地址空间时,就可以实现进程间的共享内存。
对于内存文件映射的整体架构如下图:
--链表--
拿单链表为例,链表节点的next域保存了链表的下一个节点的虚拟地址,从虚拟地址转化为物理地址为上图中由VMA到page结构的过程。
--进程间共享内存--
共享内存的基本简化结构如下图,那么,如果我们共享了一块物理内存,在进程地址空间中的虚拟地址大概率是不相等的,那么我们如何保证在进程1设置了next域后,在进程2中访问合法呢?
没错,我猜聪明的你一定也想到了,next域不在保存下一个节点的虚拟地址,而是保存相对于共享内存块的首地址的偏移。
--共享内存中的链表--
展开共享内存区域
假设我们现在要生成的链表为:
在内存中表现为:
此时的链表节点为([value, next]):
此时内存地址空间中为:
这些数据在进程1和进程2中是相同的,所以进程2通过遍历下面结构
即可遍历链表。
--实现--
上述步骤,实现起来非常简单,具体可参见源码:
<https://gitee.com/rtoax/test/commit/52116f9a074d41e7bf430caa40808d42102e79bb>
这里做简单介绍。
定义了两个数据结构,分别保存共享内存信息,和当前写指针对应的位置偏移:
struct mll_hdr {
size_t curr_off;
};
typedef struct mlinklist_s {
char filename[32];
int fd;
size_t size;
char *addr;
struct mll_hdr *hdr;
}mll_t;
链表节点我将其定义为:
struct ll_node {
int i;
union {
struct ll_node *next;
size_t offset;
};
};
创建共享内存,区分读写:
void create_mll(mll_t * mll, const char *file, size_t size, int write)
{
mll->size = size;
strncpy(mll->filename, file, sizeof(mll->filename));
int flag;
if(write) {
flag = O_RDWR|O_CREAT|O_TRUNC;
} else {
flag = O_RDWR;
}
mll->fd = open(file, flag, 0644);
if(mll->fd == -1) {
perror("open\\n");
exit(1);
}
if((ftruncate(mll->fd, mll->size)) == -1) {
perror("ftruncate\\n");
exit(1);
}
mll->addr = mmap(NULL, mll->size, PROT_WRITE|PROT_READ, MAP_SHARED, mll->fd, 0);
if(mll->addr == MAP_FAILED) {
perror("mmap\\n");
exit(1);
}
mll->hdr = (struct mll_hdr*)mll->addr;
if(write) {
mll->hdr->curr_off = sizeof(struct mll_hdr);
}
}
这里需要注意的是 flag 的赋值在读写测试不同的。其次,读者会根据写者的偏移决定是否有数据在链表中。
然后,就可以插入链表节点了:
void mll_insert(mll_t * mll, struct ll_node *node)
{
struct ll_node *_node = (struct ll_node *)(mll->addr + mll->hdr->curr_off);
_node->i = node->i;
if(mll->hdr->curr_off > sizeof(struct mll_hdr)) {
struct ll_node *_prev = (struct ll_node *)(mll->addr + mll->hdr->curr_off - sizeof(struct ll_node));
_prev->offset = mll->hdr->curr_off;
}
_node->next = NULL;
mll->hdr->curr_off += sizeof(struct ll_node);
}
遍历链表节点:
void mll_foreach(mll_t * mll, void (*foreach)(struct ll_node *node))
{
if(mll->hdr->curr_off <= sizeof(struct mll_hdr)) {
return ;
}
struct ll_node *_node = (struct ll_node *)(mll->addr + sizeof(struct mll_hdr));
do {
foreach(_node);
if(!_node->next) break;
_node = (struct ll_node *)(mll->addr + _node->offset);
} while(_node);
}
下面给出生产者代码:
#include <stdio.h>
#include <mlinklist.h>
#include <common.h>
int main()
{
int i;
mll_t mll;
struct ll_node nodes[] = {
{1, NULL}, {2, NULL}, {3, NULL}, {4, NULL},
};
create_mll(&mll, MMAP_FILENAME, sizeof(struct ll_node)*LINKLIST_SIZE, 1);
for(i=0; i<sizeof(nodes)/sizeof(nodes[0]) && i < LINKLIST_SIZE; i++) {
mll_insert(&mll, &nodes[i]);
}
}
消费者(读者,并没有实际出队)的代码:
#include <stdio.h>
#include <mlinklist.h>
#include <common.h>
void foreach(struct ll_node *node)
{
printf("foreach: %d %ld.\\n", node->i, node->offset);
}
int main()
{
mll_t mll;
create_mll(&mll, MMAP_FILENAME, sizeof(struct ll_node)*LINKLIST_SIZE, 0);
mll_foreach(&mll, foreach);
}
详情请关注公众号 <全波形反演>
--参考--
-
《mmap从低向高增长的legacy模式和从高向低增长的modern模式》
-
https://rtoax.blog.csdn.net/article/details/118602363
-
-
《mmap文件映射与缺页异常》
-
https://rtoax.blog.csdn.net/article/details/118602477
-
以上是关于进程间链表实现的主要内容,如果未能解决你的问题,请参考以下文章
NC41 最长无重复子数组/NC133链表的奇偶重排/NC116把数字翻译成字符串/NC135 股票交易的最大收益/NC126换钱的最少货币数/NC45实现二叉树先序,中序和后序遍历(递归)(代码片段
在 Python 多处理进程中运行较慢的 OpenCV 代码片段
817. Linked List Components - LeetCode
java 简单的代码片段,展示如何将javaagent附加到运行JVM进程
LINUX PID 1和SYSTEMD PID 0 是内核的一部分,主要用于内进换页,内核初始化的最后一步就是启动 init 进程。这个进程是系统的第一个进程,PID 为 1,又叫超级进程(代码片段