Linux系统编程之命名管道与共享内存

在上一篇博客中,我们已经熟悉并使用了匿名管道,这篇博客我们将讲述进程间通信另外两种常见方式——命名管道与共享内存。

1.命名管道

管道是使用文件的方式,进行进程之间的通信。因此对于管道的操作,实际上还是用诸如write,read等接口实现。

匿名管道应用的一个限制就是只能在具有亲缘关系(如父进程与子进程、兄弟进程)之间进行通信。如果想在不相关的进程间进行数据交换,可以使用FIFO文件来做这种工作。

这里的FIFO文件即我们所说的命名管道。必须强调的是,虽然FIFO是一种文件,但实际上数据的读写都是在操作系统开辟的内存缓冲区中进行的,并不会真的写入磁盘中。如果那样,进程间通信的效率将会极大降低!

1.1 创建命名管道

1.1.1 使用命令行创建

在命令行中使用 mkfifo 管道名的方式来创建命名管道。如下图

Linux系统编程之命名管道与共享内存

可以看到文件类型为p,即管道类型的文件。文件大小为0,即便写入到pipe中的数据没有被另一个进程读出,依然是0!因为根本不会将数据写入到磁盘中。

下例中我们将hello world重定向到pipe中,并且从pipe中读出数据显示到屏幕。使用两个shell,进入到同一个目录下,一个在命令行输入 echo hello world >pipe,另一个输入 cat < pipe, 可以看到在另一个shell的屏幕上出现了hello world。

Linux系统编程之命名管道与共享内存

1.1.2 使用接口创建

在命令行输入 man 3 mkfifo 后可以看到如下内容:

Linux系统编程之命名管道与共享内存

该接口一共有两个参数,其中第一个创建的fifo文件的路径,第二个是文件权限,与我们之前学习的文件操作的权限一模一样。返回值如果等于0则创建成功,-1则创建失败。

1.2 匿名管道和命名管道的区别

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,打开用open。
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。其余操方式与文件的操作没有区别。

  • system V共享内存

2.1 什么是共享内存?

共享内存是最快的IPC形式,是系统在内存中开辟一块内存,用于进程之间共享地进行操作。这片内存区域经过页表映射到通信进程各自的地址空间中,不同的进程可以通过操作自己的地址空间,来操作共享内存。如下图所示:

Linux系统编程之命名管道与共享内存

2.2 共享内存函数

共享内存的创建及销毁一般分为以下几步:

  1. 操作系统在内存中申请一片内存
  2. 将共享内存挂接到进程地址空间中
  3. 使用完毕后,将共享内存和进程地址空间去关联
  4. 操作系统销毁共享内存

因此,操作系统提供了以下几组接口:

2.2.1 ftok函数

key_t ftok(const char *pathname, int proj_id);

该函数有两个参数,第一个是路径名称,第二个是项目id。两者配合用于创建唯一的key值,该值可以在系统内标定唯一的通信资源。(为什么这里不说标定唯一的共享内存,是因为system v包含共享内存,消息队列,信号量三种通信方式。三种通信方式都是使用ftok函数创建唯一的key来标定唯一性的。)

2.2.2 shmget函数

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

该函数用来创建共享内存。有三个参数:

key即系统层面上这个共享内存段名字,就是我们用ftok获取的唯一值。

