文章目录
1. 共享内存原理及图解
共享内存通过页表映射到进程的进程虚拟地址空间的共享区中,进程通过操控共享区的内容,间接操作共享内存中的内容
不同的进程将同一块共享内存映射到自己的共享区中,通过操作各自的共享区,以达到进程间数据交换
2. 共享内存相关函数及命令
2.1 创建共享内存函数
shmget函数
int shmget(key_t key, size_t size, int shmflg);
功能:开辟一段共享内存,用共享内存标识符key去标识这块内存,并返回共享内存的操作句柄
头文件:
- sys/ipc.h
- sys/shm.h
参数:
- key : 共享内存标识符,相当于共享内存的身份证,唯一标识共享内存区域
- size : 需要开辟的共享内存大小,单位是字节
- shmflg
宏 | 含义 |
---|---|
IPC_CREAT | 若共享内存不存在,则创建 |
IPC_EXCL | 与IPC_CREAT一同使用,为了确保共享内存已经存在时,会报告错误 |
返回值:创建成功返回 共享内存操作句柄 ,创建失败返回 -1
共享内存标识符:用于唯一标识某块共享内存的,进程可以通过共享内存标识符找到这块共享内存
共享内存操作句柄:用于操作这块内存的
代码演示shmget函数
#include <stdio.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define KEY 0x89898989
int main()
{
int shmid = shmget(KEY,1024,IPC_CREAT);
if(shmid < 0)
{
perror("shmget");
return -1;
}
printf("shmid : %d\n",shmid);
return 0;
}
[gongruiyang@localhost Testshm]$ ./shm
shmid : 425985
2.2 查看共享内存命令
ipcs命令用于查看所有进程间通信(IPC)信息,-m选项是所有信息中过滤出共享内存段的进程间通信信息
ipcs -m
命令演示
[gongruiyang@localhost Testshm]$ ipcs -m
------------ 共享内存段 --------------
key shmid owner 权限 字节 nattch status
0x00000000 262144 gongruiyan 600 524288 2 dest
0x89898989 425985 gongruiyan 0 1024 0 dest
0x00000000 393218 gongruiyan 600 524288 2 dest
- key : 共享内存标识符,若为
- shmid : 共享内存操作句柄
- owner : 所属用户,谁创建的
- 权限 : 对该共享内存的操作权限
- 字节 : 共享内存大小
- nattch : 附加的进程数量
- status : 共享内存段的状态,dest表示销毁
上面的程序创建了一个进程标识符为0x89898989的共享内存段,虽然程序已经执行结束,但是共享内存段仍然存在,说明共享内存的生命周期的结束不是跟随程序的,而是跟随内核的
2.3 删除共享内存命令
ipcrm用于删除进程间通信资源,-m代表删除的是共享内存段
ipcrm -m 共享内存操作句柄
命令演示
[gongruiyang@localhost Testshm]$ ipcs -m
------------ 共享内存段 --------------
key shmid owner 权限 字节 nattch status
0x00000000 262144 gongruiyan 600 524288 2 dest
0x89898989 425985 gongruiyan 0 1024 0 dest
0x00000000 393218 gongruiyan 600 524288 2 dest
[gongruiyang@localhost TestPipeCS]$ ipcrm -m 425985
[gongruiyang@localhost TestPipeCS]$ ipcs -m
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 gongruiyan 600 524288 2 dest
0x00000000 393218 gongruiyan 600 524288 2 dest
2.4 创建共享内存时赋予读写权限
由上面程序创建出来的共享内存的权限为0可以看出,我们对该共享内存无任何权限,我们可以在创建共享内存的时候对共享内存属性进行修改,增加 权限
int shmid = shmget(KEY,1024,IPC_CREAT | 0664);
2.5 将自定义的共享内存映射到进程虚拟地址的共享区中
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:将指定的共享内存段映射到进程虚拟地址的共享区中的某一块中
头文件:
- sys/types.h
- sys/shm.h
参数:
- shmid : 共享内存操作句柄
- shmaddr : 映射到共享区的哪一块地址,需要映射到哪一块地址就填哪块地址;如果填入NULL就代表想让操作系统替我们选择一个合适的地址,
- shmflg :
宏 | 含义 |
---|---|
SHM_RDONLY | 规定当前进程对该共享内存有 只读权限 |
0 | 规定当前进程对该共享内存有 读写权限 |
返回值:返回 映射到的共享区 的地址
程序演示
#include <stdio.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define KEY 0x89898989
int main()
{
int shmid = shmget(KEY,1024,IPC_CREAT | 0664);
if(shmid < 0)
{
perror("shmget");
return -1;
}
void* lp = shmat(shmid,NULL,0); //让系统分配共享区,该进程对共享内存的权限为 可读可写
printf("%p\n",lp);
return 0;
}
[gongruiyang@localhost Testshm]$ ./shm
0xffffffffffffffff
2.6 单进程对共享内存进行的读写操作
程序思路:
- 创建一块共享内存标识符为KEY的共享内存
- 将创建出来的共享内存附加到本进程中,让系统分配共享区,权限为读写
- 本进程向共享内存中写入数据
- 本进程从共享内存中读取数据
程序演示:单进程对共享内存进行读写操作
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define KEY 0x89898989
int main()
{
//1.创建一块共享内存标识符为KEY的共享内存
int shmid = shmget(KEY,1024,IPC_CREAT | 0664);
if(shmid < 0)
{
perror("shmget");
return -1;
}
//2.将创建出来的共享内存附加到本进程中,让系统分配共享区,权限为读写
void* lp = shmat(shmid,NULL,0);
//3.本进程向共享内存中写入数据
const char* w_buf = "Hello World!\n";
strcpy((char*)lp,w_buf);
//4.本进程从共享内存中读取数据
printf("%s\n",lp);
return 0;
}
2.7 多进程读写分离操作共享内存
程序演示:一个进程写入数据 一个进程读取数据
读进程
#include <stdio.h>
#include <unistd.h>
#include <sys/shm.h>
#include <string.h>
#define KEY 0x89898989
int main()
{
int shmid = shmget(KEY, 1024, IPC_CREAT | 0664);
if(shmid < 0)
{
perror("shmget");
return -1;
}
void* lp = shmat(shmid, NULL, 0);
printf("%s\n", (char*)lp);
while(1)
{
sleep(1);
}
return 0;
}
写进程
#include <stdio.h>
#include <unistd.h>
#include <sys/shm.h>
#include <string.h>
#define KEY 0x89898989
int main()
{
int shmid = shmget(KEY, 1024, IPC_CREAT | 0664);
if(shmid < 0)
{
perror("shmget");
return -1;
}
const char* w_buf = "Hello World!\n";
void* lp = shmat(shmid, NULL, 0);
strcpy((char*)lp, w_buf);
while(1)
{
sleep(1);
}
return 0;
}
进程读取共享内存中数据时,并不会将共享内存中的数据拿走,数据依然存在共享内存之中
2.8 将共享内存与进程进行分离
int shmdt(const void *shmaddr);
功能:将进程中附加到共享区的某个共享内存段进行分离开
头文件:
- sys/types.h
- sys/shm.h
参数:
- shmaddr : shmat返回值
返回值:0 代表分离成功 ; -1 代表分离失败
程序演示
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define KEY 0x89898989
int main()
{
//1.创建一块共享内存标识符为KEY的共享内存
int shmid = shmget(KEY,1024,IPC_CREAT | 0664);
if(shmid < 0)
{
perror("shmget");
return -1;
}
//2.将创建出来的共享内存附加到本进程中,让系统分配共享区,权限为读写
void* lp = shmat(shmid,NULL,0);
while(1)
{
sleep(1);
}
return 0;
}
[gongruiyang@localhost Testshm]$ ipcs -m
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 gongruiyan 600 524288 2 dest
0x89898989 524289 gongruiyan 664 1024 1
0x00000000 393218 gongruiyan 600 524288 2 dest
由此可以看出:附加在 共享内存标识符为0x89898989 上的进程数量nattch为1
进程分离后的程序及现象
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define KEY 0x89898989
int main()
{
//1.创建一块共享内存标识符为KEY的共享内存
int shmid = shmget(KEY,1024,IPC_CREAT | 0664);
if(shmid < 0)
{
perror("shmget");
return -1;
}
//2.将创建出来的共享内存附加到本进程中,让系统分配共享区,权限为读写
void* lp = shmat(shmid,NULL,0);
//将本进程与共享内存分离
int ret_shmdt = shmdt(lp);
if(ret_shmdt < 0)
perror("shmdt");
else
printf("shmdt success!\n");
while(1)
{
sleep(1);
}
return 0;
}
[gongruiyang@localhost Testshm]$ ipcs -m
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x00000000 262144 gongruiyan 600 524288 2 dest
0x89898989 557057 gongruiyan 664 1024 0
0x00000000 393218 gongruiyan 600 524288 2 dest
2.9 操作共享内存属性
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:操作共享内存的属性
头文件:
- sys/types.h
- sys/shm.h
参数:
- shhmid : 共享内存操作句柄
- cmd : 告诉shmctl函数需要做什么操作,操作可以填入以下的宏
宏 | 含义 |
---|---|
IPC_STAT | 获取共享内存属性,将指定的共享内存属性填入buf中 |
IPC_SET | 设置共享内存数,将指定的共享内存属性按照buf设置 |
IPC_RMID | 删除共享内存,buf直接传递NULL |
- buf : 共享内存结构体
shmid_ds结构体源码
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions + SHM_DEST and SHM_LOCKED flags */
unsigned short __seq; /* Sequence number */
};
程序演示:获取共享内存属性,并输出共享内存大小
#include <stdio.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define KEY 0x89898989
int main()
{
int shmid = shmget(KEY,1024,IPC_CREAT | 0664);
if(shmid < 0)
{
perror("shmget");
return -1;
}
struct shmid_ds buf;
shmctl(shmid, IPC_STAT, &buf);
// 获取 共享内存段 大小
printf("shm size : %ld字节\n",buf.shm_segsz);
return 0;
}
[gongruiyang@localhost Testshm]$ ./shm
shm size : 1024字节
2.10删除共享内存总结
- 当使用ipcrm命令或者shmctl函数删除共享内存之后,共享内存段就被释放了
- 共享内存标识符被设置为0x00000000,表示任何进程都无法通过之前的标识符来寻找该共享内存
- 共享内存的状态会被设置成dest(destroy),表示被销毁
- 如果这块被释放的共享内存块依然还有进程映射,即nattch不为0,则描述共享内存的结构体就不会被释放,直到所有依附于该共享内存的进程全部分离后,即nattch为0时,描述该共享内存的结构体才会被释放