进程通信方式:共享内存
共享内存.png共享内存本质上就是每个进程将虚拟地址空间指向共享内存块中,当一个进程往一个共享内存快中写入了数据,共享这个内存区域的所有进程就可用都看到其中的内容。
mmap
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
返回错误类型:
1 EACCES:访问出错
2 EAGAIN:文件已被锁定,或者太多的内存已被锁定
3 EBADF:fd不是有效的文件描述词
4 EINVAL:一个或者多个参数无效
5 ENFILE:已达到系统对打开文件的限制
6 ENODEV:指定文件所在的文件系统不支持内存映射
7 ENOMEM:内存不足,或者进程已超出最大内存映射数量
8 EPERM:权能不足,操作不允许
9 ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志
10 SIGSEGV:试着向只读区写入
11 SIGBUS:试着访问不属于进程的内存区
-
start
映射区的开始地址 -
length
映射区的长度 -
port
期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起1 PROT_EXEC :页内容可以被执行 2 PROT_READ :页内容可以被读取 3 PROT_WRITE :页可以被写入 4 PROT_NONE :页不可访问
-
flags
指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体。1 MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。 2 MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。 3 MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。 4 MAP_DENYWRITE //这个标志被忽略。 5 MAP_EXECUTABLE //同上 6 MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。 7 MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。 8 MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。 9 MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。 10 MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。 11 MAP_FILE //兼容标志,被忽略。 12 MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。 13 MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。 14 MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。
-
fd
有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1 -
offset
被映射对象内容的起点
mmap 在 write 和 read 时发生了什么?
write:
1、进程(用户态)将需要写入的数据直接copy到对应的mmap地址(内存copy)
2、若mmap地址未对应物理内存,则产生缺页异常,由内核处理
3、若已对应,则直接copy到对应的物理内存
4、由操作系统调用,将脏页回写到磁盘(通常是异步的)
read:
image.png
mmap要比普通的read系统调用少了一次copy的过程。
参考文章:
认真分析mmap:是什么 为什么 怎么用
https://www.jianshu.com/p/755338d11865
shmget
- API
- 创建共享内存
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg); 返回值:成功返回共享内存的id,失败返回-1;
- key:和上面介绍的信号量的semget函数的参数key一样;
- size:表示要申请的共享内存的大小,一般是4k的整数倍;
- flags:
IPC_CREAT
和IPC_EXCL
一起使用,则创建一个新的共享内存,否则返回-1。IPC_CREAT
单独使用时返回一个共享内存,有就直接返回,没有就创建。
- 挂接函数
shmat的作用是将申请的共享内存挂接在该进程的页表上,是将虚拟内存和物理内存相对应;void *shmat(int shmid); 返回值:返回这块内存的虚拟地址;
- 去挂接函数
shmdt的作用是去挂接,将这块共享内存从页表上剥离下来,去除两者的映射关系int shmdt(const void *shmaddr); 返回值:失败返回-1;
- 删除共享内存
shmctl用来设置共享内存的属性。当cmd是IPC_RMID时可以用来删除一块共享内存。int shmctl(int shmid,int cmd,const void* addr);
- 创建共享内存
共享内存类似消息队列和信号量,它的生命周期也是随内核的,除非用命令才可以删除该共享内存。
ipcs -m //查看创建的共享内存的个数
ipcrm -m shm_id //删除共享内存
- 示例
#ifndef __COMM__ #define __COMM__ #include<stdio.h> #include<sys/types.h> #include<sys/ipc.h> #include<sys/shm.h> #include<unistd.h> #define PATHNAME "." #define PROCID 0x6666 #define SIZE 4096*1 int CreatShm(); int GetShm(); int DestroyShm(int shm_id); static int CommShm(int flag); #endif
#include"comm.h" int CreatShm() { return CommShm(IPC_CREAT | IPC_EXCL | 0666); } static int CommShm(int flag) { key_t key = ftok(PATHNAME, PROCID); if (key < 0) { perror("ftok"); return -1; } int shm_id = shmget(key, SIZE, flag); if (shm_id < 0) { perror("shmget"); return -2; } return shm_id; } int GetShm() { return CommShm(IPC_CREAT); } int DestroyShm(int shm_id) { int ret = shmctl(shm_id, IPC_RMID, NULL); if (ret < 0) { perror("shmctl"); return -1; } return 0; }
#include"comm.h" void testserver() { int shm_id = CreatShm(); printf("shm_id=%d\n", shm_id); char *mem = (char *)shmat(shm_id, NULL, 0); while (1) { sleep(1); printf("%s\n", mem); } shmdt(mem); DestroyShm(shm_id); } int main() { testserver(); return 0; }
#include"comm.h" void testclient() { int shm_id = GetShm(); char *mem = (char *)shmat(shm_id, NULL, 0); int index = 0; while(1) { sleep(1); mem[index++] = 'A'; index %= (SIZE - 1); mem[index] = '\0'; } shmdt(mem); DestroyShm(shm_id); } int main() { testclient(); return 0; }
进程通信方式:套接字
-
域套接字
-
TCP套接字
网友评论