System V IPC 之共享内存

时间:2021-08-23 20:04:10

IPC 是进程间通信(Interprocess Communication)的缩写,通常指允许用户态进程执行系列操作的一组机制:

  • 通过信号量与其他进程进行同步
  • 向其他进程发送消息或者从其他进程接收消息
  • 和其他进程共享一段内存区

System V IPC 最初是在一个名为 "Columbus Unix" 的开发版 Unix 变种中引入的,之后在 AT&T 的 System III 中采用。现在在大部分 Unix 系统 (包括 Linux) 中都可以找到。

IPC 资源包含信号量消息队列共享内存三种。IPC 的数据结构是在进程请求 IPC 资源时动态创建的。每个 IPC 资源都是持久的:除非被进程显式地释放,否则永远驻留在内存中(直到系统关闭)。IPC 资源可以由任一进程使用,包括那些不共享祖先进程所创建的资源的进程。
由于一个进程可能需要同类型的多个 IPC 资源,因此每个新资源都是使用一个 32 位的 IPC 关键字来标识的,这和系统的目录树中的文件路径名类似。每个 IPC 资源都有一个 32 位的 IPC 标识符,这与和打开文件相关的文件描述符有些类似。IPC 标识符由内核分配给 IPC 资源,在系统内部是唯一的,而 IPC 关键字可以由程序员*地选择。
当两个或者更多的进程要通过一个 IPC 资源进行通信时,这些进程都要引用该资源的 IPC 标识符。

共享内存是进程间通信的一种最基本、最快速的机制。共享内存是两个或多个进程共享同一块内存区域,并通过该内存区域实现数据交换的进程间通信机制。通常是由一个进程开辟一块共享内存区域,然后允许多个进程对此区域进行访问。由于不需要使用中间介质,而是数据由内存直接映射到进程空间,因此共享内存是最快速的进程间通信机制。
使用共享内存有两种方法:映射 /dev/mem 设备和内存映像文件。本文主要通过 demo 演示通过映射 /dev/mem 设备实现共享内存的方法。

共享内存的最大不足之处在于,由于多个进程对同一块内存区具有访问的权限,各个进程之间的同步问题显得尤为突出。必须控制同一时刻只有一个进程对共享内存区域写入数据,否则将造成数据的混乱。同步控制的问题,笔者将在随后的文章中介绍如何通过信号量解决。

共享内存相关的数据结构

ipc_perm 结构

对于每一个进程间通信机制的对象,都有一个 ipc_perm 结构与之相对应,该结构的定义如下:

struct ipc_perm
{
uid_t uid;
gid_t gid;
uid_t cuid;
gid_t cgid;
mode_t mode;
ulong seq;
key_t key;
}

该结构用于记录对象的各种相关信息,各个字段的具体含义如下:
uid:所有者的有效用户 ID。
gid:所有者的有效组 ID。
cuid:创建者的有效用户 ID。
cgid:创建者的有效组 ID。
mode:表示此对象的访问权限。
seq:对象的应用序号。
key:对象的键。

shmid_ds 结构

每个共享内存都有与之相对应的 shmid_ds 结构,其定义如下:

struct shmid_ds
{
struct ipc_perm shm_perm;
int shm_segsz;
pid_t shm_cpid;
pid_t shm_lpid;
ulong shm_nattch;
time_t shm_atime;
time_t shm_dtiem;
time_t shm_ctime;
}

此机构记录了一个共享内存的各种属性,该结构的各个字段的含义如下:
shm_perm:对应于该共享内存的 ipc_perm 结构。
shm_segsz:以字节表示的共享内存区域的大小。
shm_lpid:最近一次调用 shmop 函数的进程 ID。
shm_cpid:创建该共享内存的进程 ID。
shm_nattch:当前使用该共享内存区域的进程数。
shm_atime:最近一次附加操作的时间。
shm_dtime:最近一次分离操作的时间。
shm_ctime:最近一次改变的时间。

操作共享内存的函数

创建或打开共享内存

要使用共享内存,首先要创建一个共享内存区域,创建共享内存区域的函数声明如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h> int shmget(key_t key, size_t size, int flg);

函数 shmget 除了可用于创建一个新的共享内存外,也可用于打开一个已存在的共享内存。其中,参数 key 表示所创建或打开的共享内存的键。参数 size 表示共享内存区域的大小,只在创建一个新的共享内存时生效。参数 flag 表示调用函数的操作类型,也可用于设置共享内存的访问权限。
当函数调用成功时,返回值为共享内存的引用标识符;调用失败时,返回值为 -1。

附加共享内存

当一个共享内存创建或打开后,某个进程如果要使用该共享内存,则必须将这个共享内存区域附加到它的地址空间中。附加操作的函数声明如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int flag);

