浅学进程间通信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)
相关推荐
碎梦归途3 小时前
思科网络设备配置命令大全,涵盖从交换机到路由器的核心配置命令
linux·运维·服务器·网络·网络协议·路由器·交换机
小天源3 小时前
nginx在centos7上热升级步骤
linux·服务器·nginx
AZ996ZA4 小时前
自学linux第十八天:【Linux运维实战】系统性能优化与安全加固精要
linux·运维·安全·性能优化
大虾别跑4 小时前
OpenClaw已上线:我的电脑开始自己打工了
linux·ai·openclaw
weixin_437044645 小时前
Netbox批量添加设备——堆叠设备
linux·网络·python
hhy_smile5 小时前
Ubuntu24.04 环境配置自动脚本
linux·ubuntu·自动化·bash
宴之敖者、6 小时前
Linux——\r,\n和缓冲区
linux·运维·服务器
LuDvei6 小时前
LINUX错误提示函数
linux·运维·服务器
未来可期LJ6 小时前
【Linux 系统】进程间的通信方式
linux·服务器
Abona6 小时前
C语言嵌入式全栈Demo
linux·c语言·面试