Linux常见的进程间通信

news2024/11/28 18:44:18

目录

  • 管道
    • pipe匿名管道
      • 接口介绍
      • 示例代码
    • fifo命名管道
      • 接口介绍
      • 代码示例
      • 匿名管道与命名管道的区别
  • shm共享内存
    • 接口介绍
    • 相关指令
    • 代码示例
    • 特点总结
  • 信号
  • 信号量
  • socket套接字

管道

在这里插入图片描述

管道是一种较老的,半双工通信方式,即数据只能向一个方向流动(即一个进程进行写操作,一个进程进行读操作);

在这里插入图片描述
如果要进行双向通信,则需要建立起两个管道。
在这里插入图片描述

管道分为两种,匿名管道命名管道;

pipe匿名管道

匿名管道就是具有血缘关系的进程进行通信,常见于父子进程之间。

父子进程创建匿名管道(半双工)的过程如下:

在这里插入图片描述

可以看到,匿名管道通信的原理就是某个父进程在他的fd文件描述符数组中维护了匿名管道文件,子进程继承之后双方用于通信;

匿名管道文件的实质和标准IO类似,是在内核中的一片特定缓冲区; 因此数据交互的时候,涉及到用户态和内核态之间的数据拷贝,效率不高的;

(后面要讲的共享内存shm是直接映射到共享内存区,不需要进行拷贝,高效)

接口介绍

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

示例代码

#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int pipe_fds[2];
    int ret = pipe(pipe_fds); // 父亲创建管道,0为读端,1为写端
    if (ret == -1)
    {
        perror("pipe");
        return 1;
    }
    int pid = fork(); // 创建子进程
    if (pid < 0)
    {
        // error
        perror("fork");
        return 2;
    }
    else if (pid == 0)
    {
        // child
        close(pipe_fds[1]); // 子进程关闭写端

        char buf[128];
        // 子进程从管道中读取数据
        read(pipe_fds[0], buf, sizeof(buf) - 1);
        printf("%s\n", buf);
        close(pipe_fds[0]);
    }
    else
    {
        // father
        close(pipe_fds[0]); // 父进程关闭读端

        // 父进程往管道内写数据
        const char *msg = "I am father.\n";
        write(pipe_fds[1], msg, strlen(msg));
        close(pipe_fds[1]);
    }
    return 0;
}

运行结果

在这里插入图片描述

fifo命名管道

FIFO,也叫做命名管道,它是一种文件类型。

  1. FIFO可以在无关的进程之间交换数据,与匿名管道不同;
  2. FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。(这个文件仅用于双方通信)

接口介绍

#include <sys/types.h>
#include <sys/stat.h>

//创建一个命名管道
int mkfifo(const char *pathname, mode_t mode);
参数:
第一个参数为这个特殊文件的路径;

第二个参数mode 与 open 函数中的 mode 相同,设置标志位。  
当 open 一个 FIFO 时,这个FIFO是否设置非阻塞标志(O_NONBLOCK)的区别:
1.若没设置 O_NONBLOCK(默认),只读的一方 open打开 这个FIFO 要 阻塞到 某个其他进程为写 而打开它
                      类似的,只写的一方 open打开 这个FIFO 要 阻塞到 某个其他进程为读 而打开它
2.若设置了 O_NONBLOCK,则只读 open打开时会立即返回。(执行下面的代码,如果有人向这个fd写了,那么这边就能拿到,不用阻塞等待处理)
                       只写 open 打开时,如果没有进程已经为读而打开该将出错返回 -1 其 errno 置 ENXIO,否则打开成功,直接就可以写入数据了;

下面是借助fifo通信的模型:

在这里插入图片描述

结论:

