linux进程间的通信(C): 共享内存

一、共享内存介绍

共享内存是三个IPC(Inter-Process Communication)机制中的一个。

它允许两个不相关的进程访问同一个逻辑内存。

共享内存是在两个正在进行的进程之间传递数据的一种非常有效的方式。

大多数的共享内存的实现,

都把由不同进程之间共享的内存安排为同一段物理内存

共享内存是由IPC为进程创建一个特殊的地址范围,

它将出现在该进程的地址空间中。

其他进程可以将同一段共享内存连接它们自己的地址空间中。

所有进程都可以访问共享内存中的地址,

就好像它们是由malloc分配的一样。

如果某个进程向共享内存写入了数据,

所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。

二、共享内存的同步

共享内存为在多个进程之间共享和传递数据提供了一种有效的方式。

但是它并未提供同步机制

所以我们通常需要用其他的机制来同步对共享内存的访问。

我们通常是用共享内存来提供对大块内存区域的有效访问,

同时通过传递小消息来同步对该内存的访问。

在第一个进程结束对共享内存的写操作之前,

并无自动的机制可以阻止第二个进程开始对它进行读取。

对共享内存访问的同步控制必须由程序员来负责。

下图显示了共享内存是如何共存的:

图中的箭头显示了每个进程的逻辑地址空间到可用物理内存的映射关系。

三、共享内存使用的函数

  1. #include <sys/shm.h>
  2. int shmget(key_t key, size_t size, int shmflg);
  3. void *shmat(int shm_id, const void *shm_addr, int shmflg);
  4. int shmdt(const void *shm_addr);
  5. int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

1. shmget函数

该函数用来创建共享内存:

  1. int shmget(key_t key, size_t size, int shmflg);

参数:

key : 和信号量一样,程序需要提供一个参数key,

它有效地为共享内存段命名。

有一个特殊的键值IPC_PRIVATE,

它用于创建一个只属于创建进程的共享内存,

通常不会用到。

size: 以字节为单位指定需要共享的内存容量。

shmflag: 包含9个比特的权限标志,

它们的作用与创建文件时使用的mode标志是一样。

由IPC_CREAT定义的一个特殊比特必须和权限标志按位或

才能创建一个新的共享内存段。

NOTE:

权限标志对共享内存非常有用,

因为它允许一个进程创建的共享内存可以被共享内存的创建者所拥有的进程写入,

同时其它用户创建的进程只能读取共享内存。

我们可以利用这个功能来提供一种有效的对数据进行只读访问的方法,

通过将数据放共享内存并设置它的权限,

就可以避免数据被其他用户修改。

返回值:

创建成功,则返回一个非负整数,即共享内存标识;

如果失败,则返回-1.

2. shmat函数

第一次创建共享内存段时,它不能被任何进程访问。

要想启动对该内存的访问,

必须将其连接到一个进程的地址空间。

这个工作由shmat函数完成:

  1. void *shmat(int shm_id, const void *shm_addr, int shmflg);

参数:

shm_id : 由shmget返回的共享内存标识。

shm_add: 指定共享内存连接到当前进程中的地址位置。

它通常是一个空指针,

表示让系统来选择共享内存出现的地址。

shmflg : 是一组标志。

它的两个可能取值是:

SHM_RND, 和shm_add联合使用,

用来控制共享内存连接的地址。

SHM_RDONLY, 它使连接的内存只读

返回值:

如果调用成功, 返回一个指向共享内存第一个字节的指针;

如果失败,返回-1.

共享内存的读写权限由它的属主(共享内存的创建者),

它的访问权限和当前进程的属主决定。

共享内存的访问权限类似于文件的访问权限。

3. shmdt

将共享内存从当前进程中分离。

  1. int shmdt(const void *shm_addr);

shm_addr: shmat返回的地址指针。

成功时,返回0,

失败时,返回-1.

NOTE:

共享内存分离并未删除它,

只是使得该共享内存对当前进程不再可用。

4. shmctl

