linux 进程间通信-----共享内存(上)

文章内容参考网络、unix网络编程-进程间通信(卷二)

共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。

他允许两个无关的进程访问相同的逻辑内存。共享内存是在两个运行的程序之间传递数据的有效手段。尽管X/Open标准并没有要求,很可能绝大数的共享内存实现都是会将不同进程之间正在共享的内存安排在相同的物理内存中。

共享内存为在多个进程之间共享与传递数据提供一个有效的手段。因为他并没有提供同步的方法,所以通常我们需要使用其他的机制来同步对共享内存的访问。通常,我们也许会使用共享内存来提供对大块内存区的有效访问,并且传递少量的消息来同步对此内存的访问。

共享内存是由IPC为一个进程所创建并且出现在这个进程的地址空间中的一段特殊的地址序列。其他的进程可以将同样的共享内存段关联到他们自己的地址空间中。所有的进程都可以访问这段内存地址,就如同这段内存是由malloc所分配的。如果一个进程写入共享内存,这些改变立即就可以为访问相同共享内存的其他进程所见。

用于共享内存的函数如下:
#include<sys/shm.h>

void *shmat(intshm_id, const void *shm_addr, int shmflg);
int shmctl(intshm_id, int cmd, struct shmid_ds *buf);
int shmdt(constvoid *shm_addr);
int shmget(key_tkey, size_t size, int shmflg);

通常需要在包含shm.h文件之前包含sys/types.hsys/ipc.h这两个头文件。

shmget 
我们使用shmget函数创建共享内存:
int shmget(key_tkey, size_t size, int shmflg);

/* key: 标识符的规则

*  size:共享存储段的字节数
shmflg:读写的权限
*  返回值:成功返回共享存储的id,失败返回-1
*/
这个函数也提供了 key ,这可以有效的命名共享内存段,而且 shmget 函数会返回一个共享内存标识符,这个标识符可以用于后续的共享内存函数中。还有一个特殊的关键值, IPC_PRIVATE ,这可以创建进程私有的共享内存。我们通常并不会使用这个值,而且与信号量相类似,我们会发现私有的共享内存在许多 Linux 系统上实际上并不是私有的。

第三个参数,shmflg,是由9个权限标记所组成的,这些标记的使用与用于创建文件的模型参数相同。IPC_CREAT定义了一个特殊位,必须与权限标记进行位或操作来创建一个新的共享内存段。设置IPC_CREAT标记并且传递一个已经存在的共享内存段并不是错误。如果不需要,IPC_CREAT只是简单的被忽略掉。
权限标记是十分有用的,因为这些权限标记可以允许创建共享内存所有者进程可以写入而其他用户所创建的进程只能读取的共享内存。我们可以应用这个特点通过将数据放入共享内存中,从而提供对于只读数据的有效访问,而不必担心数据被其他用户修改的风险。
如果共享内存成功创建,shmget会返回一个非负整数,共享内存标识符。如果失败,则会返回-1

shmat

void *shmat(intshm_id, const void *shm_addr, int shmflg);

/*shm _id:共享存储的id shm_id ,是由 shmget 函数所返回的共享内存标识符。
* shm_addr:一般为0,表示连接到由内核选择的第一个可用地址上,否则,如果flag没有指定SHM_RND,则连接到addr所指定的地址上,如果flag为SHM_RND,则地址取整
* shmflg:如前所述,一般为0
* 返回值:如果成功,返回共享存储段地址,出错返回-1
*/
当我们第一次创建一个共享内存段时,他并不能为任何进程所访问。为了能够访问共享内存,我们必须将其与一个进程地址空间关联到一起。我们可以使用 shmat 函数来达到这一目的:
 
第三个参数, shmflg ,是一个位标记集合。两个可能的值为 SHM_RND SHM_RDONLY 。前者与 shm_addr 联合,控制将被关联的共享内存所在的地址;而后者使得关联的内存只读。通常很少需要来控制被关联的内存所在的地址;我们通常应允许系统来为我们选择一个地址,否则就会使得程序变得高度硬件相关。

共享内存将会依据所有者(共享内存的创建者),权限与当前进程的所有者而具有读或写权限。共享内存上的权限与文件上的权限相类似。

shmfgl &SHM_RDONLY
为真的情况是这个规则的一个例外。此时共享内存并不可写,尽管权限已经允许了写访问。

shmdt 

int shmdt(void *addr);

