Linux--进程间通信

news2024/11/24 6:23:47

目录

    • 1. 进程间通信目的
    • 2. 管道
      • 2.1 管道特性(匿名管道)
        • 2.1.1 单向通信
        • 2.1.2 面向字节流
      • 2.2 管道的大小
      • 2.3 命名管道
    • 3. system V进程间通信
      • 3.1 shmget函数
        • 3.1.1 key VS shmid
      • 3.2 shmctl函数
      • 3.3 shmat函数 VS shmdt函数:
      • 3.4 测试
    • 4. 感性认识
      • 4.1 什么是临界资源
      • 4.2 什么是临界区
      • 4.3 什么是原子性
      • 4.4 什么是互斥
      • 4.5 什么是同步
      • 4.6 什么是信号量

1. 进程间通信目的

进程间通信的本质是让不同进程看到同一份数据。

数据传输:一个进程需要将它的数据发送给另一个进程

资源共享:多个进程之间共享同样的资源。

通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

2. 管道

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

管道的本质就是OS中的管道的本质是内核中的缓冲区,通过内核缓冲区实现通信。

  • 管道创建函数:

匿名管道只能父子进程间通信

创建无名管道:

在这里插入图片描述

参数fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端;用来读到打开的两个fd

返回值:成功返回0,失败返回错误代码

2.1 管道特性(匿名管道)

2.1.1 单向通信

管道是一个只能单向通信的通信信道

管道存在的原因:由于进程是独立的,那么想要实现进程间的通信成本就会比较大,所以首要解决的问题就是如何使两个进程看到同一份资源。

通过管道就可以实现:利用子进程继承父进程资源的特性,把管道继承下来,达到让不同的进程看到同一份资源的目的。

首先创建无名管道:

  int pipefd[2];
  if(pipe(pipefd) != 0){
    perror("pipe failed!\n");
    exit(1);
  }
  printf("pipefd[0] = %d\n",pipefd[0]);//0
  printf("pipefd[1] = %d\n",pipefd[1]);//1

其中0是读端,1是写端;若要实现子进程写入,父进程读出,首先要关闭子进程的读出端,也就是pipefd[0]:

  if(fork() == 0){
    //child
    //0是读端
    close(pipefd[0]);
    const char* msg = "aaaaaa\n";
    while(1){
      sleep(1);
      write(pipefd[1],msg,strlen(msg));
    }
    exit(0);
  }

再关闭父进程的写端pipefd[1]:

  //parent
  close(pipefd[1]);
  while(1){
    //sleep(1);
    char buffer[64] = {0};
    ssize_t s = read(pipefd[0],buffer,sizeof(buffer)-1);
    if(s <= 0){
      break;
    }
    else{
      buffer[s] = 0;
      printf("child said to father# %s",buffer);
    }
  }

上述代码形成的结果:

在这里插入图片描述

2.1.2 面向字节流

管道的传输是通过字节方式

上述代码运行结果:

在这里插入图片描述

可以看见,每隔一秒子进程写入,随后父进程读出,打印在屏幕上,也就是每隔一秒钟会打印一次。

但是如果不让子进程休眠,而让父进程每隔一秒读一次:

在这里插入图片描述

可以看见每隔一秒读出来的数据是很多行,这是因为一秒内子进程往缓冲区写入了这么多的数据,而没有识别到分隔符的话,能打印多少取决于子进程在这个过程中能打印多少字节,这便是面向字节流。

管道的读写有四种情况:

  1. 读端不读或者读的慢,写端要等读端;
  2. 读端关闭,写端收到SIGPIPE信号直接终止;
  3. 写段不写或者写的慢,读端要等写端;
  4. 写端关闭,读端读完pipe内部的数据然后在读,会读到0,表示读到文件结尾。

2.2 管道的大小

修改上述代码,每次子进程写入字符串a,父进程依旧死循环,但是不读:

  int count = 0;
  if(fork() == 0){
    close(pipefd[0]);
    const char* msg = "aaaaaa\n";
    while(1){
      write(pipefd[1],"a",1);
      count++;
      printf("count = %d\n",count);
    }
    exit(0);
  }
  //parent
  close(pipefd[1]);
  while(1){}
  return 0;
}

可以看到输出结果是count = 65536后,不再增加:

在这里插入图片描述

在这里插入图片描述

  • 结论

65536正好是64KB,这说明管道的大小正是664KB。

2.3 命名管道

创建命名管道函数mkfifo:

在这里插入图片描述

