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)

大家都在看

  • linux版powershell中,tab补全,linux外部命令参数名,的模块介绍

    关键字 linux powershell pwsh 补全 complete bash zsh 摘要:linux用户的福音!在linux版powershell中,补全linux外部命…

    Linux 2023年6月14日
    079
  • Python:给定一个不超过5位的正整数,判断有几位

    方法一:作比较 方法二:使用整除实现,除完后如果是个0或不是个0,这种方法引入了计算,效率会降低,所以能加就不要减,能乘就不要除,能不计算就不计算 方法三: 方法四:字符串处理实现…

    Linux 2023年6月7日
    084
  • 部署前后端为独立的 Docker 节点

    在『服务器部署 Vue 和 Django 项目的全记录』一文中,介绍了在服务器中使用 Nginx 部署前后端项目的过程。然而,当 Web 应用流量增多时,需要考虑负载均衡、流量分发…

    Linux 2023年6月7日
    0105
  • Linux内核模块管理(命令)

    1.什么是 Linux 内核模块? 内核模块是可以根据需要加载到内核中或从内核中卸载的代码块,因此无需重启就可以扩展内核的功能。事实上,除非用户使用类似lsmod这样的命令来查询模…

    Linux 2023年6月8日
    083
  • 十一、服务介绍及端口

    服务管理简介服务器的作用主要是什么?主要是通过网络来提供服务,比如apache提供一个web服务,mysql提供一个数据库服务,dns提供一个域名解析服务,ftp提供一个文件服务器…

    Linux 2023年6月7日
    099
  • 【spring-boot】Redis的整合与使用详解

    在pom.xml中添加依赖 org.springframework.boot spring-boot-starter-data-redis 2.2.1.RELEASE io.let…

    Linux 2023年5月28日
    085
  • CentOS7为php7.2安装php-redis扩展

    先下载phpredis-develop 安装unzip、zip解压工具 解压后会多了个phpredis-develop的目录。进入目录 安装phpize模块 执行phpize 查找…

    Linux 2023年5月28日
    072
  • Wine 运行百度云盘 中文乱码解决;wine中文乱码解决;fedora 34 运行百度网盘;

    今天需要下个 imagenet 的 ILSVRC2012 数据集,找到了网友在百度网盘中分享的下载好的; 但是因为本人使用的是 fedora 34 系统,所以尝试下载 百度网盘 l…

    Linux 2023年6月14日
    094
  • 我的第一个程序

    新建一个java文件 文件后缀名为.java Hello.java 【注意点】系统可能没有显示文件后缀名,我们需要手动代开 编写代码 public class Hello{    …

    Linux 2023年6月7日
    057
  • VirtualBox 和宿主机挂载共享文件夹 步骤记录

    问题记录 这个功能不常用(感觉这个步骤很繁琐,用finalshell连ssh就能很溜),但是有时候在公司网络受限的时候安装不了ssh,只能用这个挂载的方式。 防止后期遗忘步骤,我把…

    Linux 2023年6月6日
    0148
  • linux下中文输入法问题

    故事背景:最近在做资产上报相关功能,要支持中文输入,如果正常快捷方式启动程序没问题,但是升级或者卸载重新安装,自启的时候是使用su usr -C XX.sh启动,root下启动没办…

    Linux 2023年6月13日
    076
  • 消息中间件MQ的学习境界和路线

    在《深入理解Java类加载机制,再也不用死记硬背了》里我提到了对于一门语言的”会”的三个层次。本篇将以知识地图的形式展现学习消息中间件MQ各个层次要掌握的内…

    Linux 2023年6月14日
    0100
  • Kasini3000 batch modify the password for windows node

    https://gitee.com/chuanjiao10/kasini3000 win,linux devops automation batch script framewor…

    Linux 2023年6月13日
    092
  • RabbitMQ超详细安装教程(Linux)

    镜像下载、域名解析、时间同步请点击阿里云开源镜像站 1、简介 官网:https://www.rabbitmq.com/ RabbitMQ是一个开源的遵循AMQP协议实现的基于Erl…

    Linux 2023年5月27日
    0161
  • PyTorch介绍-使用 TORCH.AUTOGRAD 自动微分

    训练神经网络时,最常用的算法就是 反向传播。在该算法中,参数(模型权重)会根据损失函数关于对应参数的梯度进行调整。 为了计算这些梯度,PyTorch内置了名为 torch.auto…

    Linux 2023年6月14日
    0103
  • 接口

    一.抽象方法及抽象类 1-1 抽象方法 抽象方法:这种方法是不完整的,仅有声明而没有方法体。 public abstract void f(); 1-2 抽象类 包含抽象方法的类一…

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