Linux进程编程(4)共享内存
共享内存是什么
共享内存是一种进程间通信机制,允许多个进程共享同一段内存区域,以便实现高效的数据交换。这些进程可以是同一台计算机上的不同线程、父子进程,也可以是不同计算机上的网络进程。
为什么要共享内存:
- 高效数据传输: 共享内存允许进程之间直接访问相同的内存区域,因此数据传输效率高,适用于大量数据的快速传递。
- 避免数据拷贝: 与其他通信方式(如管道、消息队列)相比,共享内存避免了数据的多次拷贝,提高了性能。
- 实时通信: 共享内存支持实时通信,特别适用于需要快速响应的场景。
- 资源共享: 进程可以在共享内存中存储共享的数据结构、缓冲区等,方便多个进程访问和操作。
一、用到的函数及其作用
函数原型、返回值、作用和参数说明:
-
key_t ftok(const char *pathname, int proj_id);
- 返回值:若成功,返回一个键值(key_t类型),若失败,返回-1。
- 作用:用于生成一个键值,该键值可以用于标识共享内存、消息队列等进程间通信的资源。
- 参数:
pathname
是一个字符串,表示生成键值的文件路径。proj_id
是一个整数,用于进一步区分不同的资源。
-
int shmget(key_t key, size_t size, int shmflg);
- 返回值:若成功,返回共享内存标识符(int类型);若失败,返回-1。
- 作用:创建或获取一个共享内存段。
- 参数:
key
是键值,用于标识共享内存。size
是共享内存段的大小(以字节为单位)。shmflg
是标志位,指定共享内存的权限和操作方式。
-
void *shmat(int shmid, const void *shmaddr, int shmflg);
- 返回值:若成功,返回附加的共享内存段的指针(
void *
类型);若失败,返回-1。 - 作用:将共享内存段附加到进程的地址空间,使得进程可以访问共享内存中的数据。
- 参数:
shmid
是共享内存标识符。shmaddr
通常设置为NULL,让系统选择适当的地址。shmflg
是标志位,指定共享内存的操作方式。
- 返回值:若成功,返回附加的共享内存段的指针(
-
int shmdt(const void *shmaddr);
- 返回值:若成功,返回0;若失败,返回-1。
- 作用:将附加的共享内存段从进程的地址空间中分离,不再能够访问共享内存的数据。
- 参数:
shmaddr
是共享内存段的指针,通常是shmat()
返回的指针。
-
pid_t fork(void);
- 返回值:若成功,返回子进程的PID(正整数)给父进程,返回0给子进程;若失败,返回-1。
- 作用:创建一个新的子进程,子进程是父进程的复制,从调用
fork()
的地方开始执行。 - 参数:无。
-
int wait(int *status);
- 返回值:若成功,返回结束的子进程的PID;若失败,返回-1。
- 作用:等待任何一个子进程结束,并返回结束的子进程的PID。如果
status
不为NULL,会将子进程的退出状态存储在status
中。 - 参数:
status
是一个指向整数的指针,用于存储子进程的退出状态。
-
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 返回值:若成功,返回0;若失败,返回-1。
- 作用:用于控制和查询共享内存的信息,如删除共享内存。
- 参数:
shmid
是共享内存标识符。cmd
是控制命令,可以是IPC_RMID
表示删除共享内存。buf
是一个指向struct shmid_ds
结构的指针,用于存储共享内存的信息。
二、实现代码
1.共享内存实现代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main() {
int shmid; // 共享内存标识符
key_t key = ftok(".", 123); // 使用ftok生成键值,用于标识共享内存
shmid = shmget(key, sizeof(int), IPC_CREAT | 0666); // 创建共享内存段
int *data = (int*)shmat(shmid, NULL, 0); // 将共享内存附加到进程地址空间
*data = 42; // 向共享内存写入数据
pid_t pid = fork(); // 创建子进程
if (pid < 0) {
fprintf(stderr, "进程创建失败\n");
return 1;
} else if (pid == 0) { // 子进程
printf("子进程读取共享数据:%d\n", *data);
shmdt(data); // 分离共享内存
} else { // 父进程
wait(NULL); // 等待子进程结束
printf("父进程读取共享数据:%d\n", *data);
shmdt(data); // 分离共享内存
shmctl(shmid, IPC_RMID, NULL); // 删除共享内存
}
return 0;
}
代码逻辑和实现目的:
1. 导入必要的头文件,包括对进程、共享内存和其他系统调用函数的声明。
2. 使用ftok()函数生成一个键值,这个键值将用于标识共享内存区域。
3. 使用shmget()函数创建一个共享内存段。IPC_CREAT标志表示如果共享内存不存在则创建,0666指定了权限。
4. 使用shmat()函数将共享内存段附加到当前进程的地址空间,返回一个指向共享内存的指针。
5. 向共享内存中写入数据,将整数值42存储在共享内存中。
6. 使用fork()创建一个子进程,子进程将继承父进程的地址空间和共享内存。
7. 在父进程中,如果fork()成功,则返回子进程的进程ID(PID),如果失败则输出错误信息。
8. 在子进程中,通过共享内存指针读取并输出共享内存中的数据,然后使用shmdt()函数分离共享内存。
9. 在父进程中,使用wait()函数等待子进程结束,确保在父进程输出共享内存数据前,子进程已完成。
10. 在父进程中,再次使用shmdt()函数分离共享内存,然后使用shmctl()函数删除共享内存。
这个程序的目的是演示如何使用共享内存在父子进程之间共享数据。父进程创建了一个共享内存区域,并向其中写入了数据。然后它创建了一个子进程,子进程从共享内存中读取数据并输出。父进程等待子进程结束,然后也从共享内存中读取数据并输出。最后,父进程删除了共享内存区域,确保资源的释放。这个实例展示了进程之间如何共享数据,以及如何使用共享内存的系统调用。
2.使用共享内存进行进程间通信时的逻辑
当使用共享内存进行进程间通信时,通常的逻辑如下:
-
生成键值(key): 使用
ftok()
函数生成一个键值,这个键值将用于标识共享内存区域。这个键值需要与其他进程间通信方式(如消息队列)共享同一个键值,以确保通信能够建立。 -
创建共享内存: 使用
shmget()
函数创建一个共享内存段。为共享内存指定大小,并使用标志位来设置权限和操作方式。如果共享内存已经存在,可以通过键值获取已经创建的共享内存。 -
附加共享内存: 使用
shmat()
函数将共享内存段附加到进程的地址空间,以便进程可以访问其中的数据。需要指定共享内存标识符和一些标志。 -
读写共享内存: 进程可以通过访问共享内存中的数据进行读写操作。这里的读写不需要特殊的函数,直接通过指针就可以进行。
-
分离共享内存: 使用
shmdt()
函数将共享内存段从进程的地址空间中分离,不再能够访问共享内存的数据。这是一个重要的步骤,以免引发访问非法内存的问题。 -
删除共享内存(可选): 使用
shmctl()
函数删除共享内存段。这是一个可选步骤,如果不需要共享内存了,可以通过这个函数将其清理。
总体来说,共享内存的逻辑是:生成键值、创建共享内存、附加共享内存、读写共享内存、分离共享内存,以及可选的删除共享内存。这个逻辑允许进程在不同的时间、不同的空间中进行数据共享,以实现进程间的高效通信。
总结
共享内存是进程间高效通信的一种方式,适用于需要大量数据传输、实时通信和资源共享的场景。在代码中,通过共享内存,父子进程可以共享数据,通过控制函数和适当的顺序,实现了数据的交换和处理。注意要小心处理同步和互斥,以避免数据竞争等问题。