mmap和信号量实现进程间通信相关
- mmap
- 1. mmap 使用的注意事项
- 2. mmap的两种映射
- 3. mmap调用接口以及参数
- 4. 使用存储映射区实现父子进程间通信(有名)
- 父子进程通信的三种方式
- unlink
- 5. 创建匿名存储映射区
- 6. 通过存储映射区实现非血缘关系进程间的通信
- 信号量
- 无名信号量+mmap,父子进程间通信
- 有名信号量实现不想关进程间通信(同步)
mmap
高赞-通透的解释-解决自己内存映射相关问题的解释
原理参考:mmap原理、mmap原理参考
1. mmap 使用的注意事项
(1)当 open 一个文件时,如果指定了 O_CREAT 标志并且文件不存在,就会新创建一个文件作为映射文件,此时必须调用 ftruncate 或者 lseek+write 设置文件长度,否则任然可以调用 mmap,但是对存储映射区的引用会产生 SIGBUS。另外,如果映射的长度超过了文件长度,访问超过文件长度的映射区也会出错。
(2)munmap 释放映射区时传入的指针必须指向最初分配的位置,否则将会出错(中途可移动映射区指针,但是必须从最开始分配的位置开始释放)。
(3)mmap 指定对映射区的访问权限时不能超过 open 对文件指定的访问权限。
(4)mmap 建立映射区时隐含一次对文件的读,因此打开文件时读权限是必须有的,即使你只进行写操作。
2. mmap的两种映射
对于有血缘关系的进程间通信:
- 有名内存映射区
- 匿名内存映射区(推荐)
对于无血缘关系的进程间通信:
- (只能用)有名内存映射区
3. mmap调用接口以及参数
void* mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t len);
4. 使用存储映射区实现父子进程间通信(有名)
父子进程通信的三种方式
- mmap MAP_ANONYMOUS:
在支持MAP_ANONYMOUS的系统上,直接用匿名共享内存即可,
mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, 0); - mmap /dev/zero:
有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero
文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。 - shmget shmat shmctl:
shmget 是老式的system V 共享内存模式,很多系统都支持这种方法。
注意:父子进程的内存空间遵循 读时共享、写时复制,但打开的文件和 mmap 建立的存储映射区在父子进程之间是一直共享的,因此可通过 mmap 建立存储映射区实现父子进程之间的通信。通过以下代码可说明:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
int var = 10;
int main() {
int* p;
int fd;
pid_t pid;
if ((fd = open("tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0) {
perror("open tmpfile");
exit(1);
}
unlink("tmpfile"); //解除硬链接,即删除了临时文件唯一的目录项,使之具备被删除的条件,但此时并未删除
ftruncate(fd, 4); //创建文件大小
p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED) {
perror("mmap failed");
exit(1);
}
close(fd);
if ((pid = fork()) == 0) { //child
sleep(1);
printf("In child: *p = %d, var = %d\n", *p, var);
}
else if (pid > 0) { //parent
*p = 2000; //修改映射区
var = 1000; //修改全局变量
printf("In parent: *p = %d, var = %d\n", *p, var);
wait(NULL); //等待子进程执行完成
if (munmap(p, 4) < 0) { //释放映射区
perror("munmap failed");
exit (1);
}
}
return 0;
}
注意:对于创建的文件需要使用
truncate
或者ftruncate
先进行文件大小的开辟,才能往文件里写东西,写得超过这个大小会报SIGSEGV或者SEGBUS错误。具体解释看解释
unlink
unlink()会删除参数pathname 指定的文件。 如果该文件名为最后连接点, 但有其他进程打开了此文件, 则在所有关于此文件的文件描述词皆关闭后才会删除. 如果参数pathname 为一符号连接, 则此连接会被删除。
我们知道Linux中文件是用inode节点来区分文件的,当我们删除一个文件的时候并不一定系统就会释放inode节点的内容。当满足下面的要求的时候系统才会释放inode节点的内容:
- inode中记录指向该节点的硬链接数为0,
- 没有进程打开指向该节点的文件
使用unlink函数删除文件的时候,只会删除 目录项 ,并且将inode节点的硬链接数目减一而已,并不一定会释放inode节点。如果此时没有进程正在打开该文件或者有其他文件指向该inode节点,该inode节点将会被释放;如果此时有进程正在打开一个文件,而此时使用unlink删除了该文件,那么此时只是删除了目录项,并没有释放,因为此时仍然有进程在打开这个文件。
unlink函数的另一个用途就是用来创建临时文件,如果在程序中使用open创建了一个文件后,然后立即使用 unlink 函数删除文件,由于此时进程正在打开该文件,所以系统并不会释放该文件的 inode 节点,而只是删除其目录项。当进程退出时,该inode节点将会立即被释放。临时文件可以用在进程间通信中的 有名管道 通信中
5. 创建匿名存储映射区
以上方法虽然实现了父子进程之间的通信,但是每次都要依赖一个文件,如果是一个临时文件,打开后马上进行了 unlink 使文件具备了被释放的条件,在进程结束后文件就被释放,因此这个文件根本就没有存在的必要,可通过匿名映射区避免这种情况。但是匿名映射区只能实现有血缘关系的进程间的通信。所有类 Unix 系统可以借助文件 /dev/zero
实现匿名映射区
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
int var = 10;
int main() {
int* p;
pid_t pid;
//注意这里加上 MAP_ANON 参数并将文件描述符指定为 -1
p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (p == MAP_FAILED) {
perror("mmap failed");
exit(1);
}
if ((pid = fork()) == 0) { //child
sleep(1);
printf("In child: *p = %d, var = %d\n", *p, var);
}
else if (pid > 0) { //parent
*p = 2000; //修改映射区
var = 1000; //修改全局变量
printf("In parent: *p = %d, var = %d\n", *p, var);
wait(NULL); //等待子进程执行完成
if (munmap(p, 4) < 0) { //释放映射区
perror("munmap failed");
exit (1);
}
}
return 0;
}
6. 通过存储映射区实现非血缘关系进程间的通信
非血缘关系的进程间不能通过匿名映射区实现
写进程:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
//这里换一种数据结构进行通信
struct STU {
int id;
char name[20];
char sex;
};
void sys_err(char* s) {
perror(s);
exit(1);
}
int main() {
int fd;
struct STU stu = {10, "xiaoming", 'm'};
struct STU* mm;
if ((fd = open("tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0)
sys_err("open tmpfile");
//unlink("tmpfile"); //写进程不能立即进行 unlink,因为他要保证写进程能找到这个目录项
ftruncate(fd, sizeof(stu)); //创建文件大小
mm = mmap(NULL, sizeof(stu), PROT_WRITE, MAP_SHARED, fd, 0);
if (mm == MAP_FAILED)
sys_err("mmap failed");
close(fd);
while (stu.id++ < 100) {
memcpy(mm, &stu, sizeof(stu));
sleep(1);
}
unlink("tmpfile");
munmap(mm, sizeof(stu));
return 0;
}
读进程:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
//这里换一种数据结构进行通信
struct STU {
int id;
char name[20];
char sex;
};
void sys_err(char* s) {
perror(s);
exit(1);
}
int main() {
int fd;
struct STU stu;
struct STU* mm;
if ((fd = open("tmpfile", O_RDONLY)) < 0)
sys_err("open tmpfile");
unlink("tmpfile"); //读进程可以立即进行 unlink
ftruncate(fd, sizeof(stu));
mm = mmap(NULL, sizeof(stu), PROT_READ, MAP_SHARED, fd, 0);
if (mm == MAP_FAILED)
sys_err("mmap failed");
close(fd);
while (mm->id < 100) {
printf("id = %d, name = %s, sex = %c \n", mm->id, mm->name, mm->sex);
sleep(1);
}
munmap(mm, sizeof(stu));
return 0;
}
信号量
sem_t
分为有名和无名。有名的sem_t通过sem_open
来创建, 而无名的sem_t通过sem_init
的初始化。
无名信号量主要用于线程间的通信,保存在内存中,如果想要在进程间同步就必须把无名信号量放在进程间的共享内存中。而在进程间的通信中同步用的通常是有名信号量。有名信号量一般保存在/dev/shm/ 目录下。像文件一样存储在文件系统中。
有名信号量参数详解
有名信号量和无名信号量的区别和联系:
- 无名信号量的创建信号量函数是sem_init,有名信号量的则是sem_open函数。
- 无名信号量的删除信号量函数是sem_destroy,有名信号量的则是用sem_close函数关闭有名信号量,但是想要把信号量从文件系统删除得用sem_unlink函数。
无名信号量+mmap,父子进程间通信
#include <semaphore.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
void *createSharedMemory(size_t size) {
void *addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0);
if (addr == MAP_FAILED) {
return NULL;
}
return addr;
}
void freeSharedMemory(void *addr, size_t size)
{
if (munmap(addr, size) == -1) {
printf("munmap(%p, %d) failed", addr, (int)size);
}
}
int main(int argc, char **argv)
{
sem_t* mutex = (sem_t*)createSharedMemory(sizeof(sem_t));
int fd, i, count = 0, nloop = 200, zero = 0, *ptr;
// sem_t mutex;
//open a file and map it into memory
fd = open("log.txt", O_RDWR | O_CREAT, S_IRWXU);
write(fd, &zero, sizeof(int));
unlink("log.txt"); //解除硬链接,即删除了临时文件唯一的目录项,使之具备被删除的条件,但此时并未删除
// ftruncate(fd, 4);
ptr = (int*)mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
/* create, initialize semaphore */
if (sem_init(mutex, 1, 1) < 0) //
{
perror("semaphore initilization");
exit(0);
}
if (fork() == 0) { /* child process*/
for (i = 0; i < nloop; i++) {
sem_wait(mutex);
printf("child: %d\n", (*ptr)++);
sem_post(mutex);
}
exit(0);
}
/* back to parent process */
for (i = 0; i < nloop; i++) {
sem_wait(mutex);
printf("parent: %d\n", (*ptr)++);
sem_post(mutex);
}
exit(0);
}
注意:信号量也需要在共享区才能使得两个进程访问到,不然即使pshare=1
也没用
有名信号量实现不想关进程间通信(同步)
/* share_memory_name_sem_1.c*/
#include <unistd.h>
#include <stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/shm.h>
#include <string.h>
#define BUFF 128
int main ()
{
int count=0;
char write_buf[] = "helloworld";
int i = 0;
sem_t *sem_1 = NULL;
sem_t *sem_2 = NULL;
sem_1 = sem_open("write_name_sem_1", O_CREAT|O_RDWR, 0666, 1); //信号量值为 1
sem_2 = sem_open("write_name_sem_2", O_CREAT|O_RDWR, 0666, 0); //信号量值为 1
if( (sem_1 == SEM_FAILED) | (sem_2 == SEM_FAILED))
{
perror("sem_open");
exit(-1);
}
int shmid;
char *shmaddr;//共享内存地址
// 使用约定的键值创建共享内存
//if((shmid=shmget(IPC_PRIVATE,BUFF,0666))<0)
if((shmid=shmget((key_t) 1234,BUFF, 0666|IPC_CREAT))<0)
{
perror("shmget");
exit(-1);
}
else
printf("Create shared memory,id = %d\n",shmid);
/*映射共享内存*/
if((shmaddr=shmat(shmid,0,0))<(char *)0)
{
perror("shmat");
exit(-1);
}
else
printf("process 1 shmat shared memory success\n");
/*信号量减一,P 操作*/
sem_wait(sem_1);
//往共享内存追加写数据
strncat(shmaddr,write_buf,sizeof(write_buf));
/*信号量加一,V 操作*/
sem_post(sem_2);
sem_close(sem_1); //关闭有名信号量 sem_1
sem_close(sem_2); //关闭有名信号量 sem_2
//把共享内存从当前进程中分离
if(shmdt(shmaddr) == -1)
{
perror("shmdt");
exit(-1);
}
return 0;
}
/* share_memory_name_sem_2.c*/
#include <unistd.h>
#include <stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/shm.h>
#include <string.h>
#include <stddef.h>
#define BUFF 128
int main ()
{
int count=0;
char write_buf[] = "2";
int i = 0;
sem_t *sem_1 = NULL;
sem_t *sem_2 = NULL;
sem_1 = sem_open("write_name_sem_1", O_CREAT|O_RDWR, 0666, 1); //信号量值为 1
sem_2 = sem_open("write_name_sem_2", O_CREAT|O_RDWR, 0666, 0); //信号量值为 1
if( (sem_1 == SEM_FAILED) | (sem_2 == SEM_FAILED))
{
perror("sem_open");
exit(-1);
}
int shmid;
char *shmaddr;//共享内存地址
// 使用约定的键值创建共享内存
//if((shmid=shmget(IPC_PRIVATE,BUFF,0666))<0)
if((shmid=shmget((key_t) 1234,BUFF, 0666|IPC_CREAT))<0)
{
perror("shmget");
exit(-1);
}
else
printf("Create shared memory,id = %d\n",shmid);
/*映射共享内存*/
if((shmaddr=shmat(shmid,0,0))<(char *)0)
{
perror("shmat");
exit(-1);
}
else
printf("process 2 shmat shared memory success\n");
/*信号量减一,P 操作*/
sem_wait(sem_2);
//读取共享内存的数据
printf("read from share memory: %s\n",shmaddr);
/*信号量加一,V 操作*/
sem_post(sem_1);
sem_close(sem_1); //关闭有名信号量 sem_1
sem_close(sem_2); //关闭有名信号量 sem_2
sleep(2);
printf("remove write_name_sem_1\n");
//删除有名信号量
if(sem_unlink("write_name_sem_1") < 0)
{
perror("sem_unlink");
}
printf("remove write_name_sem_2\n");
if(sem_unlink("write_name_sem_2") < 0)
{
perror("sem_unlink");
}
printf("detach shmaddr\n");
//把共享内存从当前进程中分离
if(shmdt(shmaddr) == -1)
{
perror("shmdt");
exit(-1);
}
printf("remove shmaddr\n");
//删除共享内存
if(shmctl(shmid, IPC_RMID, 0) == -1)
{
perror("shmctl");
exit(-1);
}
printf("it is end!\n");
return 0;
}
更多有名信号量例子