目录
28共享内存介绍
共享内存示意图
共享内存区是最快的IPC形式,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
即每个进程地址空间都有一个共享存储器的映射区,当这块区域都映射到相同的真正的物理地址空间时,可以通过这块区域进行数据交换,例如共享库就是这么实现的,很多进程都会使用同一个函数如printf,也许在真正的物理地址空间中只存在一份printf.o ,然后所有进程都映射到这一份printf.o 就实现了共享。
管道、消息队列与共享内存传递数据对比
用管道或者消息队列传递数据
用共享内存传递数据:
即使用共享内存传递数据比用消息队列和管道来说,减少了进入内核的次数,提高了效率。
mmap函数
函数原型
#include <sys/mman.h> void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
函数功能
将文件或者设备空间映射到共享内存区。
返回值
成功返回映射到的内存区的起始地址;失败返回-1
参数说明
/* addr: 要映射的起始地址,通常指定为NULL,让内核自动选择 len:映射到进程地址空间的字节数 prot:映射区保护方式 flags:标志 fd:文件描述符 offset:从文件头开始的偏移量,必须是页大小的整数倍(在32位体系统结构上通常是4K) */
/* prot 参数取值: PROT_EXEC 表示映射的这一段可执行,例如映射共享库 PROT_READ 表示映射的这一段可读 PROT_WRITE 表示映射的这一段可写 PROT_NONE 表示映射的这一段不可访问 */
/* flag参数有很多种取值,这里只讲两种,其它取值可查看mmap(2) MAP_SHARED 多个进程对同一个文件的映射是共享的,一个进程对映射的内存做了修改,另一个进程也会看到这种变化。 MAP_PRIVATE 多个进程对同一个文件的映射不是共享的,一个进程对映射的内存做了修改,另一个进程并不会看到这种变化,也不会真的写到文件中去。 */
内存映射文件示意图:
如果mmap成功则返回映射首地址,如果出错则返回常数MAP_FAILED。当进程终止时,该进程的映射内存会自动解除,也可以调用munmap解除映射:
mmap 编程注意点:
- 1、映射不能改变文件的大小;
- 2、可用于进程间通信的有效地址空间不完全受限于被映射文件的大小;
- 3、文件一旦被映射后,所有对映射区域的访问实际上是对内存区域的访问。映射区域内
munmap函数
函数原型
int munmap(void *addr, size_t len);
函数功能
取消mmap函数建立的映射
返回值
成功返回0;失败返回-1
参数说明
addr: 映射的内存起始地址 len:映射到进程地址空间的字节数
msync函数
函数原型
函数功能
返回值
参数说明
29System V共享内存
共享内存数据结构
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 */ ... };
同样地,第一个成员是共有的IPC内核数据结构,其余是私有成员。
共享内存函数
shmge
函数原型
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg);
函数功能
用来创建共享内存
返回值
成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
参数说明
key:这个共享内存段名字 size:共享内存大小 shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
shmat
函数原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
函数功能
将共享内存段连接到进程地址空间
返回值
成功返回一个指针,指向共享内存第一个字节;失败返回-1
参数说明
shmid: 共享内存标识 shmaddr:指定连接的地址 shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
/* shmaddr为NULL,核心自动选择一个地址 shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。 shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。 公式:shmaddr - (shmaddr % SHMLBA) shmflg=SHM_RDONLY,表示连接操作用来只读共享内存 */
shmdt
函数原型
int shmdt(const void *shmaddr);
函数功能
将共享内存段与当前进程脱离
返回值
成功返回0;失败返回-1
参数说明
shmaddr: 由shmat所返回的指针 注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl
函数原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
函数功能
用于控制共享内存
返回值
成功返回0;失败返回-1
参数说明
shmid:由shmget返回的共享内存标识码 cmd:将要采取的动作(有三个可取值) buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
/* cmd 的取值如下,与消息队列类似: IPC_STAT 把shmid_ds结构中的数据设置为共享内存的当前关联值 IPC_SET 在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值 IPC_RMID 删除共享内存段 */
共享内存示例
shm_write.c
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/types.h>
#include<unistd.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/mman.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
typedef struct stu
{
char name[32];
int age;
} STU;
int main(int argc, char *argv[])
{
int shmid;
shmid = shmget(1234, sizeof(STU), IPC_CREAT | 0666);
if (shmid == -1)
ERR_EXIT("shmget");
STU *p;
p = shmat(shmid, NULL, 0);
if (p == (void *) - 1)
ERR_EXIT("shmat");
strcpy(p->name, "lisi");
p->age = 20;
shmdt(p);
return 0;
}
shm_read.c
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/types.h>
#include<unistd.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/mman.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
typedef struct stu
{
char name[32];
int age;
} STU;
int main(int argc, char *argv[])
{
int shmid;
shmid = shmget(1234, 0, 0);
if (shmid == -1)
ERR_EXIT("shmget");
STU *p;
p = shmat(shmid, NULL, 0);
if (p == (void *) - 1)
ERR_EXIT("shmat");
printf("name = %s age = %d\n", p->name, p->age);
shmdt(p);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}