参数:第一个参数代表需要创建命名管道的文件的路径,第二个代表管道文件的权限。

返回值:返回值等于零创建成功,-1则创建失败。

命名管道可以实现两个进程之间的通信。

如果有一个进程创建了管道,那么另一个进程可以直接使用该管道来进行通信。

举例:实现进程间通信

  • 客户端

在这里插入图片描述

  • 服务端

在这里插入图片描述

  • 头文件
    在这里插入图片描述

可以看见在make以后,不仅产生了两个可执行文件,还产生了fifo文件,这个文件就是管道文件,由服务端的mkfifo函数调用生成,权限是自己设置的:

在这里插入图片描述

运行结果就是在客户端可以发送信息给服务端接收,本质就是通过管道完成的:
在这里插入图片描述

  • 提示:

命名管道之所以叫命名管道,是因为进程间通信的方式是通过管道名,也就是说这个管道一定要有名字;而对于匿名管道,可以没有名字的原因是:它是通过父子间进程继承的方式看到同一份资源,并不需要通过管道文件名。

命名管道的文件名只是标识符,并非其真实的通信介质,只是用来让不同进程找到同一块缓冲区。所以磁盘空间大小并不决定通信内容的大小,而由缓冲区决定。

3. system V进程间通信

进程间通信有三个内容:共享内存、消息队列、信号量。这里解释共享内存。

上述方式都是基于文件的进程间通信,而system V进程间通信是基于OS层面专门给进程间通信设计的一种方案。可以说,同一主机间的进程间通信方案,就是system V方案。

其中有一个部分叫做共享内存;

共享内存区是最快的IPC形式。 一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据

3.1 shmget函数

  • 创建共享内存函数shmget

在这里插入图片描述

  • 参数
  1. 第一个参数是标识符,作为“共同”的一个共享内存,需要有独特的、公有的标志来表示:

我们也可以自己设置,但是一般是采用ftok函数来获得:两个参数分别代表自定义路径名和自定义项目id,若设置失败则返回-1:

在这里插入图片描述

生成key值:

#include "commend.h"
#define PATH_NAME "./"
#define PROJ_ID 0x6666
int main()
{
    key_t key = ftok(PATH_NAME,PROJ_ID);
    if(key < 0){
      perror("ftok");
      return 1;
    }
    printf("%u\n",key);
    return 0;
}

输出结果:

在这里插入图片描述

此时如果另一个进程想要与此进程进行通信,必须执行与之相同的代码生成相同的key值。

  1. 第二个参数是申请共享内存的大小,建议是4KB的整数倍

  2. 第三个参数是标志位,如果单独使用IPC_CREAT,或者flag为0:不存在共享内存就会创建一个,如果创建的共享内存已存在,就会直接返回当前已存在的共享内存。

在这里插入图片描述

对于IPC_EXCL,单独使用没有意义,但是一起使用的话,上述的规则就变成:不存在共享内存则创建之;如果已经有了,则返回出错。

其意义是:如果调用成功,那么这一定是个全新的、没人使用过的共享内存。

在刚才的代码基础上,申请共享内存:

#include "commend.h"
#include <sys/ipc.h>
#include <sys/shm.h>
#define PATH_NAME "./"
#define PROJ_ID 0x6666
#define SIZE 4066
int main()
{
    //代码省略
    //创建全新的id,如已存在则报错
    int shmid = shmget(key,SIZE,IPC_CREAT|IPC_EXCL);
    if(shmid < 0){
      perror("shmget");
      return 2;
    }
    printf("key:%u,shmid:%d\n",key,shmid);
    return 0;
}

输出结果:

在这里插入图片描述

ipcs指令是查看system资源的指令,默认查看三个内容:消息队列、共享内存、信号量:

在这里插入图片描述

执行ipcs -m以后,单独查看共享内存:
在这里插入图片描述

可以看见,执行完可执行文件以后(进程退出),此时的系统仍然存在共享内存,并没有被释放。

这说明system V的IPC资源,生命周期是随内核的,只能通过程序员的指令或者是OS重启来进行释放。 (删除指令ipcrm -m + shmid,不加shmid默认删除第一个)

3.1.1 key VS shmid

key:只是用来在系统层面进行标识唯一性的,不能用来管理共享内存;

shmid:是OS给用户提供的id,用来在用户层进行共享内存管理。

对于这两个概念,key可以类比为struct file,也就是fd的地址,具有唯一性;而shmid类比成fd,用来管理文件。

