Linux---进程间通信 | 管道 | PIPE | MKFIFO | 共享内存 | 消息队列

管道

管道是UNIX中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的数据流称为一个管道。

一个文件,可以被多个进程打开吗?可以,那如果一个进程打开文件,往文件里面写数据,另一个进程打开文件,读取文件里面的数据。这样可以把文件写到磁盘上,进行读写操作。在之前,我们就用过管道的操作。

ps -ajx | head -1

比如说这个查看进程的指令。在进程那篇文章里进程使用。

ps -ajx是一个指令,在运行的时候就变成了一个进程。head -1也会变成一个进程。| 是管道。


曾经我们说过,每一个进程都有一个tack_struct,每一个文件再打开的时候,会有文件描述符,文件描述符在文件描述符表中。task_struct 中会有指针指向文件描述符表。

进程打开文件的之后,都要有struct file对象。默认会打开三个文件---标准输入,标准输出,标准错误。

再打开一个新文件的时候,系统会自动的查找文件描述符表,将最小的文件描述符给新打开的文件。

将新打开的文件的地址填入到文件描述符表中,然后将3号位置返回给fd。


进程间通信的本质前提是需要先让不同的进程,看到同一份资源。这样就可以完成进程间的通信了。

管道其实就是文件,只不过,这个文件不是磁盘文件。

既然父子进程之间资源是共享的,那么子进程关闭fd=0的文件,子进程会受到影响吗?其实不会,因为struct file中是存在一个引用计数器的概念的。

匿名管道

父进程想写数据,就把读端关掉了,子进程要读数据,就把写端关掉了,这样就形成了一个单项连同的管道了。

include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码
#include <iostream>
#include <unistd.h>

int main()
{
    int fd[2];
    pipe(fd);
    pid_t id = fork();
    if (id > 0)
    {
        close(fd[0]);
        std::string msg = "hello world";
        write(fd[1], msg.c_str(), msg.size());
    }
    else if (id == 0)
    {
        close(fd[1]);
        char msg[1024];
        read(fd[0], msg, 1024);
        std::cout << std::string(msg) << std::endl;
    }
    
    return 0;
}

这样就能完成两个进程间的通信了。

但是,pipe只能完成具有血缘关系(父子,爷孙,兄弟,只不过常用来完成父子间的通信)的进程通信。因为父子间的资源是共享的,两个互不相干之间的资源可不是共享的。

代码中的管道是没有名字的,所以称他为匿名管道。


管道的特征

  1. 具有血缘关系的进程进行进程间通信
  2. 管道只能单向通信
  3. 父子进程是会进程协同的,同步与互斥的 --- 保护管道文件的数据安全
  4. 管道是面向字节流的
  5. 管道是基于文件的,而文件的生命周期是随进程的

管道的四种情况

  1. 读写端正常,管道如果为空,读端就要阻塞
  2. 读写段正常,管道如果被写满,写端就要阻塞
  3. 读端正常读,写端关闭,读端就会读到0,表明读到了文件pipe结尾,不会被阻塞
  4. 写端正常写入,读端关闭了。操作系统就要杀掉正在写入的进程。(通过信号杀掉)

命名管道

| 是一种管道,还有一种管道是 mkfifo

mkfifo可以完成两个互不相关进程间的通信。

创建管道文件后往管道文件中写内容,会造成阻塞。

此时我们通过另一个窗口将管道中的数据读取出来,阻塞的那一端也会放开。


#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char * pathname,mode_t mode);
参数一:创建管道的名字
参数二:文件的权限
返回值:成功0,失败-1
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
    // 创建管道文件
    int n = mkfifo("MYMKFIFO", 0664);
    if (n < 0)
    {
        std::cerr << "mkfifo" << std::endl;
        exit(-1);
    }
    // 打开文件
    int fd = open("MYMKFIFO",O_RDWR);
    if (fd < 0)
    {
        std::cerr << "open" << std::endl;
    }
    // 向管道文件中写入内容
    std::string line;
    while (true)
    {
        std::cout << "Please Enter@ ";
        std::cin >> line;
        write(fd, line.c_str(), line.size());
    }
    close(fd);

    // 删除管道
    sleep(5);
    n = unlink("MYMKFIFO");
    if (n < 0)
    {
        std::cerr << "unlink" << std::endl;
        exit(-1);
    }
    return 0;
}

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
    int fd = open("MYMKFIFO", O_RDWR);
    if (fd < 0)
    {
        std::cerr << "open" << std::endl;
        exit(-1);
    }

    std::string line;
    while (true)
    {
        char buf[1024];
        int n = read(fd, buf, 1024);
        if (n == -1)
        {
            break;
        }
        std::cout << std::string(buf) << std::endl;
    }

    close(fd);
    return 0;
}
.PHONY:all
all:mypipe mymkfifo
mypipe:mypipe.cc
	g++ -o $@ $^
