当前位置: 首页 > 科技观察

Linux进程间通信——使用共享内存

时间:2023-03-12 13:36:04 科技观察

下面将讲解另一种进程间通信方式,使用共享内存。1、什么是共享内存顾名思义,共享内存就是让两个不相关的进程访问同一块逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常被安排为同一块物理内存。进程可以将同一块共享内存连接到自己的地址空间,所有进程都可以访问共享内存中的地址,就好像是C语言函数malloc分配的内存一样。但是,如果一个进程将数据写入共享内存,则更改将立即影响可以访问同一共享内存的任何其他进程。特别提醒:共享内存不提供同步机制,即没有自动机制阻止第二个进程在第一个进程写完共享内存之前开始读取它。所以我们通常需要使用其他机制来同步访问共享内存,比如前面提到的信号量。关于信号量的更多信息,可以参考我的另一篇文章:Linux进程间通信——使用信号量二、共享内存的使用,和信号量一样,也提供了一套函数接口供Linux中共享内存使用,接口使用共享共存与信号量非常相似,比使用信号量的接口更简单。它们在头文件sys/shm.h中声明。1.shmget函数这个函数用来创建共享内存。其原型为:[cpp]viewplaincopyintshmget(key_tkey,size_tsize,intshmflg);第一个参数与信号量的semget函数相同。程序需要提供一个参数key(非零整数),有效命名共享内存段,shmget函数在shmget函数成功时返回一个与该key关联的共享内存标识符(非负整数),即用于后续的共享内存功能。调用失败返回-1。不相关的进程可以通过这个函数的返回值访问同一块共享内存,它代表了程序可能要使用的某个资源。程序对所有共享内存的访问是间接的。程序首先调用shmget函数并提供一个key,然后系统生成一个对应的共享内存标识符(shmget函数的返回值),只有shmget函数直接使用信号量key,其他所有信号量函数都使用信号量标识符由semget函数符号返回。第二个参数size指定需要共享的内存容量,单位为字节。第三个参数,shmflg为权限标志,其作用与open函数的mode参数相同。,要创建它,可以使用IPC_CREAT或操作来完成。共享内存的权限标志与文件的读写权限相同,例如0644,表示允许进程创建的共享内存被拥有的进程读写共享内存由内存创建者共享,而其他用户创建的进程只能读取共享内存。2、shmat函数第一次创建共享内存时,不能被任何进程访问。shmat函数的作用是启动对共享内存的访问,将共享内存连接到当前进程的地址空间。其原型如下:[cpp]viewplaincopyvoid*shmat(intshm_id,constvoid*shm_addr,intshmflg);第一个参数shm_id是shmget函数返回的共享内存标识符。第二个参数shm_addr,指定共享内存连接到当前进程的地址位置,通常为空,表示允许系统选择共享内存的地址。第三个参数,shm_flg是一组flags,通常为0。当调用成功时,返回指向共享内存第一个字节的指针。如果调用失败,则返回-1.3。shmdt函数用于将共享内存与当前进程分开。请注意,分离共享内存并不会删除它,它只是使共享内存不再可用于当前进程。其原型如下:[cpp]viewplaincopyintshmdt(constvoid*shmaddr);参数shmaddr是shmat函数返回的地址指针。调用成功返回0,失败返回-1.4。shmctl函数与信号量的semctl函数相同,用于控制Sharedmemory,其原型如下:[cpp]viewplaincopyintshmctl(intshm_id,intcommand,structshmid_ds*buf);第一个参数shm_id是shmget函数返回的共享内存标识符。第二个参数command是要进行的操作,可以取以下三个值:IPC_STAT:将shmid_ds结构中的数据设置为共享内存当前关联值,即用共享内存的当前关联值。IPC_SET:如果进程有足够的权限,将当前共享内存的关联值设置为shmid_ds结构中给定的值IPC_RMID:删除共享内存段的第三个参数,buf是一个结构体指针,指向共享内存mode和access权限的结构。shmid_ds结构至少包括以下成员:[cpp]viewplaincopystructshmid_ds{uid_tshm_perm.uid;uid_tshm_perm.gid;mode_tshm_perm.mode;};3、使用共享内存进行进程间通信说了这么多,该实战了。下面用两个不相关的进程来说明进程之间如何通过共享内存进行通信。其中一个文件shmread.c创建共享内存并读取其中的信息,另一个文件shmwrite.c将数据写入共享内存。为了便于操作和数据结构的统一,对这两个文件定义了相同的数据结构,定义在文件shmdata.c中。结构体shared_use_st中写的作为可读可写标志,非0:表示可读,0表示可写,text是内存中的一个文件。shmdata.h源码如下:[cpp]viewplaincopy#ifndef_SHMDATA_H_HEADER#define_SHMDATA_H_HEADER#defineTEXT_SZ2048structshared_use_st{intwritten;//作为标志,非0:表示可读,0表示可写chartext[TEXT_SZ];//记录写入和readingThefetchedtext};#endif源文件shmread.c的源代码如下:[cpp]viewplaincopy#include#include#include#include#include"shmdata.h"intmain(){intrunning=1;//程序是否继续运行的标志void*shm=NULL;//分配的共享内存的原始首地址structshared_use_st*shared;//指向shmintshmid;//共享内存标识符//创建共享内存shmid=shmget((key_t)1234,sizeof(structshared_use_st),0666|IPC_CREAT);if(shmid==-1){fprintf(stderr,"shmgetfailed\n");exit(EXIT_FAILURE);}//将共享内存连接到当前进程的地址空间shm=shmat(shmid,0,0);if(shm==(void*)-1){fprintf(stderr,"shmatfailed\n");exit(EXIT_FAILURE);}printf("\nMemoryattachedat%X\n",(int)shm);//设置共享内存shared=(structshared_use_st*)shm;shared->written=0;while(running)//读取数据inthesharedmemory{//没有进程设置数据到共享内存,有数据可读if(shared->written!=0){printf("Youwrote:%s",shared->text);sleep(rand()%3);//读取数据后,设置written,使共享内存段可写shared->written=0;//进入end,退出循环(program)if(strncmp(shared->text,"end",3)==0)running=0;}else//有其他进程写数据,不能读数据sleep(1);}//共享内存Detach从当前进程if(shmdt(shm)==-1){fprintf(stderr,"shmdtfailed\n");exit(EXIT_FAILURE);}//删除共享内存if(shmctl(shmid,IPC_RMID,0)==-1){fprintf(stderr,"shmctl(IPC_RMID)failed\n");exit(EXIT_FAILURE);}exit(EXIT_SUCCESS);}源文件shmwrite.c的源代码如下:[cpp]viewplaincopy#include#include#include#include#include#include"shmdata.h"intmain(){intrunning=1;void*shm=NULL;structshared_use_st*shared=NULL;charbuffer[BUFSIZ+1];//用于保存输入文本intshmid;//创建共享内存shmid=shmget((key_t)1234,sizeof(structshared_use_st),0666|IPC_CREAT);if(shmid==-1){fprintf(stderr,"shmgetfailed\n");exit(EXIT_FAILURE);}//将共享内存连接到当前进程的地址空间shm=shmat(shmid,(void*)0,0);if(shm==(void*)-1){fprintf(标准错误,"shmatfailed\n");exit(EXIT_FAILURE);}printf("Memoryattachedat%X\n",(int)shm);//设置共享内存shared=(structshared_use_st*)shm;while(running)//共享内存Writedatain{//数据还没有被读取,等待数据被读取,不能向共享内存写入文本while(shared->written==1){sleep(1);printf("Waiting...\n");}//写入数据到共享内存printf("Entersometext:");fgets(buffer,BUFSIZ,stdin);strncpy(shared->text,buffer,TEXT_SZ);//写入数据,设置written使共享内存段可读shared->written=1;//进入end,退出循环(程序)if(strncmp(buffer,"end",3)==0)running=0;}//分开来自当前进程的共享内存if(shmdt(shm)==-1){fprintf(stderr,"shmdtfailed\n");exit(EXIT_FAILURE);}sleep(2);exit(EXIT_SUCCESS);}再看看Lookat运行结果:分析:1.程序shmread创建了一块共享内存,然后连接到自己的地址空间,在共享内存的开头使用了一个结构体struct_use_st埃默里。这个结构上写着一个标志。当共享内存中还有其他共享内存时,当一个进程向其写入数据时,写入共享内存的内容被设置为0,程序等待。当不为0时,表示没有进程向共享内存写入数据,程序从共享内存中读取数据并输出。然后复位,将共享内存中写入的设置为0,即让其由shmwrite进程写入。2.程序shmwrite获取共享内存,连接到自己的地址空间。检查共享内存中写入的是否为0,如果不是,说明共享内存中的数据还没有读完,等待其他进程读完,提示用户等待。如果共享内存的写入值为0,表示没有其他进程读取共享内存,提示用户输入文本,共享内存中的写入值再次设置为1,表示写入完成,其他进程可以读取共享内存。4.前面例子的安全性讨论这个程序是不安全的。当多个程序同时对共享内存读写数据时,就会出现问题。您可能认为您可以改变written的使用方式。比如一个进程只有write为0时才能向共享内存写入数据,write不为0时进程才能读到。同时written加1,读完减1。这有点像文件锁中读写锁的作用。乍一看,它似乎有效。但是这些都不是原子操作,所以这种做法是行不通的。想象一下,当written为0时,如果两个进程同时访问共享内存,会发现written为0,那么两个进程都会往里面写,显然不会。当written为1时,两个进程同时读取共享内存时也是如此。当两个进程都完成读取时,写入变为-1。要使程序安全执行,必须有进程同步系统来保证进入临界区的操作是原子操作。例如,可以使用上面提到的信号量来同步进程。因为信号量操作是原子的。五。使用共享内存的优缺点1.优点:可以看出使用共享内存进行进程间通信确实很方便,而且函数的接口也很简单。数据共享也使得进程之间不需要传输数据,而且是直接访问内存,也加快了程序的运行效率。同时,它不需要像匿名管道那样要求通信过程有一定的父子关系。2、缺点:共享内存不提供同步机制,这使得我们在使用共享内存进行进程间通信时,往往需要借助其他手段来进行进程间同步。