Linux SystemV通信包括: 共享内存,消息队列,信号量 其中信号量为了进程的同步与互斥而设计的 共享内存和消息队列为了进程间传递数据设计
这里讨论其中之一的共享内存
1.共享内存原理
我们在创建进程时Linux操作系统会创建mm_struct进程地址空间,进程地址空间通过页表映射到物理内存上。 当两个进程的mm_struct进程地址空间通过页表指向同一块物理内存时,就实现了共享内存的过程。 注意:要和父子进程写时拷贝做区别。父子进程在没有修改数据时指向同一块物理空间,但当父子进程有数据修改时,操作系统会修改页表使其指向不同的物理空间。
2共享内存建立与释放的过程
①申请共享内存
shmget函数(sys/ipc.h -sys/shm.h)(创建共享内存)
原型: int shmget(key_t key, size_t size,int shmflg); 参数: key:这个共享内存段名字 size:共享内存大小 shmflg:创建共享内存的选项。 返回值:成功返回一个非负整数,即该共享内存段的标识码(共享内存句柄),失败返回-1。在用户层标识共享内存
保证共享内存的唯一性ftok函数的代码(sys/types.h sys/ipc.h)
为了保证共享内存的唯一性,在shemget函数传入的第一个参数key来保证共享内存唯一。这个唯一的值,表明这个是新创建的共享内存,否则说明这个共享内存存在了。
下面用这个生成唯一序列 pathname:路径名 proj_id:数据。可以任意指定 如果生成序列成功,返回这个序列,失败返回-1
调用函数ftok函数,传入路径和整形,系统会根据算法生成序列,我们用这个序列来标定共享内存。如果生成的序列不唯一,则修改路径或整数直到唯一
commen.h
1 #pragma once
2
3 #include<stdio.h>
4 #include<sys/shm.h>
5 #include<sys/types.h>
6 #include<sys/ipc.h>
7
8 #define PATH "/home/dodamce/SharePace"
9
10 #define PROJ_ID 0x666
11
12 #define SIZE 4096
sever.c
1 #include"commen.h"
2
3 int main()
4 {
5 key_t key=ftok(PATH,PROJ_ID);
6 if(key<0)
7 {
8 printf("Error\n");
9 }
10 else
11 {
12 printf("%d\n",key);
13 }
14 return 0;
15 }
如上图,我们生成了一个唯一的序列
shmflg:创建共享内存的选项。
- IPC_CREAT:如果存在共享内存,直接返回这块空间。如果这块空间不存在,先创建再返回这块空间。所以这个选项调用成功后一定可以获得一块共享内存,但是无法保证是否为新的共享内存。
- IPC_EXCL :不能单独使用,无意义,经常与IPC_CREAT一起使用。
- IPC_CREAT| IPC_CREAT:创建新的共享内存,如果这个共享内存存在了出错返回。上面的选项调用成功一定会生成一块新的共享内存
- 还可以设置共享内存权限,这个权限设置与文件权限设置相同为8进制数字,默认为0,无任何权限
#include"commen.h"
int main()
{
key_t key=ftok(PATH,PROJ_ID);
if(key<0)
{
perror("Error\n");
return 1;
}
int shm=shmget(key,SIZE,IPC_CREAT|IPC_EXCL);
if(shm<0)
{
perror("Shmget error\n");
return 2;
}
return 0;
命令查看共享内存(ipcs -m)
注意:进程退出了,但是共享内存不会自动释放。 perms为共享内存权限,当在shmget没有设置时为默认值0 nattch为使用这块共享内存的进程数
当我们在运行程序时发现共享内存已经存在了
这点与管道通信不同,管道生命周期随进程,进程退出管道自动删除。 但进程退出,共享内存不会释放,它的生命周期随内核。
②共享内存挂接到进程空间(建立映射关系)
shmat函数(sys/types.h -sys/shm.h)
参数解释: shmid:共享内存在用户区的编号。 shmaddr:将共享内存映射到进程地址空间的哪一个位置,不关心可以设为NULL由操作系统设置。 shmflg:挂接共享内存时设置的属性。 eg:SHM_RDONLY设置为只能读取共享内存的数据,设置为0表示可读可写。
返回值: 成功挂接返回映射到进程地址空间(虚拟地址)的首地址,失败返回-1;
注意:在进行共享内存挂接时共享内存一定有权限,否则挂接失败
#include"commen.h"
int main()
{
key_t key=ftok(PATH,PROJ_ID);
if(key<0)
{
perror("Error\n");
return 1;
}
int shm=shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);
if(shm<0)
{
perror("Shmget error\n");
return 2;
}
printf("attach begin:\n");
shmat(shm,NULL,0);
sleep(5);
printf("attach end\n");
sleep(1);
printf("end\n");
shmctl(shm,IPC_RMID,NULL);
return 0;
}
运行后查看共享内存ipcs -m可以看到申请的共享内存已有一个进程挂接
③去关联共享内存(取消映射关系)
shmdt函数(sys/type.h -sys/shm.h)
通过上一步共享内存映射到虚拟空间地址就可以取消与共享内存的映射
#include"commen.h"
int main()
{
key_t key=ftok(PATH,PROJ_ID);
if(key<0)
{
perror("Error\n");
return 1;
}
int shm=shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);
if(shm<0)
{
perror("Shmget error\n");
return 2;
}
printf("attach begin:\n");
char* begin= shmat(shm,NULL,0);
sleep(5);
printf("attach end\n");
shmdt(begin);
sleep(1);
printf("end\n");
shmctl(shm,IPC_RMID,NULL);
return 0;
}
运行结果下图:
可任意看到去关联后挂接数减少1
④释放共享内存(将资源返回给操作系统)
命令归还申请的共享内存ipcrm -m)
用命令删除共享内存不用key值删除,使用的是共享内存用户层shmid号删除
调用(shmctl)函数(共享内存控制)归还共享内存(sys/ipc.h -sys/shm.h)
函数原型: 参数解释: shmid:要归还的共享内存的用户层编号。 cmd:执行选项,IPC_RMID表示删除共享内存 返回值: 成功返回0,失败返回-1
#include"commen.h"
int main()
{
key_t key=ftok(PATH,PROJ_ID);
if(key<0)
{
perror("Error\n");
return 1;
}
int shm=shmget(key,SIZE,IPC_CREAT|IPC_EXCL);
if(shm<0)
{
perror("Shmget error\n");
return 2;
}
sleep(5);
shmctl(shm,IPC_RMID,NULL);
return 0;
}
运行结果:
如上图,进程退出后共享内存也归还回操作系统。
3.共享内存的使用
共享内存的使用在共享内存挂接到进程地址空间到去关联共享内存之间
通过共享内存挂接后返回的映射地址来使用共享内存
模拟client进程与sever进程通信
思路:sevser进程创建共享内存后,client与sever挂接到同一块共享内存上。client发送数据,sever接受数据。只要保证两个进程看到同一个key值就可以保证两个进程可以挂接到同一个共享内存上。
将client与sever进程共有代码放到commen.h上 commen.h
#pragma once
#include<stdio.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<unistd.h>
#define PATH "/home/dodamce/SharePace"
#define PROJ_ID 0x666
#define SIZE 4096
sever.c 打印client发送到共享内存的数据
#include"commen.h"
int main()
{
key_t key = ftok(PATH, PROJ_ID);
if (key < 0)
{
perror("Error\n");
return 1;
}
int shm = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666);
if (shm < 0)
{
perror("Shmget error\n");
return 2;
}
char* begin = shmat(shm, NULL, 0);
while (1)
{
printf("client:%s\n", begin);
sleep(1);
}
shmdt(begin);
shmctl(shm, IPC_RMID, NULL);
return 0;
}
client.c
#include"commen.h"
int main()
{
key_t key = ftok(PATH, PROJ_ID);
if (key < 0) {
perror("ftok error");
return 1;
}
int shm = shmget(key, SIZE, IPC_CREAT);
if (shm < 0) {
perror("shmget error");
return 2;
}
char* begin = shmat(shm, NULL, 0);
int size = 0;
while (1)
{
begin[size] = '1' + size;
size++;
begin[size] = '\0';
}
shmdt(begin);
return 0;
}
结果:
4.共享内存通信与管道通信的区别
1.建立共享内存后,在使用共享内存时没有使用系统调用接口。进程挂接共享内存后,对共享内存的修改其他挂接进程立即可以收到,不用进行拷贝。 管道通信:确立完读写进程后,进程要调用write(),read()等系统接口读写。数据需要先拷贝到管道文件中在通信。 共享内存是所有进程通信间最快的。 2.共享内存不提供任何保护机制,没有互斥与同步机制。管道文件存在互斥与同步机制
|