mymkfifo:mymkfifo.cc
	g++ -o $@ $^

.PHONY:clean
clean:
	rm  mypipe mymkfifo

这样,就完成了两个进程间的通信。

共享内存

每一个进程都有自己对应的内核数据结构,内核数据结构中的指针指向一个页表,通过页表的映射关系来找到物理内存,另一个进程也是通过页表的映射,来找到物理内存,那么理论上这两个进程就可以找到物理内存中的同一块内存来完成共享内存。当然,这些操作不会是进程直接做的,是由操作系统来完成。如何管理共享内存呢?先描述,在组织。

这块共享内存有多少进程关联起来了?这块内存多大?这就由一个内核结构体来描述共享内存了,经过操作系统操作后,最后都会变成对某一个数据结构的增删查改。

下面就是对一些函数的理解了。

shmget(创建共享内存)

功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存大小(单位是字节)
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
    IPC_CREAT(单独使用):如果你创建的共享内存不存在,就创建,存在,就获取并返回。
	IPC_CREAT | IPC_EXCL:如果你创建的共享内存不存在,就创建,存在,就出错返回。
    	确保如果我们申请陈工了一个共享内存,这个共享内存一定是一个新的
	IPC_EXCL:不单独使用

其实还有点问题,如何知道这个共享内存是否存在?如果保证让不同的进程看到同一个共享内存呢?

通过key这个参数可以完成。

  1. key是一个数字,这个数字是几,不重要。关键在于它必须在内核中具有唯一性,能够让不同的进程进行唯一性标识。
  2. 第一个进程可以通过key创建共享内存,第二个之后的进程,只要拿着同一个key 就可以和第一个进程看到同一个共享内存了。
  3. 对于一个已经创建好的共享内存,key在哪?共享内存是由操作系统创建的,操作系统要对共享内存进行管理,所以key在共享内存的描述对象中。
  4. 第一次创建的时候,必须有一个key了。如何形成一个key?通过ftok这个系统调用接口。
  5. key跟路径有点类似,是唯一的。

这个ftok不会在内存中去遍历key,找到一个没有使用过的key,它内置的有一套算法,由pathname和proj_id进行了数值计算即可。这两个参数由用户自己决定。

#include "comm.hpp"

int main()
{
    int shmid = CreateShm();

    return 0;
}
#ifndef __COMM_HPP__
#define __COMM_HPP__

#include <iostream>
#include <string>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <cstring>
#include <unistd.h>
#include "/home/sxk/mylog/log.hpp"

const std::string pathname = "/home/sxk";
const int proj_id = 0x777777;
const int size = 4096;

key_t GetKey()
{
    /*
        通过ftok创建出一个唯一的key
    */
    key_t k = ftok(pathname.c_str(), proj_id);
    if (k < 0)
    {
        std::cerr << "ftok fail" << std::endl;
        exit(1);
    }
    std::cout << "ftok success, key :" << k << std::endl;
    return k;
}

int GetShareMemHelper(int flag)
{
     /*
        用key创建出一块共享内存
    */
    key_t k = GetKey();
    int shmid = shmget(k, size, flag);
    if (shmid < 0)
    {
        std::cerr << "shmget fail" << std::endl;
        exit(2);
    }
    std::cout << "ftok success, shmid :" << shmid << std::endl;

    return shmid;
}

int CreateShm()
{
    return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}

int GetShm()
{
    return GetShareMemHelper(IPC_CREAT);
}

#endif
.PHONY:all
all:proca procb

proca:proca.cc
	g++ -o $@ $^ -g -std=c++11
procb:procb.cc
	g++ -o $@ $^ -g -std=c++11

.PHONY:clean
clean:
	rm -f proca procb

