对话 UNIX: 通过共享内存进行进程间通信_VMware, Unix及操作系统讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  VMware, Unix及操作系统讨论区 »
总帖数
1
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 4127 | 回复: 0   主题: 对话 UNIX: 通过共享内存进行进程间通信        下一篇 
cc
注册用户
等级:中校
经验:1900
发帖:195
精华:0
注册:2011-7-25
状态:离线
发送短消息息给cc 加好友    发送短消息息给cc 发消息
发表于: IP:您无权察看 2011-8-18 16:29:00 | [全部帖] [楼主帖] 楼主

从表面上看,UNIX® 应用程序单独控制底层主机。它随时可以访问处理器,它的内存是神圣不可侵犯的,连接它的设备只为它服务。但是表面现象会骗人,这样有如君主一般的绝对地位只是幻想而已。UNIX 系统同时运行大量应用程序,有限的物理资源要在它们之间共享。处理器能力被划分为时间片,应用程序映像经常被换入和换出真实内存,设备访问由需求驱动,还受到访问权限的限制。尽管您的 shell 提示符不断闪烁,但是 UNIX 系统并非只是等着您发出命令,在幕后有许多活动正在进行。

常用缩略词


  • API:应用程序编程接口
  • IPv4:Internet 协议第 4 版
  • IPv6:Internet 协议第 6 版
  • POSIX:UNIX 的可移植操作系统接口


尽管涉及一些复杂的机制,但是大多数应用程序不会注意到资源实际上是共享的,它们似乎是独享资源。但是,可以编写相互交互的应用程序。例如,一个应用程序收集或生成数据,而另一个应用程序同时监视进度并分析信息。另一个例子是即时交换消息的聊天系统,其中有两个对等的应用程序相互收发数据。Secure Shell (ssh) 也是这样,它可以在两个完全不同的主机之间进行协作。在这些情况下,代码都要连接另一段独立的代码以交换信息,这常常需要使用某种协议协商和控制交换过程。

UNIX 为实现这样的进程间通信

提供了多种技术。一些技术提供同一主机上的进程间通信,其他技术可以实现主机到主机的信息交换。另外,各种技术的速度不同,所以必须选择最合适自己需求的技术。还必须进行协调(实施时间控制和排他控制)。例如,如果一个应用程序产生数据,另一个应用程序消费数据,那么当读完共享池时消费者必须停下来等待生产者。另一方面,如果消费者无法足够快地读取池,生产者必须慢下来或暂停。

总结在典型的 UNIX 系统上可用的进程间通信形式。

表 1. UNIX 中的进程间通信
名称 说明 范围 用途 文件 在典型的 UNIX 文件中读写数据。任意数量的进程都可以互操作。 本地 共享大数据集 管道 使用专用的文件描述符在两个进程之间传输数据。通信只在父进程和子进程之间进行。 本地 简单的数据共享,比如生产者和消费者 命名管道 通过专用的文件描述符在进程之间交换数据。通信可以在同一主机上的任意两个对等进程之间进行。 本地 生产者和消费者或命令-控制,比如 MySQL 和它的命令行查询工具 信号 通过中断通知应用程序某一情况。 本地 无法在信号中传输数据,所以信号主要用于进程管理 共享内存 通过在同一内存段中读写数据共享信息。 本地 任何类型的协作,尤其适合需要安全性的情况 套接字 完成特殊的设置过程之后,使用一般的输入/输出操作传输数据。 本地或远程 FTP、ssh

Apache Web Server 等网络服务

