文章目录
前言
本文主要探讨 linux 下进程间的通信方式之共享内存,最后通过一个简单的示例基于共享内存实现一个父子进程之间数据的读写功能。
1. 共享内存的概念
1.1 什么是共享内存
所谓共享内存就是两个或多个进程地址通过页表映射到同一片物理内存区域,各个进程间可以往这块共同内存区域读写数据以达到进程间的相互通信。因为都是直接在内存中读写数据,避免了数据在进程之间拷贝,因此通信速度非常快。
1.2 linux的内存管理机制
说起共享内存不得不提一下 linux 的内存管理机制了。linux 内存管理是指对系统内存的分配、释放、映射、回收等一系列操作的管理, 今天主要来讲一下内存映射。
1.3 内存映射
所谓的内存映射其实就是将进程的虚拟逻辑内存地址映射到真实的物理内存地址, 其中涉及到了地址转换(将逻辑地址转换为真实的物理地址)
在linux操作系统中所有的进程都共享同一块物理内存,逻辑上各自都拥有一块连续的虚拟内存。当一个进程要访问某个数据的时候会给出该数据的逻辑地址,系统通过地址映射算法将逻辑地址转化成物理地址,再访问物理内存单元中存储的数据。
为了方便逻辑地址到物理地址的映射,大多数操作系统都采用了分页式的内存管理机制,通过页的映射来实现逻辑地址到物理地址的转换。
在页式内存管理机制中,页是内存管理的基本单元,linux系统中一页的默认大小通常为4KB。物理内存也以4KB作为基本单元进行存储,称之为页框。操作系统为每一个进程维护一张表,这张表记录了每一个页到页框的映射关系,这张表称之为页表。当一个进程需要访问某个数据的时候就会通过这张表间接地访问到存储在物理内存的数据了。
2. 共享内存的接口分类
linux下的共享内存接口主要分System V、POSIX和共享文件映射(mmap)三类。
对比项 | System V | POSIX | 共享文件映射(mmap) |
---|---|---|---|
初始化方式 | 需要使用特定的IPC键 | 不需要特定的IPC键 | 基于文件映射 |
内存管理 | 更底层的控制 | 更现代化的接口 | 依赖于文件系统 |
性能 | 较高 | 较高 | 中等(受文件系统影响) |
移植性 | 可能存在兼容性问题 | 更好的可移植性 | 较好的可移植性 |
API复杂度 | 较高 | 中等 | 较低 |
同步机制 | 需要额外实现 | 需要额外实现 | 依赖于文件系统的同步 |
大小限制 | 可能受系统限制 | 更灵活的大小配置 | 受文件大小限制 |
适用场景 | 适用于需要复杂控制的共享内存 | 简单共享内存和进程间通信 | 大文件共享和内存映射 |
本文先来探讨一下System V共享内存。
3. 共享内存的相关操作函数
3.1 ftok函数(获取一个key值)
函数原型 | key_t ftok(const char *pathname, int proj_id); |
功能 | 基于文件路径和proj_id子序号生成一个键值 |
参数 | |
pathname: 文件路径 | |
proj_id: 子序号 | |
返回值 | 成功返回一个键值,失败返回-1并设置errno指明错误的原因 |
3.2 shmget函数(创建或获取一个共享内存描述符)
函数原型 | int shmget(key_t key, size_t size, int shmflg); |
功能 | 创建或者获取一个System V共享内存段并返回一个操作描述符 |
参数 | |
key: 键值,唯一标识一个消息队列,可取由ftok创建的key值或指定的一个非负整数值 | |
size_t: 申请共享内存的大小,一般为页(4k)的整数倍 | |
shmflg: 这是一组标志,用于控制 shmget 函数的行为 | |
返回值 | 成功返回一个共享内存操作符,失败返回-1并设置errno指明错误的原因 |
关于msgflg我已经在上一章介绍消息队列的时候就详细讲过了,这里就不再讲了,可以参考一下msgget函数介绍那段。
传送门:Linux进程间的通信方式(一)System V 消息队列
3.3 shmat函数(映射共享内存地址空间)
函数原型 | void *shmat(int shmid, const void *shmaddr, int shmflg); |
功能 | 将当前进程的地址空间中的一段地址映射到到共享内存段上 |
参数 | |
shmid: 由shmget函数返回的共享内存标识符 | |
shmaddr: 指定共享内存映射的地址,一般写NULL表示让系统自己来选 | |
shmflg: 一组标志位,用于设置该段共享内存的属性,一般写0表示该段共享内存可读可写 | |
返回值 | 成功则返回一个共享内存段映射地址(void*类型), 失败返回(void *) -1类型指针并设置errno指明错误原因 |
3.4 shmdt函数(解除共享内存映射)
函数原型 | int shmdt(const void *shmaddr); |
功能 | 将共享内存从当前进程中分离出来 |
参数 | shmaddr: shmat函数返回的共享内存地址映射地址 |
返回值 | 成功返回0,失败返回-1并设置errno指明错误的原因 |
3.5 shmctl函数(共享内存控制)
函数原型 | int shmctl(int shmid, int cmd, struct shmid_ds *buf); |
功能 | 控制共享内存段 |
参数 | |
shmid: 由shmget函数返回的共享内存标识符 | |
cmd: 控制命令,用于执行指定的操作,比如删除共享内存段、修改权限等 | |
buf: 共享内存管理结构体 | |
返回值 | 成功返回0,失败返回-1并设置errno指明错误的原因 |
关于cmd控制指令常见有以下几种:
控制指令 | 描述 |
---|---|
IPC_STAT | 获取共享内存的状态,把共享内存的shmid_ds结构复制到buf中 |
IPC_SET | 改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内 |
IPC_RMID | 删除共享内存段 |
关于shmid_ds结构体的定义以及具体含义如下:
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Creation time/time of last
modification via shmctl() */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
关于ipc_perm结构体的定义以及具体含义如下:
struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions + SHM_DEST and
SHM_LOCKED flags */
unsigned short __seq; /* Sequence number */
};
4. 共享内存应用示例
4.1 System V 共享内存应用示例代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#define MSG_SIZE 1024
#define SHM_PAGE_SIZE 1024*4
#define SHM_PATH_NAME "/"
#define SHM_PROJ_ID 99
typedef struct {
int msg_type;
char msg[MSG_SIZE];
}MsgFrame;
/* 申请或获取一个共享内存并返回操作标识符 */
int shm_init(const char *path, int proj_id, int shm_pagesize)
{
key_t keyval = ftok(path, proj_id);
int shm_id = shmget(keyval, shm_pagesize, IPC_CREAT | IPC_EXCL | 0666);
if (shm_id < 0) {
if (errno == EEXIST) {
shm_id = shmget(keyval, 0, 0);
printf("the shared memory segment whose key is %d and shmid is %d is already exist!\n", keyval, shm_id);
return shm_id;
} else
perror("shmget");
}
return shm_id;
}
int main(int argc, char *argv[])
{
int ret, shm_id;
char msgbuf[MSG_SIZE];
/*创建或者获取一个共享内存标识符*/
shm_id = shm_init(SHM_PATH_NAME, SHM_PROJ_ID, SHM_PAGE_SIZE);
if (shm_id < 0) {
printf("get share memory fail !\n");
return -1;
}
pid_t pid = fork();
if (pid < 0) {
perror("fork");
return -1;
} else if (pid == 0) { /* child */
printf("I am child,pid:%d,ppid:%d\n", getpid(), getppid());
/*将当前进程地址空间映射到共享内存中*/
MsgFrame *msg_ptr = (MsgFrame *)shmat(shm_id, NULL, 0);
if (msg_ptr == (MsgFrame *)(-1)) {
perror("shmat");
return -1;
}
/*从共享内存中读取数据*/
for (int i = 0; i < 30; i++) {
printf("read,msg_type:%d,msg:%s\n", msg_ptr->msg_type, msg_ptr->msg);
sleep(1);
}
exit(0);
} else if (pid > 0) {
printf("I am father,pid:%d,ppid:%d\n", getpid(), getppid());
/*将当前进程地址空间映射到共享内存中*/
MsgFrame *msg_ptr = (MsgFrame *)shmat(shm_id, NULL, 0);
if (msg_ptr == (MsgFrame *)(-1)) {
perror("shmat");
return -1;
}
/*写入数据到共享内存去*/
for (int i = 0; i < 25; i++) {
msg_ptr->msg_type = i;
memset(msgbuf, 0, sizeof(msgbuf));
sprintf(msgbuf, "number:%d hello world !", i);
memcpy(msg_ptr->msg, msgbuf, strlen(msgbuf));
printf("write,msg_type:%d,msg:%s\n", msg_ptr->msg_type, msg_ptr->msg);
sleep(1);
}
/*阻塞等待子进程结束*/
ret = wait(NULL);
if (ret == -1) {
perror("wait");
return -1;
}
/*将共享内存从当前进程分离出来*/
if (shmdt(msg_ptr) == -1)
perror("shmdt");
/*删除共享内存段*/
if (shmctl(shm_id, IPC_RMID, NULL) == -1)
perror("shmctl");
}
return 0;
}
4.2 应用示例讲解
程序首先通过 shm_get 函数获取到一个共享内存的操作符,接着使用 fork 函数创建出子进程之后父子进程再调用 shmat 函数各自将各自进程的内存地址空间映射到共享内存中去,然后再分别向共享内存中读写数据以达到父子进程间的通信。最后父子进程各自完成数据读写操作之后调用 shmdt 函数解除共享内存映射并在父进程中调用 shmctl 函数删除共享内存。
简而言之,该代码实现了一个基于共享内存实现父子进程之间数据的读写功能。
4.3 共享内存的不足之处
共享内存允许多个进程直接访问同一块内存区域,避免了数据在进程之间拷贝,因此通信速度非常快。由于不需要频繁的数据拷贝和上下文切换,共享内存可以支持高带宽的数据传输,对于需要传输大块数据的应用(如多媒体处理、大文件传输等),共享内存是一种非常适合的方式。
虽然共享内存有这么多的优点,但是它也有存在不足之处。共享内存本身并不提供同步机制, 当多个进程同时访问临界资源的时候就有可能会造成数据的读写冲突从而导致读取到不正确的数据。
那有些什么方法可以弥补这种不足呢?答案当然是有的,比如下一章节要讲到的信号量就可以解决这个问题。
总结
共享内存通信具有高效、适合大数据量传输的优点,但也存在同步复杂、安全性和管理方面的挑战。在选择使用共享内存时,需要综合考虑应用场景和需求,合理设计和实现同步机制,确保数据的一致性和安全性。
标签:映射,int,System,内存,Linux,msg,共享内存,shm From: https://blog.csdn.net/Mr_Jaychong/article/details/139966647