前言:用户地址空间占内存的四分之三,在操作系统内存地址空间中,有内核级页表来映射,因此页表分为用户级页表和内核级页表,但他们最终都由操作系统托管
例如把数据写入文件(例如管道)就是将数据从用户缓冲区写入到操作系统缓冲区(从用户拷贝到内核),读出数据就是将数据从操作系统缓冲区读到用户缓冲区(从内核拷贝到用户)。
system V共享内存
- 共享内存原理
将同一块物理内存映射到进程各自的虚拟地址空间就可以实现数据共享,因为都可以通过自己的虚拟地址空间访问
特性:(1)共享内存是system V进程间通信中速度最快的
如图:
因为共享内存直接通过虚拟地址空间访问物理内存,进行数据共享,而其他通信方式需要先将数据拷入内核,再从内核拷贝出来,才能实现通信,因此共享内存的通信相较于其它方式,少了两次数据拷贝操作,因此速度最快;
(2)共享内存不提供同步与互斥机制,需要用户自己去完成 - 共享内存函数
(1)创建/打开共享内存:shmget函数
函数原型:
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
key表示在内核中唯一的一份共享内存资源,通常由我们自己指定(只要与系统中的不冲突即可),size表示共享内存大小,共享内存在创建时是以内存页为单位的,因此size必须是4k的倍数,shmflg由9个权限标志构成,与创建文件用的mode模式标志一样
返回值:成功返回一个非负整数,即该共享内存段的操作句柄(标识符),失败返回-1
相关宏:IPC_CREAT、IPC_EXCL,当适用IPC_CREAT时,表示当没有共享内存时就创建,如果有就打开使用,而当这两个宏一起使用时,表示当没有共享内存时就创建,有就出错,也就是如果只使用IPC_CREAT时,获取的共享内存不会出错,而当使用这两个宏时,返回成功时,获取的共享内存一定是全新创建的共享内存。
要生成key,有一个方法ftok,它的函数原型例如:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
两个参数由自己指定,返回值是两个参数糅合之后生成一个随机值,即从文件的inode节点号中取出一部分数据从proj_id中取出一部分数据进行合并来生成key值,它的返回值不与系统冲突,如果冲突了,对参数进行调整即可。
接下来就可以创建共享内存,例如:
先vim Makefile
ser=server#使用变量,等号右边是变量内容
cli=client
cc=gcc
.PHONY:all
all:$(ser) $(cli)#表示一次生成的可执行程序是ser和cli
$(ser):server.c
$(cc) -o $@ $^
$(cli):client.c
$(cc) -o $@ $^
.PHONY:clean
clean:
rm -f $(ser) $(cli)
这个Makefile使用了变量,在Makefile中使用变量等号后面跟变量内容(注意没有空格),这样就比较简便,然后我们实现
vim server.c
int main()
{
key_t k=ftok(PATH_NAME,PROJ_ID);
if(k<0)
{
printf("ftok error\n");
return 1;
}
printf("%u\n",k);
int shm_id=shmget(k,SIZE,IPC_CREAT|IPC_EXCL);
if(shm_id<0)
{
printf("shmget error!\n");
return 2;//错误码设置为2
}
return 0;
}
这时./server运行结果就是
[Daisy@localhost shmget]$ ./server
1711276104
使用ipcs -m命令可以查看共享内存种类
[Daisy@localhost shmget]$ ipcs -m
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 Daisy 600 524288 2 目标
0x00000000 360449 Daisy 600 4194304 2 目标
0x00000000 458754 Daisy 600 524288 2 目标
0x66000048 491523 Daisy 0 4096 0
此时可以看到最后一行的确是创建的共享内存,再次运行就会显示出错,例如:
[Daisy@localhost shmget]$ ./server
1711276104
shmget error!
可以看出生成了一个随机key值,创建了共享内存,但是再次创建这块内存发现出错,可以看出共享内存生命周期随内核
使用命令ipcrm -m +shmid来删除指定共享内存,例如:
[Daisy@localhost shmget]$ ipcs -m
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 Daisy 600 524288 2 目标
0x00000000 360449 Daisy 600 4194304 2 目标
0x00000000 458754 Daisy 600 524288 2 目标
0x66000048 491523 Daisy 0 4096 0
[Daisy@localhost shmget]$ ipcrm -m 491523
[Daisy@localhost shmget]$ ipcs -m
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 Daisy 600 524288 2 目标
0x00000000 360449 Daisy 600 4194304 2 目标
0x00000000 458754 Daisy 600 524288 2 目标
就删除了shmid为491523的共享内存,此时再运行server进程,不报错
[Daisy@localhost shmget]$ ./server
1711276104
(2)控制共享内存:shmctl函数,它的函数原型是
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
其中参数shmid表示返回的共享内存标识符;cmd表示将要采取的动作(包括3个可取值:IPC_STAT、IPC_SET、IPC_RMID)这里我们只看一个IPC_RMID表示销毁共享内存,也就是删除共享内存;buf表示指向一个保存着共享内存的模式状态和访问权限的数据结构,这里删除共享内存,将buf设置为NULL即可
返回值:成功返回0,失败返回-1
例如:vim server.c添加删除共享内存功能
#include <stdio.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#define PATH_NAME "/tmp"
#define PROJ_ID 0x6666
#define SIZE 4096
int main()
{
key_t k=ftok(PATH_NAME,PROJ_ID);
if(k<0)
{
printf("ftok error\n");
return 1;
}
printf("%u\n",k);
int shm_id=shmget(k,SIZE,IPC_CREAT|IPC_EXCL);
if(shm_id<0)
{
printf("shmget error!\n");
return 2;//错误码设置为2
}
shmctl(shm_id,IPC_RMID,NULL);//删除共享内存
return 0;
}
此时可以编译运行,得到
[Daisy@localhost shmget]$ ./server
1711276104
[Daisy@localhost shmget]$ ./server
1711276104
[Daisy@localhost shmget]$ ./server
1711276104
运行了3次都没有报错,ipcs -m查看一下
[Daisy@localhost shmget]$ ipcs -m
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 Daisy 600 524288 2 目标
0x00000000 360449 Daisy 600 4194304 2 目标
0x00000000 458754 Daisy 600 524288 2 目标
发现的确没有创建的共享内存,表示已经删除了共享内存
(3)将共享内存段映射到进程地址空间:shmat函数
函数原型是:
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
其中参数shmid表示共享内存标识符,shmaddr表示指定映射的虚拟地址空间的首地址(一般设为NULL),shmflg的两个可能取值是SHM_RND和SHM_RDONLY(只读方式)
返回值:成功就返回一个指针指向共享内存第一个字节,失败返回-1
例如给server.c增加挂接功能
#include <stdio.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#define PATH_NAME "/tmp"
#define PROJ_ID 0x6666
#define SIZE 4096
int main()
{
key_t k=ftok(PATH_NAME,PROJ_ID);
if(k<0)
{
printf("ftok error\n");
return 1;
}
printf("%u\n",k);
int shm_id=shmget(k,SIZE,IPC_CREAT|IPC_EXCL|0644);
if(shm_id<0)
{
printf("shmget error!\n");
return 2;//错误码设置为2
}
//挂接
char* p=(char*)shmat(shm_id,NULL,0);
shmctl(shm_id,IPC_RMID,NULL);//删除共享内存
return 0;
}
这就实现了挂接,p代表申请的4096个字节大小的共享内存
(4)解除映射关系(共享内存会记录当前的进程映射链接数):
shmdt函数
函数原型:
#include <sys/shm.h>
int shmdt(const void *shmaddr);
其中参数shmaddr表示shmat返回的指针
返回值:成功返回0,失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
给server.c中增加shmdt功能:
#include <stdio.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#define PATH_NAME "/tmp"
#define PROJ_ID 0x6666
#define SIZE 4096
int main()
{
key_t k=ftok(PATH_NAME,PROJ_ID);
if(k<0)
{
printf("ftok error\n");
return 1;
}
printf("%u\n",k);
int shm_id=shmget(k,SIZE,IPC_CREAT|IPC_EXCL|0644);
if(shm_id<0)
{
printf("shmget error!\n");
return 2;//错误码设置为2
}
sleep(3);//方便观察结果
//挂接
char* p=(char*)shmat(shm_id,NULL,0);
sleep(3);
shmdt(p);//脱离挂接
sleep(3);
shmctl(shm_id,IPC_RMID,NULL);//删除共享内存
sleep(3);
return 0;
}
此时运行启动另一终端来进行监控,输入命令while :;do ipcs -m;echo "##############;sleep 1;done,查看当前共享内存状态:
[Daisy@localhost shmget]$ while :;do ipcs -m;echo "###############################";sleep 1;done
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 Daisy 600 524288 2 目标
0x00000000 360449 Daisy 600 4194304 2 目标
0x00000000 458754 Daisy 600 524288 2 目标
0x66000048 753667 Daisy 644 4096 0
###############################
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 Daisy 600 524288 2 目标
0x00000000 360449 Daisy 600 4194304 2 目标
0x00000000 458754 Daisy 600 524288 2 目标
0x66000048 753667 Daisy 644 4096 0
###############################
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 Daisy 600 524288 2 目标
0x00000000 360449 Daisy 600 4194304 2 目标
0x00000000 458754 Daisy 600 524288 2 目标
0x66000048 753667 Daisy 644 4096 0
###############################
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 Daisy 600 524288 2 目标
0x00000000 360449 Daisy 600 4194304 2 目标
0x00000000 458754 Daisy 600 524288 2 目标
0x66000048 753667 Daisy 644 4096 1
###############################
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 Daisy 600 524288 2 目标
0x00000000 360449 Daisy 600 4194304 2 目标
0x00000000 458754 Daisy 600 524288 2 目标
0x66000048 753667 Daisy 644 4096 1
###############################
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 Daisy 600 524288 2 目标
0x00000000 360449 Daisy 600 4194304 2 目标
0x00000000 458754 Daisy 600 524288 2 目标
0x66000048 753667 Daisy 644 4096 1
###############################
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 Daisy 600 524288 2 目标
0x00000000 360449 Daisy 600 4194304 2 目标
0x00000000 458754 Daisy 600 524288 2 目标
0x66000048 753667 Daisy 644 4096 0
###############################
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 Daisy 600 524288 2 目标
0x00000000 360449 Daisy 600 4194304 2 目标
0x00000000 458754 Daisy 600 524288 2 目标
0x66000048 753667 Daisy 644 4096 0
###############################
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 Daisy 600 524288 2 目标
0x00000000 360449 Daisy 600 4194304 2 目标
0x00000000 458754 Daisy 600 524288 2 目标
0x66000048 753667 Daisy 644 4096 0
###############################
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 Daisy 600 524288 2 目标
0x00000000 360449 Daisy 600 4194304 2 目标
0x00000000 458754 Daisy 600 524288 2 目标
###############################
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 Daisy 600 524288 2 目标
0x00000000 360449 Daisy 600 4194304 2 目标
0x00000000 458754 Daisy 600 524288 2 目标
###############################
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 Daisy 600 524288 2 目标
0x00000000 360449 Daisy 600 4194304 2 目标
0x00000000 458754 Daisy 600 524288 2 目标
###############################
^C
可以看出,在前三秒中创建了共享内存,但是状态为0,说明此时没有挂接,然后3秒过后,进入挂接状态,状态为1,3秒后,共享内存仍旧存在,但是此时状态变为0,说明脱离挂接,3秒后,共享内存不存在了,说明删除了。
此时server.c已经实现完毕,现在可以实现client.c客户端
vim client.c
#include <stdio.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
#define PATH_NAME "/tmp"
#define PROJ_ID 0x6666
#define SIZE 4096
int main()
{
key_t k=ftok(PATH_NAME,PROJ_ID);
if(k<0)
{
printf("ftok error\n");
return 1;
}
printf("%u\n",k);
int shm_id=shmget(k,SIZE,0);//此时客户端不用创建共享内存,只用获取即可
if(shm_id<0)
{
printf("shmget error!\n");
return 2;//错误码设置为2
}
sleep(3);//方便观察结果
//挂接
char* p=(char*)shmat(shm_id,NULL,0);
sleep(15);
shmdt(p);//脱离挂接
sleep(3);
//不需要删除共享内存,因为他没有创建共享内存,所以不用删除
return 0;
}
客户端只需要使用共享内存即可,不用创建,也不用删除,此时将server.c中的挂接维持10秒,将脱离维持10秒,将client.c中的挂接维持15秒,进行测试,此时让服务端和客户端一起跑,然后在另一终端看效果,可以看出的确实现了共享内存。
此时实现通信
这时将服务端改为:
#include <stdio.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
#define PATH_NAME "/tmp"
#define PROJ_ID 0x6666
#define SIZE 4096
int main()
{
key_t k=ftok(PATH_NAME,PROJ_ID);
if(k<0)
{
printf("ftok error\n");
return 1;
}
int shm_id=shmget(k,SIZE,IPC_CREAT|IPC_EXCL|0644);
if(shm_id<0)
{
printf("shmget error!\n");
return 2;//错误码设置为2
}
//挂接
char* p=(char*)shmat(shm_id,NULL,0);
int i=0;
while(i<SIZE)//使用共享内存
{
printf("%s\n",p);
sleep(1);
i++;
}
shmdt(p);//脱离挂接
shmctl(shm_id,IPC_RMID,NULL);//删除共享内存
return 0;
}
客户端实现写入内容
#include <stdio.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
#define PATH_NAME "/tmp"
#define PROJ_ID 0x6666
#define SIZE 4096
int main()
{
key_t k=ftok(PATH_NAME,PROJ_ID);
if(k<0)
{
printf("ftok error\n");
return 1;
}
int shm_id=shmget(k,SIZE,0);//此时客户端不用创建共享内存,只用获取即可
if(shm_id<0)
{
printf("shmget error!\n");
return 2;//错误码设置为2
}
//挂接
char* p=(char*)shmat(shm_id,NULL,0);
int i=0;
while(i<SIZE)
{
p[i]='a'+i;
i++;
p[i]=0;
sleep(1);
}
shmdt(p);//脱离挂接
//不需要删除共享内存,因为他没有创建共享内存,所以不用删除
return 0;
}
启动3个终端,终端1先跑服务端,终端2跑客户端,终端3检测共享内存状况
此时先运行服务端,再运行客户端,发现服务端不断打印出来字符串,
[Daisy@localhost shmget]$ ./server
a
ab
abc
abcd
abcde
abcdef
abcdefg
abcdefgh
abcdefghi
客户端没有显示,然后共享内存状态
##########################
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 Daisy 600 524288 2 目标
0x00000000 360449 Daisy 600 4194304 2 目标
0x00000000 458754 Daisy 600 524288 2 目标
0x66000048 1081347 Daisy 644 4096 2
##########################
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 Daisy 600 524288 2 目标
0x00000000 360449 Daisy 600 4194304 2 目标
0x00000000 458754 Daisy 600 524288 2 目标
0x66000048 1081347 Daisy 644 4096 2
##########################
发现刺死挂接了两个共享内存段,若是停止一个进程,状态变为1
证明了的确实现了通信
如果此时我们将字节大小SIZE改为4097,那么按理上来说共享内存应该是4097*2的大小,但是此时将server.c改动后运行,显示
[Daisy@localhost shmget]$ ipcs -m
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 Daisy 600 524288 2 目标
0x00000000 360449 Daisy 600 4194304 2 目标
0x00000000 458754 Daisy 600 524288 2 目标
0x66000048 1114115 Daisy 644 4097 1
发现显示的是4097而不是4097*2,证明共享内存是在使用时才分配字节的,最多是4097,也可能一个都不分配。
注意:共享内存没有同步和互斥,操作并不安全
源代码(github):
https://github.com/wangbiy/Linux2/commit/b76f23fab63e187f537082372decb5e7b536b403