进程间通信(三)——Posix共享内存区
1.概述
共享内存区是IPC中最快的,当内存区映射到共享它的进程的地址空间,进程间数据的传递就不再涉及内核。
但是这需要某种形式的同步,最常用的是信号量。
不再涉及内核:进程不再通过执行任何进入内核的系统调用来彼此传递数据。内核必须建立允许各个进程共享该内存区的内存映射关系,然后一值管理该内存区。
管道,FIFO和消息队列的问题是,两个进程要交换信息时,这些信息必须经由内核传递。
共享内存区可以绕过这个问题,但是一般必须同步数据。
使用内存映射文件的特性,所有的I/O都不再有内核直接参与IO,我们绝不调用read,write,lseek,可以简化我们的代码。
不是所有的文件都能进行内存映射,例如:访问终端或套接字描述符就不可以,这些类型的描述符必须使用read和write来访问。
2.相关函数
mmap把文件或Posix共享内存区对象映射到调用进程的地址空间。
三个目的:
(1) 使用普通文件,内存映射IO, open,mmap()方式,也适用于无亲缘关系的进程间。
(2) 使用特殊文件,匿名内存映射,mmap(O_ANON)或open(/dev/zero), mmap()方式,只适用于有亲缘关系的进程间。
(3) 使用shm_open提供无亲缘关系的进程间的Posix共享内存区,shm_open, mmap方式,适用于无亲缘关系的进程间。
#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
成功:被映射区的起始地址; 出错:MAP_FAILED
addr: 指定fd描述符设置到进程内空间的起始地址,通常NULL,由内核选择起始地址。
len: 映射到调用进程地址空间的字节数。从offset计数。
offset:通常0。文件映射内存的偏移量。
prot: 内存映射区的保护,通常PROT_READ | PROT_WRITE
flags: 可以指定MAP_PRIVATE:调用进程对被映射数据的修改只对该进程可见。
MAP_SHARED: 对共享该数据的所有进程可见。
mmap成功后,fd可以关闭,映射已经建立。
从进程地址空间删除一个映射关系
#include <sys/mman.h>
int munmap(void *addr, size_t len);
使硬盘上的文件与内存映射区的内容一致
int msync(void *addr, size_t len, int flags);
成功:0; 失败:-1
flags: MS_ASYNC:异步写
MS_SYNC: 同步写
MS_INVALIDATE: 使高速缓存失效
3.匿名映射
若调用mmap目的是提供一个将穿越fork由父子进程共享的映射内存区,即在只是在有亲缘关系的内存间共享内存,可以使用匿名映射。
(1) 4.4BSD提供了匿名内存内存映射,避免了文件的创建和打开。
通过flags指定MAP_SHARED | MAP_ANON, fd = -1, offset = 0。这样的内存区初始化为0。
int *ip = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, –1, 0);
(2) SVR4 提供/dev/zero设备文件,open它之后可在mmap中使用得到的描述符。
从设备读时返回的字节全是0,写往该设备的任何字节则被丢弃。许多BSD的也支持这个。
int fd = open(“/dev/zero”, O_RDWR);
int *ip = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
示例:
父子进程给共享内存区的数字加1
同步使用了:有名信号量,无名信号量,互斥锁
共享内存使用了:内存映射文件,匿名共享内存区
总结:对于这种亲缘关系的进程间通信,最好使用:匿名共享共享内存,无名信号量。
#include "unp.h"
struct shared {
// sem_t mutex; //信号量互斥
pthread_mutex_t mutex; //互斥锁互斥
int count;
} shared;
int main(int argc, char **argv)
{
if (argc != 3) {
err_quit("Usage: a.out <pathname> <#loop>");
}
int nloop = atoi(argv[2]);
// int fd = Open(argv[1], O_RDWR | O_CREAT, FILE_MODE);
// //改变文件大小的可移植的方式
// Write(fd, &shared, sizeof(shared));
// struct shared *ptr = (struct shared*)Mmap(NULL, sizeof(shared), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
// Close(fd);
//使用/dev/zero内存映射
//int fd = Open("/dev/zero", O_RDWR, FILE_MODE);
//struct shared *ptr = (struct shared*)Mmap(NULL, sizeof(shared),
// PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
//使用匿名映射
//fd = -1
//添加MAP_ANON
//只适用于亲缘进程间
struct shared *ptr = (struct shared*)Mmap(NULL, sizeof(shared),
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
//使用有名信号量实现互斥
//sem_unlink("mysem");
//sem_t *mutex = Sem_open("mysem", O_CREAT | O_EXCL, FILE_MODE, 1);
//Sem_unlink("mysem");
//使用无名信号量(基于内存)实现互斥
//第二个1:进程间共享。0:线程间共享
//第三个1:初值
//Sem_init(&ptr->mutex, 1, 1);
//使用互斥量实现互斥
//互斥量用于进程间互斥,
//需要设置共享
pthread_mutexattr_t mattr;
Pthread_mutexattr_init(&mattr);
Pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
Pthread_mutex_init(&ptr->mutex, &mattr);
//设置标准输出无缓冲
setbuf(stdout, NULL);
if (Fork() == 0) {
for (int i = 0; i < nloop; ++i) {
//有名信号量
//Sem_wait(mutex);
//无名信号量
//Sem_wait(&ptr->mutex);
//互斥量
Pthread_mutex_lock(&ptr->mutex);
printf("child: %d\n", (ptr->count)++);
Pthread_mutex_unlock(&ptr->mutex);
//Sem_post(&ptr->mutex);
//Sem_post(mutex);
}
exit(0);
}
for (int i = 0; i < nloop; ++i) {
//Sem_wait(mutex);
//Sem_wait(&ptr->mutex);
Pthread_mutex_lock(&ptr->mutex);
printf("parent: %d\n", (ptr->count)++);
Pthread_mutex_unlock(&ptr->mutex);
//Sem_post(&ptr->mutex);
//Sem_post(mutex);
}
exit(0);
}
4. Posix共享内存区
open——mmap:posix内存映射文件;
shm_open——mmap: posix共享内存区对象。
这两者叫做:Posix内存区对象。
4.1. 打开,删除
posix共享内存区需两个步骤:
(1) 指定名字参数调用shm_open,以创建一个新的共享内存区对象或打开一个已存在的共享内存区对象。
(2) 调用mmap把这个共享内存区映射到进程的地址空间。
#include <sys/mman.h>
int shm_open(const char *name, int oflag, mode_t mode);
成功:非负描述符; 出错:-1;
删除一个共享内存区对象的名字。不会影响对于其底层支撑对象的现有应用,直到对于该对象的应用全部关闭为止。
int shm_unlink(const char *name);
改变普通文件或共享内存区对象的大小
#include <unistd.h>
int ftruncate(int fd, off_t length);
已存在的共享内存区对象,或取对象大小
#include <sys/type.h>
#include <sys/stat.h>
int fstat(int fd, struct stat *buf);
struct stat {
mode_t st_mode;
uid_t st_uid;
gid_t st_gid;
off_t st_size; //size in bytes
};
示例:
两个独立的进程给共享内存区数据加1。
建立共享内存区使用:Posix内存映射文件,Posix共享内存区对象
同步使用:无名信号量,当然其他也可已。
创建共享内存区程序:
#include "unp.h"
struct shmstruct {
int count;
sem_t mutex;
} shm;
int main(int argc, char **argv)
{
//Posix 共享内存区对象
// shm_unlink("myshm");
// int fd = Shm_open("myshm", O_RDWR | O_CREAT | O_EXCL, FILE_MODE);
// Ftruncate(fd, sizeof(struct shmstruct));
// struct shmstruct *ptr = (struct shmstruct*)Mmap(NULL, sizeof(struct shmstruct), PROT_READ | PROT_WRITE,
// MAP_SHARED, fd, 0);
// Close(fd);
//Posix内存映射文件
int fd = Open("myopenshm", O_RDWR | O_CREAT, FILE_MODE);
Write(fd, &shm, sizeof(shm));
struct shmstruct *ptr = (struct shmstruct*)Mmap(NULL, sizeof(struct shmstruct), PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
Close(fd);
Sem_init(&ptr->mutex, 1, 1);
exit(0);
}
给共享内存区加1程序:
#include "unp.h"
struct shmstruct {
int count;
sem_t mutex;
};
int main(int argc, char **argv)
{
if (argc != 2) {
err_quit("Usage: a.out <#loops>");
}
int nloops = atoi(argv[1]);
//int fd = Shm_open("myshm", O_RDWR, FILE_MODE);
//struct shmstruct *ptr = (struct shmstruct*)Mmap(NULL, sizeof(struct shmstruct),
// PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
//Close(fd);
int fd = Open("myopenshm", O_RDWR, FILE_MODE);
struct shmstruct *ptr = (struct shmstruct*)Mmap(NULL, sizeof(struct shmstruct),
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
Close(fd);
pid_t pid = getpid();
for (int i = 0; i < nloops; ++i) {
Sem_wait(&ptr->mutex);
printf("pid = %ld: %d\n", (long)pid, ptr->count++);
Sem_post(&ptr->mutex);
}
exit(0);
}