/* addr:共享存储段的地址,以前调用shmat时的返回值
*  shmdt将使相关shmid_ds结构中的shm_nattch计数器值减1
*/
shmdt 函数将共享内存与当前进程相分离。他传递一个指向由 shmat 所返回的地址的指针。如果成功,则会返回 0 ;如果失败,则会返回 -1 。注意,分离共享内存并不会删除他;他只是使得内存对于当前进程不可用。

 shmctl 
共享内存的控制函数要比复杂的信号量控制函数简单得多:
int shmctl(intshm_id, int command, struct shmid_ds *buf);

/*shmid:共享存储段的id

* cmd:一些命令,有:IPC_STAT,IPC_RMID,SHM_LOCK,SHM_UNLOCK

*/
shmid_ds 结构至少具有下列成员:
struct shmid_ds {

   uid_t shm_perm.uid;
   uid_t shm_perm.gid;
   mode_t shm_perm.mode;
}
第二个参数, command ,是要执行的动作。他可以有三个值:
命令        描述
IPC_STAT   
设置 shmid_ds 结构中的数据反射与共享内存相关联的值。
IPC_SET      
如果进程有相应的权限,将与共享内存相关联的值设置为 shmid_ds 数据结构中所提供的值。
IPC_RMID   
删除共享内存段。
第三个参数, buf ,是一个指向包含共享内存模式与权限的结构的指针。
如果成功,则返回 0 ,如果失败,则会返回 -1 X/Open 并没有说明如果我们尝试删除一个已经关联的共享内存时会发生什么。通常,一个已经关联但是被删除的共享内存通常会继续发挥作用,直到他与最后一个进程相分离。然而,因为这个行为并没有被规范,所以最好不要依赖于他。

下面通过一个具体的例子来说明,一个往共享内存中写,一个从共享内存中读取,但是写程序在使用参数IPC_PRIVATE,是系统自动分配的key,由系统产生的,并且没有任何标志独特性的key_t的值。那么读共享内存的程序怎么获取shm_id?

1.建立具有非常鲜明特征,独特的key值

2.通过信号量,消息队列或者管道(FIFO)传输这个shm_id

就第一种方法尝试

write_ftok.c
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>

typedef struct
{
   char name;
  int age;
}stu;

int main(int argc,char **argv)
{
     int shm_id,i;
     key_t key;
     char temp_char;
     stu*p_map;
     char *name="/dev/shm/myshm1";
     
     key = ftok(name,0);
     printf("key=%d\n",key);
     if(key==-1)
                 perror("ftok error");
     shm_id=shmget(IPC_PRIVATE,4096,IPC_CREAT);
                     printf("shm_id=%d\n",shm_id);
     if(shm_id==-1)
        {perror("shmget error");return;}
     p_map=(stu*)shmat(shm_id,NULL,0);
     temp_char='a';
     for(i=0;i<10;i++)
     {
        (*(p_map+i)).name=temp_char;
        temp_char+=1;
        (*(p_map+i)).age=20+i;
     }
     if(shmdt(p_map)==-1)
    perror("detach error");
     
     return 0;
}

读取内存的程序read_ftok.c

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>

typedef struct
{
  char name;
  int age;
}stu;
int main(int argc,char **argv)
{
     int shm_id,i;
     key_t key;
     people *p_map;
     char *name="/dev/shm/myshm1";
     
     key = ftok(name,0);
     if(key==-1)
                 perror("ftok error");
     shm_id=shmget(key,4096,IPC_CREAT);
                    printf("shm_id=%d\n",shm_id);
     if(shm_id==-1)
        {perror("shmget error");return;}
     p_map=(stu*)shmat(shm_id,NULL,0);
     p_map=(stu*)shmat(393216,NULL,0);
     for(i=0;i<10;i++)
     {
        printf("name----------%c\n",(*(p_map+i)).name);
        printf("age-----------%d\n",(*(p_map+i)).age);
     }
     if(shmdt(p_map)==-1)
    perror("detach error");

    return 0;
}

ftok
key_t ftok( char * fname, int id )
frame:文件的全路径,要求文件存在且进程可访问
id:第几个共享内存
返回值:返回key值
注明:系统V共享内存与mmap相比,就是系统V共享内存从来不把数据真正写到磁盘文件中去,而mmap机制在munmap的时候将数据写到磁盘文件
发现原来系统V共享内存也需要文件的支撑
当然,共享内存没有这么容易,还有大量的同步要做




你可能感兴趣的:(linux,进程间通信)