正如前面提到的,每种技术满足不同的需求。假设多个进程之间的协作的复杂性大体相当,每种方法的优点和缺点如下:

  • 通过一般的 UNIX 文件共享数据很简单,因为它使用大家熟悉的文件操作。但是,通过文件系统共享数据很慢,因为磁盘输入和输出操作的效率远远比不上内存。另外,只通过文件读写数据很难协调。最后,在文件中保存敏感数据是不安全的,因为根用户和拥有特权的其他用户可以访问这些信息。对于只读或只写的数据,适合使用文件。
  • 管道和命名管道也很简单。它们在连接的两端使用两个标准的文件描述符 — 一个只执行读操作,另一个只执行写操作。但是,管道只能在父进程和子进程之间使用,不能在任意两个进程之间使用。命名管道克服了这个缺点,是在同一系统上交换数据的好方法。但是,管道和命名管道都不提供随机访问,因为它们都作为先入先出 (FIFO) 设备。
  • 信号无法在进程之间传输数据。一般情况下,信号应该只用于在进程之间通知异常情况。
  • 共享内存适合比较大的数据集,因为它使用内存,支持快速的随机访问。共享内存的实现有点儿复杂,尽管如此,对于多个进程之间的主机

    协作,共享内存是不错的方法。

  • 套接字的功能与命名管道很相似,但是可以跨主机。本地套接字

    (也称为

    UNIX 套接字

    )只能进行本地(同一主机上的)连接。

    Inet

    Inet6 套接字分别使用 IPv4

    IPv6 协议,它们接受远程连接(也可以通过本地机器的 Internet 寻址机制接受本地连接)。网络应用程序显然应该选择套接字,比如分布式处理或 web 浏览器。所需的代码比命名管道复杂一点儿,但是模式是固定的,在任何 UNIX 网络编程书中都有介绍。

现在不考虑主机

应用程序通信,看看如何通过共享内存在同一主机上进行进程间通信。



共享内存的工作方式

顾名思义,共享内存让一段内存可供多个进程访问。用特殊的系统调用(即对 UNIX 内核的请求)分配和释放内存并设置权限;通过一般的读写操作读写内存段中的数据。

共享内存并不是从某一进程拥有的内存中划分出来的;进程的内存总是私有的。共享内存是从系统的空闲内存池中分配的,希望访问它的每个进程连接它。这个连接过程称为映射,它给共享内存段分配每个进程的地址空间中的本地地址。图 1图 2图 3

图 4 说明此过程:

  1. 假设在同一系统上有两个进程 A

    B 正在运行(见 图 1),它们可以通过共享内存进行协作和共享信息。在图中 A

    B 采用不同大小的图形,以此强调应用程序不必相同。

    图 1. 两个进程在同一个主机上运行,执行不同的代码
    北京联动北方科技有限公司

  2. 图 2 中,进程 A 请求一个共享内存段。进程 A 对这个内存段进行初始化,让它准备好接受访问。这个过程还给内存段命名,让其他进程可以找到它。通常,内存段名称并不是动态分配的;而是众所周知的,比如使用头文件中的常量,其他代码可以方便地引用它。

    图 2. 一个进程请求共享内存段
    北京联动北方科技有限公司

  3. 进程 A 把共享内存段连接(即映射)到自己的地址空间。进程 B 通过它的命名管道找到这个内存段,也把它映射到自己的地址空间,见 图 3。两个进程扩大了,表示包含共享内存段。

    图 3. 两个进程连接(即映射)共享内存段
    北京联动北方科技有限公司

  4. 最后,在 图 4 中,进程 A

    B 可以随意读写共享内存段。按照与本地进程内存相同的方式对待共享内存。read()

    write() 的作用与一般情况下一样。

    图 4. 两个或更多进程现在可以通过共同的内存共享数据
    北京联动北方科技有限公司

这些图中所示的许多工作可以通过 UNIX 共享内存 API 执行。实际上,有两套共享内存 API:POSIX API 和比较老(但是仍然有效)的 System V API。因为 POSIX 是 UNIX

Linux® 及其衍生系统上的公认标准,所以我们使用此版本。另外,POSIX API 使用简单的文件描述符执行读写,大家应该更熟悉。

POSIX 为创建、映射、同步和取消共享内存段提供五个入口点:

  • shm_open():创建共享内存段或连接到现有的已命名内存段。这个系统调用返回一个文件描述符。
  • shm_unlink():根据(shm_open() 返回的)文件描述符,删除共享内存段。实际上,这个内存段直到访问它的所有进程都退出时才会删除,这与在 UNIX 中删除文件很相似。但是,调用 shm_unlink() (通常由原来创建共享内存段的进程调用)之后,其他进程就无法访问这个内存段了。
  • mmap():把共享内存段映射到进程的内存。这个系统调用需要 shm_open() 返回的文件描述符,它返回指向内存的指针。(在某些情况下,还可以把一般文件或另一个设备的文件描述符映射到内存。对这些操作的讨论超出了本文的范围;具体方法请查阅操作系统的 mmap() 文档。)
  • munmap():作用与 mmap() 相反。
  • msync():用来让共享内存段与文件系统同步 — 当把文件映射到内存时,这种技术有用。