通过上述表达,知道了想要保证不同进程看到的是同一个共享内存,需要我们形成的key的算法和原始数据是一样的,就能形成同一个ID,达到目的。

这里的key同时也会被设置进入 内核中的关于共享内存的结构数据中。

3.2 shmctl函数

  • 控制共享内存函数shmctl

在这里插入图片描述

对于参数cmd,表示将要采取的动作,有三个可取值:

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

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

  3. IPC_RMID:删除共享内存段。

对于第三个参数,代表的就是控制共享内存的数据结构,里面包含了key,每个进程能找到相同的key,就能找到对应的共享内存:

在这里插入图片描述

3.3 shmat函数 VS shmdt函数:

挂接函数(shmat)与去挂接(shmdt)函数

在这里插入图片描述

对于挂接函数来说:
参数:shmaddr表示要挂接的共享内存的起始地址,shmflg代表对应标志位;(shmaddr为NULL,核心自动选择一个地址)
返回值:成功返回一个指针变量,存放共享内存起始地址,失败返回-1.

对于去挂接函数来说:
参数:由shmat所返回的指针返回值:成功返回0,失败返回-1;(可以类比malloc函数的返回值)

去挂接作用是让进程和共享内存去挂接,而不是清除共享内存。

 //获取key值
 key_t key = ftok(PATH_NAME,PROJ_ID);
 if(key < 0){
   perror("ftok");
   return 1;
 }
 //获取共享内存
 int shmid = shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);
 if(shmid < 0){
   perror("shmget");
   return 2;
 }
 //printf("key:%u,shmid:%d\n",key,shmid);
 //sleep(10)

 //挂接
 char* mem = (char*)shmat(shmid,NULL,0);
 //去挂接
 shmdt(mem);
 
 //控制共享内存(删除)
 shmctl(shmid,IPC_RMID,NULL);

3.4 测试

在服务端申请并且挂接内存,然后往打印:

  //如果client端不进行写入
  //server端进行读取
  //获取key值
  key_t key = ftok(PATH_NAME,PROJ_ID);
  if(key < 0){
    perror("ftok");
    return 1;
  }
  //获取共享内存
  int shmid = shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);
  if(shmid < 0){
    perror("shmget");
    return 2;
  }
  //挂接
  char* mem = (char*)shmat(shmid,NULL,0);
  while(1){
    printf("%s\n",mem);
    sleep(2);
  }
  //去挂接
  shmdt(mem); 
  //控制共享内存(删除)
  shmctl(shmid,IPC_RMID,NULL);

运行结果:

在这里插入图片描述

可以看出,当client端没有写入的时候,server端依旧在读取,并不会等待client写入,只不过读取的是空白字符。

但是当client端进行写入:

  key_t key = ftok(PATH_NAME,PROJ_ID);
  int shmid = shmget(key,SIZE,0);
  //挂接
  char* mem = (char*)shmat(shmid,NULL,0);
  
  while(1){
    sleep(2);
    strcpy(mem,"i am process A\n");
  }
  //去挂接
  shmdt(mem);
  shmctl(shmid,IPC_RMID,NULL);

在这里插入图片描述

可以看见server端可以接收到client端写来的消息。

虽然是进行通信,但是不使用read等系统接口,是如何做到将进程A的数据给到进程B并且进程B将其打印出来的?

本质原因:在这个过程中并没有像管道通信那样调用系统接口read或者write(这两个接口的本质是将数据从内核拷贝到用户,或者从用户拷贝到内核),所以,共享内存一旦建立好并映射进自己进程的地址空间,该进程就可以直接看到共享内存,就如malloc空间一般,不需要任何系统调用接口。

这里虽然使用了字符串拷贝函数,但是也可以直接通过下标操作修改地址mem对应的值。

由此,共享内存是所有进程空间通信中速度最快的。

4. 感性认识

4.1 什么是临界资源

凡是需要进程间通信,就会引入被多个进程看到的资源(通信资源),同时,也引入了一个新的问题,临界资源的问题。

**凡是被多个执行流能够同时访问的资源就是临界资源。**同时向显示器打印,进程间通信的时候,管道、共享内存、消息队列等都是临界资源。

4.2 什么是临界区

进程的代码是有很多的,其中,用来访问临界资源的代码,就叫临界区。

4.3 什么是原子性

一件事要么不做,要做就做完,没有中间态,这就是原子性。

4.4 什么是互斥

在任意一个时候,只能允许一个执行流进入临界资源,执行它自己的临界区。

4.5 什么是同步