参数 shmid 表示要附加的共享内存区域的引用标识符。参数 shmaddr 和 flag 共通决定共享内存区域要附加到的地址值。比如设置 shmaddr 为 0 时,系统将自动查找进程地址空间,将共享内存区域附加到第一块有效内存区域上,此时 flag 参数无效。
当函数调用成功时,返回值为指向共享内存区域的指针;调用失败时,返回值为 -1。

分离共享内存

当一个进程对共享内存区域的访问完成后,可以调用 shmdt 函数使共享内存区域与该进程的地址空间分离,shmdt 函数的声明如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h> int shmdt(const void *shmaddr);

此函数仅用于将共享内存区域与进程的地址空间分离,并不删除共享内存本身。参数 shmaddr 为指向要分离的共享内存区域的指针(就是调用 shmat 函数的返回值)。该函数调用成功时返回 0;调用失败时返回 -1。

共享内存的控制

对共享内存区域的具体控制操作是通过函数 shmctl 来实现的,shmctl 函数的声明如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数 shmid 为共享内存的引用标识符。参数 cmd 表示调用该函数希望执行的操作。参数 buf 是指向 shmid_ds 结构体的指针。参数 cmd 的取值和对应的操作如下:
SHM_LOCK:将共享内存区域上锁。
IPC_RMID:用于删除共享内存。
IPC_SET:按参数 buf 指向的结构中的值设置该共享内存对应的 shmid_ds 结构。
IPC_STAT:用于取得该共享内存区域的 shmid_ds 结构,保存到 buf 指向的缓冲区。
SHM_UNLOCK:将上锁的共享内存区域释放。

进程间通过共享内存通信的 demo

下面我们创建两个程序 demoa 和 demob 来简单的演示进程间如何通过共享内存通信。其中 demoa 的代码如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h> #define BUF_SIZE 1024
#define MYKEY 24 int main(void)
{
int shmid;
char *shmptr;
// 创建或打开内存共享区域
if((shmid=shmget(MYKEY,BUF_SIZE,IPC_CREAT))==-){
printf("shmget error!\n");
exit();
}
if((shmptr=shmat(shmid,,))==(void*)-){
printf("shmat error!\n");
exit();
}
while(){
// 把用户的输入存到共享内存区域中
printf("input:");
scanf("%s",shmptr);
}
exit();
}

demoa 程序创建或打开 key 为 24 的共享内存区域,并把用户输入的字符串存入这个共享内存区域。把上面的代码保存到文件 shm_a.c 文件中,并用下面的命令编译:

$ gcc -Wall shm_a.c -o demoa

下面是 demob 的代码:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> #define BUF_SIZE 1024
#define MYKEY 24 int main(void)
{
int shmid;
char *shmptr;
// 创建或打开内存共享区域
if((shmid=shmget(MYKEY,BUF_SIZE,IPC_CREAT))==-){
printf("shmget error!\n");
exit();
}
if((shmptr=shmat(shmid,,))==(void*)-){
fprintf(stderr,"shmat error!\n");
exit();
}
while(){
// 每隔 3 秒从共享内存中取一次数据并打印到控制台
printf("string:%s\n",shmptr);
sleep();
}
exit();
}

demob 程序创建或打开 key 为 24 的共享内存区域,然后每隔 3 秒从共享内存中取一次数据并打印到控制台。这样通过共享内存程序 demob 就可以获取到 demoa 程序中的数据。 把上面的代码保存到文件 shm_b.c 文件中,并用下面的命令编译:

$ gcc -Wall shm_b.c -o demob

接下来分别运行 demoa 和 demob,然后尝试在 demoa 中输入一些字符串:

System V IPC 之共享内存

demob 完全不关心 demoa 在干什么,只是机械的每隔 3 秒钟去共享内存中取一次数据,取到什么就输出什么。

管理 ipc 资源的基本命令

我们在 demoa 和 demob 中并没有通过 shmctl 函数在适当的时机删除创建的共享内存区域,所以当程序 demoa 和 demob 退出后,我们创建的 key 为 24 的共享内存区域仍然驻留在系统的内存中。
Linux 系统默认自带了一些管理 ipc 资源的基本命令,比如 ipcsipcmkipcrm。我们可以使用 ipcs 命令查看系统中的 ipc 资源:

$ ipcs -m

System V IPC 之共享内存

红框中的共享内存就是我们的 demo 程序创建的,第一列的 key 0x18 换算成十进制就是 24。
现在我们已经不需要这个共享内存区域了,所以可以使用下面的命令把它删除掉:

$ sudo ipcrm -M 

当然,除了删除 ipc 资源,我们还可以通过 ipcmk 命令创建 ipc 资源。关于 ipcs、ipcmk 和 kpcrm 这三个命令的具体用法请参考相关的 man page,此文不再赘述。

总结