1、数据 还是存储在内核的缓冲区当中的(fifo创建的管道文件只是一个特殊文件,不存东西的文件,底层机制和匿名管道一样的 都是拿内核缓冲区做中介

2、管道文件的作用是为了让不同的进程可以找到这块缓冲区 (这点匿名管道做不到)

代码示例

write.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
//       int mkfifo(const char *pathname, mode_t mode);
 
int main()
{
     if(mkfifo("myfifo",0600) == -1 && errno != EEXIST)//创建命名管道
        {
                printf("mkfifo failed\n");
                perror("why");
        }
    
        int nread;
        char buf[30] = "message from myfifo";
 	
       
 
        int fd = open("./myfifo",O_WRONLY);//以 只写 的方式打开管道,程序阻塞在这,直到其他进程为读而打开它
        if(fd < 0)
        {
                printf("write open failed\n");
        }
        else
        {
                printf("write open success\n");
        }
 
        while(1)//不断的通过管道(open的fd)给read端发送数据发送
        {
                sleep(1);
                write(fd,buf,strlen(buf));
        }
        close(fd);
 
        return 0;
}

先运行write端,则命名管道就会创建好,然后等待着read端的到来,进行管道通信;

read.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
 
int main()
{
        int nread;
        char buf[30] = {'\0'};
 
 
        int fd = open("./myfifo",O_RDONLY);//以 只读的形式打开管道,程序阻塞在这,直到有另一个进程对其执行写操作
        if(fd < 0)
        {
                printf("read open failed\n");
        }else
        {
                printf("read open successn\n");
        }
 
        while(1)//反复收取数据并打印出来
        {
                nread = read(fd,buf,sizeof(buf));
                printf("read %d byte,context is:%s\n",nread,buf);
        }
 
        close(fd);
 
        return 0;
}

运行结果:

在这里插入图片描述

可以看到,当write端创建命名管道之后不断通过管道给read端发送数据,read端通过命名管道收到数据并打印出来;

匿名管道与命名管道的区别

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

shm共享内存

在这里插入图片描述

共享内存根据其名字就可以推测与内存中的共享区有关。实际上,共享内存的使用要比管道的简单

在这里插入图片描述

接口介绍

1.key_t ftok(const char *pathname, int proj_id);
功能:用来生成System V IPC密钥,key是用来唯一标识共享内存块的值 file to key
参数
pathname:共享内存文件的给定路径名
proj_id:project id
这两个参数可以随意设置,只不过要保证使用共享内存的进程这两个参数设置需一样。
返回值:成功返回生成的key值,失败返回-1
    
    
2. int shmget(key_t key, size_t size, int shmflg); //get创建
功能:用来创建共享内存句柄
参数
 key:这个共享内存段名字
 size:共享内存大小
 shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
     
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
     
     
3. void *shmat(int shmid, const void *shmaddr, int shmflg);//attach 链接
功能:将共享内存段连接到进程地址空间
参数
 shmid: 共享内存标识码
 shmaddr:指定连接的地址
 shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个字节;失败返回-1
     
4. int shmdt(const void *shmaddr);//detach 脱离
功能:将共享内存段与当前进程脱离
参数
 shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离 不等于 删除共享内存段
     
5. int shmctl(int shmid, int cmd, struct shmid_ds *buf);//control 控制
功能:用于控制实际的共享内存
参数
 shmid:由shmget返回的共享内存标识码
 cmd:将要采取的动作(有三个可取值,如下表)
 buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

shmctl中cmd的几种命令:

在这里插入图片描述

对于共享内存的key和shmid,我们可以类比文件中的inode与fd的关系。
虽然文件系统一inode唯一标识文件,但在实际使用中仍是以fd文件描述符去操作文件。\

相关指令

ipcs -m:查看当前共享内存的信息  //ipc == (Inter-Process Communication,进程间通信)

ipcrm -m shmid:删除对应shmid的共享内存块

在这里插入图片描述

代码示例

server进程创建共享内存,获取key值及shmid;

client进程通过shmid去挂接共享内存,然后观察两个进程通过共享内存进行通信的现象:

server.c

#define _SVID_SOURCE 1

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

//创建key时的两个参数,s c需要统一
#define PATH_NAME "/home/lyl/2022-3-20"
#define PROJ_ID 0x6666

#define SIZE 4097

int main()
{
  //获取key值
  key_t key = ftok(PATH_NAME, PROJ_ID);
  if(key == -1)
  {
    perror("ftok");
    return 1;
  }
  printf("key: %x\n", key);
  //获取shmid
  int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0644);//若不存在则创建共享内存,若存在则报错
  if(shmid == -1)
  {
    perror("shmget");
    return 2;
  }
  printf("shmid:%d\n",shmid);
  //让进程挂接共享内存,形成关联
  char* addr = (char*)shmat(shmid, NULL, 0); 
  printf("server attached on shared memory\n");
  if(addr == (char*)-1)
  {
    perror("shmat");
    return 3;
  }
  printf("addr:%p\n", addr);


  //从共享内存首地址读数据 并打印
  while(1)
  {
    printf("%s\n", addr);
    sleep(1);
  }

  //解除关联
  shmdt(addr);
  printf("server attached off shared memory\n");
  shmctl(shmid, IPC_RMID, NULL);
  printf("server deleted shared memory\n");
  return 0;
}