共享内存的控制函数

  1. int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

shmid_ds结构至少包含以下成员:

  1. struct shmid_ds {
  2. uid_t shm_perm.uid;
  3. uid_t shm_perm.gid;
  4. mode_t shm_perm.mode;
  5. }

参数:

shm_id : 是shmget返回的共享内存标识符。

command: 是要采取的动作,

它可以取3个值:

IPC_STAT  把shmid_ds结构中的数据设置为共享内存的当前关联值

IPC_SET   如果进程有足够的权限,

就把共享内存的当前关联值设置为shmid_ds结构中给出的值

IPC_RMID  删除共享内存段

buf    : 是一个指针,

包含共享内存模式和访问权限的结构。

返回值:

成功时,返回0,

失败时,返回-1.

四、示例

典型的消费者-生产者程序,

第一个程序(消费者)将创建一个共享内存段,

然后把写到它里面的数据都显示出来。

第二个程序(生产者)将连接一个已有的共享内存段,

并允许我们向其中输入数据。

shm_com.h

  1. #define TEXT_SZ 2048
  2. struct shared_use_st {
  3. int written_by_you;
  4. char some_text[TEXT_SZ];
  5. };

当有数据写入这个结构中时,

我们用结构中的written_by_you标志来通知消费者。

需要传输的文本长度2K是随意定的。

shm1.c 消费者程序

  1. #include <unistd.h>
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <string.h>
  5. #include <sys/shm.h>
  6. #include "shm_com.h"
  7. int main()
  8. {
  9. int running = 1;
  10. void *shared_memory = (void *)0;
  11. struct shared_use_st *shared_stuff;
  12. int shmid;
  13. srand((unsigned int)getpid());
  14. shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
  15. if (shmid == -1) {
  16. fprintf(stderr, "shmget failed\n");
  17. exit(EXIT_FAILURE);
  18. }

现在,让程序可以访问这个共享内存:

  1. shared_memory = shmat(shmid, (void *)0, 0);
  2. if (shared_memory == (void *)-1) {
  3. fprintf(stderr, "shmat failed\n");
  4. exit(EXIT_FAILURE);
  5. }
  6. printf("Memory attached at %X\n", (int)shared_memory);

程序的下一部分将shared_memory分配给shared_stuff,

然后它输出written_by_you中的文本。

循环将一直执行到在written_by_you中找到end字符串为止。

sleep调用强迫消费者程序在临界区域多待一会,

让生产者程序等待:

  1. shared_stuff = (struct shared_use_st *)shared_memory;
  2. shared_stuff->written_by_you = 0;
  3. while(running)
  4. {
  5. if (shared_stuff->written_by_you)
  6. {
  7. printf("You wrote: %s", shared_stuff->some_text);
  8. sleep( rand() % 4 ); /* make the other process wait for us ! */
  9. shared_stuff->written_by_you = 0;
  10. if (strncmp(shared_stuff->some_text, “end”, 3) == 0) {
  11. running = 0;
  12. }
  13. }
  14. }

最后,共享内存被分离,然后被删除:

  1. if (shmdt(shared_memory) == -1)
  2. {
  3. fprintf(stderr, "shmdt failed\n");
  4. exit(EXIT_FAILURE);
  5. }
  6. if (shmctl(shmid, IPC_RMID, 0) == -1)
  7. {
  8. fprintf(stderr, "shmctl(IPC_RMID) failed\n");
  9. exit(EXIT_FAILURE);
  10. }
  11. exit(EXIT_SUCCESS);
  12. }

shm2.c 生产者程序

