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

简介: 作用:用于多个进程间数据共享特性:最快的进程间通信方式,共享内存区是最快的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操作


目录
相关文章
|
2月前
麒麟系统mate-indicators进程占用内存过高问题解决
【10月更文挑战第7天】麒麟系统mate-indicators进程占用内存过高问题解决
308 2
|
3月前
|
存储 Linux 调度
深入理解操作系统:从进程管理到内存分配
【8月更文挑战第44天】本文将带你深入操作系统的核心,探索其背后的原理和机制。我们将从进程管理开始,理解如何创建、调度和管理进程。然后,我们将探讨内存分配,了解操作系统如何管理计算机的内存资源。最后,我们将通过一些代码示例,展示这些概念是如何在实际操作系统中实现的。无论你是初学者还是有经验的开发者,这篇文章都将为你提供新的视角和深入的理解。
|
22天前
|
C语言 开发者 内存技术
探索操作系统核心:从进程管理到内存分配
本文将深入探讨操作系统的两大核心功能——进程管理和内存分配。通过直观的代码示例,我们将了解如何在操作系统中实现这些基本功能,以及它们如何影响系统性能和稳定性。文章旨在为读者提供一个清晰的操作系统内部工作机制视角,同时强调理解和掌握这些概念对于任何软件开发人员的重要性。
|
21天前
|
Linux 调度 C语言
深入理解操作系统:从进程管理到内存优化
本文旨在为读者提供一次深入浅出的操作系统之旅,从进程管理的基本概念出发,逐步探索到内存管理的高级技巧。我们将通过实际代码示例,揭示操作系统如何高效地调度和优化资源,确保系统稳定运行。无论你是初学者还是有一定基础的开发者,这篇文章都将为你打开一扇了解操作系统深层工作原理的大门。
|
1月前
|
算法 调度 开发者
深入理解操作系统:从进程管理到内存分配
本文旨在为读者提供一个深入浅出的操作系统知识之旅,从进程管理的基础概念出发,探索内存分配的策略与技巧。我们将通过实际代码示例,揭示操作系统背后的逻辑与奥秘,帮助读者构建起对操作系统工作原理的直观理解。文章不仅涵盖理论知识,还提供实践操作的指导,使读者能够将抽象的概念转化为具体的技能。无论你是初学者还是有一定基础的开发者,都能在这篇文章中找到有价值的信息和启发。
|
1月前
|
算法 调度 C++
深入理解操作系统:从进程管理到内存分配
【10月更文挑战第42天】本文将带你进入操作系统的神秘世界,探索其核心概念和关键技术。我们将从进程管理开始,了解操作系统如何协调和管理多个程序的运行;然后,我们将深入研究内存分配,看看操作系统如何有效地分配和管理计算机的内存资源。通过这篇文章,你将获得对操作系统工作原理的深入理解,并学会如何编写高效的代码来利用这些原理。
|
1月前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?
|
1月前
|
消息中间件 存储 供应链
进程间通信方式-----消息队列通信
【10月更文挑战第29天】消息队列通信是一种强大而灵活的进程间通信机制,它通过异步通信、解耦和缓冲等特性,为分布式系统和多进程应用提供了高效的通信方式。在实际应用中,需要根据具体的需求和场景,合理地选择和使用消息队列,以充分发挥其优势,同时注意其可能带来的复杂性和性能开销等问题。
|
2月前
|
缓存 算法 调度
深入浅出操作系统:从进程管理到内存优化
本文旨在为读者提供一次深入浅出的操作系统之旅。我们将从进程管理的基本概念出发,逐步深入到内存管理的复杂世界,最终探索如何通过实践技巧来优化系统性能。文章将结合理论与实践,通过代码示例,帮助读者更好地理解操作系统的核心机制及其在日常技术工作中的重要性。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开一扇通往操作系统深层次理解的大门。
|
2月前
麒麟系统mate-indicators进程占用内存过高问题解决
【10月更文挑战第5天】麒麟系统mate-indicators进程占用内存过高问题解决
200 0