进程间通信----共享内存
最底层的原理:让不同的进程看到相同的物理资源(将不同进程的 虚拟内存地址 映射到 同一块相同的物理内存上)
对共享内存的操作没有进行检测 PV操作需要用户自己来完成
对共享内存的操作 执行向上取整 按页申请 (如一页不够就用两页 但用户可以使用的 还是他自己申请的那么大 如申请4097B 实际申请8kB 但用户只能用4097B【一页4096B】)
生命周期随内核 不随进程(进程退出 进程不主动删除 共享内存 则共享内存还在)
1、用到的函数
int shmget(key_t key, size_t size, int shmflg); 【创建共享内存 | 获得共享内存】
key: 由ftok()函数返回的标识符
size:字节为单位所需的共享内存容量
shmflg:IPC_CREAT
IPC_EXCL
IPC_CREAT|IPC_EXCL
IPC_CREAT|IPC_EXCL|0666
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
[ 控制共享内存]
shm_id:shmget返回的共享内存标识符
cmd:
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即把共享内存的当前设置值保存在shmid_ds中。
IPC_SET:如果进程有足够权限,把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID :删除共享内存
void *shmat(int shmid, const void *shmaddr, int shmflg);
at是attach
[挂接*(将创建的共享内存与进程的虚拟内存相关联 让进程可以向操作malloc的空间一样操作共享内存) 使得ipcs -m 中nattch选项加1]
返回值可以理解为(当成)malloc申请的资源地址 【其实两个是完全不一样的】
成功返回指向共享内存的指针,失败返回-1
const void *shmaddr在哪开辟 可以让系统自己决定(NULL)
int shmflg 开辟的属性读写权限 可以让系统自己决定(NULL)
int shmdt(const void *shmaddr);
dt是detach[删除挂接 使得ipcs -m 中nattch选项减去1]
注意: 对共享内存的操作没有进行 先后顺序检测 任何进程都可以操作 要实现互斥 就要自己用信号量进行加锁的 PV操作
2、查看的命令:
ipcs -m
写一个监视的脚本
while :; do ipcs -m|grep "root"; sleep 1; echo "###########"; done
3、练习程序
------------------------------shm.h--------------------------------------
/* 共享内存*/ #pragma once #include <stdio.h> #include <sys/shm.h> #include <sys/ipc.h> #include <unistd.h> #define __PATH__ "." #define __PROJECT__ 1234 #define __SHM_SIZE__ (4*1024) // 4k对齐 //#define __SHM_SIZE__ (5*1024) // 4k对齐 int get_shm(); char* at_shm(int shm_id); int delete_shm(void *addr); int rm_shm(int shm_id);
----------------------------shm.c----------------------------------------
#include "shm.h" // 创建 shm int get_shm() { key_t key = ftok(__PATH__,__PROJECT__); int flag = IPC_CREAT|IPC_EXCL|0666; int shm_id = shmget(key, __SHM_SIZE__, flag); if (shm_id == -1) { perror("shmget error"); } else { printf("creat shm success\n"); } return shm_id; } char* at_shm(int shm_id) { // 挂接shm return (char*)shmat(shm_id, NULL,0 ); } // 解除 挂接 int delete_shm(void *addr) { return shmdt(addr); } // success return 0 error return -1 int rm_shm(int shm_id) { return shmctl(shm_id, IPC_RMID,NULL); }
----------------------------------------test_shm.c------------------
#include "shm.h" #include <sys/types.h> #include <sys/wait.h> int main() { int shm_id = get_shm(); pid_t id = fork(); if (id < 0) { printf("fork child error\n"); } else if (id == 0) { // child sleep(2); char* buf = at_shm(shm_id); sleep(4); int i = 0; while (i < 4096) { buf[i] = 'X'; i++; } buf[4095] = '\0'; sleep(3); delete_shm(buf); sleep(3); } else { // father sleep(4); char* buf = at_shm(shm_id); sleep(2); printf("%s\n", buf); delete_shm(buf); sleep(2); //waitpid(id, NULL, 0); wait(NULL); rm_shm(shm_id); } return 0; }
-------------------------------------运行截图---------------------------------
监视脚本:
[root@localhost bozi]# while :; do ipcs -m | grep root; sleep 1;echo "##########";done 0xd200649e 1802257 root 666 4096 0 ########## 0xd200649e 1802257 root 666 4096 0 ########## 0xd200649e 1802257 root 666 4096 1 ########## 0xd200649e 1802257 root 666 4096 1 ########## 0xd200649e 1802257 root 666 4096 2 ########## 0xd200649e 1802257 root 666 4096 2 ########## 0xd200649e 1802257 root 666 4096 1 ########## 0xd200649e 1802257 root 666 4096 1 ########## 0xd200649e 1802257 root 666 4096 1 ########## 0xd200649e 1802257 root 666 4096 0
首先由父进程创建共享内存,父进程创建子进程,父子进程都关联了共享内存,关联的个数由0变为2,因父进程休息时间短,取消关联,关联个数变为1,最后子进程也取消关联,关联个数变为0,最后将共享内存销毁。
4、mmap函数
mmap将一个文件或者其他对象映射进内存。mmap也可以实现共享内存。mmap函数调用使得进程之间通过映射同一个文件实现共享内存。文件被映射到进程地址空间后,进程可以像读写内存一样对文件进行操作。
函数:
#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); void *mmap64(void *addr, size_t length, int prot, int flags,int fd, off64_t offset); int munmap(void *addr, size_t length);
addr:映射区的开始地址,设置为NULL时表示系统决定映射区的起始地址
length:映射区的长度。长度单位为字节
prot:期望的内存保护标志。取以下几个值:
PORT_EXEC:页内容可以被执行 PORT_READ:页内容可以被读取
PORT_WRITE:页内容可以被写入 PROT_NONE:页内容不可访问
flag:指定映射对象的类型,映射选项与映射页是否可以共享。
MAP_SHARED:与其他所有映射这个对象的进程共享映射空间。
MAP_PRIVATE:建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。
MAP_FIXED :使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空 间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。
fd: 有效的文件描述符。返回,由一般open()函数,其值可以设置为-1.此时需要指定flags参数为 MAP_ANON,表明进行的是匿名映射。
offset:被映射对象内容的起点。
返回值:成功,返回被映射区的指针;失败,返回-1.
设备操作:
mmap操作提供了一种机制,让用户程序直接访问设备内存,这种机制,相比较在用户空间和内核空间互相拷贝数据,效率更高。在要求高性能的应用中比较常用。mmap映射内存必须是页面大小的整数倍,面向流的设备不能进行mmap,mmap的实现和硬件有关。
结论:
1、 最终被映射文件的内容的长度不会超过文件本身的初始大小,即映射不能改变文件的大小;
2、可以用于进程通信的有效地址空间大小大体上受限于被映射文件的大小,但不完全受限于文件大小。打开文件被截短为5个people结构大小,而在 map_normalfile1中初始化了10个people数据结构,在恰当时候(map_normalfile1输出initialize over 之后,输出umap ok之前)调用map_normalfile2会发现map_normalfile2将输出全部10个people结构的值,后面将给出详细讨论。
注:在linux中,内存的保护是以页为基本单位的,即使被映射文件只有一个字节大小,内核也会为映射分配一个页面大小的内存。当被映射文件小于一个页面大小时,进程可以对从mmap()返回地址开始的一个页面大小进行访问,而不会出错;但是,如果对一个页面以外的地址空间进行访问,则导致错误发生,后面将进一步描述。因此,可用于进程间通信的有效地址空间大小不会超过文件大小及一个页面大小的和。
3、文件一旦被映射后,调用mmap()的进程对返回地址的访问是对某一内存区域的访问,暂时脱离了磁盘上文件的影响。所有对mmap()返回地址空间的操作只在内存中有意义,只有在调用了munmap()后或者msync()时,才把内存中的相应内容写回磁盘文件,所写内容仍然不能超过文件的大小。
测试代码:
#include<stdio.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #define _PATH_NAME_ "./mmap.tmp" int main() { //int fd = open(_PATH_NAME_,O_RDONLY); int fd = open(_PATH_NAME_, O_RDWR); if (fd < 0) { printf("open error\n"); return -1; } int len = lseek(fd, 0, SEEK_END);// 测文件长度 char* ptr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (ptr < 0) { close(fd); printf("mmap error\n"); return -3; } close(fd); // 在之前关闭文件 也没事 因为映射到内存 操作都是在内存中 printf("%s\n:>", ptr); scanf("%s",ptr); // 写的字符串超过之前文件的大小 则后面超出的不写入文件 printf("%s\n", ptr); return 0; }
运行:
[bozi@localhost test_20160723]$ ./mmap aaaaaaaaaaa :>asaaaaaaaasssssss asaaaaaaaasssssss [bozi@localhost test_20160723]$ cat mmap.tmp asaaaaaaaas[bozi@localhost test_20160723]$
只写入了文件大小的内容 后面超出的不写入