进程间通信-共享内存shmat
进程间通信三种方式
在linux系统中,每个进程都有独立的虚拟空间地址,通过MMU地址转换将虚拟地址与物理地址进行映射,每个进程相同的虚拟地址空间都会映射到不同的物理地址,每个进程在物理内存空间都是相互独立和隔离的。
不同的进程之间如果需要相互通信,该怎么办?因为不同的进程在物理内存上是相互隔离的,所以需要借助第三方工具来完成进程间通信。其实进程间通信的本质就是交换数据,进程间交换数据有三种方式:通过文件、通过内核、共享内存。
- 通过文件:AB进程通过访问同一个磁盘文件(I/O访问)进行数据交换
- 通过内核:进程间用户空间是相互独立,但是内核空间都是同一个, 因此可以通过内核这个中介去进行数据交换
- 共享内存:每个进程间的虚拟地址会映射到不同的物理地址,如果允许映射到同一块物理地址就可以进行数据交换
共享内存特点
- 共享内存 VS 通过文件:共享内存读写速度更快
- 共享内存VS通过内核:抛弃了内核“代理人”角色,让两个进程直接通过一块内存通信。减少了内存拷贝(从用户拷贝到内核、从内核拷贝到用户空间),减少了2次系统调用,提高系统性能
- 共享内存缺点:是共享内存并未提供同步机制,所以需要用其他机制来同步对共享内存的方位,这将由程序员来完成。一般可以通过信号量、互斥锁、文件锁等配合使用,防止数据的踩踏。
共享内存原理
共享内存是由IPC为进程创建的一个特殊的地址范围,出现在该进程的地址空间中,其他进程可以将同一段共享内存连接到他们自己的地址空间中。所有进程都可以访问共享内存中的地址,就好像他们是由malloc分配的一样。如果一个进程向共享内存中写了数据,那么其他进程将立刻能够看到。
共享内存使用
创建/获取共享内存shmget
该函数用来创建/获取共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
- key:IPC 对象的键值,一般为IPC_PRIVATE或ftok返回的key值
- size:共享内存的大学,一般为存物理页的整数倍
- shmflg:IPC_CREAT:如果不存在与制定的key对应的段,那么就创建一个新段;IPC_EXCL:若key制定的内存存在且制定了IPC_CREAT,返回EEXIST错误;
- 返回值:共享内存的标识符ID
映射共享内存shmat
该函数将shmid标识的共享内存引入到当前进程的虚拟地址空间
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
- shmid:共享内存的IPC对象ID
- shmaddr:若为NULL:共享内存会被attach到一个合适的虚拟地址空间,建议使用NULL;不为NULL:系统会根据参数及地址边界对齐等分配一个合适的地址
- shmflg:IPC_RDONLY:附加只读权限,不指定的话默认是读写权限;IPC_REMAP:替换位于shmaddr处的任意既有映射:共享内存段或内存映射;
- 返回值:共享内存段的地址
共享内存读写
共享内存的读写就要注意共享内存多进程访问同步,一般可以通过信号量、互斥锁、文件锁等配合使用,防止数据的踩踏。
解除内存映射shmdt
该函数解除内存映射,将共享内存分离出当前进程的地址空间
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
- shmaddr:共享内存地址
注意,函数shmdt仅仅是使进程和共享内存脱离关系,将共享内存的引用计数减1,并为删除共享内存。通过#ipc -m就可查看某个IPC对象的状态,其中“连接数”就是表示该共享内存对象被引用的计数。
删除共享内存
上面看到,shmdt仅仅是使进程和共享内存脱离关系,并未删除共享内存。当共享内存的引用次数未0,可以调用shmctl的IPC_RMID命令才会删除共享内存。或者进程结束后,也会被删除掉。
shmctl获取/设置共享内存对象属性
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- shmid:共享内存的对象ID
- cmd:IPC_RMID:删除共享内存段及关联的shmid_ds数据结构
- buf:指向包含共享模式和访问权限的结构
- 返回值:成功返回0,失败返回-1
更多的cmd可以通过man shmct去查看。
生产者-消费者代码示例
下面是一个简单的生产者-消费者模型,生产者producer进程负责将用户输入的数据写到共享内存中,消费者customer进程负责将共享内存中的读出来并打印出来。下面的程序示例通过共享内存中的变量written_by_you标记进行一个读写的同步,保证读写操作是互斥的。
//share.h
#define TEXT_SZ 2048
struct shared_use_st
{
int written_by_you;
char some_text[TEXT_SZ];
};
//customer.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "share.h"
int main()
{
int running = 1;
void *shared_memory = (void *)0;
struct shared_use_st *shared_stuff;
int shmid;
srand((unsigned int)getpid());
shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
if (shmid == -1)
{
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
shared_memory = shmat(shmid, (void *)0, 0);
if (shared_memory == (void *)-1) {
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attached at %X\n", (int)shared_memory);
shared_stuff = (struct shared_use_st *)shared_memory;
shared_stuff->written_by_you = 0;
while(running)
{
if (shared_stuff->written_by_you)
{
printf("You wrote: %s", shared_stuff->some_text);
sleep( rand() % 4 );
shared_stuff->written_by_you = 0;
if (strncmp(shared_stuff->some_text, "end", 3) == 0)
{
running = 0;
}
}
}
if (shmdt(shared_memory) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
if (shmctl(shmid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "shmctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
printf("customer exit.\n");
exit(EXIT_SUCCESS);
}
//producer.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "share.h"
int main()
{
int running = 1;
void *shared_memory = (void *)0;
struct shared_use_st *shared_stuff;
char buffer[BUFSIZ];
int shmid;
shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
if (shmid == -1)
{
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
shared_memory = shmat(shmid, (void *)0, 0);
if (shared_memory == (void *)-1)
{
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attached at %X\n", (int)shared_memory);
shared_stuff = (struct shared_use_st *)shared_memory;
while(running)
{
while(shared_stuff->written_by_you == 1)
{
sleep(1);
printf("waiting for client...\n");
}
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
strncpy(shared_stuff->some_text, buffer, TEXT_SZ);
shared_stuff->written_by_you = 1;
if (strncmp(buffer, "end", 3) == 0)
{
running = 0;
}
}
if (shmdt(shared_memory) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
printf("producer exit.\n");
exit(EXIT_SUCCESS);
}
参考资料
[1]嵌入式C语音自我修养,王立涛
[2]大连理工大学《嵌入式软件设计》慕课