通过它向消费者程序输入数据。

  1. #include <unistd.h>
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <string.h>
  5. #include <sys/shm.h>
  6. #include "shm_com.h"
  7. int main()
  8. {
  9. int running = 1;
  10. void *shared_memory = (void *)0;
  11. struct shared_use_st *shared_stuff;
  12. char buffer[BUFSIZ];
  13. int shmid;
  14. shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
  15. if (shmid == -1)
  16. {
  17. fprintf(stderr, "shmget failed\n");
  18. exit(EXIT_FAILURE);
  19. }
  20. shared_memory = shmat(shmid, (void *)0, 0);
  21. if (shared_memory == (void *)-1)
  22. {
  23. fprintf(stderr, "shmat failed\n");
  24. exit(EXIT_FAILURE);
  25. }
  26. printf("Memory attached at %X\n", (int)shared_memory);
  27. shared_stuff = (struct shared_use_st *)shared_memory;
  28. while(running)
  29. {
  30. while(shared_stuff->written_by_you == 1)
  31. {
  32. sleep(1);
  33. printf("waiting for client...\n");
  34. }
  35. printf("Enter some text: ");
  36. fgets(buffer, BUFSIZ, stdin);
  37. strncpy(shared_stuff->some_text, buffer, TEXT_SZ);
  38. shared_stuff->written_by_you = 1;
  39. if (strncmp(buffer, "end", 3) == 0) {
  40. running = 0;
  41. }
  42. }
  43. if (shmdt(shared_memory) == -1) {
  44. fprintf(stderr, "shmdt failed\n");
  45. exit(EXIT_FAILURE);
  46. }
  47. exit(EXIT_SUCCESS);
  48. }

运行程序,

将看到如下所示的样本输出:

  1. $ ./shm1 &
  2. [1] 294
  3. Memory attached at 40017000
  4. $ ./shm2
  5. Memory attached at 40017000
  6. Enter some text: hello
  7. You wrote: hello
  8. waiting for client...
  9. waiting for client...
  10. Enter some text:
  11. You wrote:
  12. waiting for client...
  13. waiting for client...
  14. waiting for client...
  15. Enter some text: end
  16. You wrote: end
  17. $

程序解析:

消费者程序

创建共享内存段,

然后将它连接到它自己的地址空间中,

并且,

我们在共享内存的开始处使用了一个结构shared_use_st.

该结构中有个标志written_by_you,

当共享内存中有数据写入时,就设置这个标志。

这个标志被设置时,

程序就从共享内存中读取文本,

将它打印出来,

然后清除这个标志,表示已经读完数据。

我们用一个特殊字符串end来退出循环。

接下来,

程序分离共享内存段并删除它。

生产者程序

使用相同的键值1234来取得并连接同一个共享内存段,

然后提示用户输入一些文本。

如果标志written_by_you被设置,

生产者就知道消费都进程还未读完上一次的数据,

因此就继续等待。

当其它进程清除了这个标志后,

生产者写入新的数据并设置这个标志。

它还使用字符串end来终止并分离共享内存段。

这里提供的同步标志written_by_you,

它是一个非常缺乏效率的忙等待(不停地循环)。

但在实际编程中,

应该使用信号量,

或通过传递消息(使用管道或IPC消息),

或生成信号

的方法来提供读写之间的更有效的同步机制。

时间: 2024-10-10 10:55:56

linux进程间的通信(C): 共享内存的相关文章

linux进程间的通信之 共享内存

一.共享内存介绍 共享内存是三个IPC(Inter-Process Communication)机制中的一个. 它允许两个不相关的进程访问同一个逻辑内存. 共享内存是在两个正在进行的进程之间传递数据的一种非常有效的方式. 大多数的共享内存的实现, 都把由不同进程之间共享的内存安排为同一段物理内存. 共享内存是由IPC为进程创建一个特殊的地址范围, 它将出现在该进程的地址空间中. 其他进程可以将同一段共享内存连接它们自己的地址空间中. 所有进程都可以访问共享内存中的地址, 就好像它们是由mallo

Linux进程间的通信

一.管道 管道是Linux支持的最初Unix IPC形式之一,具有以下特点: A. 管道是半双工的,数据只能向一个方向流动: B. 需要双工通信时,需要建立起两个管道: C. 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程): D. 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中. 匿名管道的创建:该函数创建的管道的两端处于一个进程中间,在实际应用中没有太大意义;因此,一