size是共享内存大小,一般是页的整数倍。(一个page是4096 bytes

shmflg是由九个权限标志构成的,它们的用法和创建文件时使用的mode模式标志是一样的。IPC_CREATE 的用法是如果key标定的共享内存不存在则创建,若存在则直接使用。IPC_EXCL一般要配合IPC_CREATE使用,即key标定的共享内存不存在则创建,若存在则报错,常用于申请开辟共享内存的一端。除此之外,在创建共享内存时,还应该标定该共享内存的权限(与文件中设置权限如出一辙),如0664等。

返回值:成功返回一个非负整数,即该共享内存段的标识码(注意,该标识码是用户层面的标识码,简单,方便用户使用!而key是系统层面的!;失败返回-1。)

2.2.3 shmat函数

void* shmat(int shmid, const void* shmaddr, int shmflg);

该函数用于将共享内存挂接到进程地址空间中。

shmid参数是使用shmget获取的用户层面的共享内存标识符。

shmaddr参数是挂接到进程地址空间中的起始虚拟地址,用于一般不关心,设置为NULL,让操作系统自己选择分配。

shmflg参数:0即对该共享内存有读写权限,另一个SHM_RDONLY即对该共享内存有只读权限。

返回值:成功返回一个指针,指向共享内存起始地址;失败返回-1。

2.2.4 shmdt函数

int shmdt (const void* shmaddr);

该函数用于将共享内存与当前进程去关联。

唯一的一个参数shmaddr是由shmat所返回的指针。

去关联成功则返回0,失败则返回-1。

注意:去关联与销毁共享内存是完全不同的两个概念!

2.2.5 shmctl函数

int shmctl(int shmid, int cmd, struct shmid_ds* buf);

功能:用于控制共享内存(常用于销毁共享内存)

第一个参数shmid是shmget函数返回的共享内存标识符,cmd有三个可以采取的动作,常使用IPC_RMID来销毁共享内存,buf指向一个保存着共享内存的模式状态和访问权的数据结构,如果是为了销毁该共享内存,直接置为NULL。

成功返回0,失败返回-1。

Linux系统编程之命名管道与共享内存

3.共享内存的数据结构

struct shmid_ds {
    struct ipc_perm     shm_perm;  /* operation perms */
    int         shm_segsz;  /* size of segment (bytes) */
    __kernel_time_t     shm_atime;  /* last attach time */
    __kernel_time_t     shm_dtime;  /* last detach time */
    __kernel_time_t     shm_ctime;  /* last change time */
    __kernel_ipc_pid_t  shm_cpid;   /* pid of creator */
    __kernel_ipc_pid_t  shm_lpid;   /* pid of last operator */
    unsigned short      shm_nattch; /* no. of current attaches */
    unsigned short      shm_unused; /* compatibility */
    void            *shm_unused2;   /* ditto - used by DIPC */
    void            *shm_unused3;   /* unused */
};

最前面的 struct ipc_perm shm_perm是system v的三种通信方式共有的,该结构体内就有key值。

unsigned short shm_nattch;这个字段即挂接该共享内存的进程数。

4.使用共享内存的例子

4.1实例代码

Makefile:

.PHONY: all
all: server client
client: client.c comm.c
    gcc -o $@ $^
server: server.c comm.c
    gcc -o $@ $^

.PHONY:clean
clean:
    rm -f client server

comm.h

#ifndef COMM_H
#define COMM_H
#include <stdio.h>
#include <sys types.h>
#include <sys ipc.h>
#include <sys shm.h>
#include <unistd.h>
#define PATHNAME "."
#define PROJ_ID 0x66
int createShm(int size);
int destroyShm(int shmid);
int getShm(int size);
#endif
</unistd.h></sys></sys></sys></stdio.h>

comm.c

#include "comm.h"

static int commShm(int size, int flags)
{
  key_t _key = ftok(PATHNAME, PROJ_ID);
  if(_key < 0)
  {
    perror("ftok");
    return -1;
  }
  int shmid = shmget(_key, size, flags);
  if(shmid < 0)
  {
    perror("shmget");
    return -2;
  }
  return shmid;
}

int destroyShm(int shmid)
{
  if(shmctl(shmid, IPC_RMID, NULL) < 0)
  {
    perror("shmctl");
    return -1;
  }
  return 0;
}

int createShm(int size)
{
  return commShm(size, IPC_CREAT|IPC_EXCL|0666);
}

//creatShm&#x662F;server&#x7AEF;&#x521B;&#x5EFA;shm&#xFF0C;getShm&#x662F;client&#x7AEF;&#x83B7;&#x53D6;&#x521B;&#x5EFA;&#x597D;&#x7684;shm
int getShm(int size)
{
  return commShm(size, IPC_CREAT);
}

server.c

#include "comm.h"

int main()
{
  int shmid = createShm(4096);

  char* addr = (char*)shmat(shmid, NULL, 0);
  sleep(2);
  while(1)
  {
    printf("client# %s\n",addr);
    sleep(1);
  }

  shmdt(addr);
  sleep(2);
  destroyShm(shmid);
  return 0;
}

client.c

#include "comm.h"

int main()
{
    int shmid = getShm(4096);
    sleep(1);
    char *addr = (char*)shmat(shmid, NULL, 0);
    sleep(2);
    int i = 0;
    while(1)
        addr[i] = 'A'+i;
        i++;
        addr[i] = 0;
        sleep(1);
    }

    shmdt(addr);
    sleep(2);
    return 0;
}