使用共享内存的过程是,用 shm_open() 创建内存段,用 write() 或 ftruncate() 设置它的大小,用 mmap() 把它映射到进程内存,执行其他参与者需要的操作。当使用完时,原来的进程调用 munmap()

shm_unlink(),然后退出。



示例应用程序

清单 1 给出一个简单的共享内存示例。(代码取自 John Fusco 撰写的 The Linux Programmer's Toolbox

一书 [由 Prentice Hall Professional 于 2007 年 3 月出版,ISBN 0132198576],已经得到出版商的使用授权。)代码实现通过共享内存段通信的父进程和子进程。

清单 1. 共享内存示例


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/wait.h>

void error_and_die(const char *msg) {
       perror(msg);
       exit(EXIT_FAILURE);
}

int main(int argc, char *argv[]) {
       int r;

       const char *memname = "sample";
       const size_t region_size = sysconf(_SC_PAGE_SIZE);

       int fd = shm_open(memname, O_CREAT | O_TRUNC | O_RDWR, 0666);
       if (fd == -1)
       error_and_die("shm_open");

       r = ftruncate(fd, region_size);
       if (r != 0)
       error_and_die("ftruncate");

       void *ptr = mmap(0, region_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
       if (ptr == MAP_FAILED)
       error_and_die("mmap");
       close(fd);

       pid_t pid = fork();

       if (pid == 0) {
             u_long *d = (u_long *) ptr;
             *d = 0xdbeebee;
             exit(0);
       }
       else {
             int status;
             waitpid(pid, &status, 0);
             printf("child wrote %#lx\n", *(u_long *) ptr);
       }

       r = munmap(ptr, region_size);
       if (r != 0)
       error_and_die("munmap");

       r = shm_unlink(memname);
       if (r != 0)
       error_and_die("shm_unlink");

       return 0;
}


下面是代码中的一些要点:

  • 对 shm_open() 的调用看起来应该很熟悉;它与 open() 函数很相似,包括初始化内存段和设置权限的方式。在这里,内存段是全局可读、全局可写的。如果调用成功,返回下一个未使用的文件描述符;否则,返回 -1 并相应地设置 errno。
  • ftruncate() 把文件的大小设置为 region_size 字节,这以前设置为系统的标准页面大小。sysconf() 是 libc 的组成部分。(还可以使用 shell 工具 getconf 检查系统的配置设置。)
  • mmap() 连接共享内存段,返回用于对内存段直接读写字节的指针。PROT_READ

    PROT_WRITE 分别表示可以读和写这个内存段中的页面。MAP_SHARED 表示对这个内存段的任何修改应该向所有参与共享的进程 “公开”。
  • 如果您使用过 fork(),那么应该熟悉代码的计算部分。执行 fork 之后,父进程和子进程获得打开的所有文件描述符和数据值的拷贝,所以指针对于它们都是有效的。但是,pid 不同。子进程获得 0,父进程获得子进程的进程 ID,这个变量的值决定执行哪个 if/then/else 分支。子进程向指针写一些字节,然后退出。父进程等待子进程退出,然后读取它写的数据。
  • 但是,在父进程退出之前,它必须释放共享内存。用 munmap()

    shm_unlink() 完成这个步骤。

这个示例非常简单。真实的应用程序会使用信号量或其他技术控制对共享内存段的读写。这种控制通常因应用程序而异,如果您的 UNIX 版本不是开放源码的,可以在 Berkeley Software Distribution (BSD)

Linux 源代码中找到许多示例。



结束语

因为 UNIX 同时运行许多应用程序,所以它是非常适合监视、数据收集、协作和分布式计算以及客户机-服务器应用程序的平台。共享内存是速度最快的进程间通信技术,而且非常灵活。还可以把文件映射到内存,这是加快数据访问的理想解决方案。




赞(0)    操作        顶端 
总帖数
1
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论