浅学进程间通信2(共享内存&信号量)

共享内存(Shared Memory)

Linux 共享内存(System V SHM / POSIX SHM)并不是"基于某个磁盘文件系统",而是基于内核自己实现的 tmpfs文件系统 。 具体位置:

  1. POSIX 共享内存

挂载点:/dev/shm

文件系统类型:tmpfs(内核 2.4 以后固定挂载,用户不可见磁盘后备)。

  1. System V 共享内存(shmget/shmat 系列)

内核同样把每个段放在匿名的 tmpfs 页上,通过 shm_mnt 这一内部挂载点(用户不可见)管理;用户空间看到的 /proc/sysvipc/shm 只是只读接口,真正的页在 tmpfs 中。

因此,答案一句话:

Linux 共享内存基于内核内置的 tmpfs 文件系统,对用户可见的部分挂在 /dev/shm。

补充:

  • tmpfs 的页可以被 swap,因此严格说不是"纯物理内存",但一定不占用任何磁盘块。
  • 从内核源码角度看,无论是 mm/shmem.c(POSIX)还是 ipc/shm.c(System V),最终都调用 shmem 层的接口,而 shmem 就是 tmpfs 的实现。 共享内存是一种高效的进程间通信方式,不同编程语言提供了不同级别的API支持:

C/C++ 封装的共享内存API

1. POSIX 共享内存(Linux/Unix)

arduino 复制代码
#include <sys/mman.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
// 主要函数: 
int shm_open(const char *name, int oflag, mode_t mode); // 创建/打开 
int ftruncate(int fd, off_t length); // 设置大小 
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); // 映射 
int munmap(void *addr, size_t length); // 取消映射 
int shm_unlink(const char *name); // 删除

示例:

ini 复制代码
// 创建共享内存
int fd = shm_open("/myshm", O_CREAT | O_RDWR, 0666); 
ftruncate(fd, SIZE); 
char *ptr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

2. System V 共享内存(Unix/Linux 平台通用)

arduino 复制代码
#include <sys/shm.h> 
// 主要函数: 
key:唯一标识新创建的共享内存 
size:共享内存的大小,向上取整成PAGE_SIZE的倍数 
shmflg:一些标志信息. 
    IPC_CREAT:根据key判断对应的内存段是否存在,如果不存在,则创建;如果存在,则返回已经存在的共享内存段 
    IPC_EXCL: 和IPC_CREAT一起用,如果已经存在key对应的共享内存则失败 
int shmget(key_t key, size_t size, int shmflg); // 创建/获取(返回根据key生成的shmid) shmat------》映射共享内存到进程的虚拟地址空间,返回映射的虚拟内存段的起始地址 

shmid:共享内存的唯一标识id, shmget返回值。 
shmaddr:内存映射起始地址,如果是NULL的话,内核会帮你分配。 
shmflg:是一组标志位,通常为0 
void *shmat(int shmid, const void *shmaddr, int shmflg); // 映射共享内存 shmdt------》解除映射,如果成功返回0,否则返回-1 shmaddr:内存映射起始地址 

int shmdt(const void *shmaddr); // 解除共享内存映射 

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

Java 封装的共享内存API

1. Java NIO - MappedByteBuffer

java 复制代码
import java.io.RandomAccessFile; 
import java.nio.MappedByteBuffer; 
import java.nio.channels.FileChannel; 

// 创建内存映射文件 
RandomAccessFile file = new RandomAccessFile("file.dat", "rw"); 
FileChannel channel = file.getChannel(); 

// 映射到内存
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 1024); 

// 读写操作 
buffer.put(0, (byte) 42); 
byte value = buffer.get(0);

数据安全问题

在一个"进程"内, 多个线程同时更新共享资源, 有数据并发安全问题,解决方式如下:

  1. 原子操作
  2. 锁机制__管理
  3. 信号量 (并发编程)

多个进程同时更新共享内存(共享资源),也有数据安全问题,解决方式如下:

  1. IPC的信号量 (semaphore)

IPC的信号量

原理思想和【操作系统: 并发编程】中的信号量是一样的, IPC的信号量把共享内存看成临界区,但是二者具体实现完全不一样。

  1. IPC的信号量实现非常复杂,在内核态中实现。 用于多个进程之间
  2. 并发编程的信号量是在用户态实现,基于原子操作实现。用于同一个进程的多个线程之间

下图,展示了进程A, 进程B都对共享内存做写操作,信号量在其中的重要作用,保证临界区"共享内存"同一时间只有一个进程在操作

1.System V ipc 信号量

c 复制代码
#include <sys/types.h>
#include <sys/ipc.h> 
#include <sys/sem.h> 

获取 或 创建 一个信号量集 
key:唯一标识新创建的信号量集 
nsems:信号量集中的 信号量 的 数量 
semflg: 一些标志信息 
    IPC_CREAT:根据key判断对应的共享内存段是否存在,如果不存在则创建,如果存在则返回已经存在的共享内存段 
    IPC_EXCL: 和IPC_CREAT一起用,如果已经存在key对应的共享内存则失败 
int semget(key_t key, int nsems, int semflg) 

信号量的控制操作 
semid: 唯一标识新创建的信号量集 
semnum: 信号量集中 的 第几个信号量(你要对 哪个 信号量 做控制操作) cmd: 设置信号量方式 
int semctl(int semid, int semnum, int cmd, .....) 

信号量操作 
semid: 唯一标识新创建的信号量集 
sops: 对指定信号量执行的一组操作 
nsops: sops的个数 
int semop(int semid, struct sembuf *sops, unsigned nsops)
相关推荐
AI+程序员在路上2 小时前
嵌入式Linux中添加ftp服务器的简易方法
linux·运维·服务器
煤球王子2 小时前
浅学进程间通信3(消息队列)
linux
无垠的广袤2 小时前
【工业树莓派 CM0 NANO 单板计算机】基于舵机和人脸识别的智能门禁系统
linux·python·opencv·yolo·ai·树莓派
ホロHoro3 小时前
数据结构非线性部分(二)review
linux·服务器·数据结构
wang6021252183 小时前
Git部署项目配置密钥-Linux系统
linux·运维·git
xlp666hub3 小时前
链表与它在 Linux 内核中的实现
linux·数据结构
倔强的石头1063 小时前
【Linux指南】进程控制系列(四)进程替换 ——exec 系列函数全解析与应用
linux·运维·bash
悾说3 小时前
xRDP实现Linux图形化通过Windows RDP访问Linux远程桌面
linux·运维·windows
tianyuanwo3 小时前
解决Anolis/CentOS 8下Python 3.11 SELinux模块缺失:从原理到实战的完整指南
linux·centos·python3.11