mmap
mmap可以把一个文件映射到进程内存里面,之后操作这块内存就相当于操作文件,文件存在于文件系统里面,不同的进程都能读取这个文件,所以mmap就可以当作IPC来使用。
代码
这里写了一个producer和consumer来模拟mmap在不同的进程之间共享数据。
msg.h
#ifndef MSG_H
#define MSG_H
#define MMAP_FILE "test_mmap"
#define MSG_ARRAY_SIZE 4096
#include <pthread.h>
#include <errno.h>
#include <stdio.h>
struct msg {
double c;
int a;
char b;
};
struct mmap_struct {
pthread_mutex_t mutex;
struct msg msg_arr[MSG_ARRAY_SIZE];
int msg_count;
};
int init_mutex(pthread_mutex_t* mutex) {
pthread_mutexattr_t mutex_attr;
pthread_mutexattr_init(&mutex_attr);
pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
pthread_mutexattr_setrobust(&mutex_attr, PTHREAD_MUTEX_ROBUST);
int error = pthread_mutex_init(mutex, &mutex_attr);
if (error == EBUSY) {
printf("Mutex do not need to reinit.\n");
}
return 0;
}
#endif //MSG_H
producer.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "msg.h"
int main(int argc, char** argv) {
int fd = open(MMAP_FILE, O_RDWR, 0777);
if (fd < 0) {
perror("open error");
return -1;
}
struct stat sb;
fstat(fd, &sb);
printf("%ld.\n", sb.st_size);
struct mmap_struct* ms = (struct mmap_struct*) mmap(NULL, sizeof(struct mmap_struct), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (ms == MAP_FAILED) {
perror("mmap error");
return -1;
}
init_mutex(&ms->mutex);
memset(ms->msg_arr, 0, sizeof(struct msg) * MSG_ARRAY_SIZE);
ms->msg_count = 0;
int i = 0;
double j = 1.00;
while (1) {
int lock_error = pthread_mutex_lock(&ms->mutex);
if (lock_error != 0) {
if (lock_error == EOWNERDEAD) {
pthread_mutex_consistent(&ms->mutex);
} else if (lock_error == ENOTRECOVERABLE) {
pthread_mutex_destroy(&ms->mutex);
init_mutex(&ms->mutex);
}
}
struct msg cur_msg;
memset(&cur_msg, 0, sizeof(struct msg));
cur_msg.a = ++i;
cur_msg.b = 'a';
cur_msg.c = (j = j + 0.01);
memmove((void*)&ms->msg_arr[ms->msg_count], &cur_msg, sizeof(struct msg));
if (ms->msg_count++ >= MSG_ARRAY_SIZE) {
ms->msg_count = 0;
}
printf("Success Produce %d msgs.\n", ms->msg_count);
pthread_mutex_unlock(&ms->mutex);
sleep(1);
}
return 0;
}
consumer.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "msg.h"
int clear_last_msg(struct mmap_struct* ms) {
int msg_len = ms->msg_count;
memset(&ms->msg_arr[msg_len], 0, sizeof(struct msg));
return 0;
}
int main(int argc, char** argv) {
int fd = open(MMAP_FILE, O_RDWR, 0777);
if (fd < 0) {
perror("open error");
return -1;
}
struct mmap_struct* ms = (struct mmap_struct*) mmap(NULL, sizeof(struct mmap_struct), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (ms == MAP_FAILED) {
perror("mmap error");
return -1;
}
int cur_msg_index = 0;
while (1) {
int lock_error = pthread_mutex_lock(&ms->mutex);
if (lock_error != 0) {
if (lock_error == EOWNERDEAD) {
pthread_mutex_consistent(&ms->mutex);
clear_last_msg(ms);
} else if (lock_error == ENOTRECOVERABLE) {
pthread_mutex_destroy(&ms->mutex);
init_mutex(&ms->mutex);
}
}
int msg_len = ms->msg_count;
printf("Total Msg Len is : %d.\n", msg_len);
struct msg current_msg;
memset(¤t_msg, 0, sizeof(struct msg));
if (cur_msg_index < msg_len) {
memmove(¤t_msg, &ms->msg_arr[cur_msg_index++], sizeof(struct msg));
printf("%f %d %c.\n", current_msg.c, current_msg.a, current_msg.b);
}
pthread_mutex_unlock(&ms->mutex);
sleep(2);
}
return 0;
}
注意点
- test_mmap file
这个文件是dd写出来的,具体的命令:
dd if=/dev/zeor of=test_mmap count=256
- 在初始化mutex的时候,设置了两个mutex的属性,分别是:
-
PTHREAD_PROCESS_SHARED
这个属性主要是为了让锁能在不同的进程之间的线程使用,正好符合这个demo的情况。 -
PTHREAD_MUTEX_ROBUST
当持有锁的线程异常退出,但是没有unlock锁的时候,这个属性是很有用的。可以使用pthread_mutexattr_setrobust 来设置这个属性。
man page里面写有:如果设置了这个属性,那么当锁持有者异常退出但是没有释放锁的情况下,另外一个线程再去获取这个锁的话,会返回一个EOWNERDEAD的错误码,这样我们就可以调用pthread_mutex_consistent来强制获取这个锁。
如果另外的线程没有执行pthread_mutex_consistent但是调用了unlock操作时,下一个尝试获取锁的线程会得到ENOTRECOVERABLE的错误,在这种情况下,唯一的合法的操作就是把这个锁销毁掉。所以我们针对消费者或者生产者挂掉,但是没有释放锁的情况,需要做一些处理:
- 先要把锁据为己有,再进行操作
- 如果锁已经失效,那么重构这个锁。
在msg.h文件里面有一个初始化锁的函数,在man page里面有写,如果当我们调用pthread_mutex_init 函数去初始化一把已经初始化过的锁会返回EBUSY错误码,这种情况下我们就只用提示一下这把锁已经初始化过即可。
-
知识点
- 关于mmap的使用。
- 锁的易主、死锁情况的处理。
- dd命令使用。
- 如何检查mutex是否被初始化。
- 进程间的锁的使用。