4.2 效果展示

在./server运行之前,在命令行输入ipcs -m命令,查看此时共享内存情况。发现系统中此时没有共享内存。

Linux系统编程之命名管道与共享内存

./server运行后,此时可以看到nattach变为1,即有一个进程挂接到该共享内存。

Linux系统编程之命名管道与共享内存

运行./client后,此时nattch变为2,即此时有两个进程挂接到该共享内存。

Linux系统编程之命名管道与共享内存

在./client端ctrl+C之后,发现./server端还在打印,此时打印的数据不再改变,因为没有进程向共享内存中写入数据。此时nattch变为1。

Linux系统编程之命名管道与共享内存

在关闭./server之后,发现nattch变为1。但是,标识符为5的共享内存仍然存在!!也就是说, 共享内存的生命周期随内核!!而不是随进程!!

Linux系统编程之命名管道与共享内存

4.3 共享内存的特性

通过4.2中的例子,我们可以得到共享内存的以下特性:

1.共享内存的生命周期随内存。

2.系统层面是用key来标识共享内存的,而用户层面是通过shmid来进行标识,且shmid比key要简单得多。

3.可以使用 ipcs -m命令来查看共享内存的状态,使用 ipcrm -m +shmid来删除共享内存。

4.删除共享内存是销毁内存中的内存空间,而去关联实际上是删除进程页表中进程地址空间和对应共享内存的映射关系。

5.如果在删除的时候,nattch字段为0,那么内核中描述共享内存的结构体也被释放了。如果在删除的时候,nattch字段不为0,那么key会变为0x00000000.表示当前共享内存不能被其他进程挂接,共享内存的status变为destroy。如下图所示:在client运行过程中使用ipcrm -m 6删除6号共享内存,再使用ipcs -m查看,此时key为0x00000000,status为dest。

Linux系统编程之命名管道与共享内存

在共享内存status为dest时,一旦该共享内存的进程挂接数为0,共享内存将会被立即销毁。如下图所示。在./client被crtl C后,使用ipcs -m命令,此时系统中再无共享内存。

Linux系统编程之命名管道与共享内存

6.共享内存不提供同步与互斥机制(回想一下,管道是否提供)。

7.共享内存是最快的进程通信方式。因为共享内存写是覆盖写的方式,读是直接访问地址。而我们之前所学习的管道,需要通过系统调用(如write,read)来进行数据的读写,相比之下,共享内存的数据拷贝次数更少,因此效率也会更高。

3.其他进程通信方式

除了共享内存,system v还提供了消息队列和信号量两种进程通信方式,其中消息队列还是为了实现进程间的数据交换,而信号量主要是用于实现进程之间的同步与互斥机制。

3.1 system V消息队列

消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法。

每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。

IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核。

3.2 system V信号量

信号量主要用于同步和互斥的。

由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥。

系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。在进程中涉及到互斥资源的程序段叫临界区。

关于同步与互斥,我们将在多线程部分重点学习。