client.c

#define _SVID_SOURCE 1

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>

//创建key时的两个参数,s c需要统一
#define PATH_NAME "/home/lyl/2022-3-20"
#define PROJ_ID 0x6666

#define SIZE 4097

int main()
{
  //获取key值
  key_t key = ftok(PATH_NAME, PROJ_ID);
  if(key == -1)
  {
    perror("ftok");
    return 1;
  }
  //获取shmid
  int shmid = shmget(key, SIZE, IPC_CREAT);//不需要自己创建共享内存,server创建好了,直接获取shmid即可
  if(shmid == -1)
  {
    perror("shmget");
    return 2;
  }
  //让进程挂接共享内存,形成关联
  char* addr = (char*)shmat(shmid, NULL, 0); 
  printf("client attached on shared memory\n"); 
  //TODO
  
  const char* msg = "I am process client\n";//等会用于通信的数据
	
  //逐字符向共享内存写数据
  for(size_t i = 0; i < strlen(msg); i++)
  {
    addr[i] = *(msg + i);
    sleep(1);
  }

  //接触关联
  shmdt(addr);
  printf("client attached off shared memory\n");

  return 0;
}

client进程向共享内存中不断写入数据,然后server进程从共享内存中读取数据并打印。

在这里插入图片描述

通过分析,共享内存区别于管道借助阻塞式read和write进行通信而是直接对同一块内存进行操作

因此共享内存通信的两个进程独立,不像管道会存在阻塞现象。

而且使用之后的共享内存中数据不会自动清除,下次使用还能拿到上次通信的数据,因此每次进程结束后都需要主动释放共享内存,否则再次执行进程时会报错;

特点总结

  • 共享内存不存在同步与互斥机制,使用的进程相互独立。因此对共享内存的操作是非进程安全的
  • 共享内存只有在当前映射链接数为0时,才能被被真正删除
  • 共享内存由于不需要 内核态与用户态的 拷贝数据,因此时进程通信中最快的形式
  • 共享内存的生命周期随内核,只要不主动删除,其就会随内核一直存在,除非重启系统。

信号

在这里插入图片描述

信号一般用于一些异常情况下的进程间通信,是一种异步通信,它的数据结构一般就是一个数字。

在Linux操作系统中,为了响应各种各样的事件,提供了几十种信号,分别代表不同的意义。我们可以通过kill -l命令,查看所有的信号。

运行在shell终端的进程,我们可以通过键盘输入某些组合键的时候,给进程发送信号。例如

Ctrl+C产生 SIGINT 信号,表示终止该进程;
Ctrl+Z产生 SIGTSTP 信号,表示停止该进程,但还未结束;
如果进程在后台运行,可以通过kill命令的方式给进程发送信号,但前提需要知道运行中的进程PID号,例如:

kill -9 1050,表示给PID为1050的进程发送SIGKILL 信号,用来立即结束该进程(例如:win下在任务管理器右键结束进程);

所以,信号事件的来源主要有硬件来源(如键盘Cltr+C)软件来源(如kill命令)

信号是进程间通信机制中唯一的异步通信机制

进程需要为信号设置相应的监听处理,当监听到特定信号时,接着执行相应的操作,类似很多编程语言里的通知机制。

关于信号的更多理解和操作,可以移步这篇博客

信号量

在这里插入图片描述

关于进程间信号量的通信的原理,移步本人这篇文章;

将其与本文解耦的原因是,信号量作为同步的一种重要机制,并且是保证临界区资源正确被访问的重要手段(通过计数器方式),需要系统理解和学习;

同时,信号量自身也是邻接资源,它内部的PV原语保证了他的操作原子性;

socket套接字

在这里插入图片描述

关于进程间socket套接字的通信原理,移步本人这篇文章;

