# System V共享内存全解析:从创建到映射的完整实践
在Linux进程通信机制中,共享内存是最快的数据交换方式。System V共享内存作为经典的IPC机制,允许多个进程直接访问同一块物理内存区域。本文将详细解析从shmget到内存映射的完整流程。
## 共享内存的基本原理
共享内存的核心思想是:多个进程通过映射同一块物理内存区域来实现数据共享。这种方式避免了数据在用户空间和内核空间之间的复制,从而获得极高的通信效率。
```c
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
// 共享内存的基本操作流程
void shared_memory_workflow() {
// 1. 生成唯一的key
// 2. 创建/获取共享内存段
// 3. 映射到进程地址空间
// 4. 使用共享内存
// 5. 解除映射
// 6. 删除共享内存段(可选)
}
```
## 键值生成与共享内存创建
### ftok函数:生成System V IPC键值
```c
#include <stdio.h>
#include <stdlib.h>
key_t generate_shared_memory_key(const char *pathname, int proj_id) {
// pathname: 存在的文件路径(通常使用程序文件自身)
// proj_id: 项目标识符(0-255)
key_t key = ftok(pathname, proj_id);
if (key == -1) {
perror("ftok failed");
return -1;
}
printf("Generated key: 0x%08x\n", key);
printf("From path: %s, proj_id: %d\n", pathname, proj_id);
return key;
}
// 使用示例
void demonstrate_ftok() {
// 使用当前程序文件生成key
key_t key1 = generate_shared_memory_key("/proc/self/exe", 'A');
// 使用特定文件生成key
key_t key2 = generate_shared_memory_key("/tmp/shared_mem.lock", 123);
// 注意:相同的pathname和proj_id组合总是生成相同的key
}
```
### shmget:创建或获取共享内存段
```c
// 共享内存创建函数
int create_shared_memory_segment(key_t key, size_t size, int flags) {
// size: 共享内存段大小(字节)
// flags: 权限标志和创建选项
int shmid = shmget(key, size, flags);
if (shmid == -1) {
perror("shmget failed");
return -1;
}
printf("Shared memory segment created/retrieved:\n");
printf(" SHMID: %d\n", shmid);
printf(" Key: 0x%08x\n", key);
printf(" Size: %zu bytes\n", size);
return shmid;
}
// 不同的创建方式
void different_creation_methods() {
key_t key = ftok("/tmp", 'S');
// 方法1:创建新的共享内存段
int shmid1 = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);
// IPC_CREAT: 如果不存在则创建
// IPC_EXCL: 如果已存在则失败
// 0666: 读写权限
// 方法2:获取已存在的共享内存段
int shmid2 = shmget(key, 0, 0); // size为0表示不创建新段
// 方法3:创建私有共享内存(只在进程族内共享)
int shmid3 = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0666);
// 方法4:创建大页共享内存(需要系统支持)
// int shmid4 = shmget(key, 2*1024*1024, IPC_CREAT | SHM_HUGETLB | 0666);
}
```
## 共享内存的映射与使用
### shmat:将共享内存映射到进程空间
```c
#include <sys/shm.h>
// 内存映射函数
void* map_shared_memory(int shmid, int flags) {
// flags参数:
// 0 - 默认,可读可写
// SHM_RDONLY - 只读映射
// SHM_RND - 对齐地址
// SHM_REMAP - 重新映射(Linux特有)
void* addr = shmat(shmid, NULL, flags);
if (addr == (void*)-1) {
perror("shmat failed");
return NULL;
}
printf("Shared memory mapped at address: %p\n", addr);
return addr;
}
// 指定映射地址
void* map_at_specific_address(int shmid, void* desired_addr) {
// 尝试在指定地址映射
void* addr = shmat(shmid, desired_addr, 0);
if (addr == (void*)-1) {
perror("shmat with specific address failed");
return NULL;
}
printf("Mapped at desired address: %p (requested: %p)\n",
addr, desired_addr);
return addr;
}
```
### 完整的映射示例
```c
// 生产者进程:创建并写入共享内存
void shared_memory_producer() {
// 1. 生成key
key_t key = ftok("/tmp/shared_mem_demo", 'P');
// 2. 创建共享内存段(1MB大小)
int shmid = shmget(key, 1024*1024, IPC_CREAT | IPC_EXCL | 0666);
if (shmid == -1) {
if (errno == EEXIST) {
// 已存在,获取现有段
shmid = shmget(key, 0, 0);
} else {
perror("shmget failed");
return;
}
}
// 3. 映射到进程地址空间
void* shared_mem = shmat(shmid, NULL, 0);
if (shared_mem == (void*)-1) {
perror("shmat failed");
return;
}
// 4. 使用共享内存
printf("Producer: Shared memory mapped at %p\n", shared_mem);
// 写入数据结构
typedef struct {
int version;
int data_count;
char data[1024];
} shared_data_t;
shared_data_t* data = (shared_data_t*)shared_mem;
data->version = 1;
data->data_count = 100;
strcpy(data->data, "Hello from producer");
printf("Producer: Wrote version=%d, count=%d\n",
data->version, data->data_count);
// 5. 保持映射,让消费者可以读取
printf("Producer: Press Enter to exit and clean up...\n");
getchar();
// 6. 解除映射
if (shmdt(shared_mem) == -1) {
perror("shmdt failed");
}
// 7. 删除共享内存段
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("shmctl IPC_RMID failed");
} else {
printf("Producer: Shared memory segment removed\n");
}
}
```
## 共享内存同步机制
### 使用信号量同步共享内存访问
```c
#include <sys/sem.h>
// 共享内存同步结构
typedef struct {
int shmid; // 共享内存ID
int semid; // 信号量ID
void* addr; // 映射地址
size_t size; // 大小
} synchronized_shm_t;
// 初始化同步的共享内存
int init_synchronized_shm(synchronized_shm_t* shm, key_t shm_key,
size_t size, int sem_count) {
memset(shm, 0, sizeof(*shm));
shm->size = size;
// 创建共享内存
shm->shmid = shmget(shm_key, size, IPC_CREAT | IPC_EXCL | 0666);
if (shm->shmid == -1 && errno == EEXIST) {
shm->shmid = shmget(shm_key, 0, 0);
}
if (shm->shmid == -1) {
perror("Failed to create/get shared memory");
return -1;
}
// 创建信号量(用于同步)
key_t sem_key = shm_key + 1;
shm->semid = semget(sem_key, sem_count, IPC_CREAT | IPC_EXCL | 0666);
if (shm->semid == -1 && errno == EEXIST) {
shm->semid = semget(sem_key, sem_count, 0);
}
if (shm->semid == -1) {
perror("Failed to create/get semaphore");
// 清理共享内存
shmctl(shm->shmid, IPC_RMID, NULL);
return -1;
}
// 初始化信号量
union semun arg;
for (int i = 0; i < sem_count; i++) {
arg.val = (i == 0) ? 1 : 0; // 第一个信号量作为互斥锁
semctl(shm->semid, i, SETVAL, arg);
}
// 映射共享内存
shm->addr = shmat(shm->shmid, NULL, 0);
if (shm->addr == (void*)-1) {
perror("shmat failed");
shmctl(shm->shmid, IPC_RMID, NULL);
semctl(shm->semid, 0, IPC_RMID);
return -1;
}
printf("Initialized synchronized shared memory:\n");
printf(" SHMID: %d\n", shm->shmid);
printf(" SEMID: %d\n", shm->semid);
printf(" Address: %p\n", shm->addr);
printf(" Size: %zu bytes\n", shm->size);
return 0;
}
// 带锁的共享内存访问
void write_to_synchronized_shm(synchronized_shm_t* shm, const void* data,
size_t offset, size_t size) {
// P操作:等待信号量
struct sembuf op = {0, -1, SEM_UNDO};
semop(shm->semid, &op, 1);
// 写入数据
if (offset + size <= shm->size) {
memcpy((char*)shm->addr + offset, data, size);
} else {
fprintf(stderr, "Write exceeds shared memory boundary\n");
}
// V操作:释放信号量
op.sem_op = 1;
semop(shm->semid, &op, 1);
}
```
## 共享内存控制与监控
### shmctl:共享内存控制操作
```c
#include <sys/shm.h>
// 获取共享内存信息
void print_shared_memory_info(int shmid) {
struct shmid_ds buf;
if (shmctl(shmid, IPC_STAT, &buf) == -1) {
perror("shmctl IPC_STAT failed");
return;
}
printf("\n=== Shared Memory Information ===\n");
printf("Segment ID: %d\n", shmid);
printf("Key: 0x%08x\n", buf.shm_perm.__key);
printf("Size: %lu bytes\n", buf.shm_segsz);
printf("Attached processes: %lu\n", buf.shm_nattch);
printf("Creator PID: %d\n", buf.shm_cpid);
printf("Last attach PID: %d\n", buf.shm_lpid);
printf("Last attach time: %ld\n", buf.shm_atime);
printf("Last detach time: %ld\n", buf.shm_dtime);
printf("Last change time: %ld\n", buf.shm_ctime);
printf("Permissions: %o (owner: %d/%d)\n",
buf.shm_perm.mode,
buf.shm_perm.uid, buf.shm_perm.gid);
// 检查是否被标记为删除
if (buf.shm_perm.mode & SHM_DEST) {
printf("Warning: Segment marked for deletion\n");
}
}
// 修改共享内存属性
int modify_shared_memory_properties(int shmid, uid_t uid, mode_t mode) {
struct shmid_ds buf;
// 首先获取当前状态
if (shmctl(shmid, IPC_STAT, &buf) == -1) {
perror("shmctl IPC_STAT failed");
return -1;
}
<"dsn.h4k7.org.cn"><"abb.h4k7.org.cn"><"avg.h4k7.org.cn">
// 修改权限
buf.shm_perm.uid = uid;
buf.shm_perm.mode = mode;
// 应用修改
if (shmctl(shmid, IPC_SET, &buf) == -1) {
perror("shmctl IPC_SET failed");
return -1;
}
printf("Shared memory properties updated\n");
return 0;
}
// 锁定/解锁共享内存(防止交换)
int lock_shared_memory(int shmid, int lock) {
// lock: 1-锁定, 0-解锁
if (shmctl(shmid, lock ? SHM_LOCK : SHM_UNLOCK, NULL) == -1) {
perror(lock ? "SHM_LOCK failed" : "SHM_UNLOCK failed");
return -1;
}
printf("Shared memory %s\n", lock ? "locked" : "unlocked");
return 0;
}
```
## 高级特性:共享内存与内存映射
### 与传统mmap的比较
```c
#include <sys/mman.h>
#include <fcntl.h>
// 使用mmap创建共享内存(POSIX方式)
void* create_shared_memory_mmap(const char* name, size_t size) {
// 创建共享内存文件描述符
int fd = shm_open(name, O_CREAT | O_RDWR, 0666);
if (fd == -1) {
perror("shm_open failed");
return NULL;
}
// 设置文件大小
if (ftruncate(fd, size) == -1) {
perror("ftruncate failed");
close(fd);
return NULL;
}
// 内存映射
void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
close(fd);
if (addr == MAP_FAILED) {
perror("mmap failed");
return NULL;
}
printf("POSIX shared memory created:\n");
printf(" Name: %s\n", name);
printf(" Size: %zu bytes\n", size);
printf(" Address: %p\n", addr);
return addr;
}
// 比较System V和POSIX共享内存
void compare_shared_memory_methods() {
printf("\n=== System V vs POSIX Shared Memory ===\n");
printf("\nSystem V (shmget):\n");
printf(" - 使用key标识(ftok生成)\n");
printf(" - 独立于文件系统\n");
printf(" - 需要显式删除(shmctl IPC_RMID)\n");
printf(" - 支持大页内存(SHM_HUGETLB)\n");
printf("\nPOSIX (shm_open + mmap):\n");
printf(" - 使用名称标识(/dev/shm/)\n");
printf(" - 基于文件系统\n");
printf(" - 自动清理(可配置)\n");
printf(" - 更符合现代POSIX标准\n");
printf(" - 支持匿名映射\n");
}
```
### 大页共享内存配置
```c
// 大页共享内存示例(需要系统支持)
#ifdef SHM_HUGETLB
void hugepage_shared_memory_demo() {
key_t key = ftok("/tmp", 'H');
// 尝试创建2MB大页共享内存
int shmid = shmget(key, 2*1024*1024,
IPC_CREAT | SHM_HUGETLB | 0666);
if (shmid == -1) {
if (errno == ENOSYS) {
printf("Hugepages not supported by system\n");
} else if (errno == ENOMEM) {
printf("Not enough hugepages available\n");
} else {
perror("shmget with hugepages failed");
}
return;
}
void* addr = shmat(shmid, NULL, 0);
if (addr == (void*)-1) {
perror("shmat failed");
return;
}
printf("Hugepage shared memory created:\n");
printf(" SHMID: %d\n", shmid);
printf(" Size: 2MB\n");
printf(" Address: %p\n", addr);
// 使用大页内存...
shmdt(addr);
shmctl(shmid, IPC_RMID, NULL);
}
#endif
```
## 实际应用:进程间数据交换
### 生产者-消费者模型实现
```c
// 共享内存环形缓冲区
typedef struct {
int head; // 写入位置
int tail; // 读取位置
int count; // 当前数据数量
int capacity; // 缓冲区容量
char data[]; // 柔性数组,实际数据
} ring_buffer_t;
// 通过共享内存创建环形缓冲区
ring_buffer_t* create_shared_ring_buffer(key_t key, int capacity) {
// 计算总大小
size_t total_size = sizeof(ring_buffer_t) + capacity;
// 创建共享内存
int shmid = shmget(key, total_size, IPC_CREAT | IPC_EXCL | 0666);
if (shmid == -1 && errno == EEXIST) {
shmid = shmget(key, 0, 0);
}
if (shmid == -1) {
perror("shmget failed");
return NULL;
}
// 映射到内存
ring_buffer_t* buffer = (ring_buffer_t*)shmat(shmid, NULL, 0);
if (buffer == (void*)-1) {
perror("shmat failed");
return NULL;
}
// 如果是新创建的,初始化缓冲区
struct shmid_ds buf;
if (shmctl(shmid, IPC_STAT, &buf) == 0 && buf.shm_cpid == getpid()) {
buffer->head = 0;
buffer->tail = 0;
buffer->count = 0;
buffer->capacity = capacity;
memset(buffer->data, 0, capacity);
printf("New ring buffer created in shared memory\n");
} else {
printf("Attached to existing ring buffer\n");
}
printf("Ring buffer info:\n");
printf(" Capacity: %d bytes\n", buffer->capacity);
printf(" Current count: %d\n", buffer->count);
printf(" Address: %p\n", buffer);
return buffer;
}
// 向环形缓冲区写入数据
int ring_buffer_write(ring_buffer_t* buffer, const void* data, size_t size) {
if (size > buffer->capacity - sizeof(ring_buffer_t)) {
fprintf(stderr, "Data too large for buffer\n");
return -1;
}
// 检查是否有足够空间
if (buffer->count + size > buffer->capacity) {
fprintf(stderr, "Buffer full\n");
return -1;
}
// 写入数据
int write_pos = buffer->head;
for (size_t i = 0; i < size; i++) {
buffer->data[write_pos] = ((char*)data)[i];
write_pos = (write_pos + 1) % buffer->capacity;
}
// 更新头部指针
buffer->head = write_pos;
buffer->count += size;
return 0;
}
// 从环形缓冲区读取数据
int ring_buffer_read(ring_buffer_t* buffer, void* data, size_t size) {
if (size > buffer->count) {
fprintf(stderr, "Not enough data in buffer\n");
return -1;
}
// 读取数据
int read_pos = buffer->tail;
for (size_t i = 0; i < size; i++) {
((char*)data)[i] = buffer->data[read_pos];
read_pos = (read_pos + 1) % buffer->capacity;
}
// 更新尾部指针
buffer->tail = read_pos;
buffer->count -= size;
return 0;
}
```
## 系统管理工具
```bash
#!/bin/bash
# shared_memory_manager.sh - 共享内存管理工具
# 显示共享内存信息
show_shm_info() {
echo "=== System V Shared Memory ==="
ipcs -m
echo -e "\n=== POSIX Shared Memory (/dev/shm/) ==="
ls -la /dev/shm/ 2>/dev/null || echo "No POSIX shared memory"
}
# 清理共享内存
cleanup_shm() {
local shmid=$1
<"avr.h4k7.org.cn"><"evs.h4k7.org.cn"><"vrb.h4k7.org.cn">
if [ -z "$shmid" ]; then
echo "Usage: $0 cleanup <shmid>"
return 1
fi
# 检查共享内存段是否存在
if ipcs -m | grep -q " $shmid "; then
# 获取附加的进程数
nattch=$(ipcs -m -i $shmid | grep "attached" | awk '{print $NF}')
if [ "$nattch" -eq 0 ]; then
ipcrm -m $shmid
echo "Shared memory segment $shmid removed"
else
echo "Cannot remove: $nattch processes still attached"
echo "Attached processes:"
ipcs -m -i $shmid | grep -A 20 "attached"
fi
else
echo "Shared memory segment $shmid not found"
fi
}
# 监控共享内存使用
monitor_shm_usage() {
watch -n 2 'echo "=== Shared Memory Usage ===";
echo -e "SHMID\t\tSize\t\tAttached\tKey";
ipcs -m | tail -n +4 | awk '\''{printf "%-10s %-12s %-10s %-10s\n", $2, $5, $6, $1}'\'''
}
# 主函数
main() {
case "$1" in
show)
show_shm_info
;;
cleanup)
cleanup_shm "$2"
;;
monitor)
monitor_shm_usage
;;
*)
echo "Usage: $0 {show|cleanup <shmid>|monitor}"
exit 1
;;
esac
}
main "$@"
```
## 性能优化与最佳实践
### 内存对齐与缓存优化
```c
// 优化共享内存数据结构
typedef struct __attribute__((aligned(64))) {
// 64字节对齐,避免缓存行伪共享
_Atomic int write_index;
char padding1[64 - sizeof(_Atomic int)];
_Atomic int read_index;
char padding2[64 - sizeof(_Atomic int)];
volatile int data_ready;
char padding3[64 - sizeof(volatile int)];
char data[4096];
} cache_optimized_shm_t;
// 使用示例
void use_optimized_structure(key_t key) {
int shmid = shmget(key, sizeof(cache_optimized_shm_t),
IPC_CREAT | 0666);
if (shmid == -1) return;
cache_optimized_shm_t* shm = (cache_optimized_shm_t*)shmat(shmid, NULL, 0);
if (shm == (void*)-1) return;
// 不同字段位于不同缓存行,减少伪共享
atomic_store(&shm->write_index, 0);
atomic_store(&shm->read_index, 0);
shm->data_ready = 0;
shmdt(shm);
}
```
### 错误处理与资源管理
```c
// 健壮的共享内存处理封装
typedef struct {
int shmid;
void* addr;
int is_owner;
} shm_handle_t;
int create_shm_handle(shm_handle_t* handle, key_t key,
size_t size, int create_new) {
memset(handle, 0, sizeof(*handle));
int flags = 0666;
if (create_new) {
flags |= IPC_CREAT | IPC_EXCL;
handle->is_owner = 1;
}
handle->shmid = shmget(key, size, flags);
if (handle->shmid == -1) {
perror("shmget failed");
return -1;
}
handle->addr = shmat(handle->shmid, NULL, 0);
if (handle->addr == (void*)-1) {
perror("shmat failed");
// 如果是创建者,清理共享内存
if (handle->is_owner) {
shmctl(handle->shmid, IPC_RMID, NULL);
}
return -1;
}
return 0;
}
void destroy_shm_handle(shm_handle_t* handle) {
if (handle->addr && handle->addr != (void*)-1) {
shmdt(handle->addr);
}
if (handle->is_owner && handle->shmid != -1) {
// 检查是否还有其他进程附加
struct shmid_ds buf;
if (shmctl(handle->shmid, IPC_STAT, &buf) == 0) {
if (buf.shm_nattch == 0) {
shmctl(handle->shmid, IPC_RMID, NULL);
} else {
fprintf(stderr, "Warning: %lu processes still attached\n",
buf.shm_nattch);
}
}
}
memset(handle, 0, sizeof(*handle));
}
```
## 总结与选择指南
System V共享内存是Linux IPC中的重要组成部分,具有以下特点:
**优势**:
- 极高的数据传输性能(无需内核复制)
- 支持大内存区域
- 独立于文件系统
- 成熟的同步机制配合
**劣势**:
- 需要显式同步机制
- 资源管理复杂
- 可能产生内存泄漏
- 现代系统中POSIX替代方案更受欢迎
**适用场景**:
- 需要高性能进程间通信
- 大量数据交换
- 实时系统
- 遗留系统维护
**最佳实践建议**:
1. 始终实现适当的同步机制(信号量等)
2. 使用`IPC_EXCL`标志避免竞争条件
3. 实现完善的错误处理和资源清理
4. 考虑使用大页内存提高性能
5. 监控共享内存使用情况
虽然现代Linux更推荐使用POSIX共享内存,但理解System V共享内存的工作原理仍然重要。它不仅有助于维护遗留系统,还能加深对Linux内存管理和进程通信的理解。在实际项目中,应根据具体需求选择最合适的共享内存实现方式。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/19545/viewspace-3110104/ ,如需转载,请注明出处,否则将追究法律责任。