本文简单的介绍了 IPC 相关的基本概念和共享内存编程中的一些结构与函数。并通过一个简单的 demo 演示了共享内存工作的基本原理。由于 demo 中没有采取任何同步技术,demob 的输出就显得有些杂乱无章。在接下来介绍信号量的文章中,我们会在 demo 中通过信号量来同步共享内存的访问。

参考:
《深入理解 Linux 内核》
《Linux 环境下 C 编程指南》

System V IPC 之共享内存的更多相关文章

  1. System V IPC&lpar;3&rpar;-共享内存

    一.概述                                                    1.共享内存允许多个进程共享物理内存的同一块内存区. 2.与管道和消息队列不同,共享内存 ...

  2. 五十、进程间通信——System V IPC 之共享内存

    50.1 共享内存 50.1.1 共享内存的概念 共享内存区域是被多个进程共享的一部分物理内存 多个进程都可把该共享内存映射到自己的虚拟内存空间.所有用户空间的进程若要操作共享内存,都要将其映射到自己 ...

  3. System V IPC 之信号量

    本文继<System V IPC 之共享内存>之后接着介绍 System V IPC 的信号量编程.在开始正式的内容前让我们先概要的了解一下 Linux 中信号量的分类. 信号量的分类 在 ...

  4. 进程间通信IPC之--共享内存

    每个进程各自有不同的用户地址空间,任何一个进 程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲 区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲 ...

  5. 第3章 System V IPC

    3.1 概述 System V IPC 包含:System V消息队列.System V信号量.System V共享内存. 3.2 key_t 键和 ftok函数 这三种类型的System V IPC ...

  6. 《Unix网络编程》卷2 读书笔记 第3章- System V IPC

    1. 概述 三种类型的System V IPC:System V 消息队列.System V 信号量.System V 共享内存区 System V IPC在访问它们的函数和内核为它们维护的信息上共享 ...

  7. 从并发处理谈PHP进程间通信(二)System V IPC

    .container { margin-right: auto; margin-left: auto; padding-left: 15px; padding-right: 15px } .conta ...

  8. System V IPC 之消息队列

    消息队列和共享内存.信号量一样,同属 System V IPC 通信机制.消息队列是一系列连续排列的消息,保存在内核中,通过消息队列的引用标识符来访问.使用消息队列的好处是对每个消息指定了特定消息类型 ...

  9. 四十九、进程间通信——System V IPC 之消息队列

    49.1 System V IPC 介绍 49.1.1 System V IPC 概述 UNIX 系统存在信号.管道和命名管道等基本进程间通讯机制 System V 引入了三种高级进程间通信机制 消息 ...

随机推荐

  1. ENode 2&period;6 架构与设计简介以及全新案例分享

    前言 ENode是一个应用开发框架,为开发人员提供了一整套基于DDD+CQRS+ES+EDA架构风格的解决方案.ENode从发布1.0开始到现在的差不多两年时间,我几乎每周都在更新设计或实现代码.以至 ...

  2. jQuery工具函数(转)

    原文地址:http://www.cnblogs.com/kissdodog/archive/2012/12/27/2835561.html 作者:逆心 ------------------------ ...

  3. 兼容IE&comma; Chrome的ajax function

    gAjax.js var gAjax = (function () { /* paramObj:{ url: request url, method: GET or POST, encode: cha ...

  4. OLEDB和ODBC的区别&lpar;优缺点&rpar;

    ODBC是一种连接数据库的开放标准,OLEDB(对象链接和嵌入数据库)位于ODBC层与应用程序之间. 在你的ASP页面里,ADO是位于OLEDB之上的应用程序. 你的ADO调用先被送到OLEDB,然后 ...

  5. Java问题汇集&lpar;2&rpar;

    1.Error setting driver on UnpooledDataSource 具体:Exception in thread "main" org.apache.ibat ...

  6. 小学生之SpringMVC

    1. Springmvc是什么? Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基 ...

  7. rsyslog 同时发生nginx 访问日志和错误日志

    input(type="imfile" File="/var/log/nginx/access.log" Tag="zjzc-frontend01-a ...

  8. T&lowbar;SQL编程赋值、分支语句、循环

    咱们在C#中会常用到赋值.循环.分支语句什么的 今天咱们来看下当初在C#用到的一点东西放到SQL中是怎么使用的 创建变量 在C#中创建一个值类型变量很简单 int a:这就可以了 SQL: decla ...

  9. jsp&colon;forward动作功能

    jsp:forward动作:引导请求者进入新的页面 例子:login.jsp <center><p>用户登录 </p> <form name="fo ...

  10. BZOJ 1093 &lbrack;ZJOI2007&rsqb;最大半连通子图 - Tarjan 缩点

    Description 定义一个半联通图为 : 对任意的两个点$u, v$,都有存在一条路径从$u$到$v$, 或从$v$到$u$. 给出一个有向图, 要求出节点最多的半联通子图,  并求出方案数. ...