将其与本文解耦的原因是,socket是一个庞大的学习内容,除了能本地进程间通信之外,也能跨网络进程间通信,并且是学习TCP,UDP协议的重要知识点;需要系统理解和学习

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力扣 2303. 计算应缴税款总额

题目 给你一个下标从 0 开始的二维整数数组 brackets &#xff0c;其中 brackets[i] [upperi, percenti] &#xff0c;表示第 i 个税级的上限是 upperi &#xff0c;征收的税率为 percenti 。税级按上限 从低到高排序&#xff08;在满足 0 < i < brackets.length 的前提…

spring笔记下(AOP、事务管理)

一、AOP概述 1. AOP介绍 AOP(Aspect Oriented Programming)&#xff1a;面向切面编程&#xff0c;一种编程范式&#xff0c;指导开发者如何组织程序结构&#xff0c;是oop的延续。&#xff08;OOP面向对象编程&#xff09; AOP作用&#xff1a;在不惊动原始设计的基础上为其进…

LeetCode刷题模版:221 - 230

目录 简介221. 最大正方形222. 完全二叉树的节点个数223. 矩形面积224. 基本计算器【未理解】225. 用队列实现栈226. 翻转二叉树227. 基本计算器 II【未理解】228. 汇总区间229. 多数元素 II230. 二叉搜索树中第K小的元素结语简介 Hello! 非常感谢您阅读海轰的文章,倘若文中有…

【学习打卡 Free-Excel 】Task6 查找函数

文章目录1. VLOOKUP用法示例注意情况一情况二2. 通配符用法示例3.XLOOKUP用法匹配类型搜索模式1. VLOOKUP 用法 【VLOOKUP函数】VLOOKUP&#xff08;要查找的值&#xff0c;查找区域&#xff0c;要返回的结果在查找区域的第几列&#xff0c;精确匹配或近似匹配&#xff09;示…

JAVA Web 常见问题解决方案(持续更新,欢迎投稿常见问题)

大家有什么问题未解决的可以试图联系博主&#xff0c;群号在主页的详细资料 都是博主自己学习过程中遇到的问题&#xff0c;大家有什么常见问题欢迎Git 问题集目录 找到适合自己版本的pom依赖 IDEA实体类快速创建 get(),set(),toString()方法 报错Cannot find class: com.mysq…

【图卷积网络】03-空域卷积介绍 (一)

注&#xff1a;本文为3.1-3.2 空域卷积视频笔记&#xff0c;仅供个人学习使用 1、谱域图卷积 1.1 回顾 上篇博客【图卷积神经网络】02-谱域图卷积介绍讲到了三个经典的谱域图卷积&#xff1a; SCNN用可学习的对角矩阵来代替谱域的卷积核。 ChebNet采用Chebyshev多项式代替谱…

webpack使用Ammo.js - 在react中使用Ammo.js