4.6 什么是信号量

命名管道和匿名管道、共享内存、消息队列,都是以传输数据为目的;而信号量不是以传输数据为目的,是通过共享“资源”的方式,来达到多个进程的同步和互斥的目的。

信号量的本质,是一个计数器,类似int count;是用来衡量临界资源中资源数目的。

可以这么理解:一个电影院相当于一个临界资源,而买票系统的计数系统会有上限,这个技术系统就是信号量。

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

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

相关文章

R语言中的多类别问题的绩效衡量:F1-score 和广义AUC

最近我们被客户要求撰写关于分类的研究报告&#xff0c;包括一些图形和统计输出。对于分类问题&#xff0c;通常根据与分类器关联的混淆矩阵来定义分类器性能。根据混淆矩阵 &#xff0c;可以计算灵敏度&#xff08;召回率&#xff09;&#xff0c;特异性和精度。 对于二进制…

基于javaweb物业管理系统的设计与实现/小区物业管理系统

摘 要 随着世界经济快速的发展&#xff0c;全国各地的城市规模不断扩大&#xff0c;住进城市的人口日益增多&#xff0c;房地产行业在现代社会的发展中有着重要的作用&#xff0c;有越来越多的人居住在小区里。 因此&#xff0c;一套高效并且无差错的物业管理系统软件在现代社会…

基于Android的校园一卡通App平台

演示视频信息&#xff1a; A6604基于Android的校园一卡通一、研究背景、目的及意义 &#xff08;一&#xff09;研究背景 二十一世纪是信息化的时代&#xff0c;信息化建设成为我们的首要任务。当前我国大力发展信息产业&#xff0c;在全国范围内各行各业开始实施信息化…

为什么要上机械制造业ERP系统?对企业有什么帮助?

在日益竞争激烈的市场背景下&#xff0c;机械制造企业提供的产品需要具有更短的交货期、更高的质量、更好的服务。而机械行业由于其工艺复杂的生产特点&#xff0c;工艺及在制品管理困难&#xff0c;单纯的靠手工记账处理&#xff0c;已经难以满足现代企业科学管理的需要。只有…

艾美捷IFN-gamma体内抗体参数及应用

艾美捷IFN-gamma体内抗体背景&#xff1a; 干扰素γ&#xff08;IFN-γ&#xff09;或II型干扰素是一种二聚可溶性细胞因子&#xff0c;是II型干扰素的唯1成员。它是一种细胞因子&#xff0c;对抵抗病毒和细胞内细菌感染的先天性和适应性免疫以及肿瘤控制至关重要。IFNG主要由…

TensorFlow平台应用

目录 一&#xff1a;TensorFlow简介 二&#xff1a;TensorFlow工作形式 三&#xff1a;图/Session 四&#xff1a;安装tensorflow 五&#xff1a;张量 六&#xff1a;变量/常量 七&#xff1a;创建数据流图、会话 八&#xff1a;张量经典创建方法 九&#xff1a;变量赋…

[Java EE初阶]Thread 类的基本用法

本就无大事,庸人觅闲愁. 文章目录1. 线程创建2. 线程中断2.1 通知终止后立即终止2.2 通知终止,通知之后线程继续执行2.3 通知终止后,添加操作后终止3. 线程等待4. 线程休眠5. 获取线程实例1. 线程创建 创建线程有五个方法 详情见我的另一个文章 https://editor.csdn.net/md/?…

【K8S系列】第十二讲:Service进阶

目录 ​编辑 序言 1.Service介绍 1.1 什么是Service 1.2 Service 类型 1.2.1 NodePort 1.2.2 LoadBalancer 1.2.3 ExternalName 1.2.4 ClusterIP 2.yaml名词解释 3.投票 序言 当发现自己的才华撑不起野心时&#xff0c;就安静下来学习吧 三言两语&#xff0c;不如细…

Unity 灯光

初始化时&#xff0c;系统默认会给一个灯光&#xff0c;类型为定向光。 定向光意为&#xff0c;从无穷远处照射过来的平行光&#xff0c;因此每个图形的阴影的方向一致 灯光的系统参数 阴影类型&#xff1a;①无阴影 ②硬阴影 ③软阴影 &#xff08;注意&#xff09;阴影类型最…

力扣(LeetCode)164. 最大间距(C++)

