进程间通信之共享内存(简单介绍消息队列和信号量)

简介: 作用:用于多个进程间数据共享特性:最快的进程间通信方式,共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据原理:开辟出一块物理内存地址,然后多个进程都映射到自己的虚拟地址空间中,通过虚拟地址直接访问物理内存中的数据

system V共享内存


作用:用于多个进程间数据共享

特性:最快的进程间通信方式,共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据

原理:开辟出一块物理内存地址,然后多个进程都映射到自己的虚拟地址空间中,通过虚拟地址直接访问物理内存中的数据

image.png

相当于是进程直接看到的数据。举个例子,管道就是从写端的数据拷贝到管道缓冲区,再从管道缓冲区拷贝到自己的进程空间,相对于共享内存是多了两次拷贝


共享内存示意图


image.pngimage.png

共享内存数据结构


struct shmid_ds {
  struct ipc_perm shm_perm; /* operation perms */
  int shm_segsz; /* size of segment (bytes) */
  __kernel_time_t shm_atime; /* last attach time */
  __kernel_time_t shm_dtime; /* last detach time */
  __kernel_time_t shm_ctime; /* last change time */
  __kernel_ipc_pid_t shm_cpid; /* pid of creator */
  __kernel_ipc_pid_t shm_lpid; /* pid of last operator */
  unsigned short shm_nattch; /* no. of current attaches */
  unsigned short shm_unused; /* compatibility */
  void *shm_unused2; /* ditto - used by DIPC */
  void *shm_unused3; /* unused */
};


共享内存函数


shmget函数

功能:用来创建共享内存

原型

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

参数

 key:这个共享内存段名字

 size:共享内存大小,一般是PAGE_SIZE的整数倍(4096字节)

 shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的

返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1


shmflg: IPC CREAT | IPC EXCL | 0664


IPC_ CREAT

 如果共享内存不存在则创建打开,若已经存在则直接打开

IPC_ EXCL

 与IPC_ CREAT搭配使用,共享内存不存在则创建打开,若存在则报错返回 ,一个程序只能启动一次的程序

mode_ flags

 共享内存的访问权限0664


shmat函数


功能:

将共享内存段连接到进程地址空间

原型

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

参数

shmid: 共享内存标识 shmaddr:指定连接的地址

shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY(只读,前提是具备读的权限)

返回值:成功返回一个指针,指向共享内存第一个节;失败返回(void)-1


shmdt函数


功能:将共享内存段与当前进程脱离

原型 int shmdt(const void *shmaddr);

参数

shmaddr: 由shmat所返回的指针

返回值:

成功返回0;失败返回-1

注意:将共享内存段与当前进程脱离不等于删除共享内存段


shmctl函数


功能:用于控制共享内存

原型

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数

shmid:由shmget返回的共享内存标识码

cmd:将要采取的动作(有三个可取值)

buf:指向一个保存着共享内存的模式状态和访问权限的数据结构

返回值:成功返回0;失败返回-1

image.png

注意:这里常用的是IPC_RMID,但是并非直接删除,而是做一个标记,删除最终还是由操作系统进行


这里我们思考一个问题:

多个进程,访问同一个共享内存,突然有一个进程要删除了共享内存,如何避免其他进程不会出现错误呢?

其实共享内存是有个当前的映射连接计数(表示现在有多少进程正在访问)所以这里的RMID叫做标记删除,并不是真的删除,而是标记一下, 被标记的共享内存将不再接受新的映射而是等当前的映射连接计数为0时,再实际删除删除由系统完成,进程所做的删除操作,其实只是标记一下。


简单举例:

image.png

举例2

测试代码结构

# ls
client.c comm.c comm.h Makefile server.c
# cat Makefile
.PHONY:all
all:server client
client:client.c comm.c
gcc -o $@ $^
server:server.c comm.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f client server

comm.h

#ifndef _COMM_H_
#define _COMM_H_
# include <stdio.h>
# include <sys/types.h>
# include <sys/ipc.h>
# include <sys/shm.h>
# define PATHNAME "."
# define PROJ_ID 0x6666
int createShm(int size);
int destroyShm(int shmid);
int getShm(int size);
# endif

comm.c

#include "comm.h"
static int commShm(int size, int flags)
{
  key_t key = ftok(PATHNAME, PROJ_ID);
  if (key < 0) {
    perror("ftok");
    return -1;
  }
  int shmid = 0;
  if ((shmid = shmget(_key, size, flags)) < 0) {
    perror("shmget");
    return -2;
  }
  return shmid;
}
int destroyShm(int shmid)
{
  if (shmctl(shmid, IPC_RMID, NULL) < 0) {
    perror("shmctl");
    return -1;
  }
  return 0;
}
int createShm(int size)
{
  return commShm(size, IPC_CREAT | IPC_EXCL | 0666);
}
int getShm(int size)
{
  return commShm(size, IPC_CREAT);
}

server.c

#include "comm.h"
int main()
{
  int shmid = createShm(4096);
  char* addr = shmat(shmid, NULL, 0);
  sleep(2);
  int i = 0;
  while (i++ < 26) {
    printf("client# %s\n", addr);
    sleep(1);
  }
  shmdt(addr);
  sleep(2);
  destroyShm(shmid);
  return 0;
}

client.c

#include "comm.h"
int main()
{
  int shmid = getShm(4096);
  sleep(1);
  char* addr = shmat(shmid, NULL, 0);
  sleep(2);
  int i = 0;
  while (i < 26) {
    addr[i] = 'A' + i;
    i++;
    addr[i] = 0;
    sleep(1);
  }
  shmdt(addr);
  sleep(2);
  return 0;
}

结果演示