运行之后可以发现,key为1996557648,操作系统内标定的唯一性,shmid为15,只在你的进程内,用来表示资源的唯一性。

当我们再次运行这个程序的时候,就会出现错误。

通过 ipcs -m可以查看共享内存资源。

此时可以看到,我们的进程已经退出了,但是资源还是存在。说明用户如果不主动关闭,共享内存会一直存在。除非内核重启或者用户关闭。

通过 ipcrm -m可以删除共享内存资源。 ipcrm -m shmid

关掉之后在运行代码。

共享内存在创建的时候还有一个权限的问题,毕竟总会存在不同的进程对数据有不同的需求。

所以在shmget的时候,可以shmget(k, size, IPC_CREAT | IPC_EXCL | 0666),0666跟open打开文件,若文件不存在则创建文件的权限设置一样。


共享内存的大小一般建议是4096的整数倍。如果大小给4097,实际上操作系统给你的是4096 * 2的大小。

shmat(挂接)

功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1(跟malloc有点相似)
    ---
shmaddr为NULL,核心自动选择一个地址(一般设置为null就行)
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr -
(shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存
#include "comm.hpp"

int main()
{
    int shmid = CreateShm();
    std::cout << "creat shm done" << std::endl;
    sleep(3);
    char * shmaddr = (char*)shmat(shmid, nullptr, 0);
    sleep(5);
    return 0;
}

添加挂接之后。

会发现,nattch由0变成1,nattch代表的就是挂接数量。

shmdt(脱离)

功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

既然能挂接,那么也一定可以脱离。

#include "comm.hpp"

int main()
{
    int shmid = CreateShm();
    std::cout << "creat shm done" << std::endl;
    sleep(3);
    char * shmaddr = (char*)shmat(shmid, nullptr, 0);
    sleep(3);
    int n = shmdt((void*)shmaddr);
    if (n < 0)
    {
        std::cerr << "shmdt fail " << std::endl;
    }
    sleep(5);
    return 0;
}

可以观察到,nattch由0->1->0。

shmctl

功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

命令

说明

IPC_STAT

把shmid_ds结构中的数据设置为共享内存的当前关联值

IPC_SET

在进程有足够权限的前提下,把共享内存的当前关联值设为shmid_ds数据结构中给出的值

IPC_RMID

删除共享内存段

#include "comm.hpp"

int main()
{
    int shmid = CreateShm();
    std::cout << "creat shm done" << std::endl;
    sleep(3);
    char * shmaddr = (char*)shmat(shmid, nullptr, 0);
    sleep(3);
    int n = shmdt((void*)shmaddr);
    if (n < 0)
    {
        std::cerr << "shmdt fail " << std::endl;
    }
    sleep(5);
    shmctl(shmid, IPC_RMID, nullptr);
    return 0;
}

这样就完成了删除的操作。

实现两个进程间的通信

#include "comm.hpp"

int main()
{
    int shmid = CreateShm();    
    char* shmaddr = (char*)shmat(shmid, nullptr, 0);
    if (*(int*)shmaddr == -1)
    {
        perror("shmat");
        exit(-1);
    }
    
    
    while (true)
    {
        std::cout << "client say@ " << shmaddr << std::endl;
        sleep(1);

    }

    shmdt((void*)shmaddr);
    shmctl(shmid, IPC_RMID, nullptr);

    return 0;
}
#include "comm.hpp"

int main()
{
    int shmid = GetShm();    
    char* shmaddr = (char*)shmat(shmid, nullptr, 0);
    if (*(int*)shmaddr == -1)
    {
        perror("shmat");
        exit(-1);
    }
    while (true)
    {
        fgets(shmaddr, 4096, stdin);
    }

    shmdt((void*)shmaddr);



    return 0;
}

共享内存的特性

  1. 共享内存没有同步互斥之类的保护机制
  2. 共享内存是进程间通信中,速度最快的
  3. 共享内存内部的数据,由用户自己维护

SISTEM V 消息队列

所谓的消息队列,还是由操作系统创建的。

要想通过消息队列进行通信,那么必须得先让两个进程看到同一份资源,这份资源可以是文件缓冲区或者内存块或者是队列。进程看到的资源不同,通信的方式也不同。

共享内存是先要让不同的进程看到同一块共享内存,也就是找到唯一的key,消息队列跟它一样,要先让两个不同的进程看到同一个队列。然后不同的进程可以向内核中发送数据块。那么,进程A发送数据块,进程B也发送数据块,如何区分AB进程的数据块呢?向内核中发送带类型的数据块。有了类型,就可以区分不同的进程了。这个队列是要由操作系统创建,当创建了n个消息队列之后,操作系统要对消息队列进行管理,其实就是对某一个数据结构的增删查改的管理。

msgget(创建)

key如何来?还是通过ftok,msgflg的用法跟共享内存中的shmget参数中的msgflg用法一样。
返回值:
如果成功,返回所创建的消息队列的ID,失败则返回-1

msgctl(删除)

用法跟共享内存中的shmctl一样,用来删除共享内存的(第二个参数置为 IPC_RMID)。

msgsnd(发送数据块)

msgsnd
参数一:消息队列ID
参数二:提供对应的缓冲区,因为要发送数据
参数三:发送数据的大小
参数四:默认为0就行了

msgrcv
参数一:消息队列ID
参数二:提供对应的缓冲区,因为要接受数据
参数三:接受数据的大小
参数四:对应的消息类型
参数四:默认为0就行了 

消息队列的接口跟共享内存的接口用法基本类似。

通过 ipcs -q可以查看所创建的消息队列

通过 ipcrm -q可以删除所创建的消息队列

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/370616.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SQL Server之DML触发器

一、如何创建一个触发器呢 触发器的定义语言如下&#xff1a; CREATE [ OR ALTER ] TRIGGER trigger_nameon {table_name | view_name}{for | After | Instead of }[ insert, update,delete ]assql_statement从这个定义语言我们可以知道如下信息&#xff1a; trigger_name&…

supervision区域行人计数和轨迹追踪初步尝试

1、背景介绍 最近&#xff0c;一位朋友向我介绍了定位与视觉融合的需求&#xff0c;我发现这个想法非常有价值。恰逢我了解到了Supervision框架&#xff0c;便决定尝试运用它来进行初步的测试。这样做不仅有助于探索可以实际应用的项目&#xff0c;还能促进我自己在研究创新方…

035 Arrays类

示例 int[] nums new int[10]; // fill Arrays.fill(nums, 666); System.out.println(Arrays.toString(nums)); // sort nums new int[]{1, 3, 5, 7, 9, 2, 4, 6, 8}; Arrays.sort(nums); System.out.println(Arrays.toString(nums)); // equals int[] nums2 new int[]{1,…

Linux 驱动开发基础知识——内核对设备树的处理与使用(十)

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;Vir2021GKBS &#x1f43c;本文由…

angular2 开发遇到的问题

1&#xff1a;插件使用&#xff0c;要一同引入 不然报错 “ \ Changes detected. Rebuilding...X [ERROR] NG8001: sf-dashboard-overview is not a known element:”

Golang 并发控制方式有哪些

Go语言中的goroutine是一种轻量级的线程&#xff0c;其优点在于占用资源少、切换成本低&#xff0c;能够高效地实现并发操作。但如何对这些并发的goroutine进行控制呢&#xff1f; 一提到并发控制&#xff0c;大家最先想到到的是锁。Go中同样提供了锁的相关机制&#xff0c;包…

C++进阶(十)哈希的应用——位图布隆过滤器

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、位图1、位图概念2、位图的实现3、位图的应用 二、布隆过滤器1、布隆过滤器提出2、布隆过滤…

ZYNQ:CAN总线功能应用

前言 上篇文章解决了ZYNQ搭建PS和PL系统的问题&#xff0c;相当于完成最小系统板搭建。因此&#xff0c;本篇文章主要用于记录搭建CAN外设系统会出现的问题。由于ZYNQ系统包含PS和PL两个部分&#xff0c;PS部分往往问题较少&#xff0c;所以考虑先搭建PS系统的CAN外设系统。熟…

微信网页授权之使用完整服务解决方案

目录 微信网页授权能力调整造成的问题 能力调整的内容和理由 原有运行方案 is_snapshotuser字段 改造原有方案 如何复现测试场景 小结 微信网页授权能力调整造成的问题 依附于第三方的开发&#xff0c;做为开发者经常会遇到第三方进行规范和开发的调整&#xff0c;如开…

PCL安装以及CGAL构建三维凸包

基础理论专栏目录 - 知乎 (zhihu.com) 凸包问题——概述 - 知乎 (zhihu.com) 1、安装PCL 安装pcl,我的是window10,vs2019。我安装的是1.13 win10系统下 VS2019点云库PCL1.12.0的安装与配置_windows 10使用pcl-CSDN博客 照着上述博客进行配置&#xff0c;再结合这个设置环境变…

微信小程序(三十三)promise异步写法

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.promise异步与普通异步的写法区别 2.promise异步的优势 源码&#xff1a; index.wxml <view class"preview" bind:tap"onChoose"><image src"{{avatar}}" mode"…

WorkPlus Meet视频会议系统,支持局域网部署

随着科技的不断发展&#xff0c;视频会议系统已经成为企业、教育机构和医疗领域等各行各业远程协作和沟通的重要工具。恒拓高科的WorkPlus Meet视频会议系统以其强大的功能和便捷的操作&#xff0c;满足了不同行业的实际需求&#xff0c;成为市场上备受青睐的解决方案。 在金融…

Vue3+TS+Vite+Pinia最全学习总结

VUE3介绍 vue2和vue3之间的区别 因为需要遍历data对象上所有属性&#xff0c;所以如果data对象属性结构嵌套很深&#xff0c;就会存在性能问题。因为需要遍历属性&#xff0c;所有需要提前知道对象上有哪些属性&#xff0c;才能将其转化为getter和setter,所以vue2中无法将data新…

【详细教程】Kubernetes集群部署:使用kubeadm创建集群

文章目录 一、虚拟机准备&#xff08;一&#xff09;主机基本配置&#xff08;二&#xff09;安装docker&#xff08;三&#xff09;配置cri-docker环境&#xff08;四&#xff09;安装kubeadm、kubelet、kubectl&#xff08;五&#xff09;克隆主机 二、环境配置工作&#xff…

阿里计算巢:开启数据集市场的宝库,助力AI研究和应用

阿里计算巢 阿里数据巢提供了一个丰富的数据集市场&#xff0c;官方地址&#xff1a; https://computenest.console.aliyun.com/dataset/service/cn-hangzhou 可以看到数据集内容涵盖了多个领域&#xff0c;且还在不断增加中。关键是免费&#xff01;且支持下载到本地。 以下…

MC插件服教程-paper+游戏云VPS

首先必须要先买一台VPS&#xff0c;这里以i9的机型做演示 购买完成等待大约1分钟服务器就会创建完成&#xff0c;之后在管理页可以看到服务器的连接信息 image772356 43 KB 首先复制下远程连接地址&#xff0c;此处即k.rainplay.cn:13192 之后在系统里搜索“rdp”或“远程桌面…

一文学会yum源配置(联网/未联网)以及yum常用命令

1、yum源介绍 yum&#xff08;Yellow dog Updater Modified的简称&#xff09;&#xff0c;yum的宗旨是自动化地升级&#xff0c;安装/移除rpm包&#xff0c;收集rpm包的相关信息&#xff0c;检查依赖性并自动提示用户解决。yum的关键之处是要有可靠的repository&#xff0c;顾…

Linux安装svn服务器和权限配置_亲测成功

Linux安装svn服务器和权限配置_亲测成功 SVN简介 SVN是Subversion的简称&#xff0c;是一个开放源代码的版本控制系统&#xff0c;通过采用分支管理系统的高效管理&#xff0c;简而言之就是用于多个人共同开发同一个项目&#xff0c;实现共享资源&#xff0c;实现最终集中式的…

C# OMRON PLC FINS TCP协议简单测试

FINS(factory interface network service)通信协议是欧姆龙公司开发的用于工业自动化控制网络的指令&#xff0f;响应系统。运用 FINS指令可实现各种网络间的无缝通信&#xff0c;包括用于信息网络的 Etherne(以太网)&#xff0c;用于控制网络的Controller Link和SYSMAC LINK。…

【C++】C++入门 — 类和对象初步介绍

类和对象 1 类的作用域2 类的实例化3 类对象模型4 this指针介绍&#xff1a;特性&#xff1a; Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;下一篇文章见&#xff01;&#xff01;&#xff01; 1 类的作用域 类定义了一个新的作用域&#xff0c;类的…