当存在客户-服务程序中复制文件时候,其数据流如下,要经历四次数据复制,开销很大。具体如下:
进程调用read或是write后会陷入内核,因为这两个函数都是系统调用,进入系统调用后,内核开始读写文件,
如果采用共享内存的方式,那么将大大优化IO操作,数据流变成了如下,数据只复制两次,[1]:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。
虚拟内存
上), 通过对这段内存的读取和修改,实现对文件的读取和修改;访问同一个逻辑内存
不提供同步功能
(需要我们信号量实现)。mmap参考链接
这里简单对mmap() 和 shmget()基本原理简单说明
mmap() 基本原理
:可以把一个文件映射到进程的地址空间
(进程使用的虚拟内存),这样进程就可以通过读写这个进程地址空间来读写这个文件。
普通文件
实现共享内存shmget() 基本原理
:内核里存在着一个特殊的文件系统(shm)
,这个文件系统的存储介质不是别的,正是 RAM。
shmget()
调用之后,系统会为你在这个文件系统上创建一个文件
,但是这个时候仅仅是创建了这个文件。shmat()
了,调用 shmat()
之后,内核会这个文件映射到你的进程地址空间,这个时候你就能直接读写映射后的地址了。总结
使用流程
共享的数据
被放在一个叫做IPC共享内存区域
的地方,所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中去。shmget
获得或创建一个IPC共享内存区域
,并返回相应的标识符
。shmget
获得或创建一个共享内存区,初始化该共享内存区相应的shmid_kernel
结构体特殊文件系统shm
中,创建并打开一个同名文件,并在内存中建立起该文件的相应dentry
及inode结构
,新打开的文件不属于任何一个进程(任何进程都可以访问该共享内存区)。所有这一切都是系统调用shmget
完成的。(下面的图结合上面的流程,理解起来可能稍微容易一些)
基本原理
每一个共享内存区都有一个控制结构struct shmid_kernel
,shmid_kernel
是共享内存区域中非常重要的一个数据结构,它是存储管理和文件系统结合起来的桥梁。
struct shmid_kernel /* private to the kernel */
{
struct kern_ipc_perm shm_perm;
struct file * shm_file;
int id;
unsigned long shm_nattch;
unsigned long shm_segsz;
time_t shm_atim;
time_t shm_dtim;
time_t shm_ctim;
pid_t shm_cprid;
pid_t shm_lprid;
};
该结构中最重要的一个域应该是shm_file
,它存储了将被映射文件的地址。
内核通过数据结构struct ipc_ids shm_ids维护系统中的所有共享内存区域。
1、上图中的shm_ids.entries
变量指向一个ipc_id结构数组
,而每个ipc_id
结构数组中有个指向kern_ipc_perm结构的指针
。
2、对于系统V共享内存区来说,kern_ipc_perm的宿主是shmid_kernel结构
,shmid_kernel
是用来描述一个共享内存区域的,这样内核就能够控制系统中所有的共享区域。
3、在shmid_kernel
结构的file类型指针shm_file
指向文件系统shm中相应的文件
,这样,共享内存区域就与shm文件系统中的文件对应起来。
#include
int shmget(key_t key, size_t size, int shmflg);
返回值:
成功时返回一个新建或已经存在的的共享内存标识符,取决于shmflg的参数。失败返回-1并设置错误码。
参数说明:
key
:非0整数,它有效地为共享内存段命名,shmget()函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.size
:需要申请共享内存的大小。在操作系统中,申请内存的最小单位为页,一页是4k字节,为了避免内存碎片,我们一般申请的内存大小为页的整数倍。shmflg
:如果要创建新的共享内存,需要使用IPC_CREAT,IPC_EXCL,如果是已经存在的,可以使用IPC_CREAT或直接传0。
0644
,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。第一次创建完共享内存时,它还不能被任何进程访问,shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:
void *shmat(int shm_id, const void *shm_addr, int shmflg);
返回值:
调用成功时返回一个指向共享内存第一个字节的指针
,并且内核将使其与该共享存储段相关的shmid_ds结构中的shm_nattch计数器加1(类似于引用计数);出错返回-1。
参数说明:
shm_id
:由shmget()函数返回的共享内存标识。shm_addr
:指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址shm_flg
:是一组标志位,通常为0该函数用于将共享内存从当前进程中分离
。注意,将共享内存分离并不是删除它
,只是使该共享内存对当前进程不再可用。它的原型如下:
int shmdt(const void *shmaddr);
返回值:
调用成功时返回0,失败时返回-1.
参数说明:
shmaddr
:shmat()函数返回的地址指针int shmctl(int shmid, int cmd, struct shmid_ds *buf);
返回值:
成功返回0,失败返回-1。
参数说明:
shm_id
:shmget()函数返回的共享内存标识符。command
:要采取的操作,它可以取下面的三个值 :
IPC_RMID
:删除共享内存段buf
:一个结构指针,它指向共享内存模式和访问权限的结构,设置为NULL
即可。实例:进程间通信
shmread.c
:创建共享内存,并读取其中的信息,
shmwrite.c
:向共享内存中写入数据。
shmdata.h
:定义结构shared_use_st
中的written
作为一个可读或可写的标志,非0:表示可读,0:表示可写,text则是内存中的文件。
shmdata.h的源代码
#ifndef _SHMDATA_H_HEADER
#define _SHMDATA_H_HEADER
#define TEXT_SZ 2048
struct shared_use_st
{
int written; // 作为一个标志,非0:表示可读,0:表示可写
char text[TEXT_SZ]; // 记录写入 和 读取 的文本
};
#endif
shmread.c的源代码
#include
#include
#include
#include
#include
#include
#include "shmdata.h"
int main(int argc, char **argv)
{
void *shm = NULL;
struct shared_use_st *shared; // 指向shm
int shmid; // 共享内存标识符
// 创建共享内存
shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
if (shmid == -1)
{
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
// 将共享内存连接到当前进程的地址空间
shm = shmat(shmid, 0, 0);
if (shm == (void *)-1)
{
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
printf("\nMemory attached at %X\n", (int)shm);
// 设置共享内存
shared = (struct shared_use_st*)shm; // 注意:shm有点类似通过 malloc() 获取到的内存,所以这里需要做个 类型强制转换
shared->written = 0;
while (1) // 读取共享内存中的数据
{
// 没有进程向内存写数据,有数据可读取
if (shared->written == 1)
{
printf("You wrote: %s", shared->text);
sleep(1);
// 读取完数据,设置written使共享内存段可写
shared->written = 0;
// 输入了 end,退出循环(程序)
if (strncmp(shared->text, "end", 3) == 0)
{
break;
}
}
else // 有其他进程在写数据,不能读取数据
{
sleep(1);
}
}
// 把共享内存从当前进程中分离
if (shmdt(shm) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
// 删除共享内存
if (shmctl(shmid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "shmctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
shmwrite.c的源代码
#include
#include
#include
#include
#include
#include "shmdata.h"
int main(int argc, char **argv)
{
void *shm = NULL;
struct shared_use_st *shared = NULL;
char buffer[BUFSIZ + 1]; // 用于保存输入的文本
int shmid;
// 创建共享内存
shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
if (shmid == -1)
{
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
// 将共享内存连接到当前的进程地址空间
shm = shmat(shmid, (void *)0, 0);
if (shm == (void *)-1)
{
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attched at %X\n", (int)shm);
// 设置共享内存
shared = (struct shared_use_st *)shm;
while (1) // 向共享内存中写数据
{
// 数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本
while (shared->written == 1)
{
sleep(1);
printf("Waiting...\n");
}
// 向共享内存中写入数据
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
strncpy(shared->text, buffer, TEXT_SZ);
// 写完数据,设置written使共享内存段可读
shared->written = 1;
// 输入了end,退出循环(程序)
if (strncmp(buffer, "end", 3) == 0)
{
break;
}
}
// 把共享内存从当前进程中分离
if (shmdt(shm) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
sleep(2);
exit(EXIT_SUCCESS);
}
1、程序shmread创建共享内存
,然后将它连接到自己的地址空间。
struct_use_st
。该结构中有个标志written
:
written被设置为0
,程序等待。2、程序shmwrite取得共享内存并连接到自己的地址空间
。
written,是否为0
不为0
,表示共享内存中的数据还没有被完,则等待其他进程读取完成,并提示用户等待。为0
,表示没有其他进程对共享内存进行读取,则提示用户输入文本,written为1
,表示写完成,其他进程可对共享内存进行读操作
。linux中的两种共享内存。一种是我们的IPC通信System V版本的共享内存(shm),另外的一种就是内存映射I/O(mmap函数)
mmap()
通过映射一个普通文件
实现共享内存,shmget()
:内核里存在着一个特殊的文件系统(shm)
,这个文件系统的存储介质不是别的,正是 RAM。
速度比较
:shm保存在RAM,这样读写的速度要比磁盘要快,但是存储量不是特别大。mmap是在磁盘上建立一个文件安全比较
: mmap把文件保存在磁盘上,这个文件还保存了操作系统同步的映像,所以mmap不会丢失,但是shmget就会丢失。文件-内存的映射
。1、https://www.cnblogs.com/huxiao-tee/p/4660352.html
2、https://blog.csdn.net/mj813/article/details/52082499
3、https://www.cnblogs.com/52php/p/5861372.html
4、https://blog.csdn.net/woaiclh13/article/details/106409361