image.png

注意:共享内存没有进行同步与互斥!

image.png

特性:


1.最快的进程间通信方式

2.生命周期随内核(并不会随着打开的进程退出而被释放)


注意事项:


共享内存的访问操作存在安全问题(竞争访问出现数据二义问题)


补充:


1.共享内存的本质就是开辟一块物理内存, 让多个进程映射同-块物理内存到自己的地址空间进行访问,实现数据共享的。

2.共享内存的操作是非进程安全的,多个进程同时对共享内存读写是有可能会造成数据的交叉写入或读取,造成数据混乱

3.共享内存的删除操作并非直接删除,而是拒绝后续映射,只有在当前映射链接数为0时,表示没有进程访问了,才会真正被删除

4.共享内存生命周期随内核,只要不删除,就一-直存在于内核中,除非重启系统(当然这里指的是非手动操作,可以手动删除)


消息队列


详细可以参考这篇博客,我这里是简单总结了一下消息队列

功能:实现进程间的数据传输

本质:内核中的一个优先级队列

实现:多个进程通过访问同一一个消息队列,以添加数据节点和获取数据节点实现通信


image.png

总结:


1、采用消息队列通信比采用管道通信具有更多的灵活性,通信的进程不但没有血缘上的要求,也不需要进行同步处理。

2、消息队列是一种先进先出的队列型数据结构;

3、消息队列将输出的信息进行了打包处理,可以保证以消息为单位进行接收;

4、消息队列对信息进行分类服务,根据消息的类别进行分别处理。

5、提供消息数据自动拆分功能,同时不能接受两次发送的消息。

6、消息队列提供了不完全随机读取的服务。 7、消息队列提供了完全异步的读写服务。


信号量


(这里做简单介绍)

作用:用于实现进程间的同步与互斥

本质:是一个计数器+ pcb等待队列


P操作:对计数器进行-1操作,判断计数是否大于等于0,正确则返回;失败则阻塞,其中阻塞就是把它置位可中断休眠状态,挂到pcb等待对联中,直到被唤醒

V操作:对计数器进行+ 1操作,若计数器小于等于0,则唤醒一个等待的进程


同步实现:

通过计数器对共享资源进行计数,在获取资源之前,则进行P操作,计数满足访问条件则访问,若不满足则阻塞当产生一个资源,则进行V操作,唤醒阻塞的进程

互斥实现:

初始化计数器为1,表示资源只有一一个。

访问资源之前进行P操作;访问资源完毕之后进行V操作


目录
相关文章
|
消息中间件 存储 网络协议
从零开始掌握进程间通信:管道、信号、消息队列、共享内存大揭秘
本文详细介绍了进程间通信(IPC)的六种主要方式:管道、信号、消息队列、共享内存、信号量和套接字。每种方式都有其特点和适用场景,如管道适用于父子进程间的通信,消息队列能传递结构化数据,共享内存提供高速数据交换,信号量用于同步控制,套接字支持跨网络通信。通过对比和分析,帮助读者理解并选择合适的IPC机制,以提高系统性能和可靠性。
2412 14
|
消息中间件 Linux
Linux中的System V通信标准--共享内存、消息队列以及信号量
希望本文能帮助您更好地理解和应用System V IPC机制,构建高效的Linux应用程序。
701 48
|
消息中间件 Linux
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
1160 20
|
运维 监控 Ubuntu
【运维】如何在Ubuntu中设置一个内存守护进程来确保内存不会溢出
通过设置内存守护进程,可以有效监控和管理系统内存使用情况,防止内存溢出带来的系统崩溃和服务中断。本文介绍了如何在Ubuntu中编写和配置内存守护脚本,并将其设置为systemd服务。通过这种方式,可以在内存使用超过设定阈值时自动采取措施,确保系统稳定运行。
774 4
|
Linux 调度 C语言
深入理解操作系统:从进程管理到内存优化
本文旨在为读者提供一次深入浅出的操作系统之旅,从进程管理的基本概念出发,逐步探索到内存管理的高级技巧。我们将通过实际代码示例,揭示操作系统如何高效地调度和优化资源,确保系统稳定运行。无论你是初学者还是有一定基础的开发者,这篇文章都将为你打开一扇了解操作系统深层工作原理的大门。
285 4
|
消息中间件 C语言 RocketMQ
消息队列 MQ操作报错合集之出现"Connection reset by peer"的错误,该如何处理
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。
|
消息中间件 Java C语言
消息队列 MQ使用问题之在使用C++客户端和GBase的ESQL进行编译时出现core dump,该怎么办
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。
|
消息中间件 存储 Kafka
MQ 消息队列核心原理,12 条最全面总结!
本文总结了消息队列的12个核心原理,涵盖消息顺序性、ACK机制、持久化及高可用性等内容。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
消息中间件
手撸MQ消息队列——循环数组
队列是一种常用的数据结构,类似于栈,但采用先进先出(FIFO)的原则。生活中常见的排队场景就是队列的应用实例。在数据结构中,队列通常用数组实现,包括入队(队尾插入元素)和出队(队头移除元素)两种基本操作。本文介绍了如何用数组实现队列,包括定义数组长度、维护队头和队尾下标(front 和 tail),并通过取模运算解决下标越界问题。此外,还讨论了队列的空与满状态判断,以及并发和等待机制的实现。通过示例代码展示了队列的基本操作及优化方法,确保多线程环境下的正确性和高效性。
531 0
手撸MQ消息队列——循环数组
|
消息中间件 存储 缓存
一个用过消息队列的人,竟不知为何要用 MQ?
一个用过消息队列的人,竟不知为何要用 MQ?
676 1

热门文章

最新文章