桶排序(划分区间) 一次遍历找到区间内最大值 MaxMaxMax &#xff0c;最小值 MinMinMin 。区间 (Min,Max](Min,Max](Min,Max] 左开右闭&#xff0c;划分为 n−1n-1n−1 个长度为 lenlenlen 的区间 &#xff0c;划分的区间左开右闭&#xff0c;所以每个子区间有 len−1len-1len−…

SpringCloud学习笔记

SpringCloud学习笔记成熟分布式微服务架构包含技术模块SpringCloud与SpringBoot版本选择SpringCloud各技术模块的技术选择SpringCloud实现订单-支付微服务创建父工程(管理子工程即各个微服务)父工程的build.gradle配置父工程的settings.gradle配置创建支付子工程(payment_nativ…

物联网开发笔记(64)- 使用Micropython开发ESP32开发板之控制ILI9341 3.2寸TFT-LCD触摸屏进行LVGL图形化编程:控件显示

一、目的 这一节我们学习如何使用我们的ESP32开发板来控制ILI9341 3.2寸TFT-LCD触摸屏进行LVGL图形化编程&#xff1a;控件显示。 二、环境 ESP32 ILI9341 3.2寸TFT-LCD触摸屏 Thonny IDE 几根杜邦线 接线方法&#xff1a;见前面文章。 三、滑杆代码 import lvgl as lv i…

北京理工大学汇编语言复习重点

汇编是半开卷&#xff0c;可以带纸质资料。理论上&#xff0c;学好了以后&#xff0c;带本书进去就ok了&#xff0c;但是这次是线上&#xff0c;我还没书&#xff0c;就对着考试重点整理一点资料用于打印吧。 因为是线上&#xff0c;所以第4章基本不考框架了&#xff0c;浮点操…

工业4.0,科技引发产业革命,MES系统是数字化工厂的核心

当前&#xff0c;把握工业4.0时代信息技术引发的产业革命风口期&#xff0c;促进产业数字化转型升级&#xff0c;构建产业竞争新格局&#xff0c;实现弯道超车是难得一遇的大好时机&#xff0c;是局势所趋。在这样的大环境下&#xff0c;顺应全世界产业革命趋势&#xff0c;将数…

python的安装

python可以在所有操作系统上运行&#xff0c;但不同操作系统的安装方法有所不同 以&#xff1a;Mac OS X 和 Windows为例 为了安装 Python 并能正常使用&#xff0c;请根据你的操作系统安装提示&#xff0c;按照相应的步骤来操作。 我们还将进行一系列的测试&#xff0c;确保一…

SpringBoot实战(十)集成多数据源dynamic-datasource

目录1.Maven依赖2. DS注解3.普通Hihari连接池3.1 yml配置4.Druid连接池4.1 Druid依赖4.2 yml配置4.3 排除原生的Druid配置5. UserController6. UserServiceImpl7.测试7.1 新增数据7.2 查询数据7.3 测试结果8.源码地址&#xff1a;dynamic-datasource-spring-boot-starter 是一个…

【大数据入门核心技术-Kafka】(四)Kafka常用shell命令

目录 一、准备工作 1、Zookeeper集群安装 2、Kafka集群安装 二、常用Shell命令 1、创建Topic 2、查看创建的Topic 3、查看某一个Topic的详细信息 4、修改Topic 5、删除Topic 6、生产者发布消息命令 7、消费者接受消息命令 8、查看kafka节点数目 9、查看kafka进程 一…

再学C语言8:数据类型(6)——float和其他类型

一、_Bool类型 _Bool类型由C99引入 _Bool类型用于表示布尔值&#xff1a;逻辑值true&#xff08;真&#xff09;和false&#xff08;假&#xff09; C用值1表示true&#xff0c;用值0表示false&#xff0c;所以_Bool类型本质上也是一种整数类型 二、float、double和long do…

查找排序编程题——二分查找+冒泡排序‍♀️

目录 7-1 二分查找 输入格式: 输出格式: 输入样例: AC&#xff1a; 7-2 冒泡法排序 输入格式&#xff1a; 输出格式&#xff1a; 输入样例&#xff1a; 输出样例&#xff1a; AC&#xff1a; 附&#xff1a; 分享&#x1f31e; &#xff1a; 前途与玫瑰 来日与方…

尝试使用LTTng+TraceCompass分析一下进程周期偏移

准备工作 我先是在板子里通过LTTng Stream的方式将log保存在了上位机里。 然后在上位机中打开TraceCompass分析log。 导出整理log数据 然后我在events表格中搜索我的进程名&#xff0c;发现主要是sched_switch sched_waking sched_wakeup这三类。 然后我又搜索了一下&#x…