目录
1 内存映射基本使用
1.1 内存映射概念
1.2 内存映射的使用
2 共享内存(古老的 System V IPC)
2.1 基本概念
2.2 共享内存使用步骤
2.3 共享内存使用
掌握:内存映射概念、内存映射使用、内存映射注意事项、了解SYSTEM V 共享内存概念 共享内存使用步骤
共享内存可以通过mmap()映射普通文件, 使一个磁盘文件与内存中的一个缓冲区相映射,进程可以像访问普通内存一样对文件进行访问,不必再调用read,write。
mmap的优点
实现了用户空间和内核空间的高效交互方式
进程A与B通过共享内存,可以实现不通过磁盘,而通过内存访问,速度大大增加。
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
功能:创建共享内存映射
函数返回值:成功返回创建的映射区首地址,失败返回MAP_FAILED( ((void *) -1) ),设置errno值
参数说明:
1 addr:指定要映射的内存地址,一般设置为 NULL 让操作系统自动选择合适的内存地址。
2 length:必须>0。映射地址空间的字节数,它从被映射文件开头 offset 个字节开始算起。
3 prot:指定共享内存的访问权限。可取如下几个值的可选:PROT_READ(可读), PROT_WRITE(可写), PROT_EXEC(可执行), PROT_NONE(不可访问)。
4 flags:由以下几个常值指定:MAP_SHARED(共享的) MAP_PRIVATE(私有的), MAP_FIXED(表示必须使用 start 参数作为开始地址,如果失败不进行修正),其中,MAP_SHARED , MAP_PRIVATE必选其一,而 MAP_FIXED 则不推荐使用。MAP_ANONYMOUS(匿名映射,用于血缘关系进程间通信)
5 fd:表示要映射的文件句柄。如果匿名映射写-1。
6 offset:表示映射文件的偏移量,一般设置为 0 表示从文件头部开始映射。
示例:写文件
#include
#include
#include
#include
#include
#include
#include
#include
int main(){
void *addr;
int fd;
fd =open("test",O_RDWR); //如果只读,但是共享内存是读写,那么会报错
if(fd<0){
perror("open");
return 0;
}
int len = lseek(fd,0,SEEK_END); //取文件大小
addr = mmap(NULL,2048, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(addr == MAP_FAILED){
perror("mmap");
return 0;
}
close(fd);
int i=0;
while(i<2048){
memcpy((addr+i),"a",1);
i++;
sleep(1);
}
printf("read=%s\n",(char*)(addr));
}
示例:读文件
#include
#include
#include
#include
#include
#include
#include
#include
int main(){
void *addr;
int fd;
fd =open("test",O_RDWR);
if(fd<0){
perror("open");
return 0;
}
int len = lseek(fd,0,SEEK_END);
addr = mmap(NULL,2048, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(addr == MAP_FAILED){
perror("mmap");
return 0;
}
close(fd);
// memcpy((addr),"99999999999999",15);
while(1){
printf("read=%s\n",(char*)(addr));
sleep(1);
}
}
同时运行写、读程序,实现进程间通信
内存映射注意事项(重点)
mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。
释放内存映射
munmap函数
int munmap(void *addr, size_t length);
返回值:成功返回0,失败返回-1,并设置errno值。
函数参数:
addr:调用mmap函数成功返回的映射区首地址
length:映射区大小(即:mmap函数的第二个参数)
示例:匿名映射
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(){
void *addr;
//参数MAP_ANONYMOUS -1
addr = mmap(NULL,2048, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
if(addr == MAP_FAILED){
perror("mmap");
return 0;
}
pid_t pid;
pid = fork();
if(pid<0){
perror("fork");
return 0;
}
else if(pid>0){
memcpy(addr,"1234567890",10);
//sleep(10);//增加一个时间差的作用,避免同时,如果加了wait回收子进程,是阻塞函数可以不需要sleep
wait(NULL); //回收子进程
}else {
sleep(1);
printf("read father val=%s\n",(char *)addr);
}
munmap(addr,2048); //释放
}
IPC 对象包含: 共享内存、消息队列(过时)和信号灯集(过时)
每个IPC对象有唯一的ID 用Key关联
IPC对象创建后一直存在,直到被显式地删除
ipcs / ipcrm
共享内存
生成key
创建/打开共享内存 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
读写共享内存
撤销共享内存映射
删除共享内存对象
System V IPC - key
System V IPC – ftok (非必须,通过两个参数拼接可以实现唯一)
#include
#include
key_t ftok(const char *path, int proj_id);
共享内存创建 – shmget
#include
#include
int shmget(key_t key, int size, int shmflg);
共享内存映射 – shmat
#include
#include
void *shmat(int shmid, const void *shmaddr, int shmflg);
示例
通过指针访问共享内存,指针类型取决于共享内存中存放的数据类型 例如:在共享内存中存放键盘输入的字符串
共享内存撤销映射 – shmdt
#include
#include
int shmdt(void *shmaddr);
共享内存控制 – shmctl
#include
#include
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
共享内存 - 注意事项
每块共享内存大小有限制
查看共享内存命令
ipcs -l
cat /proc/sys/kernel/shmmax
示例
#include
#include
#include
#include
#include
int main(){
key_t key;
int shmid;
char *buf;
//1.创建key
key = ftok("keytest",100);
if(key<0){
perror("ftok");
return 0;
}
printf("key=%x\n",key);
//2.创建共享内存
shmid = shmget(key,512,IPC_CREAT|0666);
if(shmid<0){
perror("shmget");
return 0;
}
printf("shmid=%d\n",shmid);
//3.映射共享内存
buf = shmat(shmid,NULL,0);
if(buf<0){
perror("shmat");
return 0;
}
//拷贝
strcpy(buf,"hello world");
}
示例2 写共享内存
#include
#include
#include
#include
#include
int main(){
key_t key;
int shmid;
char *buf;
key = ftok("keytest",100);
if(key<0){
perror("ftok");
return 0;
}
printf("key=%x\n",key);
shmid = shmget(key,512,IPC_CREAT|0666);
if(shmid<0){
perror("shmget");
return 0;
}
printf("shmid=%d\n",shmid);
buf = shmat(shmid,NULL,0);
if(buf<0){
perror("shmat");
return 0;
}
strcpy(buf,"hello world");
}
示例3 读共享内存
#include
#include
#include
#include
#include
#include
int main(){
key_t key;
int shmid;
char *buf;
key = ftok("keytest",100);
if(key<0){
perror("ftok");
return 0;
}
printf("key=%x\n",key);
shmid = shmget(key,512,0666);//不需要CREATE
if(shmid<0){
perror("shmget");
return 0;
}
printf("shmid=%d\n",shmid);
buf = shmat(shmid,NULL,0);
if(buf<0){
perror("shmat");
return 0;
}
// strcpy(buf,"hello world");
printf("share mem=%s\n",buf);
while(1){
sleep(1);
}
//4.共享内存撤销映射
shmdt(buf);
//5.共享内存删除,防止内存泄漏
shmctl(shmid, IPC_RMID, NULL);
// printf("detach mem=%s\n",buf);
}
未删除 启动两个的状态值为2,没法撤销了,使用时候需要注意
共享内存删除的时间点
添加删除标记
nattach 变成0时真正删除
共享内存和管道是进程间通信的两种机制,它们有以下区别:
数据结构:共享内存是一块被多个进程共享的内存区域,进程可以直接读写这块内存;而管道是通过内核中的缓冲区来传递数据。
通信方式:共享内存提供了一种高效的内存共享方式,进程可以直接在共享内存中读写数据,因此速度较快;而管道是一种半双工的通信方式,数据只能单向流动,需要两个管道才能实现双向通信。
同步机制:在使用共享内存进行通信时,进程需要使用一些同步机制(如信号量、互斥锁等)来确保多个进程之间对共享内存的访问不会产生冲突;而在管道中,由于数据流是单向的,多个进程之间通常不会发生冲突。
编程复杂性:共享内存在编程上相对较为复杂,需要注意进程间的同步与互斥;而管道在编程上较为简单,但由于数据流是单向的,双向通信需要使用两个管道。
综上所述,共享内存适用于需要高效共享大量数据的场景,但需要考虑同步与互斥问题;而管道适用于简单的进程间通信,但数据流是单向的。选择合适的通信机制需要根据具体的应用场景和需求来决定。
实现两个进程使用内存映射通信程序
#include
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[])
{
pid_t pid;
void *addr;
int len;
addr = mmap(NULL, 2048, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
if(addr == MAP_FAILED)
{
perror("mmap");
return 0;
}
pid = fork();
if(pid < 0)
{
perror("fork");
return 0;
}
else if( pid > 0)
{
printf("This is father process\n");
memcpy(addr, "father write", 12);
wait(NULL);
}
else //( pid == 0)
{
printf("This is child process\n");
sleep(1);
printf("child recv:%s\n",(char *)addr);
}
munmap(addr,2048);
return 0;
}
//运行结果
linux@linux:~/Desktop$ ./mmap_test
This is father process
This is child process
child recv:father write