IPC 进程间通信方式:共享内存
原理
共享内存是最高效的进程间通信方式之一,因为它允许两个或多个进程直接访问同一块物理内存区域。这种机制避免了数据在用户空间和内核空间之间的频繁拷贝,从而显著提高了数据传输的效率。
在Linux系统中,共享内存区域由内核管理,但可以由多个进程映射到它们各自的地址空间中。这样,进程就可以像访问本地内存一样直接读写这块共享内存区域。
操作流程
-
产生Key值:
使用ftok
函数根据给定的路径名和项目ID生成一个唯一的键值(key),这个键值将用于后续的IPC对象操作。 -
申请共享内存:
使用shmget
函数根据键值申请一块共享内存区域。如果申请成功,该函数会返回一个共享内存标识符(shmid)。 -
映射共享内存:
使用shmat
函数将共享内存区域映射到进程的地址空间中,以便进程可以直接访问这块内存。 -
访问共享内存:
进程通过映射得到的地址直接读写共享内存中的数据。 -
解除映射:
使用shmdt
函数解除共享内存与进程地址空间的映射关系。 -
销毁共享内存:
当不再需要共享内存时,可以使用shmctl
函数并指定IPC_RMID
命令来删除这块共享内存。
1. ftok
- 生成key值
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
- 功能:将路径名
pathname
和工程IDproj_id
转换为唯一的key值。 - 参数:
pathname
:一个路径名。proj_id
:工程ID,通常是ASCII字符。
- 返回值:成功时返回唯一的key值,失败时返回-1。
2. shmget
- 申请共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
- 功能:使用唯一键值
key
向内核提出共享内存使用申请。 - 参数:
key
:唯一键值。size
:要申请的共享内存大小。shmflg
:访问权限和创建标志(如IPC_CREAT、IPC_EXCL)。
- 返回值:成功时返回共享内存ID,失败时返回-1。
3. shmat
- 映射共享内存
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
- 功能:将指定
shmid
对应的共享内存映射到本地内存。 - 参数:
shmid
:共享内存ID。shmaddr
:本地地址,通常为NULL表示由系统自动分配。shmflg
:访问权限(如0表示读写,SHM_RDONLY表示只读)。
- 返回值:成功时返回映射的地址,失败时返回(void*)-1。
4. shmdt
- 撤销共享内存映射
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
功能:将本地内存与共享内存断开映射关系。- 参数:
shmaddr
:要断开的映射地址。 - 返回值:成功时返回0,失败时返回-1。
5. shmctl
- 控制共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 功能:修改共享内存属性或删除共享内存对象。
- 参数:
shmid
:共享内存ID。cmd
:操作命令(如IPC_RMID表示删除对象)。buf
:指向shmid_ds
结构的指针,用于传递信息或NULL(仅删除对象时)。
- 返回值:成功时返回0,失败时返回-1。
6.示例
假设我们有两个进程a.out
和b.out
,它们需要通过共享内存交换一个pid_t
类型的进程ID。
a.out(写进程)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
key_t key = ftok("/tmp", 'A');
int shmid = shmget(key, sizeof(pid_t), IPC_CREAT | 0666);
if (shmid == -1)
{
perror("shmget");
exit(EXIT_FAILURE);
}
void *shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == (void *)-1)
{
perror("shmat");
exit(EXIT_FAILURE);
}
pid_t *pid_ptr = shmaddr;
*pid_ptr = getpid(); // 将当前进程的PID写入共享内存
shmdt(shmaddr);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
b.out(读进程)
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
key_t key = ftok("/tmp", 'A');
int shmid = shmget(key, sizeof(pid_t), 0666);
if (shmid == -1)
{
perror("shmget");
exit(EXIT_FAILURE);
}
void *shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == (void *)-1)
{
perror("shmat");
exit(EXIT_FAILURE);
}
pid_t *pid_ptr = shmaddr;
printf("Received PID: %d\n", *pid_ptr);
shmdt(shmaddr);
return 0;
}
7.总结
-
共享内存数据的存储方式是拷贝还是剪切?
答:在共享内存的情况下,数据是直接访问的,不涉及拷贝或剪切操作。进程直接通过映射的地址访问物理内存区域。 -
共享内存的数据如果多次不同进程读写会怎么样?
答:如果多个进程读写同一块共享内存区域,并且没有适当的同步机制(如信号量),那么可能会出现数据竞争和覆盖的情况