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)

大家都在看

  • windows下使用route添加路由

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

    Linux 2023年6月7日
    0109
  • Java实现栈

    package algorithm; import java.util.Arrays;import java.util.Iterator; /** @author Administ…

    Linux 2023年6月14日
    0120
  • MySQL manager or server PID file could not be found!

    [root@centos var]# service mysqld stop MySQL manager or server PID file could not be found…

    Linux 2023年6月13日
    091
  • 源码安装Nginx以及用systemctl管理

    一、源码安装Nginx: 下载 nginx软件包 进入nginx-1.20.1目录 安装依赖 /configure软件检查( ./configure–prefix=/u…

    Linux 2023年6月13日
    096
  • Redis相关监控参数【转】

    1 慢查询 默认情况下命令若是执行时间超过10ms就会被记录到日志,slowlog只会记录其命令执行的时间,不包含io往返操作,也不记录单由网络延迟引起的响应慢。如果想修改慢命令的…

    Linux 2023年5月28日
    077
  • Linux之间的文件传输方式

    大数据集群往往涉及文件复制。我在研究大数据时总结了几种方法。 [En] Big data cluster often involves file copying. I summed…

    Linux 2023年5月27日
    0133
  • 关于树莓派64位操作系统

    用过树莓派的都知道,在烧录操作系统时,官方只提供的32位的系统,这是官方经过测试和验证比较稳定的系统,对于使用4GB或8GB版本大内存树莓派用户来说,通常会将树莓派拿来充当服务器或…

    Linux 2023年5月27日
    0127
  • API 的 Authorization 头里为啥有个 Bearer

    在我们设计和使用 API 授权的时候,经常会接触到如下内容: Authorization : Bearer Tokenxxxxxx 为什么前面会有个 Bearer,直接弄成这样不是…

    Linux 2023年6月7日
    0111
  • css或html中添加空格

    posted @2022-08-03 16:31 七窍玲珑心 阅读(8 ) 评论() 编辑 Original: https://www.cnblogs.com/lzh93/p/16…

    Linux 2023年6月13日
    0116
  • 关于如何在window下执行SQLSERVER的定时备份

    引言 在使用SqlServer Express 版本的时候发现,这个版本不支持通过数据库的代理方式进行数据库的维护。 解决方案 使用SQL语句加windows任务计划的方式解决具体…

    Linux 2023年6月14日
    099
  • QNAP container station安装 redis

    打开container station,即docker,安装Redis 选择最新的即可 命令处请务必在尾部添加语句: –requirepass “yourpasswor…

    Linux 2023年5月28日
    081
  • k8s-简介

    Kubenetes是一个针对容器应用,进行自动部署,弹性伸缩和管理的开源系统,K8s 作为缩写的结果来自计算”K”和”s”之间的八个…

    Linux 2023年6月13日
    088
  • js学习笔记之for循环

    for 循环是在您希望创建循环时经常使用的工具。 for 循环的语法如下: for (语句 1; 语句 2; 语句 3) { 要执行的代码块 } 语句 1 在循环(代码块)开始之前…

    Linux 2023年6月13日
    074
  • Spring Boot 项目部署到 Linux服务器

    1.首先将SpringBoot项目打包成JAR包,然后通过FTP工具上传到Linux,执行如下命令: java -jar xxx.jar & 该命令执行后,启动jar,一旦…

    Linux 2023年6月14日
    076
  • jquery ui实现文字下拉联想

    效果图:输入”伤寒”两个字,会自动联想下拉展示带有”伤寒”两个字的内容 前端用的是jquery ui做展示,后端数据接口是json数…

    Linux 2023年6月7日
    087
  • cgroup-v1在android中的应用实现浅析

    本文档内容主要是分析android设备中cgroup v1实现了哪些控制器,他们有哪些子控制器以及如何配置这些控制器的。 我是使用红米Note4Plus的开发版本来调研分析的,手机…

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