Original: https://www.cnblogs.com/Grong/p/15635783.html
Author: 乌有先生ii
Title: Linux系统编程之命名管道与共享内存

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/585753/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • (转)redis系列之——一致性hash算法

    数据分片(sharding)分布式数据存储时,经常要考虑数据分片,避免将大量的数据放在单表或单库中,造成查询等操作的耗时过长。比如,存储订单数据时使用三个mysql库(编号0,1,…

    Linux 2023年5月28日
    0135
  • 博客被阮一峰引流后,我对“大数据”的分析与思考

    事情经过 2021年9月3日,一个普通的的早上,我照常打开了我的小博客网站echeverra,看看有没有评论,虽然知道大概率没几个人访问,更鲜有人评论,可还是想打开自己的小窝瞅上那…

    Linux 2023年6月7日
    086
  • python学习

    python中的字符串以双引号或者单引号表示 长度为L:第一个字节索引为0或-L 最后一个字节索引为L-1或-1 in是二元关系操作,用来判断左侧内容是否在右侧的集合中 float…

    Linux 2023年6月6日
    0104
  • Linux磁盘管理

    对Linux来说一切皆文件,Linux归根结底只有一个根目录,一个独立且唯一的文件结构,Linux的每个分区都是用来组成整个文件系统的一部分。所以Linux采用了磁盘挂载的方式,将…

    Linux 2023年6月8日
    0114
  • [云计算]TCP云架构-思维导图

    博客园 :当前访问的博文已被密码保护 请输入阅读密码: Original: https://www.cnblogs.com/Skybiubiu/p/16276893.htmlAut…

    Linux 2023年6月13日
    092
  • 每天一个 HTTP 状态码 205

    205 Reset Content 表示服务器成功地处理了客户端的请求,要求客户端… 205 Reset Content 205 Reset Content 表示服务器…

    Linux 2023年6月7日
    0121
  • 一劳永逸,解决.NET发布云服务器的时区问题

    国内大多数开发者使用的电脑,都是使用的北京时间,日常开发的过程中其实并没有什么不便;不过,等遇到了阿里云等云服务器,系统默认使用的时间大多为 UTC时间,这个时候,时区和时间的问题…

    Linux 2023年6月6日
    098
  • C语言练习:hackerrank十五关

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Linux 2023年6月11日
    094
  • QT和Java的跨平台

    大家基本上都知道QT是跨平台的,Java也是跨平台的,那咱们今天就来聊聊他们两个: 相同点:都是跨平台 不同点:Java 的运行是建立在虚拟机上的,在虚拟机上 一次编译到处运行,但…

    Linux 2023年6月13日
    0101
  • Tomcat性能优化方案

    你使用过tomcat的话,简单的说就是”内存溢出”. 通常情况下,这种问题出现在实际的生产环境中.产生这种问题的原因是tomcat使用较少的内存给进程,通过…

    Linux 2023年6月14日
    0112
  • zabbix自定义监控mysql主从状态和延迟

    zabbix自定义监控mysql主从状态和延迟 zabbix自定义监控mysql主从状态和延迟 zabbix自定义监控mysql主从状态 zabbix自定义监控mysql主从延迟 …

    Linux 2023年6月13日
    0122
  • shell xargs技巧

    find /home/tomcat8-hk/ -name ‘808*.sh’ | xargs -i sed ‘s@CATALINA_HOME=&…

    Linux 2023年5月28日
    0106
  • Vue3 封装 Element Plus Menu 无限级菜单组件

    本文分别使用 SFC(模板方式)和 tsx 方式对 Element Plus el-menu 组件进行二次封装,实现配置化的菜单,有了配置化的菜单,后续便可以根据路由动态渲染菜单。…

    Linux 2023年6月7日
    0148
  • 【Linux】socket通信编程

    socket通信 * – socket简介 – socket操作API函数 – 代码实现 socket简介 网络层的”ip地址&#8…

    Linux 2023年6月13日
    096
  • dotnet-cnblogs-tool使用与坑

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Linux 2023年6月6日
    084
  • 轻量级多级菜单控制框架程序(C语言)

    1、前言 作为嵌入式软件开发,可能经常会使用命令行或者显示屏等设备实现人机交互的功能,功能中通常情况都包含 UI 菜单设计;很多开发人员都会有自己的菜单框架模块,防止重复造轮子,网…

    Linux 2023年6月7日
    0119
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球