真实麻烦啊[我的项目仓库 Next.js项目 仅供参考](https://gitee.com/honbingitee/three-template-next.js/tree/feature%2Fphysics/)本文展示使用ammo.wasm.js 结合ammo.wasm.wasm的wasm版本使用方法1. 配置webpack2. 导出Ammo 修改ammo.wasm.js文件3. 删除语句 通过查找 this.…

【Flink】详解Flink的八种分区

简介 Flink是一个流处理框架&#xff0c;一个Flink-Job由多个Task/算子构成&#xff0c;逻辑层面构成一个链条&#xff0c;同时Flink支持并行操作&#xff0c;每一个并行度可以理解为一个数据管道称之为SubTask。我们画图来看一下&#xff1a; 数据会在多个算子的SubTask之间相…

【奇妙的数据结构世界】用图像和代码对链表的使用进行透彻学习 | C++

第九章 链表 目录 第九章 链表 ●前言 ●一、链表是什么&#xff1f; 1.简要介绍 2.具体情况 ●二、链表操作的关键代码段 1.类型定义 2.常用操作 ●总结 前言 简单来说&#xff0c;数据结构是一种辅助程序设计并且进行优化的方法论&#xff0c;它不仅讨论数据的存储与处…

打工人必知必会(一)——规章制度保险劳动合同变更

目录 参考 1、规章制度的生效要件 2、工资的发放形式 3、社会保险的基本规定 4、基本养老保险 5、医疗保险、失业保险、工伤保险、生育保险 6、劳动合同的变更 第一节 协商变更劳动合同 第二节 单方变更劳动合同 参考 《HR全程法律顾问&#xff1a;企业人力资源管理高…

5-6中央处理器-多处理器系统硬件多线程

文章目录一.多处理器系统&#xff08;一&#xff09;计算机体系结构分类1.单指令单数据流SISD2.单指令多数据流SIMD3.多指令单数据流MISD4.多指令多数据流MIMD&#xff08;1&#xff09;(共享内存)多处理器系统/多核处理器&#xff08;2&#xff09;多计算机系统&#xff08;二…

逆水寒魔兽老兵服副本攻略及代码分析(英雄武林风云录,后续更新舞阳城、扬州、清明等副本攻略)

文章目录一、武林风云录1&#xff09;老一&#xff1a;陈斩槐&#xff08;只有四个机制&#xff0c;dps压力不大&#xff0c;留爆发打影子就行&#xff09;&#xff08;1&#xff09;点名红色扇形区域&#xff08;2&#xff09;点名红色长条&#xff0c;注意最后还有一段大劈&a…

MongoDB入门(特点,使用场景,命令行操作,SpringData-MongoDB)

今天我们将通过这一篇博客来了解MongoDB的体系结构&#xff0c;命令行操作和在JAVA 当中使用SpringData-MongoDB 来 操作MongoDB。 如果没有安装的小伙伴 可以看一下 这篇文章 (59条消息) 开源的文档型数据库–MongoDB&#xff08;安装&#xff09;_一切总会归于平淡的博客-CS…

LeetCode[128]最长连续序列

难度&#xff1a;中等题目&#xff1a;给定一个未排序的整数数组 nums&#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。请你设计并实现时间复杂度为 O(n)的算法解决此问题。示例 1&#xff1a;输入&#xff1a;nums [100,4,2…

java中new的含义如何理解?

在Java中&#xff0c;new关键字被使用来创建一个新的对象&#xff0c;可以理解为创建的意思。使用关键字new来创建一个对象也叫类的实例化&#xff0c;使用new创建对象时&#xff0c;会调用构造方法初始化对象声明对象Cat cat 在栈内存中实例化对象 new Cat(参数); 在堆内存中每…

C++:类的static成员,友元和构造函数初始化列表

目录 一.类的构造函数的初始化列表 1.类的构造函数初始化列表的引入和介绍 2.初始化列表用于类的类对象成员的拷贝构造函数的调用 3.初始化列表的使用细则 4.使用初始化列表的一个注意事项 二.explicit关键字 三.C类的static成员 1.类中static修饰的成员变量 2.类中st…

Lesson 4.2 逻辑回归参数估计:极大似然估计、相对熵与交叉熵损失函数

文章目录一、逻辑回归参数估计基本思路1. 构建损失函数2. 损失函数求解二、利用极大似然估计进行参数估计三、熵、相对熵与交叉熵1. 熵&#xff08;entropy&#xff09;的基本概念与计算公式2. 熵的基本性质3. 相对熵&#xff08;relative entropy&#xff09;与交叉熵&#xf…

LeetCode[947]移除最多的同行或同列石头

难度&#xff1a;中等题目&#xff1a;n块石头放置在二维平面中的一些整数坐标点上。每个坐标点上最多只能有一块石头。如果一块石头的 同行或者同列 上有其他石头存在&#xff0c;那么就可以移除这块石头。给你一个长度为 n的数组 stones&#xff0c;其中 stones[i] [xi, yi]…

MATLAB算法实战应用案例精讲-【人工智能】Grover量子搜索算法(补充篇)

前言 因为量子计算的并行性, 搜索问题, 比如说数据库搜索, 最短路径问题, 加密问题, 图形着色问题等, 都被视为可以做到量子加速. Grover 算法,有时也称为量子搜索算法(quantum search algorithm),指一种在量子计算机上运行的非结构化搜索算法,是量子计算的典型算法…

LeetCode[765]情侣牵手

难度&#xff1a;困难题目&#xff1a;n对情侣坐在连续排列的 2n个座位上&#xff0c;想要牵到对方的手。人和座位由一个整数数组 row表示&#xff0c;其中 row[i]是坐在第 i 个座位上的人的 ID。情侣们按顺序编号&#xff0c;第一对是 (0, 1)&#xff0c;第二对是 (2, 3)&…