Linux进程间的通信方法

linux进程间的通信方法总结如下 通过fork函数把打开文件的描述符传递给子进程 通过wait得到子进程的终结信息 通过加锁的方式,实现几个进行共享读写某个文件 进行间通过信号通信,SIGUSR1和SIGUSR2实现用户定义功能 利用pipe进行通信 FIFO文件进行通信 mmap,几个进程映射到同一内存区 SYS IPC 消息队列,信号量(很少用) UNIX Domain Socket,常用

Linux进程IPC浅析[进程间通信SystemV共享内存]

Linux进程IPC浅析[进程间通信SystemV共享内存] 共享内存概念,概述 共享内存的相关函数 共享内存概念,概述: 共享内存区域是被多个进程共享的一部分物理内存 多个进程都可把该共享内存映射到自己的虚拟内存空间,所有用户空间的进程若要操作共享内存,都要将其映射到自己的虚拟内存空间中,通过映射的虚拟内存空间地址去操作共享内存,从而达到进程间的数据通信 共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容 本身不提供同

linux 进程间的通信

现在linux使用的进程间通信方式:(1)管道(pipe)和有名管道(FIFO)(2)信号(signal)(3)消息队列(4)共享内存(5)信号量(6)套接字(socket) 为何进行进程间的通信:A.数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间B.共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到.C.通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程).D.资源共享

Linux进程间的通信方法简介

一.本地进程间的通信方式: 1.管道(pipe) 利用管道文件可以进行进程间数据的通信,通常是具有血缘关系的父子进程间的通信方式. 管道通信为半双工模式,父子进程可以通过调用内核中的read()和write()命令来向管道文件进行读写操作. 管道通信是基于硬盘内的文件,所以I/O速度较低. 2.消息队列 消息队列是一种类似链表的数据结构,存放于内存中,因此I/O速度较管道更快,通过ipcs -q命令可以查看当前系统中被创建的消息队列. 多个不同进程可以使用同一个消息队列进行通信,消息队列中的数据

linux进程间的通信(C): 消息队列

一.消息队列(message queue) 消息队列也是System V IPC机制之一. 消息队列与命名管道类似, 但少了打开和关闭管道方面的复杂性. 但使用消息队列并未解决我们在使用命名管道时遇到的一些问题, 如管道满时的阻塞问题. 消息队列提供了一种在两个不相关进程间传递数据的简单有效的方法. 与命名管道相比, 消息队列的优势在于,它独立于发送和接收进程而存在, 这消除了在同步命名管道的打开和关闭时可能产生的一些困难. 消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法. 而且,

linux进程间的通信(C): 信号量

一.信号量简介 信号量: 用于管理对资源的访问. 荷兰计算机科学家Edsger Dijkstra提出的信号量概念 是在并发编程领域迈出的重要一步. 信号量是一个特殊的变量, 它只取正数值, 并且程序对其访问都是原子操作. 二.信号量的定义 它是一个特殊变量, 只允许对它进行等待(wait)和发送信号(signal)这两种操作, P(信号量变量): 用于等待. V(信号量变量): 用于发送信号. 这两个字母分别源于荷兰语单词 passeren, 传递,      就好像进入临界区域之前的检查点,(

信号,信号量,锁,条件变量,消息通信,共享内存,RPC (一)

在实际项目当中,经常需要把一个功能分成多个子模块实现.那么,这些子模块之间该如何关联起来呢?静态地看,模块可以看作一组完成相同功能的函数:而动态地看,模块可以是一个独立的进程.线程或者一个中断服务或者信号服务例程.根据不同的具体业务实现,它们之间可能是静态调用.动态互斥.同步.唤醒等关系.静态的调用很好实现,上层的函数调用底层的函数即可.那么,动态互斥.同步.唤醒等关系,又该如何实现呢?这就设计到我们将要讨论的信号.进程间消息通信.共享内存.线程互斥同步条件变量.RPC等手段.下面就按照Linu