Linuxの2種類の共有メモリ。1つはIPC通信システムVバージョンの共有メモリで、もう1つは本日説明したメモリマップドI / O(mmap関数)です。
mmapについて説明する前に、通常のファイルの読み取りと書き込みの原理について説明しましょう。プロセスが読み取りまたは書き込みを呼び出した後、これら2つの関数はシステムコールであるため、カーネルに分類されます。システムコールを入力すると、カーネルは次のことを開始します。カーネルを想定したファイルの読み取りと書き込みファイルを読み取るとき、カーネルは最初にファイルを独自のカーネルスペースに読み取ります。読み取り後、プロセスはカーネルのユーザーモードに戻ります。カーネルは、カーネルメモリに読み取られたデータをにコピーします。プロセスのユーザーモードメモリスペース。実際、同じファイルの内容は、最初にカーネルスペースに読み込まれ、次にカーネルスペースからユーザースペースに読み込まれる2回の読み取りに相当します。
Linuxは、ファイルの内容をメモリのセグメント(正確には仮想メモリ)にマップするメモリマッピング関数mmapを提供します。このメモリのセグメントを読み取って変更することにより、ファイルを読み取って変更できます。mmap()システム呼び出しにより、プロセスは共通ファイルをマッピングすることでメモリを共有できます。通常のファイルがプロセスのアドレス空間にマップされた後、プロセスは、他のシステムコール(読み取り、書き込み)を実行せずに、メモリにアクセスする方法でファイルにアクセスできます。
mmap図の例:
mmapシステムコールの概要
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
これは、mmapシステムによって呼び出されるインターフェイスです。mmap関数は、メモリ領域へのポインタを正常に返します。グラフ上のプロセスのアドレス空間の開始アドレスは、mmap関数の戻り値です。失敗した場合は、MAP_FAILEDを返します。
addr、特定のアドレスが開始アドレスとして使用されます。NULLに設定されている場合、システムはアドレス空間内の適切なメモリ領域を選択します。
lengthは、メモリセグメントの長さです。
protは、メモリセグメントのアクセス許可を設定するために使用されます。
fdパラメータは、マップされたファイルに対応するファイル記述子です。オープンシステムコールによって取得されます。オフセットは、どこからマップするかを設定します。
mmapを使用する際に注意が必要な事項:
非血液プロセス間通信コードにはmmapを使用します。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/mman.h>
#include<string.h>
struct STU
{
int age;
char name[20];
char sex;
};
int main(int argc,char *argv[]) //这个进程用于创建映射区进行写。
{
if(argc != 2)
{
printf("./a,out file");
exit(1);
}
struct STU student = {
10,"xiaoming",'m'};
int fd = open(argv[1],O_RDWR|O_CREAT|O_TRUNC,0644);
if(fd < 0)
{
perror("open");
exit(2);
}
ftruncate(fd,sizeof(struct STU)); //文件拓展大小。
struct STU *p = (struct STU*)mmap(NULL,sizeof(struct STU),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//创建一个结构体大小的共享映射区。共享映射区我们可以当做数组区看待。
if(p == MAP_FAILED)
{
perror("mmap");
exit(3);
}
close(fd); //关闭不用的文件描述符。
while(1)
{
memcpy(p,&student,sizeof(student));
student.age++;
sleep(1);
}
int ret = munmap(p,sizeof(student));
if(ret < 0)
{
perror("mmumap");
exit(4);
}
return 0;
}
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/mman.h>
struct STU
{
int age;
char name[20];
char sex;
};
int main(int argc,char *argv[]) //这个进程读
{
if(argc != 2)
{
printf("./a,out file");
exit(1);
}
int fd = open(argv[1],O_RDONLY,0644);
if(fd < 0)
{
perror("open");
exit(2);
}
struct STU student;
struct STU *p = (struct STU*)mmap(NULL,sizeof(struct STU),PROT_READ,MAP_SHARED,fd,0);
if(p == MAP_FAILED)
{
perror("mmap");
exit(3);
}
close(fd);
int i = 0;
while(1)
{
printf("id = %d\tname = %s\t%c\n",p->age,p->name,p->sex);
sleep(2);
}
int ret = munmap(p,sizeof(student));
if(ret < 0)
{
perror("mmumap");
exit(4);
}
return 0;
演算結果:
解析:構造体サイズの共有メモリのみが作成されるため、後で書き込んだデータが先に書き込んだデータを上書きします。
shm共有メモリの複数のプロセスのアドレス空間が同じ物理メモリにマップされます。複数のプロセスがこの物理メモリを見ることができます。共有メモリは、データをコピーせずにサーバープロセスとクライアントプロセス間の通信に提供できます。したがって、最速です。
shmメモリモデル図:
内核为每一个共享内存段维护着一个特殊的数据结构,就是shmid_ds,这个结构在include/linux/shm.h中定义
如下:
ipc_perm 结构定义于中,原型如下:
struct ipc_perm
{
key_t key; 调用shmget()时给出的关键字
uid_t uid; /*共享内存所有者的有效用户ID */
gid_t gid; /* 共享内存所有者所属组的有效组ID*/
uid_t cuid; /* 共享内存创建 者的有效用户ID*/
gid_t cgid; /* 共享内存创建者所属组的有效组ID*/
unsigned short mode; /* Permissions + SHM_DEST和SHM_LOCKED标志*/
unsignedshort seq; /* 序列号*/
};
struct shmid_ds{
struct ipc_perm shm_perm;/* 操作权限*/
int shm_segsz; /*段的大小(以字节为单位)*/
time_t shm_atime; /*最后一个进程附加到该段的时间*/
time_t shm_dtime; /*最后一个进程离开该段的时间*/
time_t shm_ctime; /*最后一个进程修改该段的时间*/
unsigned short shm_cpid; /*创建该段进程的pid*/
unsigned short shm_lpid; /*在该段上操作的最后1个进程的pid*/
short shm_nattch; /*当前附加到该段的进程的个数*/
/*下面是私有的*/
unsigned short shm_npages; /*段的大小(以页为单位)*/
unsigned long *shm_pages; /*指向frames->SHMMAX的指针数组*/
struct vm_area_struct *attaches; /*对共享段的描述*/
};
- Shmget関数プロトタイプ
Linux環境では、最初に適用された共有メモリ空間が初期化され、初期値は0x00です。
shmgetを使用して新しいメッセージキューオブジェクトを作成する場合、shmid_ds構造体メンバー変数の値は次のように設定されます。
Ÿshm_lpid、shm_nattach、shm_atime、shm_dtimeは0に設定されます。
Ÿmsg_ctimeは現在の時刻に設定されます。
Ÿshm_segszは、共有メモリのサイズに設定されます。
Ÿshmflgの読み取りおよび書き込み権限は、shm_perm.modeに配置されます。
Ÿshm_perm構造体のuidおよびcuidメンバーは、現在のプロセスの実効ユーザーIDに設定され、gidおよびcuidメンバーは、現在のプロセスの実効グループIDに設定されます。
- shmat関数プロトタイプ
- shmdt関数プロトタイプ
- Shmctl関数プロトタイプ
3。コードの実装:
次の例は、共有メモリを作成してからフックするサーバープロセスです。クライアントプロセスもフックされ、サーバープロセスは2秒ごとに文字を書き込みます。
クライアントプロセスは、メモリ共有を受信、印刷、および実現します
comm.h
#ifndef _COMM_H_
#define _COMM_H_
#include<stdio.h>
#include<sys/shm.h>
#include<errno.h>
#include<sys/types.h>
#include<unistd.h>
#define PATHNAME "."
#define PROJ_ID 0x6666
int creat_shm(size_t size);
int get_shm();
int destroy_shm(int shmid);
#endif
comm.c
#include"comm.h"
int common(size_t size,int flags)
{
key_t key = ftok(PATHNAME,PROJ_ID);
if (key < 0)
{
perror("ftok");
return -1;
}
int shmid = shmget(key,size,flags);//得到共享内存的标识符shmid。
if (shmid < 0)
{
perror("shmget");
return -2;
}
return shmid;
}
int creat_shm(size_t size)
{
return common(size,IPC_CREAT|IPC_EXCL|0x666);
}
int get_shm()
{
return common(0,IPC_CREAT);
}
int destroy_shm(int shmid)
{
if(shmctl(shmid,IPC_RMID,NULL)<0)
{
perror("shmctl");
return -3;
}
return 0;
}
server.c
#include"comm.h"
int main()
{
int shmid = creat_shm(4096);
printf("shmid = %d\n",shmid);
sleep(3);
char *buf = shmat(shmid,NULL,0);//挂接//shmat第二个参数为空,表示系统分配地址空间,返回一个虚拟地址空间。
printf("buf = %p\n",buf);
int i = 0;
while (1)
{
buf[i] = 'A'+i%26;
i++;
buf[i] = 0;
sleep(2);
}
shmdt(buf); //去挂接。
sleep(3);
destroy_shm(shmid);
return 0;
}
client.c
#include"comm.h"
int main()
{
int shmid = get_shm();
sleep(3);
char *buf = shmat(shmid,NULL,0);//新的进程挂接到那块共享内存空间,能看到共享内存中的东西。
while (1)
{
printf("%s\n",buf);
sleep(2);
}
shmdt(buf);
return 0;
}
要約mmapおよびshm:
1。mmapはディスク上にファイルを作成することであり、ディスクファイルマッピングのために各プロセスのアドレス空間にスペースが開かれます。
shmの場合、shmの各プロセスは、最終的に同じ物理メモリにマップされます。shmは物理メモリに格納されるため、読み取りと書き込みの速度はディスクの速度よりも速くなりますが、記憶容量はそれほど大きくありません。
2. mmapのもう1つの利点は、マシンの再起動時にmmapがファイルをディスクに保存するため、このファイルはオペレーティングシステムによって同期されたイメージも保存するため、mmapは失われませんが、shmgetは失われることです。